/* ============================================================
   Chenyu Wang — Product Designer Portfolio
   Soft pastel edition — periwinkle / lavender / navy

   ┌─ WANT TO TWEAK SOMETHING? ─────────────────────────────┐
   │ Start with the DESIGN TOKENS in :root below — colors,  │
   │ fonts, corner radius, layout width. Most visual        │
   │ changes only need edits there.                         │
   │ Cloud & mesh-gradient colors live in script.js         │
   │ (SETTINGS at the top of that file).                    │
   └────────────────────────────────────────────────────────┘

   Table of contents — search for the number to jump:
     [1] Design tokens (:root)
     [2] Base & shared helpers (.wrap, .eyebrow, .serif)
     [3] Background layers (mesh field, clouds, sparkles)
     [4] Nav
     [5] Hero & manifesto
     [6] Shared section scaffolding (+ reveal animation)
     [7] About (+ stat cards)
     [8] Experience timeline
     [9] Case study cards (homepage)
     [10] Skills
     [11] Contact & footer
     [12] Case study detail pages (cs-*)
     [13] Cursor & scroll progress
   ============================================================ */

/* Fonts: swap families here AND in the --serif/--sans tokens. */
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;1,400;1,500;1,600&family=Instrument+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap');

/* ============================================================
   [1] DESIGN TOKENS — edit here first
   ============================================================ */
:root {
  /* Page background (fallback + accents; the visible background
     is mostly the mesh gradient generated in script.js). */
  --bg: #f7f4fa;      /* off-white base (matches the mesh's underlayer) */
  --bg-2: #ece6f4;    /* soft lavender (case-study cover) */
  --bg-3: #f3e6ec;    /* pale pink-lilac */
  --bg-4: #e7ecf6;    /* pale blue-lilac */

  /* Text — "ink" is the deep navy used everywhere. */
  --ink: #29345a;
  /* Solid (100%-opacity) colors, not alpha-blended — alpha text would
     pick up whatever's behind it (mesh, clouds, cards), reading as
     washed out depending on what's underneath at any given moment. */
  --ink-dim: #29345a;   /* paragraphs — solid equivalent of ink @ 68% */
  --ink-faint: #9ca0b5; /* labels, hints — solid equivalent of ink @ 44% */

  /* Frosted-glass whites (cards, buttons, chips). */
  --white: #ffffff;
  --white-soft: rgba(255, 255, 255, 0.55);
  --white-faint: rgba(255, 255, 255, 0.28);
  --card: rgba(255, 255, 255, 0.42);       /* card fill */
  --card-hover: rgba(255, 255, 255, 0.62); /* card fill on hover */

  /* Hairlines & borders. */
  --line: rgba(41, 52, 90, 0.14);
  --line-strong: rgba(41, 52, 90, 0.3);

  /* Shape, motion, layout. */
  --radius: 22px;                          /* card corner radius */
  --ease: cubic-bezier(0.16, 1, 0.3, 1);   /* signature easing */
  --dur: 0.7s;                             /* base transition time */
  --maxw: 1180px;                          /* content column width */

  /* Type. */
  --serif: 'Cormorant Garamond', Georgia, serif;   /* italic accents */
  --sans: 'Instrument Sans', -apple-system, BlinkMacSystemFont, sans-serif;
}

/* ============================================================
   [2] BASE & SHARED HELPERS
   .wrap = centered content column · .eyebrow = small section
   label · .serif = italic accent inside headlines
   ============================================================ */
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }

html {
  background: var(--bg);
}
body {
  background: transparent;
  color: var(--ink);
}

