/* ============================================================
   Fontaine Noire — global styles
   Palette: near-black canvas, warm cream ink, iridescent shader.
   Type: Schibsted Grotesk (display) + Space Mono (labels/body).
   ============================================================ */

:root {
  --bg: #0b0b0c;
  --bg-soft: #0f0f11;
  --ink: #ece7db;          /* warm cream */
  --ink-dim: #a5a096;
  --ink-faint: #8d8a82;   /* lightened for WCAG AA contrast on --bg */
  --line: rgba(236, 231, 219, 0.14);
  --line-soft: rgba(236, 231, 219, 0.08);
  --accent: #f4efe3;
  --purple: #a78bfa;        /* brand purple accent */
  --purple-deep: #8570c8;   /* darker, muted purple — still ≥4.5:1 on --bg */

  --mono: "Space Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;
  --disp: "Schibsted Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;
  --body: "Mulish", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --serif: "EB Garamond", Georgia, "Times New Roman", serif; /* manifesto headline only (index) */

  --pad: clamp(20px, 5vw, 88px);
  --maxw: 1500px;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-inout: cubic-bezier(0.65, 0, 0.35, 1);
}

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

html {
  scroll-behavior: smooth;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Whole-site scale: render everything 10% smaller (user call 2026-07-20).
     Viewport units paint at 90% under this — every full-bleed / full-viewport
     rule below carries a "/ 0.9" (or "/ 0.81") compensation; keep them in
     step if this changes. */
  zoom: 0.9;
}

/* Second reduction step (user call 2026-07-20): everything BELOW the hero
   renders another 10% smaller — zoom nests multiplicatively, so these sit at
   0.9 × 0.9 = 0.81. The hero screen (header, headline, shader view) keeps
   the first step only. */
main > :not(.hero),
.site-footer { zoom: 0.9; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--disp);
  font-size: 17px;
  line-height: 1.5;
  overflow-x: hidden;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

a { color: inherit; text-decoration: none; }
h1, h2, h3, p { margin: 0; }
img { max-width: 100%; display: block; }

/* ---------- Shared layout ---------- */
.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin: 0 auto;
  padding-left: var(--pad);
  padding-right: var(--pad);
}

.section {
  position: relative;
  padding-top: clamp(64px, 9vw, 128px);
  padding-bottom: clamp(64px, 9vw, 128px);
  background: var(--bg);
  z-index: 2;
}

/* Trim the gaps around Services and Full Reporting a little (both sides are
   .section padding). The manifesto claims just under a viewport: enough that
   the CTA screen reads alone, short enough that the Services title rises into
   view at the BOTTOM edge when the CTA sits at the top (user call 2026-07-19;
   the subtracted clamp ≈ services padding-top + title height + breathing). */
.manifesto {
  padding-bottom: clamp(52px, 7vw, 100px);
  min-height: calc((100svh - clamp(135px, 21vh, 210px)) / 0.81);  /* ÷0.81 — this section sits in the nested 0.9×0.9 zoom */
  /* the quote is gone (2026-07-19) — the CTA + mission paragraph + platform
     logos sit alone, top-aligned so they follow the hero closely */
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
}
.services { padding-top: clamp(52px, 7vw, 100px); padding-bottom: clamp(52px, 7vw, 100px); }
.process { padding-top: clamp(52px, 7vw, 100px); }

.section-title {
  font-weight: 600; /* semibold — "Services" + "About us" */
  font-size: clamp(34px, 5vw, 68px);
  letter-spacing: -0.02em;
  line-height: 1.02;
  /* Extend the background-clip:text paint area past the baseline so descenders
     (g, p, y) aren't cropped; the equal negative margin keeps layout unchanged. */
  padding-bottom: 0.22em;
  margin-bottom: -0.22em;
}
/* Services heading is noticeably larger than the other section titles; ranged
   left like the rest (was centred until 2026-07-22, user call). */
.services .section-title { font-size: clamp(50px, 7.5vw, 108px); }
/* About us sits a step above the base size (user call 2026-07-20), still
   below the Services jumbo. */
#process .section-title { font-size: clamp(40px, 5.8vw, 80px); }

.big-title {
  font-weight: 500;
  font-size: clamp(52px, 10vw, 150px);
  letter-spacing: -0.03em;
  line-height: 0.92;
}
/* "Pricing" has a descender too; extend its clip paint area like the section
   titles. Scoped to #pricing — .team__heading carries its own fix (folded
   into its margin), and "Resources" has no descenders. */
#pricing .big-title { padding-bottom: 0.22em; margin-bottom: -0.22em; font-weight: 600; /* semibold, like the section titles */ }

/* Shader-style purple gradient filling the section headings ("inside the
   letters"). Reworked 2026-07-19 (user call: "clear waves going through"):
   two bright crests over deep violet troughs, higher contrast than the old
   soft shimmer, tiling seamlessly (first stop = last stop) and travelling in
   ONE direction on a linear clock so distinct waves visibly roll through the
   text. Seamless loop maths: at background-size 200%, animating position
   0%→200% shifts exactly one tile width — don't change one without the other. */
