/* ============================================================
   Portfolio hero — production styles
   Single source of truth for index.html.
   Relative url()s resolve from this file's location (repo root), so the
   ./assets/... paths match the HTML.
   ============================================================ */

/* ----- Futura LT (display) — web-licensed, shipped in assets/fonts ----- */
@font-face {
  font-family: "Futura LT";
  src: url("./assets/fonts/FuturaLT-Bold.woff2") format("woff2"),
       url("./assets/fonts/FuturaLT-Bold.woff") format("woff");
  font-weight: 700; font-style: normal; font-display: swap;
}

:root {
  /* --- semantic color tokens (mirrors the Figma "Semantic" collection) --- */
  --surface-default: #fbfbfe;
  --surface-raised:  #f9f9ff;   /* alternating case-band tint */
  --text-primary:    #252626;
  --text-secondary:  #55585b;   /* Figma var is misspelled "text/seconday" */
  --text-accent:     #4549e7;
  --accent-default:  #4549e7;
  --text-on-accent:  #fbfbfe;
  --indigo-overlay:  rgba(38, 33, 92, 0.5);
  --neutral-200:     #e0e5ea;                         /* new neutral shade (cool light grey) */
  --border-default:  var(--neutral-200);              /* hairline frame/divider */
  --logo-tint:       #55585b;                         /* solid monochrome fill for client logos */
  --surface-placeholder: #d9dbe6;                     /* case image placeholder (cool grey) */
  --surface-deep:    #26215c;                         /* indigo/900 — deep closing surface (About) */
  --accent-subtle:   #cecbf6;                         /* indigo/100 — light accent for dark surfaces */

  --font-display: "Futura LT", "Futura", "Century Gothic", "Trebuchet MS", sans-serif;
  --font-body: "Source Sans 3", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  --gutter: clamp(24px, 8.33vw, 120px);   /* 120px at 1440 */
  --stage-max: 1800px;                     /* strategy B: cap & center, image bleeds past it */
  --content-max: 678px;

  /* --- DEFAULT (Figma-frame) type & spacing ---
     Base values. Also used <961px (mobile, where the clamps scale down).
     Overridden by the viewport tiers further below. */
  --h1-size:     clamp(42px, 11vw, 112px);
  --role-size:   clamp(26px, 4vw, 40px);
  --body-size:   clamp(16px, 1.4vw, 18px);
  --gap-section: clamp(32px, 4.4vw, 64px);   /* name (H1) ↔ "Designer & Researcher" */
  --gap-desc:    40px;                         /* "Designer & Researcher" ↔ body */
  --gap-cta:     40px;                         /* body ↔ button group */
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
  font-family: var(--font-body);
  background: var(--surface-default);
  color: var(--text-primary);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;   /* guards the full-bleed image/footer */
}
img { display: block; max-width: 100%; }
.accent { color: var(--text-accent); }

/* ===== Frame (XL) ===== */
.xl {
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(360px, 46vw, 660px);
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "topbar  topbar"
    "content image"
    "footer  footer";
  min-height: 100vh;
  background: var(--surface-default);
  overflow: hidden;
}

.top-bar {
  grid-area: topbar;
  display: flex; align-items: flex-end;
  padding: clamp(20px, 5vw, 40px) var(--gutter) clamp(14px, 2.5vw, 28px);
}
.monogram {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(24px, 3vw, 32px); line-height: 1; color: var(--text-primary);
}

/* Sticky monogram bar — fixed at the top, solid, with a bottom hairline that
   matches the client-logos borders. The monogram sits with 24px above and below.
   It stays in place (no slide) and a tiny fade-in (`--visible`) reveals it once
   the user scrolls past the logos — animations.js fires this exactly when the
   logo bar's bottom hairline reaches this bar's bottom hairline, so they overlap. */
.monogram-bar {
  position: fixed; inset: 0 0 auto 0; z-index: 20;
  display: flex; align-items: center;
  padding: 24px var(--gutter);
  background: var(--surface-default);
  border-bottom: 1px solid var(--border-default);
  pointer-events: none;            /* the bar passes clicks through; only the mark is clickable */
  opacity: 0;                      /* sits in place; the fade-in reveals it (no slide) */
  visibility: hidden;              /* keep the button out of the tab order while hidden */
  transition: opacity 0.15s ease, visibility 0.15s ease;
}
.monogram-bar--visible { opacity: 1; visibility: visible; }
.monogram-bar__mark {
  font-family: var(--font-display); font-weight: 700; line-height: 1;
  font-size: clamp(24px, 3vw, 32px); color: var(--text-primary);
  /* bare button → looks like the wordmark, but clicks to scroll back to top */
  appearance: none; -webkit-appearance: none;
  margin: 0; padding: 0; border: 0; background: none;
  pointer-events: auto; cursor: pointer;
}
.monogram-bar__mark:focus-visible {
  outline: 2px solid var(--accent-default); outline-offset: 4px; border-radius: 2px;
}
/* The mark must line up with the page content (the `.xl` stage) at every width.
   The bar is fixed + full-width, so (like the case top bar) it has to mirror the
   `.xl` gutter on phone/tablet, and inset to the centred 1800px stage on desktop
   (the same padding the client-logo bar uses to align with the stage). */