body {
  margin: 0;
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

::selection {
  background: var(--ink);
  color: var(--bg);
}

a { color: inherit; text-decoration: none; }

.serif {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 500;
}

.eyebrow {
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-faint);
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.eyebrow::before {
  content: '✦';
  font-size: 10px;
  color: var(--ink-dim);
}

.wrap {
  max-width: var(--maxw);
  margin: 0 auto;
  /* left/right only — a shorthand "padding: 0 40px" here would zero out
     any element's top/bottom padding, since .wrap (a class) always beats
     a plain element selector like "section" on specificity regardless of
     source order. Elements needing vertical padding (section, .hero,
     .cs-hero, footer, etc.) set padding-top/padding-bottom themselves. */
  padding-left: 40px;
  padding-right: 40px;
}

/* ============================================================
   [3] BACKGROUND LAYERS
   .field = mesh gradient (static fallback here; the real one is
   generated per-visit in script.js) · .clouds-layer = vanta
   clouds mount · .sparkle = floating stars
   ============================================================ */
.field {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  /* Static no-JS fallback — script.js replaces this with a fresh
     generative composition using the same SETTINGS.mesh palette. */
  background:
    radial-gradient(80vw 65vh at 10% 2%, rgba(244, 181, 199, 0.55) 0%, rgba(244, 181, 199, 0) 58%),
    radial-gradient(70vw 60vh at 92% 6%, rgba(186, 208, 246, 0.5) 0%, rgba(186, 208, 246, 0) 56%),
    radial-gradient(85vw 70vh at 50% 46%, rgba(178, 161, 240, 0.55) 0%, rgba(178, 161, 240, 0) 65%),
    radial-gradient(75vw 65vh at 6% 88%, rgba(255, 214, 224, 0.48) 0%, rgba(255, 214, 224, 0) 58%),
    radial-gradient(80vw 70vh at 94% 92%, rgba(173, 199, 245, 0.55) 0%, rgba(173, 199, 245, 0) 56%),
    linear-gradient(180deg, #f7f4fc 0%, #f5f4f7 45%, #f3f5fa 100%);
}
.field::before,
.field::after {
  content: '';
  position: absolute;
  width: 70vw;
  height: 70vw;
  border-radius: 50%;
  filter: blur(110px);
  will-change: transform;
}
.field::before {
  background: radial-gradient(circle, rgba(186, 208, 246, 0.32), rgba(186, 208, 246, 0) 65%);
  top: -22%;
  left: -14%;
  animation: drift1 30s ease-in-out infinite alternate;
}
.field::after {
  background: radial-gradient(circle, rgba(178, 161, 240, 0.35), rgba(178, 161, 240, 0) 65%);
  bottom: -28%;
  right: -16%;
  animation: drift2 36s ease-in-out infinite alternate;
}
.field .halo { display: none; }

/* Dawn-sky top of the background, homepage only (body.home) — deep
   violet up top easing through lavender into a warm rose/peach glow at
   the horizon, with a soft "pre-sunrise" radial burst low in the sky,
   so the white "effortless" reads against it. Part of the background
   system but a body pseudo rather than a .field layer: .field is
   position:fixed and never scrolls, while the dawn must cover only the
   first viewport and slide away with the hero — so it needs its own
   document-anchored (scrolling) layer. Sits above .field (-1), below
   .clouds-layer (150), so the clouds are tinted from behind. The mask
   softens the bottom edge for the hand-off to the pale mesh below. */
body.home::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 100svh;
  z-index: 0;
  pointer-events: none;
  background:
    linear-gradient(
      180deg,
      rgba(66, 96, 202, 0.75) 0%,
      rgba(100, 132, 220, 0.6) 32%,
      rgba(148, 178, 234, 0.44) 58%,
      rgba(196, 204, 230, 0.34) 80%,
      rgba(240, 220, 200, 0.32) 100%
    ),
    radial-gradient(80vw 45vh at 50% 102%, rgba(248, 216, 182, 0.35) 0%, rgba(248, 216, 182, 0) 70%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 88%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 88%, transparent 100%);
}

/* Case-study pages only: a quiet creamy off-white gradient that sits
   above .field and fades in via scroll (initCaseFade() in script.js),
   washing out the vivid mesh as you scroll past the top/cover area. */
.field-fade {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(180deg, #fdfcfb 0%, #fbfaf8 45%, #faf9f6 100%);
}

/* Vanta clouds mount — full-viewport scene, masked so the clouds
   are solid and sticky in the bottom ~17% of the viewport and fade
   out higher up rather than ending in a hard rectangle. Starts at
   opacity 0; script.js scrubs it to scroll (in with the manifesto,
   gone once About fills the viewport). Colors: SETTINGS.clouds. */
.clouds-layer {
  position: fixed;
  inset: 0;
  z-index: 150;
  pointer-events: none;
  opacity: 0;
  will-change: opacity;
  mask-image: linear-gradient(to top, #000 0%, #000 17%, transparent 68%);
  -webkit-mask-image: linear-gradient(to top, #000 0%, #000 17%, transparent 68%);
}
.clouds-layer canvas {
  width: 100% !important;
  height: 100% !important;
}

/* Vanta birds mount — transparent full-viewport scene (backgroundAlpha
   0 in script.js) flying above the cloud deck (150) but behind hero
   copy (160) and nav. Opacity + a slight downward drift are scrubbed
   to scroll in initBirds(): the flock rides along as you scroll and
   fades out on the same curve as the clouds. */
.birds-layer {
  position: fixed;
  inset: 0;
  z-index: 155;
  pointer-events: none;
  opacity: 0;
}
.birds-layer canvas {
  width: 100% !important;
  height: 100% !important;
}
@keyframes drift1 {
  to { transform: translate(9vw, 12vh) scale(1.18); }
}
@keyframes drift2 {
  to { transform: translate(-7vw, -9vh) scale(1.12); }
}
@keyframes breathe {
  0%, 100% { opacity: 0.85; transform: translateX(-50%) scale(1); }
  50% { opacity: 1; transform: translateX(-50%) scale(1.08); }
}

/* Floating sparkles (count set in script.js SETTINGS). Drawn with a
   clip-path four-point star instead of the ✦ glyph — the glyph's arms
   are chunky at small sizes; the polygon's narrow waist (47%/53%)
   keeps the points slim. Size is set inline per-sparkle in script.js. */
.sparkle {
  position: fixed;
  background: rgba(255, 255, 255, 0.9);
  clip-path: polygon(50% 0%, 53% 47%, 100% 50%, 53% 53%, 50% 100%, 47% 53%, 0% 50%, 47% 47%);
  z-index: 0;
  pointer-events: none;
  animation: twinkle 5s ease-in-out infinite;
}
@keyframes twinkle {
  0%, 100% { opacity: 0.15; transform: scale(0.8); }
  50% { opacity: 0.9; transform: scale(1.15); }
}

/* ============================================================
   [13] SCROLL PROGRESS
   ============================================================ */

/* ============================================================
   [4] NAV
   ============================================================ */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 500; /* above clouds (150), hero (160) and the scroll progress bar (200) */
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 26px 40px;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), padding var(--dur) var(--ease);
  border-bottom: 1px solid transparent;
}
.nav.scrolled {
  /* No tint of its own — just frosted glass: whatever scrolls under
     the nav shows through, heavily blurred. */
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-bottom-color: var(--line);
  padding: 15px 40px;
}
.nav-logo {
  font-weight: 700;
  font-size: 17px;
  letter-spacing: -0.01em;
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.nav-logo .script {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 500;
  font-size: 19px;
  color: var(--ink-dim);
}
.nav-links {
  display: flex;
  gap: 34px;
  font-size: 15px;
  font-weight: 500;
}
.nav-links a {
  position: relative;
  color: var(--ink-dim);
  padding: 4px 0;
  transition: color 0.3s var(--ease);
}
.nav-links a::after {
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  width: 0%;
  height: 1.5px;
  background: var(--ink);
  transition: width 0.4s var(--ease);
}
.nav-links a:hover { color: var(--ink); }
.nav-links a:hover::after { width: 100%; }

.nav-cta {
  font-size: 14px;
  font-weight: 600;
  border: 1px solid var(--line-strong);
  background: var(--white-faint);
  padding: 10px 22px;
  border-radius: 999px;
  transition: all 0.35s var(--ease);
  backdrop-filter: blur(6px);
}
.nav-cta:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: #eceafd;
}

@media (max-width: 860px) {
  .nav-links { display: none; }
}

/* Scroll-in reveal animation for anything with data-reveal. */
[data-reveal] {
  opacity: 0;
  transform: translateY(30px);
  transition: opacity 1s var(--ease), transform 1s var(--ease);
}
[data-reveal].in {
  opacity: 1;
  transform: translateY(0);
}
[data-reveal-delay="1"] { transition-delay: 0.1s; }
[data-reveal-delay="2"] { transition-delay: 0.2s; }
[data-reveal-delay="3"] { transition-delay: 0.3s; }
[data-reveal-delay="4"] { transition-delay: 0.4s; }

/* ============================================================
   [5] HERO & MANIFESTO
   ============================================================ */
.hero {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  padding-top: 110px;
  padding-bottom: 80px;
  position: relative;
  z-index: 160; /* above .clouds-layer (150) so hero copy reads over the clouds */
}

.hero-kicker {
  font-size: clamp(20px, 2.6vw, 30px);
  font-weight: 500;
  color: var(--ink);
  line-height: 1.35;
}
.hero-kicker .serif { font-size: 1.08em; }

.hero-word {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 500;
  font-size: clamp(72px, 16vw, 230px);
  line-height: 0.95;
  color: var(--white);
  letter-spacing: -0.01em;
  margin: 8px 0 0;
  text-shadow: 0 2px 60px rgba(255, 255, 255, 0.45);
}

.hero-sub {
  max-width: 560px;
  margin-top: 38px;
  font-size: 17.5px;
  color: var(--ink-dim);
  line-height: 1.65;
}

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  font-weight: 600;
  padding: 15px 32px;
  border-radius: 999px;
  background: var(--ink);
  color: #eceafd;
  overflow: hidden;
  isolation: isolate;
  transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease);
  box-shadow: 0 10px 30px rgba(41, 52, 90, 0.22);
}
.btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 16px 40px rgba(41, 52, 90, 0.3);
}