.section-title,
.big-title {
  background-image: linear-gradient(105deg,
    #9161e0 0%, #6d28d9 10%, #a78bfa 24%, #efe7fb 36%, #ffffff 44%,
    #e9dffb 52%, #8b5cf6 64%, #6d28d9 74%, #c9b8f5 86%, #9161e0 100%);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  animation: titleWave 7s linear infinite;
}
@keyframes titleWave {
  from { background-position: 0% 50%; }
  to   { background-position: 200% 50%; }
}
@keyframes headingSheen {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}
@media (prefers-reduced-motion: reduce) {
  .section-title, .big-title { animation: none; }
}

.arr {
  display: inline-block;
  font-family: var(--mono);
  transition: transform 0.5s var(--ease);
}

/* Primary CTA button — shared by the contact form submit and the site CTAs. */
.cbtn {
  display: inline-block;
  font-family: var(--disp);
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.04em;
  color: #0b0b0c;
  background: linear-gradient(135deg, #c4b5fd 0%, #a78bfa 48%, #8b5cf6 100%);
  border: 0;
  border-radius: 100px;
  padding: 14px 32px;
  cursor: pointer;
  box-shadow: 0 6px 22px rgba(167, 139, 250, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.4);
  transition: transform 0.3s var(--ease), filter 0.3s, box-shadow 0.4s var(--ease);
}
.cbtn:hover {
  filter: brightness(1.06);
  transform: translateY(-2px);
  box-shadow: 0 14px 40px rgba(167, 139, 250, 0.34), inset 0 1px 0 rgba(255, 255, 255, 0.5);
}
.cbtn:active { transform: translateY(0); }

/* Sent state — the label lifts away and a checkmark draws itself in, with a
   pop and a ring pulse. js/contact.js adds .is-sent only once the Apps Script
   confirms the row was written, so the check means "stored", not "posted". */
.cbtn--send { position: relative; }
.cbtn__text {
  display: inline-block;
  position: relative;
  z-index: 2;
  transition: opacity 0.2s var(--ease), transform 0.35s var(--ease);
}
.cbtn__check {
  position: absolute;
  inset: 0;
  margin: auto;
  z-index: 2;
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0;
  transition: opacity 0.2s var(--ease) 0.12s;
}
.cbtn__check path { stroke-dasharray: 1; stroke-dashoffset: 1; }
.cbtn--send.is-sent { cursor: default; animation: cbtn-pop 0.5s var(--ease); }
.cbtn--send.is-sent:hover { transform: none; filter: none; }
.cbtn--send.is-sent .cbtn__text { opacity: 0; transform: translateY(-8px); }
.cbtn--send.is-sent .cbtn__check { opacity: 1; }
.cbtn--send.is-sent .cbtn__check path { animation: cbtn-check 0.4s var(--ease) 0.18s forwards; }
.cbtn--send.is-sent::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: inherit;
  border: 2px solid var(--purple);
  opacity: 0;
  pointer-events: none;
  animation: cbtn-ring 0.7s var(--ease) 0.1s;
}
@keyframes cbtn-pop {
  0% { transform: scale(1); }
  38% { transform: scale(0.94); }
  100% { transform: scale(1); }
}
@keyframes cbtn-check {
  to { stroke-dashoffset: 0; }
}
@keyframes cbtn-ring {
  0% { transform: scale(1); opacity: 0.7; }
  100% { transform: scale(1.3); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .cbtn__text, .cbtn__check { transition: none; }
  .cbtn--send.is-sent { animation: none; }
  .cbtn--send.is-sent::after { animation: none; }
  .cbtn--send.is-sent .cbtn__check path { animation: none; stroke-dashoffset: 0; }
}

/* Larger CTA */
/* Only used by the manifesto CTA. Scaled up 2026-07-19 (the quote above it was
   removed, so the pill + paragraph carry the whole screen). Sized to stay
   inside .manifesto__cta's 54ch column at every width — the label is long, so
   the padding floor is what keeps it off the edge on narrow screens. */
.cbtn--lg {
  font-size: clamp(20px, 2.3vw, 32px);
  letter-spacing: 0.05em;
  padding: clamp(24px, 2.8vw, 40px) clamp(40px, 5.5vw, 88px);
}

.cbtn__label { position: relative; z-index: 2; }
/* Primary CTA "shimmer letters" — the label carries the same animated purple
   sheen as the section headings (see headingSheen), in place of the live
   shader that used to fill the pill. A dark iridescent sweep (near-black →
   deep violet → near-black) glimmers through the bold label while keeping it
   legible on the purple gradient. */
.cbtn--sheen .cbtn__label {
  background-image: linear-gradient(100deg,
    #100a1c 0%, #100a1c 34%, #2c1a5c 45%, #3d2578 50%, #2c1a5c 55%, #100a1c 66%, #100a1c 100%);
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  animation: headingSheen 6.5s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .cbtn--sheen .cbtn__label { animation: none; }
}

/* Manifesto CTA label: solid opaque white (user call 2026-07-20, after
   trying washed/cream/grey variants) in place of the dark letter sheen.
   Placed after the sheen block so equal specificity resolves this way;
   delete to restore the sheen. */
.manifesto__cta .cbtn__label {
  background-image: none;
  animation: none;
  color: #fff;
  -webkit-text-fill-color: #fff;
}

/* Highlighted CTA (`.cbtn--ripple`, the pricing "Get a Free Audit") — three
   stacked motions, all inside the pill and all pure CSS (house rule: button
   motion stays CSS-only, no WebGL):
   1. the pill's purple gradient pans slowly (liquid surface),
   2. two staggered sonar rings ripple out from the centre — the shader's
      wave theme in miniature (transform/opacity only, so compositor-cheap),
   3. plus the shared letter sheen from .cbtn--sheen.
   Rings sit under .cbtn__label (z-index 2), so the text stays crisp. */
.cbtn--ripple {
  --ripple-scale: 16; /* × the 16px ring ≈ how far the rings sweep; sized to the pill */
  position: relative;
  overflow: hidden; /* clip the rings to the pill */
  background: linear-gradient(135deg,
    #c4b5fd 0%, #a78bfa 30%, #8b5cf6 50%, #a78bfa 70%, #c4b5fd 100%);
  background-size: 260% 100%;
  animation: ctaGradientPan 11s ease-in-out infinite;
}
.cbtn--ripple::before,
.cbtn--ripple::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  border-radius: 50%;
  /* a ring, not a disc — hollow centre keeps the label area clean */
  background: radial-gradient(circle,
    rgba(255, 255, 255, 0) 52%, rgba(255, 255, 255, 0.5) 62%, rgba(255, 255, 255, 0) 74%);
  transform: scale(0.3);
  opacity: 0;
  pointer-events: none;
  animation: ctaRipple 5s cubic-bezier(0.22, 1, 0.36, 1) infinite;
}
.cbtn--ripple::after { animation-delay: 2.5s; /* keep at half the ring duration */ }
@keyframes ctaGradientPan {
  0%, 100% { background-position: 0% 50%; }
  50%      { background-position: 100% 50%; }
}
/* The manifesto pill (.cbtn--lg) is much wider — rings must travel further. */
.cbtn--lg.cbtn--ripple { --ripple-scale: 44; }
@keyframes ctaRipple {
  0%   { transform: scale(0.3); opacity: 0; }
  10%  { opacity: 0.85; }
  70%  { transform: scale(var(--ripple-scale)); opacity: 0; }
  100% { transform: scale(var(--ripple-scale)); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .cbtn--ripple { animation: none; }
  .cbtn--ripple::before,
  .cbtn--ripple::after { animation: none; opacity: 0; }
}

/* ============================================================
   Shader canvas + grain
   ============================================================ */
.fx {
  position: fixed;
  inset: 0;
  /* ÷0.9: viewport units paint at 90% under the root zoom — full-bleed
     layers and full-viewport sections divide back up. Keep every "/ 0.9"
     in styles.css in step with the html zoom value. */
  width: calc(100vw / 0.9);
  height: calc(100vh / 0.9);
  z-index: 0;
  display: block;
  /* Instant backdrop so the hero looks right before the shader boots (its
     init is deferred to post-load) and on software renderers that skip it —
     the dark hero ink stays legible over it, and the field draws opaquely on
     top. Deepened 2026-07-20 to match the shader's richer purple palette
     (the old near-grey stops made the FIRST display look pre-update). Keep
     in step with fallback() in ripple.js. */
  background: radial-gradient(125% 115% at 62% 18%, #cfc5e8 0%, #b1a0d9 45%, #9781c9 100%);
}

.grain {
  position: fixed;
  inset: 0;
  width: calc(100vw / 0.9);  /* ÷0.9 — see .fx */
  height: calc(100vh / 0.9);
  z-index: 1;
  pointer-events: none;
  opacity: 0.05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ============================================================
   Header
   ============================================================ */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  width: calc(100vw / 0.9);  /* ÷0.9 — see .fx; explicit width beats right:0 */
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 24px;
  padding: clamp(18px, 2.2vw, 30px) var(--pad);
  mix-blend-mode: difference;   /* legibility over light + dark */
  color: #fff;
}
.brand {
  font-size: clamp(19px, 2vw, 24px);
  font-weight: 600;
  letter-spacing: -0.02em;
  margin-right: auto;
}
.brand__word { position: relative; }
.nav {
  display: flex;
  gap: clamp(18px, 2vw, 34px);
  font-family: var(--disp);
  font-weight: 500;
  font-size: 13.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.nav a { position: relative; padding: 4px 0; opacity: 0.85; transition: opacity 0.3s; }
.nav a:hover { opacity: 1; }
.nav a::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s var(--ease);
}
.nav a:hover::after,
.nav a.is-active::after { transform: scaleX(1); }

.nav-cta {
  font-family: var(--disp);
  font-weight: 500;
  font-size: 13.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border: 1px solid rgba(255,255,255,0.5);
  padding: 8px 16px;
  border-radius: 100px;
  transition: background 0.35s, color 0.35s;
}
.nav-cta:hover { background: #fff; color: #000; }

.nav-toggle {
  display: none;
  flex-direction: column;
  gap: 5px;
  background: none;
  border: 0;
  cursor: pointer;
  padding: 6px;
}
.nav-toggle span {
  width: 26px; height: 2px; background: #fff; display: block;
  transition: transform 0.35s var(--ease), opacity 0.2s;
}

/* ============================================================
   Hero
   ============================================================ */
.hero {
  position: relative;
  z-index: 2;
  min-height: calc(100svh / 0.9);  /* ÷0.9 — hero still fills the real viewport */
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: var(--pad);
  padding-bottom: clamp(28px, 4vw, 54px);
  background: transparent;    /* let shader show through */
}
.hero__grid { max-width: 1500px; margin: 0 auto; width: 100%; }
.hero__title {
  font-weight: 600; /* semibold, in the site display face (--disp via body) */
  font-size: clamp(58px, 12vw, 190px);
  line-height: 0.9;
  letter-spacing: -0.045em;
  color: #16130d;              /* dark ink over light shader */
  mix-blend-mode: normal;
}
/* padding extends the overflow clip past the baseline so Schibsted's
   descenders ("y" in "every", ~0.105em past the 0.9 line box) aren't cropped;
   the equal negative margin keeps the two lines' rhythm unchanged. Paired
   with heroRise starting at translateY(115%): pre-rise, the glyph ink (which
   begins ~0.075em below the span's top edge) must stay below this enlarged
   clip window (0.9em + 0.13em) — keep the pair in step. */
.hero__title .line { display: block; overflow: hidden; padding-bottom: 0.13em; margin-bottom: -0.13em; }
.hero__title .line > span { display: block; }

/* Rotating hero word ("ad" → lead → click → view → impression). The parent
   .line has overflow:hidden, which clips the word as it slides up/in. */
.hero__l2 { white-space: nowrap; }
.rot {
  display: inline-block;
  position: relative;
  vertical-align: baseline;
  transition: width 0.28s var(--ease);
}
/* inline (not inline-block) keeps the baseline aligned with " count." at the
   tight line-height; animate `top` since inline elements can't be transformed. */
.rot__word { display: inline; position: relative; top: 0; will-change: top, opacity; }
/* The highlight word ("ad") lights up in the shader's purple: a gradient flows
   through the letters (echoing the ripple) with a soft brightness pulse. Kept
   inside the glyphs — the parent .line clips overflow, so no external halo. */
.rot__word.is-glow {
  color: #34136b;                       /* solid fallback if text-clip unsupported */
  background-image: linear-gradient(100deg, #1c0a3d 0%, #34136b 32%, #4c1d95 50%, #34136b 68%, #1c0a3d 100%);
  background-size: 250% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  animation: rot-flow 3.4s linear infinite, rot-breathe 2.6s ease-in-out infinite;
}
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .rot__word.is-glow { color: transparent; -webkit-text-fill-color: transparent; }
}
@keyframes rot-flow {
  from { background-position: 0 0; }
  to   { background-position: 250% 0; }
}
@keyframes rot-breathe {
  0%, 100% { filter: brightness(1) saturate(1); }
  50%      { filter: brightness(1.2) saturate(1.3); }
}
@media (prefers-reduced-motion: reduce) {
  .rot { transition: none; }
  .rot__word { top: 0 !important; opacity: 1 !important; }
  .rot__word.is-glow { animation: none; background-position: 50% 0; }
}
.hero__sub {
  margin-top: clamp(22px, 3vw, 40px);
  max-width: 46ch;
  font-family: var(--mono);
  font-size: clamp(13px, 1.1vw, 15.5px);
  line-height: 1.65;
  color: #2c261c;
  font-style: italic;
}

/* ============================================================
   Manifesto
   ============================================================ */
/* CTA block + mission paragraph, centred as a pair in the full-viewport
   section (the "Make it simple, but significant." quote was removed
   2026-07-19). Mirrors .manifesto__body's font context so `54ch` resolves to
   the same width and the two boxes line up (the button sets its own type). */
.manifesto__cta {
  margin-inline: auto;
  max-width: 54ch;
  font-family: var(--disp); /* keep in step with .manifesto__body (54ch mirror) */
  font-size: clamp(19px, 1.8vw, 26px);
  text-align: center;
}
/* Book button keeps the grotesk face + tight tracking. */
.manifesto__cta .cbtn { font-weight: 700; letter-spacing: -0.025em; }
.manifesto__cta + .manifesto__body { margin-top: clamp(28px, 3.6vw, 52px); }
.manifesto__body {
  margin-top: clamp(36px, 5vw, 70px);
  max-width: 54ch;
  margin-inline: auto;
  /* same face + ink as the service/pricing descriptions (.svc__desc /
     .principle__desc) — the display font, bright, fully readable */
  font-family: var(--disp);
  font-size: clamp(19px, 1.8vw, 26px);
  line-height: 1.7;
  color: var(--ink);
  text-align: center;
}
/* Closing invitation sits on its own centred line under the paragraph. */
.manifesto__body-close { display: block; margin-top: 0.9em; }

/* ≥961px: the pair splits into two columns — mission paragraph with the
   platform logos centred beneath it on the left, the Book CTA on the right,
   vertically centred against the text block (user calls 2026-07-22: split
   first, then "text centered and logos centered below it"). Text and logo
   alignment come from the base rules above (both centred); the body box is
   centred in its column so the two share one centre axis. Grid areas do the
   placement, so the DOM order (CTA first) is untouched and the centred
   stack still applies as-is below the breakpoint (same 960px line the
   pricing card stacks at). These overrides sit after the base rules on
   purpose: the `.manifesto__cta + .manifesto__body` margin ties on
   specificity and must lose by source order. */
@media (min-width: 961px) {
  .manifesto .wrap {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "body  cta"
      "logos cta";
    align-items: center;
    column-gap: clamp(40px, 6vw, 100px);
  }
  .manifesto .manifesto__body {
    grid-area: body;
    margin: 0;
    justify-self: center; /* 54ch box centred in the column = one axis with the logos */
  }
  .manifesto .manifesto__logos {
    grid-area: logos;
  }
  .manifesto .manifesto__cta {
    grid-area: cta;
    margin-inline: 0;
    max-width: none;
  }
}

/* ============================================================
   Services
   ============================================================ */
.svc { margin-top: clamp(30px, 4vw, 60px); border-top: 1px solid var(--line); }
.svc__row {
  display: grid;
  grid-template-columns: minmax(240px, 1fr) 1.2fr;
  gap: clamp(20px, 3vw, 60px);
  align-items: start;
  padding: clamp(30px, 4vw, 60px) 0;
  border-bottom: 1px solid var(--line);
  transition: background 0.4s;
}
.svc__row:hover { background: rgba(236,231,219,0.02); }
/* Platform marks (Google Ads / Meta / LinkedIn) moved to the manifesto,
   centred under the mission paragraph (2026-07-19; formerly .svc__logos in
   the Campaign Management row). Slightly bigger than they were there. */
.manifesto__logos {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: clamp(22px, 2.6vw, 34px);
  margin-top: clamp(28px, 3.6vw, 48px);
}
.manifesto__logos img { height: 32px; width: auto; display: block; }
.svc__head { display: flex; align-items: baseline; gap: 16px; }
.svc__head h3 {
  font-weight: 500;
  font-size: clamp(28px, 3.4vw, 52px);
  letter-spacing: -0.02em;
  line-height: 1;
}
/* Same treatment as .principle__desc (the pricing tier copy): the display
   face at reading-large size in full ink, so the text carries real weight. */
.svc__desc {
  font-family: var(--disp);
  font-size: clamp(17px, 1.4vw, 21px);
  line-height: 1.55;
  color: var(--ink);
  max-width: 46ch;
  padding-top: 8px;
}

/* ============================================================
   Process
   ============================================================ */
.about__body {
  margin-top: clamp(40px, 5vw, 70px);
  border-top: 1px solid var(--line);
  padding-top: 32px;
}
.about__body p {
  /* matches .principle__desc — display face, larger, full ink (visibility) */
  font-family: var(--disp);
  font-size: clamp(17px, 1.4vw, 21px);
  line-height: 1.65;
  color: var(--ink);
  margin: 0 0 1.5em;
}
.about__body p:last-child { margin-bottom: 0; }

/* ============================================================
   Reveal band (shader interstitial)
   ============================================================ */
.reveal-band {
  position: relative;
  z-index: 2;
  min-height: 78vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  overflow: hidden;
}
.reveal-band__text {
  font-weight: 500;
  font-size: clamp(38px, 8vw, 128px);
  letter-spacing: -0.03em;
  color: #16130d;
  mix-blend-mode: normal;
  text-align: center;
}

/* ============================================================
   Principles
   ============================================================ */
.principles__wrap {
  display: grid;
  grid-template-columns: 0.9fr 1.1fr;
  gap: clamp(30px, 5vw, 90px);
  align-items: start;
}
.principles__title { position: sticky; top: 120px; }
.principles__list { display: flex; flex-direction: column; gap: clamp(48px, 7vw, 96px); }
.principle h3 {
  font-weight: 500;
  font-size: clamp(30px, 4vw, 58px);
  letter-spacing: -0.02em;
  margin-bottom: 18px;
}
.principle__desc {
  font-size: clamp(17px, 1.4vw, 21px);
  line-height: 1.5;
  color: var(--ink);
  max-width: 40ch;
  margin-bottom: 22px;
}
.tier__price {
  font-family: var(--disp);
  font-weight: 500;
  font-size: 14px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--purple);
  margin: -4px 0 18px;
}
.principle blockquote {
  margin: 0;
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.7;
  color: var(--ink-dim);
  border-left: 1px solid var(--line);
  padding-left: 20px;
  max-width: 48ch;
}
.principle cite {
  display: block;
  margin-top: 12px;
  font-style: normal;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* --- Featured tier ("Campaign Management" — the best offer) ---------------
   A quiet purple card lifts it out of the list: hairline border, soft
   breathing glow (opacity-only, so it stays on the compositor).
   The title carries the section headings' flowing purple sheen (the shader's
   palette, quicker cycle) and the brand's wave mark drifts beneath it.
   Pure CSS — no extra WebGL context; keep it that way. */
.principle--featured {
  position: relative;
  border: 1px solid rgba(167, 139, 250, 0.35);
  border-radius: 18px;
  padding: clamp(22px, 2.6vw, 38px);
  background:
    radial-gradient(120% 140% at 82% -12%, rgba(167, 139, 250, 0.14) 0%, rgba(167, 139, 250, 0.05) 45%, transparent 72%),
    var(--bg-soft);
  /* text column + the "Get a Free Audit" CTA on the right, vertically centred */
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: clamp(18px, 2.5vw, 44px);
}
.principle--featured > :not(.principle__cta) { grid-column: 1; }
.principle__cta {
  grid-column: 2;
  grid-row: 1 / span 3;
  align-self: center;
  justify-self: end;
  white-space: nowrap;
}
@media (max-width: 960px) {
  /* stacked layout: CTA drops below the text, ranged left */
  .principle--featured { grid-template-columns: 1fr; }
  .principle__cta { grid-column: 1; grid-row: auto; align-self: start; justify-self: start; margin-top: 6px; }
}
.principle--featured::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  box-shadow: 0 10px 48px rgba(167, 139, 250, 0.22), inset 0 0 26px rgba(167, 139, 250, 0.07);
  opacity: 0.3;
  pointer-events: none;
  animation: featuredBreathe 5.5s ease-in-out infinite;
}
@keyframes featuredBreathe {
  0%, 100% { opacity: 0.25; }
  50%      { opacity: 0.75; }
}
/* Title: the sheen gradient lives inside the letters (background-clip:text).
   The drifting brand-wave underline was removed 2026-07-22 (user call). It
   used to extend the h3's paint box past the baseline; the padding-bottom +
   negative margin now keep the p/g descenders inside the clipped gradient
   instead. */
.principle--featured h3 {
  display: inline-block;
  width: fit-content; /* grid blockifies inline-block — keep the gradient text-width */
  padding-bottom: 0.24em;
  margin-bottom: -0.24em;
  background-image: linear-gradient(105deg,
    #a78bfa 0%, #efe7fb 18%, #ffffff 33%, #9161e0 50%,
    #d9c8f7 68%, #b8a4f0 84%, #a78bfa 100%);
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  animation: headingSheen 6.5s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .principle--featured::after,
  .principle--featured h3 { animation: none; }
}

/* ============================================================
   Team
   ============================================================ */
/* "Management" has a g — extend the background-clip paint area below the
   baseline (like #pricing .big-title) so the descender's tail isn't cropped;
   the compensation is folded into the margin so spacing to the grid holds. */
.team__heading { padding-bottom: 0.12em; margin-bottom: calc(clamp(40px, 6vw, 90px) - 0.12em); text-align: center; }
.team__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(24px, 3vw, 48px);
}
.member { margin: 0; text-align: center; }
.member__photo {
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  background:
    radial-gradient(120% 120% at 50% 20%, #2a2a2d 0%, #131315 70%);
  border: 1px solid var(--line);
  display: grid;
  place-items: center;
  max-width: 60%;             /* trimmed a further 20% (was 75%); sources are 600-640px, still ~2x the drawn size → sharp */
  margin: 0 auto 26px;
  position: relative;
  overflow: hidden;
  transition: transform 0.6s var(--ease);
}
.member__photo::after {
  content: attr(data-initials);
  font-family: var(--mono);
  font-size: 40px;
  color: var(--ink-faint);
}
.member__photo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 28%;
}
.member:hover .member__photo { transform: translateY(-6px); }
.member h3 { font-weight: 500; font-size: clamp(22px, 2vw, 30px); letter-spacing: -0.01em; }
.member__role { font-family: var(--mono); font-size: 13px; color: var(--ink-dim); margin-top: 8px; }
.member__links { display: flex; gap: 16px; justify-content: center; margin-top: 16px; }
.member__links a {
  font-family: var(--mono); font-size: 13px; color: var(--ink-dim);
  width: 30px; height: 30px; border: 1px solid var(--line); border-radius: 50%;
  display: grid; place-items: center; transition: border-color 0.3s, color 0.3s;
}
.member__links a:hover { color: var(--ink); border-color: var(--ink-dim); }

.supported { margin-top: clamp(80px, 12vw, 170px); text-align: center; }
.supported .rule { height: 1px; background: var(--line); width: 100%; }
.supported__eyebrow {
  font-family: var(--body);
  font-weight: 500;
  font-size: clamp(11px, 1vw, 13px);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: clamp(44px, 6vw, 82px) 0 0;
}
/* Client logo strip — monochrome white marks on the dark theme, aligned;
   dimmed by default, brighten on hover. Wraps as more are added. */
.supported__logos {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: clamp(30px, 5vw, 70px);
  margin: clamp(30px, 4vw, 54px) 0 clamp(50px, 7vw, 90px);
}
.supported__logo { display: grid; place-items: center; }
.supported__logo img {
  height: clamp(25px, 2.7vw, 35px);
  width: auto;
  max-width: 230px;
  object-fit: contain;
  display: block;
  opacity: 0.55;
  transition: opacity 0.4s var(--ease);
}
.supported__logo img:hover { opacity: 1; }
/* Stacked lockups (mark sitting above the wordmark) need roughly double the
   height of a wordmark-only mark to sit optically level with one — and below
   ~46px their wordmark stops being legible. */
.supported__logo--stacked img { height: clamp(46px, 4.9vw, 64px); }

/* ============================================================
   Resources
   ============================================================ */
.resources__heading { text-align: center; margin-bottom: clamp(50px, 8vw, 120px); }
.resources__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(48px, 8vw, 128px);
  max-width: 1240px;   /* wide cards, still centred as a pair */
  margin: 0 auto;
}
.res { display: block; }
.res--offset { margin-top: clamp(50px, 10vw, 160px); margin-left: auto; }
.res h3 {
  font-weight: 500;
  font-size: clamp(28px, 3vw, 44px); /* sized so both titles hold 2 lines max */
  letter-spacing: -0.02em;
  line-height: 1.08;
  margin: 18px 0 20px;
}
.res:hover .arr { transform: translateX(8px); }
.res p { font-family: var(--mono); font-size: 13.5px; line-height: 1.7; color: var(--ink-dim); }

/* ============================================================
   Contact
   ============================================================ */
.contact__wrap { text-align: center; }
.contact__title {
  font-weight: 500;
  font-size: clamp(44px, 9vw, 150px);
  letter-spacing: -0.035em;
  line-height: 0.92;
  margin-bottom: clamp(34px, 5vw, 60px);
}
.contact__actions { margin-bottom: clamp(30px, 4vw, 48px); }
.contact__mail {
  font-family: var(--serif);
  font-size: clamp(15px, 2.4vw, 30px);
  border-bottom: 1px solid var(--line);
  padding-bottom: 6px;
  transition: border-color 0.4s, color 0.3s;
}
.contact__mail:hover { border-color: var(--ink); }
.contact__note {
  margin: clamp(34px, 5vw, 54px) auto 0;
  max-width: 46ch;
  font-family: var(--mono);
  font-size: 13.5px;
  line-height: 1.7;
  color: var(--ink-dim);
}
.contact__loc {
  margin-top: clamp(26px, 4vw, 44px);
  font-family: var(--body);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--purple-deep);
}

/* ============================================================
   Footer
   ============================================================ */
.site-footer {
  position: relative;
  z-index: 2;
  background: var(--bg);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 16px 24px;
  padding: 30px var(--pad);
  border-top: 1px solid var(--line);
  font-family: var(--body);
  font-size: 14px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-faint);
}
.site-footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(16px, 2vw, 28px);
}
.site-footer__links a { transition: color 0.3s; }
.site-footer a:hover { color: var(--ink); }