@media (max-width: 699.98px) { .monogram-bar { --gutter: 24px; } }
@media (min-width: 700px) and (max-width: 960px) {
  .monogram-bar { --gutter: clamp(40px, calc(40px + (100vw - 700px) * 0.2154), 96px); }
}
@media (min-width: 961px) {
  .monogram-bar { padding-inline: max(var(--gutter), calc((100vw - var(--stage-max)) / 2 + var(--gutter))); }
}
@media (prefers-reduced-motion: reduce) { .monogram-bar { transition: none; } }

.content {
  grid-area: content; align-self: center;
  display: flex; flex-direction: column; gap: var(--gap-cta);
  padding-left: var(--gutter); padding-right: clamp(16px, 3vw, 40px);
  max-width: var(--content-max);
}
.text { display: flex; flex-direction: column; gap: var(--gap-section); }
.h1 {
  display: flex; flex-direction: column;
  font-family: var(--font-display); font-weight: 700; line-height: 1; letter-spacing: -0.0125em;
  font-size: var(--h1-size);
}
.h1 .line { white-space: nowrap; }
.h1 .line--accent { color: var(--text-accent); }
.h1 .line--primary { color: var(--text-primary); }

.description { display: flex; flex-direction: column; gap: var(--gap-desc); }
.role {
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--role-size); line-height: 1.05; letter-spacing: -0.005em; color: var(--text-primary);
}
/* Tagline roll (cycling roles) — armed by animations.js (`role--roll`) only when
   JS+GSAP run and motion is allowed; otherwise the static `.role` above shows.
   The container is a fixed-height mask; each phrase is a `.role__line` of
   `.role__word`s that GSAP rolls vertically (clipped by the mask). Font-size is
   fitted in JS so the widest phrase never wraps. */
/* Height leaves room for Futura's descenders (g, p, j, y) above and below the
   centred line so `overflow: hidden` doesn't clip them; the roll distance tracks
   this height (read live in JS), so the constant-gap guarantee is unaffected. */
.role--roll { position: relative; overflow: hidden; height: 1.45em; }
.role--roll .role__line {
  position: absolute; inset: 0; margin: 0;
  display: flex; align-items: center; gap: 0.25em;
  line-height: 1; white-space: nowrap;
}
.role--roll .role__word { display: inline-block; will-change: transform; }
.role--roll .role__word--accent { color: var(--text-accent); }
.body {
  font-family: var(--font-body); font-weight: 400;
  font-size: var(--body-size); line-height: 1.5; color: var(--text-secondary);
  max-width: 518px;
}