.btn-ghost {
  background: var(--white-soft);
  color: var(--ink);
  border: 1px solid var(--line-strong);
  box-shadow: none;
  backdrop-filter: blur(8px);
}
.btn-ghost:hover {
  background: var(--white);
  box-shadow: 0 10px 30px rgba(41, 52, 90, 0.14);
}

/* Manifesto — full-screen statement lines (text in index.html). */
.manifesto {
  padding: 26vh 0;
  text-align: center;
}
.manifesto .m-line {
  font-size: clamp(28px, 4.6vw, 54px);
  font-weight: 500;
  line-height: 1.28;
  color: var(--ink);
  max-width: 880px;
  margin: 0 auto 20vh;
}
/* Scroll-scrubbed per-letter reveal (initManifesto in script.js):
   a wave sweeps each line left to right as the user scrolls — ahead
   of it letters are invisible, inside it visible but blurred, behind
   it crisp at scale 1. The per-letter values are set inline by JS;
   the short transition here only smooths discrete wheel steps.
   .ml-word / .ml-glue wrappers are nowrap so the inline-block letters
   can't line-break mid-word (or strand punctuation after a styled
   word like the .serif "uncertainty,"). */
.manifesto .m-line .ml-ch {
  display: inline-block;
  opacity: 0;
  filter: blur(10px);
  transform: scale(1.3);
  transition: opacity 0.18s linear, filter 0.18s linear,
              transform 0.18s linear;
}
.manifesto .m-line .ml-word,
.manifesto .m-line .ml-glue {
  display: inline-block;
  white-space: nowrap;
}
.manifesto .m-line:last-child { margin-bottom: 0; }
.manifesto .m-line .serif {
  font-size: 1.16em;
  color: var(--ink);
  display: inline-block;
  white-space: nowrap;
}
.manifesto .m-line.m-big {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 500;
  font-size: clamp(52px, 12vw, 280px);
  color: var(--ink);
  line-height: 1.05;
}
.manifesto .m-small {
  font-size: clamp(15px, 1.6vw, 18px);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
/* The ↓ sits on its own line under the text, centered (the manifesto
   is text-align: center, so a block span centers it). */
.manifesto .m-small .m-arrow {
  display: block;
  margin-top: 14px;
  letter-spacing: 0;
}

/* ============================================================
   [6] SHARED SECTION SCAFFOLDING
   ============================================================ */
section {
  padding-top: 130px;
  padding-bottom: 120px;
  position: relative;
}
.section-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 40px;
  margin-bottom: 66px;
  flex-wrap: wrap;
}
.section-title {
  font-size: clamp(34px, 5vw, 58px);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.08;
  margin: 16px 0 0;
  max-width: 680px;
}
.section-title .serif {
  font-size: 1.12em;
  font-weight: 500;
}
.section-note {
  max-width: 320px;
  font-size: 15px;
  color: var(--ink-dim);
  line-height: 1.6;
}