/* ============================================================
   Reveal animations
   ============================================================ */
.reveal .line > span {
  transform: translateY(115%);
  transition: transform 0.85s var(--ease);
}
.reveal.is-in .line > span { transform: translateY(0); }

.hero__sub.reveal { opacity: 0; transform: translateY(22px); transition: opacity 0.9s var(--ease), transform 0.9s var(--ease); }
.hero__sub.reveal.is-in { opacity: 1; transform: translateY(0); }

.reveal-row { opacity: 0; transform: translateY(34px); transition: opacity 0.9s var(--ease), transform 0.9s var(--ease); }
.reveal-row.is-in { opacity: 1; transform: translateY(0); }

/* Arrive from the right instead of rising ("Full Reporting"). Must sit after
   .reveal-row.is-in — equal specificity, so source order settles the transform.
   The off-screen start can't raise a horizontal scrollbar: html is overflow
   visible, so body's overflow-x:hidden propagates to the viewport and clips it. */
.reveal-row--right { transform: translateX(64px); }
.reveal-row--right.is-in { transform: translateX(0); }

/* Drop in from above ("Services"): fades in while settling slightly LOWER
   into its resting place — the inverse of the default rise. Same toggle
   mechanics, so it replays in both scroll directions like everything else. */
.reveal-row--down { transform: translateY(-28px); }
.reveal-row--down.is-in { transform: translateY(0); }