.button-group { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.button {
  font-family: var(--font-body); font-weight: 700; font-size: 16px; line-height: 1;
  padding: 20px 24px; border-radius: 80px; border: none; text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5em;
  white-space: nowrap; cursor: pointer; transition: opacity 0.15s ease;
}
/* hover/press affordance is the GSAP "Lift" (see animations.js) */
.button--primary { background: var(--accent-default); color: var(--text-on-accent); }
.button--secondary { background: transparent; color: var(--text-accent); }

.hero-image {
  grid-area: image; position: relative; min-height: 320px;
  border-top-left-radius: clamp(120px, 25vw, 368px);
  overflow: hidden; background: #d9dbe6;
}
.hero-image__photo { width: 100%; height: 100%; object-fit: cover; object-position: center 22%; }
.hero-image__overlay {
  position: absolute; inset: 0;
  background: var(--indigo-overlay); mix-blend-mode: plus-lighter; pointer-events: none;
}

.logo-ticker {
  grid-area: footer; background: var(--surface-default); min-height: 157px;
  border-top: 1px solid var(--border-default);
  border-bottom: 1px solid var(--border-default);
  display: flex; align-items: center; justify-content: center;
  padding: 24px var(--gutter);
}
/* marquee viewport — clips the moving track; when the marquee runs (is-marquee,
   added by animations.js) it fades the track at both edges so logos fade in on
   the right and out on the left. */
.logos-viewport { width: 100%; overflow: hidden; }
.logos-viewport.is-marquee {
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}
.logos {
  list-style: none; display: flex; align-items: center;
  gap: 120px;  /* spacing between logos on mobile; 160px on large (below) */
  /* no-JS / reduced-motion fallback: wrap & centre so every logo is visible */
  flex-wrap: wrap; justify-content: center;
}
@media (min-width: 961px) { .logos { gap: 160px; } }
@media (max-width: 549.98px) { .logos { gap: 64px; } }   /* tighter on small phones */
/* With JS active the strip is a single row from page load, so the reserved
   logo-area height never changes when the marquee starts (no layout jump). */
html.js .logos { flex-wrap: nowrap; justify-content: flex-start; }
/* marquee track (JS): sized to content so GSAP can slide it left */
.logos--marquee { width: max-content; }
.logos li { display: flex; align-items: center; flex: 0 0 auto; }

/* client logos: each SVG's shape recoloured to a solid fill via CSS mask (the
   mask uses the alpha/shape, so the SVG's own colours are ignored). aspect-ratio
   keeps proportions; height is tuned per logo for equal visual weight. */
.logo {
  display: block; height: 28px; background-color: var(--logo-tint);
  -webkit-mask-position: center;  mask-position: center;
  -webkit-mask-size: contain;     mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
.logo--ns            { aspect-ratio: 165 / 64;  height: 30px;
  -webkit-mask-image: url(./assets/ns.svg);            mask-image: url(./assets/ns.svg); }
.logo--backbase      { aspect-ratio: 301 / 52;  height: 22px;
  -webkit-mask-image: url(./assets/backbase.svg);      mask-image: url(./assets/backbase.svg); }
.logo--ziggo         { aspect-ratio: 184 / 64;  height: 29px;
  -webkit-mask-image: url(./assets/ziggo.svg);         mask-image: url(./assets/ziggo.svg); }
.logo--essent        { aspect-ratio: 291 / 64;  height: 22.8px;
  -webkit-mask-image: url(./assets/essent.svg);        mask-image: url(./assets/essent.svg); }
.logo--postnl        { aspect-ratio: 1 / 1;     height: 42px;
  -webkit-mask-image: url(./assets/postnl.svg);        mask-image: url(./assets/postnl.svg); }
.logo--timing        { aspect-ratio: 225 / 71;  height: 28.67px; transform: translateY(4.6px);  /* drop so its baseline aligns with essent's */
  -webkit-mask-image: url(./assets/timing.svg);        mask-image: url(./assets/timing.svg); }
.logo--libertyglobal { aspect-ratio: 89 / 99;   height: 48px;
  -webkit-mask-image: url(./assets/libertyglobal.svg); mask-image: url(./assets/libertyglobal.svg); }
.logo--toogethr      { aspect-ratio: 267 / 119; height: 30px;
  -webkit-mask-image: url(./assets/toogethr.svg);      mask-image: url(./assets/toogethr.svg); }

/* ============================================================
   DESKTOP (≥961px) — Strategy B: cap & center the stage at 1800,
   let the image + footer bleed to the viewport edges beyond it.
   ============================================================ */
@media (min-width: 961px) {
  .xl { max-width: var(--stage-max); margin-inline: auto; overflow: visible; }
  .hero-image {
    margin-right: min(0px, calc((var(--stage-max) - 100vw) / 2));
  }
  .logo-ticker {
    margin-inline: min(0px, calc((var(--stage-max) - 100vw) / 2));
    padding-inline: max(var(--gutter), calc((100vw - var(--stage-max)) / 2 + var(--gutter)));
  }
}

/* ===== type & spacing tiers — width AND height aware =====
   Shorter viewports get smaller type/spacing so the hero keeps fitting.
   Cascade order matters: LARGER first, then SMALLER (overrides on short
   screens), then EXTRA-SMALL (overrides on very short screens). */

/* ----- LARGER · only when wide (≥1561) AND genuinely tall (≥1080) -----
   The big 136px type needs real vertical room; below 1080 tall it crams,
   so wide-but-short viewports fall back to the Default tier. */
@media (min-width: 1561px) and (min-height: 1080px) {
  .xl {
    --h1-size:     clamp(112px, 8vw, 136px);
    --role-size:   clamp(40px, 2.8vw, 48px);
    --body-size:   clamp(18px, 1.2vw, 20px);
    --gap-section: clamp(64px, 5vw, 92px);
    --gap-desc:    clamp(32px, 2.4vw, 40px);
    --gap-cta:     clamp(64px, 5vw, 92px);
    --content-max: 760px;
  }
}

/* ----- 1431–1560px wide & tall: base = Figma-frame defaults ----- */

/* ----- SMALLER · narrow widths (961–1430) OR short viewports (≤860 tall) ----- */
@media (min-width: 961px) and (max-width: 1430px),
       (min-width: 961px) and (max-height: 860px) {
  .xl {
    --h1-size:     clamp(72px, 7vw, 96px);
    --role-size:   clamp(28px, 2.6vw, 34px);
    --body-size:   16px;
    --gap-section: clamp(28px, 3.4vw, 48px);
    --gap-desc:    clamp(16px, 1.8vw, 24px);
    --gap-cta:     clamp(28px, 3.4vw, 48px);
    --content-max: 600px;
  }
}

/* ----- EXTRA-SMALL · very short viewports (≤680 tall) ----- */
@media (min-width: 961px) and (max-height: 680px) {
  .xl {
    --h1-size:     clamp(56px, 5.2vw, 76px);
    --role-size:   clamp(24px, 2vw, 30px);
    --body-size:   15px;
    --gap-section: clamp(20px, 2.4vw, 32px);
    --gap-desc:    18px;
    --gap-cta:     18px;
    --content-max: 560px;
  }
}

/* ----- short viewports also trim the chrome (top bar + footer) so the hero fits ----- */
@media (min-width: 961px) and (max-height: 860px) {
  .top-bar { padding-top: clamp(16px, 3vw, 28px); padding-bottom: clamp(10px, 2vw, 18px); }
  .logo-ticker { min-height: 116px; }
}
@media (min-width: 961px) and (max-height: 680px) {
  .logo-ticker { min-height: 92px; }
}

/* ===== phones (<700px) — centered hero with a circular avatar above the name
   (Figma node 36:264): top-bar → avatar → H1 → tagline → body → buttons, each
   32px apart; avatar/H1/tagline/body centered, buttons fill the width (capped at
   240px, group centred). Tablets ≥700px keep the original stacked layout (next
   block). 699.98 (not 700) leaves no sub-pixel gap before that block's 700px. ===== */
@media (max-width: 699.98px) {
  .xl {
    --gutter: 24px;          /* tighter side margins on phones */
    --body-size: 16px;       /* body copy fixed at 16px */
    --h1-size: clamp(40px, 12vw, 48px);   /* 48px at the 402px design width */
    /* Tagline capped at the 28px design size, shrinking on narrow phones so the
       static "Designer & Researcher" never wraps (the roll fits the longest
       phrase in JS on top of this). 11.9 = Futura width per 1px; 52 = gutters + safety. */
    --role-size: min(28px, calc((100vw - 52px) / 11.9));
    --gap-section: 32px;     /* H1 ↔ tagline */
    --gap-desc:    32px;     /* tagline ↔ body */
    --gap-cta:     32px;     /* body ↔ buttons */
    grid-template-columns: minmax(0, 1fr);   /* clamp min-content so the wide marquee track can't blow the grid out */
    grid-template-rows: auto auto auto auto;
    grid-template-areas: "topbar" "image" "content" "footer";
    align-content: start;    /* stack from the top: the logo bar flows under the content, not pinned to the viewport bottom */
    min-height: 0;           /* content-height hero (no 100vh floor) so the gap from the logo bar to the first case stays constant instead of growing with viewport height */
  }
  .top-bar { padding: 32px var(--gutter) 0; }
  /* The hero "M." is hidden under JS (it lives in the sticky scroll bar), so on
     phones the top bar is just empty space above the avatar. Collapse it under JS
     so the avatar sits the same distance from the top as the gap below it (the
     space above = the space below = the avatar's 32px margin). No-JS keeps the bar
     so the brand mark still shows. */
  html.js .top-bar { display: none; }
  .monogram { font-size: 32px; }
  .logo-ticker { min-height: 105px; margin-top: 64px; }   /* ~33% shorter bar, sitting 64px below the buttons */

  /* profile photo → centered 180px circle (keeps the indigo overlay,
     clipped to the circle by the figure's overflow:hidden + 50% radius) */
  .hero-image {
    width: 180px; height: 180px; min-height: 0;
    margin: 32px auto;
    border-radius: 50%;
  }

  .content {
    align-self: start;
    padding: 0 var(--gutter);   /* gap to the logo bar comes from its 64px margin-top */
    max-width: none;
  }

  /* centre the name, tagline, and description */
  .h1   { align-items: center; text-align: center; }
  .role { text-align: center; }
  .role--roll .role__line { justify-content: center; }
  .body { text-align: center; margin-inline: auto; }

  /* CTAs: equal halves, capped at 240px each, group centred */
  .button-group { width: 100%; flex-wrap: nowrap; justify-content: center; }
  .button { flex: 1 1 0; min-width: 0; max-width: 240px; }
}

/* ===== tablet / large landscape (700–960px) — keep the ORIGINAL stacked layout
   (left-aligned content, then the banner image below). The phone block above
   doesn't apply here, so base styles + these few overrides reproduce it. ===== */
@media (min-width: 700px) and (max-width: 960px) {
  .xl {
    --gutter: clamp(40px, calc(40px + (100vw - 700px) * 0.2154), 96px);   /* side margins ramp 40px (700) → 96px (960) */
    --body-size: 16px;       /* body copy fixed at 16px */
    /* "Designer & Researcher" sized to fill the content width on ONE line.
       11.9 = string width per 1px in Futura LT Bold; 54px = 2×24 margins + safety. */
    --role-size: min(calc((100vw - 54px) / 11.9), 44px);
    grid-template-columns: minmax(0, 1fr);   /* clamp min-content so the wide marquee track can't blow the grid out */
    grid-template-rows: auto auto auto auto;
    grid-template-areas: "topbar" "content" "image" "footer";
  }
  .content {
    align-self: start;
    padding: clamp(16px,6vw,48px) var(--gutter) clamp(32px,8vw,56px);
    max-width: none;
  }
  .hero-image { min-height: clamp(300px, 62vw, 480px); border-top-left-radius: clamp(80px, 24vw, 220px); }
}

/* ===== Case studies =====
   Full-bleed alternating bands (sibling of .xl, so the band background spans the
   viewport edge-to-edge). Each band is a two-column row — text + image placeholder
   — capped and centred. Desktop (≥961px) alternates the image side and the surface
   tint band-to-band; below 961px the row stacks with the image on top (mobile design). */
.case {
  padding: clamp(64px, 12vw, 128px) var(--gutter);
  background: var(--surface-default);
}
.case:nth-child(even) { background: var(--surface-raised); }

.case__row {
  display: flex; flex-direction: column;
  gap: clamp(32px, 6vw, 56px);
  max-width: 1560px; margin-inline: auto;
}
.case__media {
  order: -1;                       /* stacked: image sits on top */
  width: 100%; height: clamp(220px, 56vw, 391px);
  background: var(--surface-placeholder);
  border-radius: clamp(24px, 5vw, 40px);
}
.case__text {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: clamp(32px, 4.4vw, 64px);
}
.case__heading {
  display: flex; flex-direction: column;
  gap: clamp(16px, 2.2vw, 32px);
  font-family: var(--font-display); font-weight: 700; line-height: 1.2;
  color: var(--text-primary);
}
.case__client { font-size: clamp(20px, 2.4vw, 24px); letter-spacing: -0.0025em; }
.case__title  { font-size: clamp(28px, 3.6vw, 40px); font-weight: 700; letter-spacing: -0.005em; }

@media (min-width: 961px) {
  .case__row { flex-direction: row; align-items: center; gap: clamp(48px, 5.5vw, 80px); }
  .case__text,
  .case__media { flex: 1 1 0; min-width: 0; }            /* equal halves */
  .case__media { order: 0; height: clamp(320px, 27vw, 391px); }
  .case:nth-child(even) .case__media { order: -1; }      /* image on the left */
}

/* ============================================================
   ABOUT / CONTACT — closing landing band (after .cases)
   Deep-indigo surface so it reads as a deliberate, distinct close — not
   another case band. Light text; inverted primary + ghost secondary CTAs
   (both keep the GSAP "Lift" — see the Buttons note below). Desktop:
   asymmetric two-column, photo LEFT (the last case's image sits right, so
   this alternates). Tablet/phone: one column; phone follows the hero's
   mobile conventions.
   ============================================================ */
.about {
  background: var(--surface-deep);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  padding: clamp(72px, 12vw, 144px) var(--gutter) clamp(104px, 17vw, 200px);  /* generous bottom = page close */
}
.about__inner {
  max-width: 1180px; margin-inline: auto;
  display: flex; flex-direction: column;       /* stacked by default (tablet + phone) */
  gap: clamp(40px, 7vw, 72px);
}
/* Photo — organic two-corner shape (echoes the hero's big-radius language);
   placeholder surface shows behind until the real file is dropped in. */
.about__media {
  position: relative; overflow: hidden;
  width: 100%; max-width: 460px; aspect-ratio: 4 / 5;
  background: var(--surface-placeholder);
  border-radius: clamp(48px, 11vw, 96px) 40px clamp(48px, 11vw, 96px) 40px;
}
.about__photo { width: 100%; height: 100%; object-fit: cover; display: block; }
.about__text {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: clamp(18px, 2.4vw, 26px); max-width: 560px;
}
.about__kicker {
  font-family: var(--font-body); font-weight: 700;
  font-size: clamp(12px, 1vw, 13px); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--accent-subtle);
}
.about__heading {
  font-family: var(--font-display); font-weight: 700; line-height: 1.15;
  font-size: clamp(34px, 5.5vw, 60px); letter-spacing: -0.0075em; color: var(--text-on-accent);
}
.about__heading .accent { color: var(--accent-subtle); }   /* two-tone dot, lightened for the dark surface */
.about__body { font-size: clamp(16px, 1.5vw, 19px); line-height: 1.6; color: rgba(255, 255, 255, 0.74); }
.about__actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: clamp(8px, 1.4vw, 14px); }