/* ============================================================
   [7] ABOUT (+ stat cards)
   ============================================================ */
.about-grid {
  display: grid;
  grid-template-columns: 1.3fr 1fr;
  gap: 80px;
  align-items: start;
}
.about-text p {
  font-size: 18.5px;
  line-height: 1.72;
  color: var(--ink-dim);
  margin: 0 0 24px;
}
.about-text strong { color: var(--ink); font-weight: 600; }

.stat-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
.stat {
  background: var(--card);
  border: 1px solid rgba(255, 255, 255, 0.65);
  border-radius: var(--radius);
  padding: 30px 26px;
  backdrop-filter: blur(10px);
  box-shadow: 0 8px 32px rgba(41, 52, 90, 0.08);
}
.stat .num {
  font-family: var(--serif);
  font-style: italic;
  font-size: 44px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1;
}
/* The → in "0→1": the serif's arrow glyph rides high next to the
   digits, so nudge it down to sit optically centered between them. */
.stat .num .num-arrow {
  display: inline-block;
  transform: translateY(0.09em);
}
.stat .label {
  margin-top: 10px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

@media (max-width: 900px) {
  .about-grid { grid-template-columns: 1fr; gap: 50px; }
}

/* ============================================================
   [8] EXPERIENCE TIMELINE
   Compact text-only rows: a slim date/company meta line above a
   title + one-line-ish description. No thumbnails — layout is a
   single column, so this is the one section without a grid.
   ============================================================ */
.timeline {
  border-top: 1px solid var(--line);
}
.tl-item {
  padding: 26px 4px;
  border-bottom: 1px solid var(--line);
}
.tl-meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 20px;
  margin-bottom: 8px;
}
.tl-date {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-faint);
  letter-spacing: 0.06em;
  /* text-transform: uppercase; */
}
.tl-body h3 {
  margin: 0 0 5px;
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.005em;
}
.tl-body p {
  margin: 0;
  color: var(--ink-dim);
  font-size: 14px;
  line-height: 1.55;
  max-width: 620px;
}
/* .tl-company {
  font-family: var(--serif);
  font-style: italic;
  font-size: 16px;
  font-weight: 500;
  color: var(--ink-dim);
} */
@media (max-width: 560px) {
  .tl-meta { flex-direction:column; gap: 2px; }
}

/* ============================================================
   [9] CASE STUDY CARDS (homepage)
   ============================================================ */
.case-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
}
.case-card {
  position: relative;
  border-radius: var(--radius);
  border: 1px solid rgba(255, 255, 255, 0.7);
  background: var(--card);
  backdrop-filter: blur(12px);
  padding: 44px 46px;
  min-height: 372px;
  display: grid;
  /* Media column: definite width (shrinks under pressure, never past
     240px) so the image's size can't feed back into the layout. */
  grid-template-columns: minmax(240px, 380px) 1fr;
  gap: 42px;
  align-items: center;
  overflow: hidden;
  transition: transform 0.5s var(--ease), background 0.5s var(--ease), box-shadow 0.5s var(--ease);
  box-shadow: 0 10px 40px rgba(41, 52, 90, 0.08);
}
/* Image slot on the card's left. An <img> inside auto-crops to fill
   (object-fit: cover); add class="fit-whole" instead to scale artwork
   down so nothing is cropped. */