/* Hero entrance plays from CSS (autoplay) so the LCP headline + its description
   paint the moment styles apply — not after the deferred module script runs.
   Same motion as the .reveal transitions above, just self-starting. */
.hero__title .line > span { animation: heroRise 0.85s var(--ease) both; }
.hero__sub.reveal { animation: heroFade 0.9s var(--ease) 0.24s both; }
/* 115% clears the 0.13em descender clip padding on .hero__title .line. */
@keyframes heroRise { from { transform: translateY(115%); } to { transform: translateY(0); } }
@keyframes heroFade { from { opacity: 0; transform: translateY(22px); } to { opacity: 1; transform: translateY(0); } }

@media (prefers-reduced-motion: reduce) {
  .reveal .line > span,
  .reveal-row,
  .hero__title .line > span,
  .hero__sub.reveal { transform: none !important; opacity: 1 !important; transition: none !important; animation: none !important; }
}

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width: 960px) {
  .nav, .nav-cta { display: none; }
  .nav-toggle { display: flex; }
  .site-header.is-open .nav {
    display: flex;
    position: fixed;
    inset: 0;
    width: calc(100vw / 0.9);  /* ÷0.9 — see .fx */
    height: calc(100vh / 0.9);
    background: var(--bg);
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 28px;
    font-size: 20px;
    mix-blend-mode: normal;
    color: var(--ink);
  }
  .svc__row { grid-template-columns: 1fr; gap: 14px; }
  .principles__wrap { grid-template-columns: 1fr; }
  .principles__title { position: static; }
  .team__grid { grid-template-columns: 1fr; max-width: 380px; margin: 0 auto; gap: 60px; }
  .resources__grid { grid-template-columns: 1fr; gap: 60px; }
  .res--offset { margin-top: 0; }
}