/* CTAs on the dark surface — inverted primary (light fill) + ghost secondary
   (white text + hairline). Both still receive the GSAP "Lift" via ctaLift; the
   ghost uses a real border (not box-shadow) so the lift's shadow can't clobber
   it, and overrides --lift-sec-bg-* so the hover/press fill stays legible on dark. */
.about .button--primary { background: var(--surface-default); color: var(--accent-default); }
.about .button--secondary {
  color: var(--text-on-accent);
  border: 1px solid rgba(255, 255, 255, 0.42);
  --lift-sec-bg-hover: rgba(255, 255, 255, 0.16);
  --lift-sec-bg-down:  rgba(255, 255, 255, 0.24);
}

/* Desktop — asymmetric two-column (photo a touch wider), vertically centred */
@media (min-width: 961px) {
  .about__inner { flex-direction: row; align-items: center; gap: clamp(64px, 7vw, 120px); }
  .about__media { flex: 1.15 1 0; max-width: 540px; }
  .about__text  { flex: 1 1 0; }
}

/* Phone — hero mobile conventions: centred text, smaller photo, capped buttons */
@media (max-width: 699.98px) {
  .about { padding-left: 24px; padding-right: 24px; }
  .about__inner { align-items: center; }
  .about__media { max-width: 280px; }
  .about__text { align-items: center; text-align: center; max-width: none; }
  .about__actions { width: 100%; justify-content: center; flex-wrap: nowrap; }
  .about__actions .button { flex: 1 1 0; min-width: 0; max-width: 240px; }
}