.case-media {
  border-radius: 18px;
  border: 1px solid rgba(255, 255, 255, 0.7);
  background:
    radial-gradient(320px circle at 30% 30%, rgba(255, 255, 255, 0.6), transparent 65%),
    var(--card);
  /* Width-driven sizing: the grid column sets the width and the
     1440:1024 ratio derives the height. (Height-driven sizing caused a
     feedback loop at mid widths: wrapping text made the card taller,
     which made the image wider, which squeezed the text further.) */
  aspect-ratio: 1440 / 1024;
  width: 100%;
  height: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  box-shadow: 0 8px 30px rgba(41, 52, 90, 0.08);
  overflow: hidden;
}
.case-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* opt-in for artwork that must not be cropped — device mockups, diagrams —
   scaled down to fit the slot instead of filling it */
.case-media img.fit-whole {
  object-fit: contain;
  padding: 14px;
}
.case-body {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-width: 0;
}
.case-card::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(500px circle at var(--mx, 50%) var(--my, 0%), rgba(255,255,255,0.55), transparent 65%);
  opacity: 0;
  transition: opacity 0.4s var(--ease);
  pointer-events: none;
}
.case-card:hover::before { opacity: 1; }
.case-card:hover {
  transform: translateY(-6px);
  background: var(--card-hover);
  box-shadow: 0 24px 60px rgba(41, 52, 90, 0.16);
}
/* Same tags as the case-study detail page hero (.cs-tagrow/.cs-tag), but
   styled here as the plain uppercase label that used to sit above the
   card title (the old .case-tag) rather than as chips — dot-separated
   inline text, left-aligned, no pill background. */
.case-card .cs-tagrow {
  justify-content: flex-start;
  margin-top: 0;
  gap: 0;
  row-gap: 4px;
}
.case-card .cs-tag {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  background: none;
  border: none;
  padding: 0;
  border-radius: 0;
  backdrop-filter: none;
}
.case-card .cs-tag:not(:last-child)::after {
  content: '·';
  margin: 0 7px;
  color: var(--ink-faint);
  font-weight: 400;
}
.case-card h3 {
  font-size: clamp(28px, 3.4vw, 38px);
  font-weight: 600;
  letter-spacing: -0.015em;
  margin: 18px 0 12px;
  line-height: 1.14;
  max-width: 760px;
}
.case-card h3 .serif { font-size: 1.1em; font-weight: 500; }
.case-card p {
  color: var(--ink-dim);
  font-size: 15.5px;
  line-height: 1.62;
  margin: 0;
  max-width: 620px;
}
.case-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 26px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 20px;
  font-weight: 600;
  color: var(--ink);
}
.case-arrow {
  width: 40px; height: 40px;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: var(--white-faint);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.4s var(--ease);
}
.case-card:hover .case-arrow {
  background: var(--ink);
  border-color: var(--ink);
  transform: rotate(45deg);
}
.case-arrow svg { width: 16px; height: 16px; }
.case-arrow svg path { stroke: var(--ink); transition: stroke 0.3s; }
.case-card:hover .case-arrow svg path { stroke: #eceafd; }

@media (max-width: 900px) {
  .case-grid { grid-template-columns: 1fr; }
  /* Stack the image above the text on narrow screens. */
  .case-card {
    grid-template-columns: 1fr;
    gap: 28px;
    padding: 30px 28px;
  }
  .case-media {
    min-height: 0;
    aspect-ratio: 16 / 9;
  }
}

/* ============================================================
   [10] SKILLS
   ============================================================ */
.skills-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}
.skill {
  background: var(--card);
  border: 1px solid rgba(255, 255, 255, 0.65);
  border-radius: var(--radius);
  padding: 30px 28px;
  backdrop-filter: blur(10px);
  transition: background 0.4s var(--ease), transform 0.4s var(--ease), box-shadow 0.4s var(--ease);
  box-shadow: 0 8px 28px rgba(41, 52, 90, 0.07);
}
.skill:hover {
  background: var(--card-hover);
  transform: translateY(-4px);
  box-shadow: 0 16px 44px rgba(41, 52, 90, 0.13);
}
.skill .n {
  font-family: var(--serif);
  font-style: italic;
  font-size: 18px;
  color: var(--ink-faint);
}
.skill h4 {
  margin: 12px 0 8px;
  font-size: 18px;
  font-weight: 600;
}
.skill p {
  margin: 0;
  font-size: 14px;
  color: var(--ink-dim);
  line-height: 1.58;
}
@media (max-width: 860px) {
  .skills-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 560px) {
  .skills-grid { grid-template-columns: 1fr; }
}

/* ============================================================
   [11] CONTACT & FOOTER
   ============================================================ */