@media (max-width: 560px) {
  body { font-size: 16px; }
  /* Phones: the centred CTA + paragraph pair gets a fixed, balanced rhythm. */
  .manifesto__cta { margin-top: 36px; }
  .manifesto__cta + .manifesto__body { margin-top: 32px; }
}

/* ============================================================
   Smooth body typeface for descriptive / reading text.
   (Small uppercase labels, nav, numbers & footer stay monospace
   for editorial contrast.) Placed last so it wins by source order.
   ============================================================ */
.hero__sub,
.principle blockquote,
.member__role,
.res p,
.contact__note {
  font-family: var(--body);
  letter-spacing: 0;
}
/* nudge reading sizes up a touch — a proportional face reads smaller than mono */
.hero__sub { font-size: clamp(14px, 1.15vw, 16.5px); line-height: 1.6; }
.res p,
.contact__note { font-size: 14.5px; }
.principle blockquote { font-size: 14px; }

/* ============================================================
   Contact page (contact.html)
   ============================================================ */
.cpage-hero {
  position: relative;
  z-index: 2;
  padding-top: clamp(140px, 22vh, 240px);
  padding-bottom: clamp(48px, 8vw, 104px);
}
/* The intro sits over the light part of the shader — dark ink, same as the
   home hero (.hero__title / .hero__sub). */