/* ============================================================
   CASE STUDY PAGE (case-ov-pay.html — Nederlandse Spoorwegen / OV-Pas)
   Built 1:1 from the Figma frame "Case study - NS" (1440px). The top bar,
   dividers and full-bleed image span the viewport; the hero + section content
   use the SAME stage as the home page — capped at --stage-max (1800px) and
   centred, with --gutter sides — so the side margins line up exactly with the
   home hero/cases (content is 1200 at the 1440 design width, growing toward
   1560 on wider screens). Type follows the documented scale (H2/H4/H5/H6 + Label /
   Label Large / Body / Caption); colours reuse the semantic tokens. Reveals are
   in animations.js (buildCasePage) — PE-safe + scroll-driven, like the home page.
   ============================================================ */
/* height:auto overrides the global `html,body{height:100%}` — otherwise the body
   is only one viewport tall, which caps the sticky top bar's range to the first
   screen (it un-sticks after that). overflow-x:clip (not hidden) keeps sticky
   working — `overflow:hidden` on the body breaks position:sticky on iOS. */
.cs-body { background: var(--surface-default); overflow-x: clip; height: auto; min-height: 100vh; }

/* Page margins — mirror the home page's side gutter at every breakpoint. The home
   sets these on `.xl` (phone 24px; tablet a 40→96px ramp); desktop (≥961px) uses
   the `:root` base clamp(24px, 8.33vw, 120px), which the case page already
   inherits. Keep in sync with the `.xl` --gutter overrides further below. */