.contact {
  text-align: center;
  padding-bottom: 120px;
}
.contact-title {
  font-size: clamp(40px, 8vw, 100px);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.04;
  margin: 20px 0 0;
}
.contact-title .serif {
  font-size: 1.1em;
  font-weight: 500;
  color: var(--ink);
}
.contact-sub {
  margin: 24px auto 0;
  max-width: 480px;
  color: var(--ink-dim);
  font-size: 17px;
}
.contact-actions {
  display: flex;
  justify-content: center;
  gap: 18px;
  margin-top: 44px;
  flex-wrap: wrap;
}
.contact-links {
  display: flex;
  justify-content: center;
  gap: 36px;
  margin-top: 60px;
  font-size: 15px;
  font-weight: 500;
}
.contact-links a {
  color: var(--ink-dim);
  position: relative;
  padding-bottom: 4px;
}
.contact-links a::after {
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  width: 0%; height: 1.5px;
  background: var(--ink);
  transition: width 0.4s var(--ease);
}
.contact-links a:hover { color: var(--ink); }
.contact-links a:hover::after { width: 100%; }

/* Footer */
footer {
  border-top: 1px solid var(--line);
  padding-top: 30px;
  padding-bottom: 40px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 13.5px;
  color: var(--ink-faint);
}
footer .f-script {
  font-family: var(--serif);
  font-style: italic;
  font-size: 16px;
}
.back-top {
  border: 1px solid var(--line-strong);
  background: var(--white-faint);
  border-radius: 999px;
  width: 44px; height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.4s var(--ease);
}
.back-top:hover {
  background: var(--ink);
  border-color: var(--ink);
}
.back-top svg path { stroke: var(--ink); transition: stroke 0.3s; }
.back-top:hover svg path { stroke: #eceafd; }

/* ============================================================
   [12] CASE STUDY DETAIL PAGES (all cs-* classes)
   ============================================================ */
.cs-hero {
  padding-top: 170px;
  padding-bottom: 80px;
  text-align: center;
}
.cs-tagrow {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 26px;
}
.cs-tag {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border: 1px solid var(--line-strong);
  background: var(--white-faint);
  backdrop-filter: blur(6px);
  padding: 8px 16px;
  border-radius: 999px;
  color: var(--ink-dim);
}
.cs-title {
  font-size: clamp(40px, 7vw, 88px);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.04;
  margin: 28px auto 0;
  max-width: 980px;
}
.cs-title .serif {
  font-size: 1.08em;
  font-weight: 500;
  color: var(--ink);
}
.cs-lede {
  max-width: 680px;
  font-size: 18.5px;
  color: var(--ink-dim);
  margin: 28px auto 0;
  line-height: 1.68;
}
.cs-meta-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 14px;
  margin-top: 60px;
  text-align: left;
}
.cs-meta-grid .stat { padding: 24px 22px; }
.cs-meta-grid .num {
  font-family: var(--sans);
  font-style: normal;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.3;
}

.cs-cover {
  margin-top: 60px;
  border-radius: var(--radius);
  border: 1px solid rgba(255, 255, 255, 0.7);
  overflow: hidden;
  /* Explicit width: inside the centered hero the cover would otherwise
     shrink-wrap its content, and an embedded iframe (width:100%) would
     collapse to zero along with it. */
  width: 100%;
  height: 46vw;
  max-height: 520px;
  min-height: 260px;
  background:
    radial-gradient(700px circle at 22% 24%, rgba(255, 226, 236, 0.85), transparent 62%),
    radial-gradient(700px circle at 78% 78%, rgba(178, 216, 245, 0.8), transparent 62%),
    radial-gradient(500px circle at 55% 40%, rgba(255,255,255,0.75), transparent 60%),
    var(--bg-2);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  box-shadow: 0 20px 60px rgba(41, 52, 90, 0.12);
}
/* Embedded video filling the cover slot (e.g. YouTube iframe). */
.cs-cover iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}
/* Native video in the cover slot — contained (not cropped) so UI
   content stays fully visible, letterboxed over the cover gradient. */
.cs-cover video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
/* Cover image filling the slot. */
.cs-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Full-width artifact/board images inside case-study content. */
.cs-img {
  display: block;
  width: 100%;
  margin: 30px 0 10px;
  border-radius: 18px;
  border: 1px solid rgba(255, 255, 255, 0.7);
  box-shadow: 0 8px 30px rgba(41, 52, 90, 0.08);
  background: #fff;
}