.cpage-title {
  font-family: var(--disp);
  font-weight: 500;
  font-size: clamp(46px, 8vw, 116px);
  letter-spacing: -0.03em;
  line-height: 0.94;
  color: #16130d;
}
.cpage-sub {
  margin-top: clamp(20px, 3vw, 32px);
  max-width: 46ch;
  font-family: var(--body);
  font-size: clamp(15px, 1.2vw, 18px);
  line-height: 1.6;
  color: #2c261c;
}

.cpage-body { padding-top: clamp(16px, 3vw, 40px); position: relative; z-index: 2; }
.cpage-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(258px, 0.82fr);
  gap: clamp(34px, 5vw, 80px);
  align-items: start;
}

/* Form */
.cform > * + * { margin-top: clamp(16px, 2.2vw, 24px); }
.cform__row { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(16px, 2vw, 24px); }
.field label {
  display: block;
  font-family: var(--body);
  font-size: 13px;
  letter-spacing: 0;
  color: var(--ink-dim);
  margin-bottom: 8px;
}
.req { color: var(--purple); }
.field input,
.field select,
.field textarea {
  width: 100%;
  font-family: var(--body);
  font-size: 15px;
  color: var(--ink);
  background-color: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 13px 15px;
  transition: border-color 0.3s, background-color 0.3s;
}
.field textarea { resize: vertical; min-height: 120px; line-height: 1.5; }
.field input::placeholder,
.field textarea::placeholder { color: var(--ink-faint); opacity: 0.75; }
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--purple);
  background-color: #141416;
}
.field input:user-invalid,
.field textarea:user-invalid { border-color: #ff8a8a; }
.field select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23a5a096' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 15px center;
}
/* Readable native options for the no-JS path. Chrome paints the popup on the
   select's own dark background, so the old `color: #111` rendered near-black
   on near-black. With JS the select is replaced by .csel below. */