@media (max-width: 699.98px) { .cs-body { --gutter: 24px; } }
@media (min-width: 700px) and (max-width: 960px) {
  .cs-body { --gutter: clamp(40px, calc(40px + (100vw - 700px) * 0.2154), 96px); }
}

/* hero + sections: same centred stage as the home `.xl` (cap + gutter), so the
   side margins match the home hero exactly; full-bleed siblings stay full width */
.cs-hero,
.cs-section { max-width: var(--stage-max); margin-inline: auto; padding-inline: var(--gutter); }

/* ----- sticky top bar (monogram home link + Back to home) ----- */
.cs-topbar {
  position: sticky; top: 0; z-index: 20;
  background: var(--surface-default);
  border-bottom: 1px solid var(--border-default);
  transition: transform 0.3s ease;
}
/* Auto-hidden on phones while scrolling down (toggled by animations.js); slides
   back the instant the user scrolls up, reaches the top, or hits the bottom.
   Larger viewports never get this class — the bar is always visible there. */
.cs-topbar--hidden { transform: translateY(-100%); }
@media (prefers-reduced-motion: reduce) { .cs-topbar { transition: none; } }
.cs-topbar__inner {
  max-width: var(--stage-max); margin-inline: auto;
  display: flex; align-items: center; gap: clamp(20px, 4vw, 32px);
  padding: 22px var(--gutter);
}
.cs-monogram {
  font-family: var(--font-display); font-weight: 700; line-height: 1;
  font-size: clamp(24px, 3vw, 28px); color: var(--text-primary); text-decoration: none;
}
.cs-monogram:focus-visible { outline: 2px solid var(--accent-default); outline-offset: 4px; border-radius: 2px; }
/* Back-to-home — a plain text link (no border, no fill), accent-coloured, with a
   leading arrow that nudges left on hover. Padding is tap-target only (invisible). */
.cs-back {
  display: inline-flex; align-items: center; gap: 0.4em;
  font-family: var(--font-body); font-weight: 700; font-size: 16px; line-height: 1;
  color: var(--text-accent); text-decoration: none; white-space: nowrap;
  padding: 6px 8px;
}
.cs-back__arrow { display: inline-block; transition: transform 0.2s ease; }
.cs-back:focus-visible { outline: 2px solid var(--accent-default); outline-offset: 4px; border-radius: 4px; }
@media (hover: hover) and (pointer: fine) {
  .cs-back:hover .cs-back__arrow { transform: translateX(-3px); }
}