.cs-body {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 70px;
  padding-top: 100px;
}
.cs-toc {
  position: sticky;
  top: 120px;
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 14px;
  font-weight: 500;
}
.cs-toc a {
  color: var(--ink-faint);
  padding: 8px 0 8px 16px;
  border-left: 1.5px solid var(--line);
  transition: all 0.3s var(--ease);
}
.cs-toc a:hover, .cs-toc a.active {
  color: var(--ink);
  border-left-color: var(--ink);
}
.cs-content h2 {
  font-family: var(--serif);
  font-style: italic;
  font-size: 38px;
  font-weight: 500;
  letter-spacing: -0.01em;
  margin: 0 0 22px;
  padding-top: 10px;
}
.cs-content > .cs-block {
  margin-bottom: 90px;
  scroll-margin-top: 120px;
}
.cs-content p {
  font-size: 17px;
  line-height: 1.78;
  color: var(--ink-dim);
  margin: 0 0 18px;
  max-width: 720px;
}
.cs-content em { color: var(--ink-faint); }
/* opt-in: a case-study block whose body copy sits at full ink weight */
.cs-content .cs-block-ink p,
.cs-content .cs-block-ink li,
.cs-content .cs-block-ink em { color: var(--ink); }
.cs-content h3 {
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin: 34px 0 12px;
}
.cs-content h4 {
  font-size: 16.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--ink);
  margin: 26px 0 10px;
}
.cs-content ul,
.cs-content ol {
  margin: 0 0 18px;
  padding-left: 20px;
  color: var(--ink-dim);
  font-size: 16.5px;
  line-height: 1.7;
}
.cs-content li { margin-bottom: 8px; }
.cs-quote {
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(24px, 3vw, 32px);
  font-weight: 500;
  color: var(--ink);
  margin: 40px 0;
  max-width: 640px;
  line-height: 1.4;
  padding: 0;
}
.cs-quote::before { content: '“'; color: var(--white); font-size: 1.4em; }
.cs-quote::after { content: '”'; color: var(--white); font-size: 1.4em; }
/* attributed pull quote: the quote marks wrap the text, not the attribution */
.cs-quote:has(.cs-quote-by)::before,
.cs-quote:has(.cs-quote-by)::after { content: none; }
.cs-quote .cs-quote-text::before { content: '“'; color: var(--white); font-size: 1.4em; }
.cs-quote .cs-quote-text::after { content: '”'; color: var(--white); font-size: 1.4em; }
.cs-quote .cs-quote-by {
  display: block;
  margin-top: 16px;
  font-family: var(--sans);
  font-style: normal;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.5;
  color: var(--ink-faint);
}
.cs-quote .cs-quote-by::before { content: '— '; }
.cs-imgrow {
  display: grid;
  /* minmax(0,…) so a wide image can never push a track past its share */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 18px;
  margin: 30px 0 10px;
}
/* any image dropped into a row fits its column, never its natural size */
.cs-imgrow img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 14px;
  border: 1px solid var(--line);
}
/* three-up row, for tall portrait screenshots shown side by side */
.cs-imgrow-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: start; }
/* a phone capture beside a desktop one: 1/3 and 2/3 columns, each image
   scaled to fit its cell at its own aspect ratio. The row's own ratio is set
   so the desktop capture fills its column exactly and the phone centres
   against the same height. */
.cs-imgrow-phone-web {
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  /* the row must not grow past the ratio-derived height, or the tall phone
     capture sets the height and the desktop one floats in dead space */
  grid-template-rows: minmax(0, 1fr);
  align-items: center;
  aspect-ratio: 1.53;
}
.cs-imgrow-phone-web img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  border: none;
}
/* level captures of differing lengths to one height, trimming from the top */
.cs-imgrow-crop img {
  aspect-ratio: 1179 / 2359;
  object-fit: cover;
  object-position: bottom;
}
/* Inline demo videos inside case-study content. */
.cs-video {
  display: block;
  width: 100%;
  margin: 30px 0 10px;
  border-radius: 18px;
  border: 1px solid rgba(255, 255, 255, 0.7);
  box-shadow: 0 8px 30px rgba(41, 52, 90, 0.08);
  background: var(--card);
}

/* ============================================================
   CASE-STUDY PASSWORD GATE (body.gated — see initCaseGate)
   Content stays hidden from first paint until unlocked, so there
   is no flash of the protected page before JS runs.
   ============================================================ */
body.gated main,
body.gated footer {
  visibility: hidden;
}
body.gated.unlocked main,
body.gated.unlocked footer {
  visibility: visible;
}
.gate {
  position: fixed;
  inset: 0;
  z-index: 400; /* above content/clouds (≤200), below the nav (500) so it stays clickable */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  transition: opacity 0.4s var(--ease);
}
.gate.gate-out {
  opacity: 0;
  pointer-events: none;
}
.gate-card {
  width: min(420px, 100%);
  text-align: center;
  padding: 44px 38px 38px;
  border-radius: var(--radius);
  border: 1px solid rgba(255, 255, 255, 0.7);
  background: var(--card);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 24px 70px rgba(41, 52, 90, 0.18);
}
.gate-lock {
  width: 52px;
  height: 52px;
  margin: 0 auto 18px;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: var(--white-faint);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
}
.gate-lock svg { width: 22px; height: 22px; }
.gate-card h2 {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin-bottom: 10px;
}
.gate-card p {
  font-size: 14.5px;
  color: var(--ink-dim);
  line-height: 1.6;
  margin-bottom: 24px;
}
.gate-card p a { text-decoration: underline; text-underline-offset: 3px; }

/* "Try it live" links inside case-study content — point out to the real
   product so a reader can play with the actual feature. Styled like
   .btn-ghost so they read as an action, not inline body text. */
/* CTA on overview (public) case-study pages linking to the gated
   full version — centered in the hero, and again at the page bottom. */