.field select option { color: var(--ink); background-color: #141416; }

/* Service dropdown — js/contact.js upgrades the native <select> into this.
   Chrome on Windows draws the native popup in a separate OS window that can't
   reach the page's webfonts, so the open list fell back to Segoe UI and ignored
   the palette. Owning the list is the only way to get Mulish + brand colours.
   The <select> stays in the DOM (hidden) as the submitted form control. */
.csel { position: relative; }
.csel__btn {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  font-family: var(--body);
  font-size: 15px;
  text-align: left;
  color: var(--ink);
  background-color: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 13px 15px;
  cursor: pointer;
  transition: border-color 0.3s, background-color 0.3s;
}
.csel__btn:focus-visible,
.csel.is-open .csel__btn {
  outline: none;
  border-color: var(--purple);
  background-color: #141416;
}
.csel__value.is-placeholder { color: var(--ink-faint); opacity: 0.75; }
.csel__arrow {
  flex: none;
  width: 14px;
  height: 14px;
  fill: none;
  stroke: var(--ink-dim);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 0.3s var(--ease);
}
.csel.is-open .csel__arrow { transform: rotate(180deg); }
.csel__list {
  position: absolute;
  z-index: 20;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  margin: 0;
  padding: 6px;
  list-style: none;
  max-height: 264px;
  overflow-y: auto;
  background-color: #141416;
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 0.2s var(--ease), transform 0.25s var(--ease), visibility 0s linear 0.25s;
}
.csel.is-open .csel__list {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition: opacity 0.2s var(--ease), transform 0.25s var(--ease), visibility 0s;
}
.csel__list:focus { outline: none; }
.csel__opt {
  font-family: var(--body);
  font-size: 15px;
  color: var(--ink);
  padding: 10px 12px;
  border-radius: 7px;
  cursor: pointer;
  transition: background-color 0.15s, color 0.15s;
}
.csel__opt:hover,
.csel__opt.is-active {
  background-color: rgba(167, 139, 250, 0.16);
  color: #fff;
}
.csel__opt[aria-selected="true"] { color: var(--purple); }
@media (prefers-reduced-motion: reduce) {
  .csel__list { transition: visibility 0s; }
  .csel__arrow { transition: none; }
}
.cform__hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.cform__foot { display: flex; flex-wrap: wrap; align-items: center; gap: 18px; }
/* Body font, not --mono: this is a prose sentence, and --mono is for small
   labels/numbers. Matches the field labels (--body at 13px). */
.cform__status { font-family: var(--body); font-size: 13px; line-height: 1.5; color: var(--purple); margin: 0; }
.cform__status.is-error { color: #ff8a8a; }

/* Side panel */
.cpage-aside {
  display: grid;
  gap: clamp(22px, 3vw, 32px);
  padding: clamp(24px, 3vw, 34px);
  border: 1px solid var(--line-soft);
  border-radius: 16px;
  background: rgba(236, 231, 219, 0.015);
}
.aside__label {
  font-family: var(--body);
  font-size: 13px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-faint);
  margin-bottom: 8px;
}
.aside__mail {
  font-family: var(--serif);
  font-size: 14.5px;
  word-break: break-all;
  border-bottom: 1px solid var(--line);
  padding-bottom: 3px;
  transition: border-color 0.3s;
}
.aside__mail:hover { border-color: var(--ink); }
.aside__val { font-family: var(--body); font-size: 16px; color: var(--ink); }

/* Entrance (CSS-only so content is never JS-dependent to appear) */
@keyframes crise { from { opacity: 0; transform: translateY(22px); } to { opacity: 1; transform: none; } }
.crise { animation: crise 0.9s var(--ease) both; }
/* Keyed to the classes, not :nth-child — adding or removing a hero element
   used to silently reshuffle the stagger onto the wrong ones. */
.cpage-title.crise { animation-delay: 0.1s; }
.cpage-sub.crise { animation-delay: 0.22s; }
.cform.crise { animation-delay: 0.32s; }
.cpage-aside.crise { animation-delay: 0.44s; }

@media (max-width: 860px) {
  .cpage-grid { grid-template-columns: 1fr; gap: clamp(34px, 8vw, 52px); }
  .cpage-aside { order: 2; }
}
@media (max-width: 520px) {
  .cform__row { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
  .crise { animation: none; }
}

/* ============================================================
   Cookie consent banner (Google Consent Mode v2 — see js/consent.js)
   Compact non-modal card, anchored bottom-right. "Reject all" and "Accept all"
   share one footprint and equal contrast: refusing must be as easy as
   accepting (CNIL).
   ============================================================ */
.consent {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 300;
  display: flex;
  justify-content: flex-end;
  padding: clamp(12px, 2vw, 24px);
  pointer-events: none;            /* clicks pass through the gutter beside the card */
  opacity: 0;
  visibility: hidden;
  transform: translateY(16px);
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease), visibility 0.4s;
}
.consent.is-open { opacity: 1; visibility: visible; transform: none; }
.consent__card {
  pointer-events: auto;
  width: 100%;
  max-width: 380px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: clamp(16px, 2vw, 22px);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(6px);
}
.consent__eyebrow {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--purple);
  margin-bottom: 8px;
}
.consent__text {
  font-family: var(--body);
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink-dim);
}
.consent__prefs {
  margin-top: 16px;
  border-top: 1px solid var(--line-soft);
}
.consent__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin: 0;
  padding: 12px 2px;
  border-bottom: 1px solid var(--line-soft);
  cursor: pointer;
}
.consent__row--locked { cursor: default; }
.consent__label { display: flex; flex-direction: column; gap: 2px; }
.consent__name {
  font-family: var(--body);
  font-weight: 600;
  font-size: 14.5px;
  color: var(--ink);
}
.consent__desc {
  font-family: var(--body);
  font-size: 13px;
  color: var(--ink-faint);
}
.consent__always {
  flex: none;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.consent__toggle {
  flex: none;
  width: 18px;
  height: 18px;
  accent-color: var(--purple);
  cursor: pointer;
}
.consent__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(14px, 2vw, 22px);
  margin-top: 18px;
}
/* Action controls read like the header nav (.nav a): uppercase display face
   with the same left-anchored underline-swipe. Reject and Accept share one
   size / face / animation (CNIL: refusing as easy as accepting) and differ only
   in colour — Accept in brand purple, Reject in cream. */