/* ----- hero ----- */
.cs-hero {
  display: flex; flex-direction: column; gap: clamp(36px, 4.4vw, 64px);
  padding-top: clamp(56px, 8.3vw, 120px); padding-bottom: clamp(28px, 4vw, 40px);
}
.cs-hero__header { display: flex; flex-direction: column; gap: clamp(18px, 2.2vw, 32px); }
.cs-hero__client {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(28px, 3.06vw, 44px); line-height: 1.205; letter-spacing: -0.005em;
  color: var(--text-primary);
}
.cs-hero__title {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(40px, 5.56vw, 80px); line-height: 1.1; letter-spacing: -0.01em;
  color: var(--text-primary);
}
.cs-keywords {
  font-family: var(--font-body); font-weight: 700;
  font-size: clamp(13px, 1.25vw, 18px); line-height: 1.4; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--text-accent);
}
.cs-keywords__sep { color: var(--text-primary); margin: 0 0.35em; }

.cs-hero__content { display: flex; flex-direction: column; gap: clamp(32px, 5.5vw, 80px); }
.cs-hero__media {
  width: 100%; height: clamp(240px, 25vw, 360px);
  background: #f5f5f5; border-radius: 24px;     /* Figma hero placeholder (lighter than the section placeholders) */
}
.cs-panel { display: flex; flex-direction: column; gap: clamp(24px, 2.8vw, 40px); }
.cs-panel__title {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(24px, 2.22vw, 32px); line-height: 1.1875; letter-spacing: -0.0025em;
  color: var(--text-primary);
}
.cs-panel__list {
  list-style: none; display: flex; flex-direction: column; gap: 20px;
  font-family: var(--font-body); font-weight: 400;
  font-size: clamp(16px, 1.25vw, 18px); line-height: 1.611; color: var(--text-secondary);
}
.cs-panel__list li { position: relative; padding-left: 27px; }
.cs-panel__list li::before { content: "•"; position: absolute; left: 8px; top: -0.02em; }

@media (min-width: 961px) {
  .cs-hero__content { flex-direction: row; align-items: center; }
  .cs-hero__media { flex: 1 1 0; min-width: 0; height: clamp(320px, 25vw, 360px); }
  .cs-panel { flex: 1 1 0; min-width: 0; }
}

/* ----- full-bleed hairline divider (40px above + below the line) ----- */
.cs-divider { height: 1px; border: 0; background: var(--border-default); margin-block: clamp(28px, 4vw, 40px); }

/* ----- generic section ----- */
.cs-section {
  display: flex; flex-direction: column; gap: clamp(28px, 3.3vw, 40px);
  padding-block: clamp(28px, 4vw, 40px);
}
.cs-section:last-child { padding-bottom: clamp(64px, 10vw, 120px); }   /* breathing room at the page foot */
.cs-section--outcome { padding-top: clamp(48px, 6.5vw, 80px); }        /* follows the full-bleed image (no divider) */
.cs-section__head { display: flex; flex-direction: column; gap: 16px; }
.cs-section__title {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(28px, 3.06vw, 44px); line-height: 1.205; letter-spacing: -0.005em;
  color: var(--text-primary);
}

/* eyebrow — numbered pill + label, both accent uppercase */
.cs-eyebrow { display: flex; align-items: center; gap: 12px; }
.cs-eyebrow__num {
  display: inline-flex; align-items: center; justify-content: center;
  height: 30px; padding-inline: 8px; border: 1px solid var(--border-default); border-radius: 999px;
  font-family: var(--font-body); font-weight: 700; font-size: 13px; line-height: 1;
  letter-spacing: 0.12em; color: var(--text-accent);
}
.cs-eyebrow__label {
  font-family: var(--font-body); font-weight: 700; font-size: 13px; line-height: 1.385;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-accent);
}

/* body prose — blank-line spacing between paragraphs matches the Figma */
.cs-prose {
  font-family: var(--font-body); font-weight: 400;
  font-size: clamp(16px, 1.25vw, 18px); line-height: 1.611; color: var(--text-secondary);
  max-width: 720px;
}
.cs-prose p + p { margin-top: 1.611em; }

/* stacked content (text then full-width figure) */
.cs-stack { display: flex; flex-direction: column; gap: clamp(28px, 3.3vw, 40px); }

/* split content (text beside a supporting image) */
.cs-split { display: flex; flex-direction: column; gap: clamp(32px, 4vw, 40px); }
.cs-split__media {
  width: 100%; min-height: clamp(240px, 56vw, 435px);
  background: var(--surface-placeholder); border-radius: 24px;
}
@media (min-width: 961px) {
  .cs-split { flex-direction: row; align-items: stretch; gap: clamp(48px, 5.5vw, 80px); }
  .cs-split__text { flex: 543 1 0; max-width: 543px; }
  .cs-split__media { flex: 577 1 0; min-height: 435px; }
}