.cs-hero .cs-try { margin: 34px auto 0; }
.cs-unlock-block {
  text-align: center;
  padding: 30px 0 10px;
  border-top: 1px solid var(--line);
  margin-top: 40px;
}
.cs-unlock-block .eyebrow { margin-bottom: 18px; }
.cs-unlock-block p {
  margin: 12px auto 22px;
  max-width: 520px;
}

.cs-refs {
  margin: 40px 0 0;
  padding-top: 20px;
  border-top: 1px solid var(--line);
  font-size: 13px;
  line-height: 1.9;
  color: var(--ink-faint);
}
.cs-refs .cs-refs-label {
  display: block;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  margin-bottom: 5px;
  opacity: 0.75;
}
.cs-refs a {
  color: var(--ink-faint);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: 1px;
  transition: color 0.25s ease, border-color 0.25s ease;
}
.cs-refs a:hover {
  color: var(--ink-dim);
  border-bottom-color: var(--line-strong);
}
.cs-refs .sep { padding: 0 7px; opacity: 0.45; }

.cs-try-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 6px 0 22px;
}
.cs-try-row .cs-try { margin: 0; }
.cs-try {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 6px 0 22px;
  padding: 11px 20px;
  border-radius: 999px;
  border: 1px solid var(--line-strong);
  background: var(--white-soft);
  backdrop-filter: blur(8px);
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  transition: background 0.3s var(--ease), box-shadow 0.3s var(--ease), transform 0.3s var(--ease);
}
.cs-try:hover {
  background: var(--white);
  box-shadow: 0 10px 26px rgba(41, 52, 90, 0.14);
  transform: translateY(-1px);
}
.cs-try svg { width: 13px; height: 13px; flex-shrink: 0; }
.gate-row {
  display: flex;
  gap: 10px;
}
.gate-row input {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 15px;
  padding: 12px 16px;
  border-radius: 999px;
  border: 1px solid var(--line-strong);
  background: rgba(255, 255, 255, 0.75);
  color: var(--ink);
  outline: none;
  transition: border-color 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
.gate-row input:focus {
  border-color: var(--ink);
  box-shadow: 0 0 0 3px rgba(41, 52, 90, 0.12);
}
.gate-row button {
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  padding: 12px 24px;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  color: #eceafd;
  cursor: pointer;
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
.gate-row button:hover {
  transform: translateY(-1px);
  box-shadow: 0 10px 26px rgba(41, 52, 90, 0.25);
}
.gate-error {
  margin-top: 14px;
  font-size: 13.5px;
  color: #b4485e;
}
.gate-shake {
  animation: gateShake 0.4s var(--ease);
}
@keyframes gateShake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-8px); }
  50% { transform: translateX(7px); }
  75% { transform: translateX(-4px); }
}

/* Previous / next navigation at the bottom of case-study pages. */
.cs-pagenav {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  border-top: 1px solid var(--line);
  padding-top: 50px;
  padding-bottom: 70px;
  margin-top: 40px;
}
.cs-pagenav .eyebrow { margin-bottom: 16px; }
.cs-pagenav h3 {
  font-size: clamp(22px, 3vw, 34px);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.2;
  /* Block-level flex: the title always sits on its own line below the
     eyebrow (both were inline before, so short titles hopped up next
     to the "Previous case study" label). */
  display: flex;
  /* Anchor the arrow to the FIRST line of the title, so multi-line
     titles wrap under themselves instead of floating around a
     vertically-centered arrow. */
  align-items: flex-start;
  gap: 14px;
  margin: 0;
  transition: color 0.3s var(--ease);
}
.cs-pagenav-next h3 { justify-content: flex-end; }
.cs-pagenav a:hover h3 { color: var(--ink-dim); }
.cs-pagenav svg {
  flex-shrink: 0;
  /* optically center the arrow on the first text line */
  margin-top: 0.16em;
}
.cs-pagenav svg path { stroke: currentColor; }
.cs-pagenav-next { text-align: right; }

@media (max-width: 900px) {
  .cs-body { grid-template-columns: 1fr; gap: 40px; }
  .cs-toc { position: static; flex-direction: row; flex-wrap: wrap; }
  .cs-meta-grid { grid-template-columns: 1fr 1fr; }
  .cs-imgrow { grid-template-columns: 1fr; }
  .cs-imgrow-3 { grid-template-columns: 1fr; }
  /* stacked: drop the shared-height ratio and let each image size naturally */
  .cs-imgrow-phone-web {
    grid-template-columns: 1fr;
    justify-items: center;
    aspect-ratio: auto;
  }
  .cs-imgrow-phone-web img { height: auto; }
  .cs-imgrow-phone-web img:first-child { max-width: 300px; }
  .cs-pagenav { grid-template-columns: 1fr; gap: 34px; }
  .cs-pagenav-next { text-align: left; }
}

/* Thin scroll-progress bar along the top edge. */
.progress {
  position: fixed;
  top: 0; left: 0;
  height: 2.5px;
  background: var(--ink);
  z-index: 200;
  width: 0%;
  transition: width 0.1s linear;
  opacity: 0.55;
}