.consent__btn {
  position: relative;
  background: none;
  border: 0;
  padding: 4px 0;
  font-family: var(--disp);
  font-weight: 500;
  font-size: 13.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  opacity: 0.85;
  transition: opacity 0.3s;
}
.consent__btn:hover { opacity: 1; }
.consent__btn::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s var(--ease);
}
.consent__btn:hover::after,
.consent__btn:focus-visible::after { transform: scaleX(1); }
.consent__btn:focus-visible { outline: none; opacity: 1; }
/* Customise / Save sit left; the two decisions push to the right. */
.consent__btn--link,
.consent__btn--save { margin-right: auto; }
.consent__btn--link { color: var(--ink-dim); }
.consent__btn--reject { color: var(--ink); }
.consent__btn--accept,
.consent__btn--save { color: var(--purple); }
@media (max-width: 560px) {
  .consent__actions { gap: 18px 24px; }
  .consent__btn--link,
  .consent__btn--save { width: 100%; margin: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .consent,
  .consent__btn,
  .consent__btn::after { transition: none; }
}

/* Footer "Cookie preferences" — a button that reads as a footer link. */
.site-footer__cookies {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  transition: color 0.3s;
}
.site-footer__cookies:hover { color: var(--ink); }

/* ============================================================
   Legal pages (terms.html) — readable long-form content on the
   opaque .cpage-body, using the site's type + purple accents.
   ============================================================ */
.legal { max-width: 74ch; margin: 0 auto; }
.legal__meta {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--purple);
  margin-bottom: clamp(28px, 5vw, 48px);
}
.legal__intro {
  font-family: var(--body);
  font-size: clamp(16px, 1.25vw, 18px);
  line-height: 1.7;
  color: var(--ink);
  margin-bottom: clamp(32px, 5vw, 56px);
}
.legal__section { margin-bottom: clamp(30px, 4.5vw, 48px); }
.legal__section h2 {
  display: flex;
  align-items: baseline;
  gap: 14px;
  font-family: var(--disp);
  font-weight: 600;
  font-size: clamp(19px, 2vw, 24px);
  letter-spacing: -0.01em;
  color: var(--ink);
  margin-bottom: 14px;
}
.legal__num {
  flex: none;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.02em;
  color: var(--purple);
}
.legal__section p,
.legal__section li {
  font-family: var(--body);
  font-size: clamp(15px, 1.05vw, 16.5px);
  line-height: 1.75;
  color: var(--ink-dim);
}
.legal__section p + p { margin-top: 14px; }
.legal__section ul { margin: 12px 0 0; padding-left: 20px; }
.legal__section li { margin-bottom: 10px; }
.legal__section li::marker { color: var(--ink-faint); }
.legal strong { color: var(--ink); font-weight: 600; }
.legal a,
.legal__inline-btn {
  color: var(--purple);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color 0.3s;
}
.legal a:hover,
.legal__inline-btn:hover { color: var(--ink); }
.legal__inline-btn { font: inherit; background: none; border: 0; padding: 0; cursor: pointer; }

/* ============================================================
   Off-screen animation parking
   ============================================================
   The infinite decorative loops (titleWave, headingSheen, ctaGradientPan,
   rot-flow) all animate a background-position, several through
   background-clip:text. Those can't go to the compositor, so each repaints on
   the main thread every frame — including while far off-screen, on top of the
   hero shader. js/main.js adds .anim-idle when the element leaves the
   viewport; see the note there.

   MUST live last in the file, and the class is doubled on purpose. Every rule
   this overrides sets the `animation` SHORTHAND, which resets
   animation-play-state to `running` — so a single .anim-idle either loses on
   specificity (.rot__word.is-glow) or on source order (.principle--featured
   and its h3). Doubling it to (0,2,x) plus last-in-file wins both. Moving this
   block up, or dropping a class, silently un-pauses whatever now follows it.

   `paused` holds the 0% keyframe rather than clearing the animation, so the
   gradient-filled headings and labels stay visible, never blank. */
.anim-idle.anim-idle,
.anim-idle.anim-idle::before,
.anim-idle.anim-idle::after,
.anim-idle.anim-idle .cbtn__label,
.anim-idle.anim-idle h3 { animation-play-state: paused; }