/* full-width figures (define / align) */
.cs-figure { width: 100%; background: var(--surface-placeholder); border-radius: 24px; }
.cs-figure--define { height: clamp(240px, 35.5vw, 512px); }
.cs-figure--align  { height: clamp(280px, 40.3vw, 580px); }

/* full-bleed image — edge to edge, no radius */
.cs-fullbleed { width: 100%; height: clamp(300px, 41vw, 590px); background: var(--surface-placeholder); }

/* outcome stats — content-width blocks spread edge-to-edge (space-between), so the
   first sits flush against the container's left edge and the last against the right
   (Figma). Stack centred on phones. */
.cs-stats { display: flex; justify-content: space-between; align-items: flex-start; padding-block: 24px; text-align: center; }
.cs-stat { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: 8px; }
.cs-stat__num {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(52px, 5.56vw, 80px); line-height: 1.1; letter-spacing: -0.01em; color: var(--text-accent);
}
.cs-stat__label { font-family: var(--font-body); font-weight: 400; font-size: 15px; line-height: 1.533; color: var(--text-secondary); }
@media (max-width: 699.98px) { .cs-stats { flex-direction: column; align-items: center; gap: 32px; } }

/* more-work tiles — image-topped cards, two-up on tablet+ */
.cs-tiles { display: flex; flex-direction: column; gap: clamp(20px, 3vw, 36px); }
.cs-tile {
  display: flex; flex-direction: column; overflow: clip; text-decoration: none; color: inherit;
  background: var(--surface-default); border: 1px solid var(--border-default);
  border-radius: clamp(28px, 3vw, 40px);
}
.cs-tile:focus-visible { outline: 2px solid var(--accent-default); outline-offset: 4px; }
.cs-tile__media { width: 100%; height: clamp(200px, 20vw, 281px); background: var(--surface-placeholder); }
.cs-tile__body { display: flex; flex-direction: column; gap: clamp(20px, 2.2vw, 32px); padding: 32px 32px 48px; }
.cs-tile__titles { display: flex; flex-direction: column; gap: 8px; }
.cs-tile__client {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(20px, 1.67vw, 24px); line-height: 1.25; letter-spacing: -0.0025em; color: var(--text-primary);
}
.cs-tile__name {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(24px, 2.22vw, 32px); line-height: 1.1875; letter-spacing: -0.0025em; color: var(--text-primary);
}
.cs-tile__link { display: inline-flex; align-items: center; gap: 0.4em; font-family: var(--font-body); font-weight: 700; font-size: 16px; line-height: 1; color: var(--text-accent); }
.cs-tile__arrow { transition: transform 0.2s ease; }
@media (min-width: 700px) {
  .cs-tiles { flex-direction: row; }
  .cs-tile { flex: 1 1 0; min-width: 0; }
}
/* clickable-card affordance (mirrors the site's hover language; hover-fine only) */
@media (hover: hover) and (pointer: fine) {
  .cs-tile { transition: transform 0.25s ease, box-shadow 0.25s ease; }
  .cs-tile:hover { transform: translateY(-4px); box-shadow: 0 18px 40px -18px rgba(37, 38, 38, 0.22); }
  .cs-tile:hover .cs-tile__arrow { transform: translateX(4px); }
}

/* ============================================================
   PAGE-LOAD REVEAL (GSAP) — experiment, branch hero-flyin-reveal.
   Pre-hide animated elements, and ONLY when JS is active (`html.js`).
   With JS off, nothing here matches → everything renders normally.
   animations.js reveals them; the reduced-motion block is the CSS
   safety net (logos settle dimmed at .62, not full opacity).
   ============================================================ */
html.js .monogram,
html.js .h1,
html.js .role,
html.js .body,
html.js .button,
html.js .logo-ticker,
html.js .hero-image { opacity: 0; }

html.js .monogram,
html.js .h1,
html.js .role,
html.js .body,
html.js .button,
html.js .logo-ticker,
html.js .hero-image { will-change: transform, opacity; }

@media (prefers-reduced-motion: reduce) {
  /* .monogram stays hidden under JS — it's shown only in the sticky scroll bar */
  html.js .h1,
  html.js .role,
  html.js .body,
  html.js .button,
  html.js .logo-ticker,
  html.js .hero-image { opacity: 1; }
  /* no marquee under reduced motion → wrap so every logo stays visible */
  html.js .logos { flex-wrap: wrap; justify-content: center; }
  html.js .monogram,
  html.js .h1,
  html.js .role,
  html.js .body,
  html.js .button,
  html.js .logo-ticker,
  html.js .hero-image { will-change: auto; }
}

/* Case study page pre-hide (only that page has .cs-reveal). buildCasePage
   reveals these — the hero/top-bar on load, each section as it scrolls in.
   Reduced motion / no-JS leave everything visible. */
html.js .cs-reveal { opacity: 0; will-change: transform, opacity; }
@media (prefers-reduced-motion: reduce) {
  html.js .cs-reveal { opacity: 1; will-change: auto; }
}
