/* ========================================================
   FONTS
   ======================================================== */
@font-face { font-family: 'Switzer'; src: url('../assets/fonts/Switzer-Light.woff') format('woff'); font-weight: 300; font-display: swap; }
@font-face { font-family: 'Switzer'; src: url('../assets/fonts/Switzer-Regular.woff') format('woff'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Switzer'; src: url('../assets/fonts/Switzer-Medium.woff') format('woff'); font-weight: 500; font-display: swap; }
@font-face { font-family: 'Switzer'; src: url('../assets/fonts/Switzer-Semibold.woff') format('woff'); font-weight: 600; font-display: swap; }
@font-face { font-family: 'Switzer'; src: url('../assets/fonts/Switzer-Bold.woff') format('woff'); font-weight: 700; font-display: swap; }
/* NOTE FOR MILA: Khand still loads from Google Fonts (see <head>). Self-host it
   the same way as Switzer above once you export a woff2, so the whole site
   works offline / without a third-party font request. */

/* ========================================================
   THEME TOKENS
   ======================================================== */
:root {
  --bg: #fbf4ea;
  --bg-panel: #ffffff;
  --fg: #2b2b2b;           /* darkened slightly from #333 for AA contrast on cream */
  --muted: #b3b3b3;
  --muted-text: #6b6b6b;    /* darkened from a lighter grey to hit 4.5:1 on --bg */
  --accent-orange: #feefe3;
  --accent-blue: #f2fbfe;
  --orange-102: #d1794d;
  --line: #b3b3b3;
  --scrim: rgba(251, 244, 234, 0.92);
  --radius-tag: 0 8px 0 8px;
  --font-display: 'Khand', sans-serif;
  --font-body: 'Switzer', Arial, sans-serif;
  --focus-ring: 2px solid #2b6cb0;
  --panel-tint: var(--light-grey, #f8f8f8);
  /* Deliberately inverted relative to the pair above — for the one
     component (My Influence) designed to always contrast against the
     page's current theme rather than follow it: dark panel when the
     site is light, flips light when the site goes dark. See the
     [data-theme="dark"] override below for the flip. */
  --influence-bg: #333333;
  --influence-fg: #f8f8f8;
  --influence-border: rgba(248, 248, 248, .25);
  --influence-media-bg: #ffffff;
  /* Same rgb as --influence-fg above, just at low alpha — used for the
     soft drifting glow behind the panel content. Kept as its own token
     rather than color-mix() for wider browser support (matches how the
     rest of this file avoids color-mix elsewhere). */
  --influence-glow: rgba(248, 248, 248, .11);
}

[data-theme="dark"] {
  --bg: #17171a;
  --bg-panel: #232327;
  --fg: #f2efe8;
  --muted: #48484d;
  --muted-text: #b3b0a9;    /* lightened for AA contrast on dark bg */
  --accent-orange: #2a221c;
  --accent-blue: #17242a;
  --line: #3a3a3f;
  --scrim: rgba(23, 23, 26, 0.92);
  --focus-ring: 2px solid #7db8ff;
  --panel-tint: rgba(35, 35, 39, 0.6); /* #232327 (--bg-panel) at 60% opacity */
  /* Inverted the other way here — light panel on the dark site theme. */
  --influence-bg: #f2efe8;
  --influence-fg: #17171a;
  --influence-border: rgba(23, 23, 26, .2);
  --influence-media-bg: #232327;
  --influence-glow: rgba(23, 23, 26, .11);
}

* { box-sizing: border-box; }
/* Global, invisible, behavior-only: every element opts out of being a
   scroll-anchor candidate. overflow-anchor doesn't inherit, so setting it
   on html/body/a container alone leaves every other element still "auto"
   and eligible to be picked as the anchor — which is exactly what was
   still nudging scroll position on the Flip Side page's close animation
   even after scoped fixes. This has zero visual/layout effect; it only
   disables the browser's automatic scroll-position compensation. */
* { overflow-anchor: none; }
html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  transition: background .3s ease, color .3s ease;
  overflow-x: hidden;
  position: relative;
}

a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
.khand { font-family: var(--font-display); }

:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ========================================================
   HALFTONE — fixed, full-viewport, visible on every page,
   generated with CSS so there's no image to load.
   ======================================================== */
.halftone-layer {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: radial-gradient(var(--fg) 1px, transparent 1.5px);
  background-size: 16px 16px;
  opacity: .07;
}
/* A dedicated copy inside the nav overlay — guarantees the texture reads
   clearly through the blurred scrim instead of depending on backdrop-filter
   sampling math. */
.nav-overlay .halftone-layer { position: absolute; z-index: 0; }

/* ========================================================
   ICONS — inline SVGs colored via currentColor so they
   invert automatically with the rest of the UI in dark mode
   ======================================================== */
.icon { color: var(--fg); display: inline-block; }
.icon svg { display: block; width: 100%; height: 100%; fill: currentColor; }

/* Full-color raster/brand marks that aren't single-color SVGs
   get a filter-based invert instead. */
.invert-on-dark { transition: filter .3s ease; }
[data-theme="dark"] .invert-on-dark { filter: invert(1) brightness(1.1); }

/* ========================================================
   TOP NAV
   ======================================================== */
.top-nav {
  position: fixed;
  top: 24px; left: 24px; right: 24px;
  z-index: 400;
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 32px;
  background: rgba(255,255,255,.35);
  border: 1px solid var(--muted);
  border-radius: 8px;
  backdrop-filter: blur(6px);
}
[data-theme="dark"] .top-nav { background: rgba(30,30,34,.5); }
@media (max-width: 1100px) {
  .top-nav { transition: background .3s ease, border-color .3s ease; }
  .top-nav.is-transparent { background: transparent; border-color: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; }
}

.logo-mark { width: 40px; height: 40px; color: var(--fg); }

.theme-toggle {
  display: flex;
  border-radius: 8px;
  overflow: hidden;
  position: relative;
  background: var(--bg-panel);
  border: 1px solid var(--muted);
}
.theme-toggle button {
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border: none; background: none; color: var(--fg);
  cursor: pointer; position: relative; z-index: 1;
  font-family: 'Material Symbols Outlined';
  font-size: 20px;
  transition: color .3s ease;
}
.theme-toggle .shade {
  position: absolute; top: 0; width: 40px; height: 40px;
  background: var(--bg); border-radius: 7px;
  border: 1px solid var(--fg);
  transition: transform .3s cubic-bezier(.65,0,.35,1);
  z-index: 0;
}
[data-theme="dark"] .theme-toggle .shade { transform: translateX(40px); }

.hamburger-btn {
  width: 40px; height: 36px;
  border: none; background: none; cursor: pointer;
  color: var(--fg);
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.hamburger-btn svg { position: absolute; transition: opacity .35s ease, transform .35s cubic-bezier(.4,0,.2,1); stroke: currentColor; }
.hamburger-btn .icon-menu { width: 26px; opacity: 1; transform: rotate(0deg); }
.hamburger-btn .icon-close { width: 18px; opacity: 0; transform: rotate(-180deg); }
.hamburger-btn[aria-expanded="true"] .icon-menu { opacity: 0; transform: rotate(180deg); }
.hamburger-btn[aria-expanded="true"] .icon-close { opacity: 1; transform: rotate(0deg); }
.hamburger-btn .close-underline {
  position: absolute; bottom: 2px; width: 18px; height: 1.5px; background: currentColor;
  opacity: 0; transition: opacity .35s ease .1s;
}
.hamburger-btn[aria-expanded="true"] .close-underline { opacity: 1; }

/* ========================================================
   FULL-SCREEN NAV OVERLAY — translucent + blurred so the
   page shows through softly instead of a flat opaque panel
   ======================================================== */
.nav-overlay {
  position: fixed; inset: 0; z-index: 300;
  background: var(--scrim);
  backdrop-filter: blur(28px);
  -webkit-backdrop-filter: blur(28px);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 40px;
  opacity: 0; pointer-events: none;
  transform: translateY(-12px);
  transition: opacity .3s ease, transform .3s ease;
  overflow: hidden;
}
.nav-overlay.open { opacity: 1; pointer-events: auto; transform: translateY(0); }
.nav-overlay .close-btn {
  position: absolute; top: 32px; right: 32px;
  background: none; border: none; font-size: 14px; cursor: pointer; color: var(--fg);
  font-family: var(--font-body); display: flex; align-items: center; gap: 6px;
}
.nav-overlay .top-row {
  position: absolute; top: 120px; left: 24px; right: 24px;
  display: flex; align-items: center; justify-content: space-between;
}
@media (min-width: 861px) {
  .nav-overlay .theme-toggle { display: none; } /* lives in the persistent top-nav instead on web/tablet */
  .nav-overlay .top-row > div:last-child { margin-left: auto; } /* toggle is hidden here, so the icon group still hugs the right edge instead of snapping to the left */
}
.icon-btn {
  width: 40px; height: 40px; border: 1px solid var(--fg); border-radius: 8px;
  display: flex; align-items: center; justify-content: center; color: var(--fg);
  background: none; cursor: pointer;
}
.icon-btn .material-symbols-outlined { font-size: 20px; }
@media (hover: hover) and (pointer: fine) {
  .icon-btn { transition: background .15s ease, color .15s ease; }
  .icon-btn:hover, .icon-btn:focus-visible { background: var(--fg); color: var(--bg); }
}

/* nav-items is absolutely positioned (like top-row) so it reliably
   clears top-row's bottom edge (top:120px + ~40px content height) by a
   fixed 24px on every viewport height, instead of competing with
   top-row for space via flex centering. */
.nav-items { position: absolute; top: 184px; left: 24px; right: 24px; display: flex; flex-direction: column; align-items: center; gap: 28px; }

/* Shared NavMenuItems component — used identically in the nav dropdown
   AND every section title-panel (Case Studies, Testimonials, Meet the
   Editor, Flip Side). One component, one place to fix. */
.nav-menu-item { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; cursor: pointer; }
.nav-menu-item .content-lockup { display: flex; align-items: center; gap: 6px; width: 100%; }
.nav-menu-item .tag {
  width: 56px; height: 56px; background: #2b2b2b; border-radius: var(--radius-tag);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  border: 1px solid transparent;
}
[data-theme="dark"] .nav-menu-item .tag { border-color: var(--muted); }
.nav-menu-item .tag img { width: 30px; height: 30px; }
.nav-menu-item .title { font-size: 32px; line-height: 1.05; text-transform: uppercase; font-weight: 600; }
.nav-menu-item .desc { font-size: 16px; color: var(--fg); margin: 0; max-width: 260px; }

/* Inverted + grow hover — scoped to a.nav-menu-item only (the actual
   clickable nav-dropdown links), not the div.nav-menu-item instances used
   as static section headers elsewhere (title panels, Flip Side intro,
   etc). Default footprint is 260x121.19; hover adds 8px of padding on
   every side (276x137.19), with a matching negative margin so the growth
   doesn't reflow the other dropdown items below it. The tag icon box is
   left alone — it's already a self-contained dark chip with a white icon
   that reads fine against either an inverted-dark (light mode hover) or
   inverted-light (dark mode hover) row background, so it doesn't need its
   own hover treatment. */
@media (hover: hover) and (pointer: fine) {
  a.nav-menu-item {
    padding: 0; margin: 0; border-radius: 8px;
    transition: padding .15s ease, margin .15s ease, background-color .15s ease, color .15s ease;
  }
  a.nav-menu-item:hover, a.nav-menu-item:focus-visible {
    padding: 8px; margin: -8px;
    background: var(--fg);
  }
  a.nav-menu-item:hover .title, a.nav-menu-item:focus-visible .title,
  a.nav-menu-item:hover .desc, a.nav-menu-item:focus-visible .desc { color: var(--bg); }
}

/* ========================================================
   HERO
   ======================================================== */
.hero {
  min-height: 100vh;
  display: flex; align-items: flex-start; justify-content: center;
  position: relative; padding: 140px 64px 48px;
  box-sizing: border-box;
}

/* Hero cover — one unified "zine cover" card. Crop marks live on this
   outer element (overflow: visible) while .hero-art below holds the
   actual overflow:hidden clip for the image — keeping both the image
   crop AND the crop marks visible at once, since a single element can't
   both clip its content and show marks positioned outside its own box. */
.hero-cover {
  position: relative; width: 945px; max-width: 100%; flex-shrink: 0;
  aspect-ratio: 850 / 616.795; max-height: calc(100vh - 140px - 48px);
  box-shadow: 0 4px 2px rgba(12,12,13,.1), 0 4px 2px rgba(12,12,13,.05);
}
.hero-art { position: absolute; inset: 0; overflow: hidden; background: var(--accent-blue); z-index: 0; }
.hero-art picture, .hero-art img { display: block; width: 100%; height: 100%; object-fit: cover; }
.hero-cover .crop { position: absolute; width: 20px; height: 20px; pointer-events: none; color: #6b6b6b; z-index: 3; }
.hero-cover .crop svg { display: block; width: 100%; height: 100%; }
.hero-cover .crop.tl { top: -24px; left: -24px; }
.hero-cover .crop.tr { top: -24px; right: -24px; }
.hero-cover .crop.bl { bottom: -24px; left: -24px; }
.hero-cover .crop.br { bottom: -24px; right: -24px; }

/* Text overlay — sits on top of the art in the left ~45% of the card,
   vertically centered, matching the Figma "Project Details" panel. */
.hero-copy {
  position: absolute; z-index: 2; top: 0; left: 0; bottom: 0;
  width: 46%;
  display: flex; flex-direction: column; justify-content: center; gap: 16px;
  padding: 32px;
  box-sizing: border-box;
}
.hero h1 {
  font-size: clamp(32px, 4.6vw, 64px); margin: 0; line-height: .95;
  text-transform: capitalize; letter-spacing: .02em;
  color: #2b2b2b;
}
.hero p { font-size: clamp(14px, 1.4vw, 20px); line-height: 1.4; max-width: 46ch; color: #2b2b2b; margin: 0; }

/* YO! chip — same black diagonal-cut tag component used for nav-menu-item
   icons/section titles across the site, scaled up for the hero. Figma
   spec: near-zero internal padding (4.9px at this scale) so the text
   fills almost the entire chip, rather than sitting small inside it. */
.hero-yo-tag {
  display: inline-flex; align-items: center; justify-content: center;
  width: clamp(80px, 11vw, 128px); height: clamp(80px, 11vw, 128px); flex-shrink: 0;
  background: #2b2b2b; border-radius: var(--radius-tag);
  border: 1px solid transparent;
  padding: 6px; box-sizing: border-box;
}
[data-theme="dark"] .hero-yo-tag { border-color: var(--muted); }
.hero-yo-tag span {
  color: #fff; font-weight: 700; font-size: clamp(40px, 6.8vw, 72px); line-height: 1;
  text-transform: uppercase; letter-spacing: .04em;
}

/* Rent Manager link — plain bordered tag, no fill, reads in both themes
   since it uses --fg for both text and border. Text is always the fixed
   card-ink color (#2b2b2b) here since it sits on the printed cover art,
   which — like cs-card — doesn't invert with the site theme. */
.hero-link {
  color: #2b2b2b; font-weight: 600; text-decoration: none;
  background: rgba(255,255,255,.7);
  border: 1.5px solid rgba(0,0,0,.9);
  padding: 1px 6px; border-radius: 2px;
  box-decoration-break: clone; -webkit-box-decoration-break: clone;
}
.hero-link:hover, .hero-link:focus-visible { text-decoration: underline; }

/* Scroll hint sits inside the cover card itself (not the outer .hero),
   absolutely positioned and centered — guarantees it's always within
   the viewport-height hero on every device, never scrolled out of view. */
.hero-cover .scroll-hint {
  position: absolute; z-index: 2; bottom: 24px; left: 50%; transform: translateX(-50%);
  font-size: 12px; letter-spacing: .1em; text-transform: uppercase;
  color: #6b6b6b; cursor: pointer; background: rgba(255,255,255,.1);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
  border: none; font-family: var(--font-display); font-weight: 700;
  padding: 4px 8px; border-radius: 4px;
}
.hero-cover .scroll-hint:hover { color: #2b2b2b; }

@media (max-width: 1100px) {
  /* Hero sits behind the top-nav, flush to the true top of the page —
     nav itself goes transparent (see .top-nav.is-transparent) until
     scroll, so the art shows through seamlessly. */
  .hero { padding: 0; min-height: auto; height: auto; }
  .hero-cover {
    width: 100%; max-width: 100%; aspect-ratio: 1185 / 2313; max-height: none;
    box-shadow: none;
  }
  .hero-cover .crop { display: none; }
  .hero-copy {
    position: absolute; width: calc(100% - 32px); left: 16px; top: 120px; bottom: auto;
    justify-content: flex-start; gap: 8px; padding: 0;
  }
  .hero h1 { font-size: clamp(28px, 11vw, 40px); }
  .hero-yo-tag { width: clamp(56px, 19vw, 76px); height: clamp(56px, 19vw, 76px); padding: 5px; }
  .hero-yo-tag span { font-size: clamp(36px, 11vw, 52px); }
  .hero p { font-size: 16px; max-width: 100%; }
  /* Fixed within the viewport (not the hero box) so it's reliably visible
     without scrolling, but z-index sits BELOW the nav overlay (300) so it's
     hidden while the menu is open, and a scroll-based .is-hidden class
     (added in main.js) fades it out once the visitor scrolls past the
     hero, instead of it persisting for the rest of the page. */
  .hero-cover .scroll-hint {
    position: fixed; bottom: 16px; z-index: 250;
    transition: opacity .25s ease;
  }
  .hero-cover .scroll-hint.is-hidden { opacity: 0; pointer-events: none; }
  /* Soft fade at the bottom of the mobile art so it blends into the page
     background instead of ending on a hard edge. */
  .hero-art::after {
    content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 140px;
    background: linear-gradient(to bottom, transparent, var(--bg));
    pointer-events: none;
  }
}

[data-animate] { opacity: 0; transform: translateY(24px); transition: opacity .7s ease, transform .7s ease; }
[data-animate].is-visible { opacity: 1; transform: translateY(0); }

/* ========================================================
   SECTION SPACING — generous breathing room between
   homepage sections instead of them touching edge to edge
   ======================================================== */
.section-gap { padding-top: 140px; padding-bottom: 140px; }

/* ========================================================
   CASE STUDIES — pinned horizontal section
   ======================================================== */
.case-studies-wrap { position: relative; height: 500vh; }
.case-studies-pin {
  position: sticky; top: 0; height: 100vh; overflow: hidden;
  display: flex; align-items: center;
}
.title-panel {
  position: absolute; left: 0; top: 0; bottom: 0; width: 280px;
  background: transparent;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  z-index: 20;
  display: flex; align-items: center; justify-content: center;
  border-right: 1px solid var(--muted);
}
.title-panel .nav-menu-item .tag { width: 64px; height: 64px; }
.title-panel .nav-menu-item .tag img { width: 34px; height: 34px; }
.title-panel .nav-menu-item .title { font-size: 26px; text-align: left; }

.cs-track { position: relative; display: flex; gap: 48px; align-items: center; padding-left: 300px; }

.cs-card {
  position: relative;
  width: 620px;
  aspect-ratio: 620 / 479;
  flex-shrink: 0;
  background: #fff;
  border: 1px solid var(--muted);
  cursor: pointer; display: block;
  transition: transform .25s cubic-bezier(.2,.8,.2,1), box-shadow .25s ease;
}
/* Real cause of the reported "sticking": the scroll-jack's update()
   loop (js/main.js) writes card.style.transform directly on every
   scroll frame (~60/sec) to track scale/position with the actual
   scroll position 1:1. The transition above applies to ANY change of
   that property though, inline-set or not — so at 60fps, with a new
   transform value arriving every ~16ms (far faster than .25s can
   resolve), the visible card was perpetually chasing a constantly
   -moving target instead of matching scroll input in real time, which
   reads as laggy/rubber-banded "sticking" (worse the faster someone
   scrolled). js/main.js toggles this class on <body> for the duration
   of each scroll-driven update, forcing the transition off exactly
   when it would otherwise fight the per-frame writes, then removes it
   once scrolling settles so hover's transition still works normally. */
body.cs-track-scrolling .cs-card { transition: none !important; }
.cs-card:hover, .cs-card:focus-visible {
  transform: scale(1.05);
  box-shadow: 0 24px 48px rgba(0,0,0,.18);
  z-index: 50 !important;
}

.cs-card .art-box { position: absolute; inset: 0; overflow: hidden; }

/* Print chrome — visible by default, fades out on hover/focus */
.cs-card .print-mark { transition: opacity .2s ease; opacity: 1; }
.cs-card:hover .print-mark, .cs-card:focus-visible .print-mark { opacity: 0; }

/* Crop marks + registration bar — real vector assets (SVG/inline), not a
   rasterized PNG, so they stay crisp at any scale instead of blurring. */
.cs-card .crop { position: absolute; width: 20px; height: 20px; pointer-events: none; color: #6b6b6b; }
.cs-card .crop svg { display: block; width: 100%; height: 100%; }
.cs-card .crop.tl { top: -24px; left: -24px; }
.cs-card .crop.tr { top: -24px; right: -24px; }
.cs-card .crop.bl { bottom: -24px; left: -24px; }
.cs-card .crop.br { bottom: -24px; right: -24px; }

.cs-card .registration-bar {
  position: absolute; top: 101%; bottom: auto; left: 0; right: 0;
  display: flex; align-items: center; justify-content: space-between;
  padding: 4px 8px 0;
  background: #fff;
}
.cs-card .registration-bar .cmyk-dots { width: 72px; height: 8px; display: block; }
.cs-card .registration-bar .reg-mark { width: 27px; height: 24px; color: #6b6b6b; }
.cs-card .registration-bar .reg-mark svg { width: 100%; height: 100%; display: block; }
.cs-card .registration-bar .paper-type { font-size: 9px; letter-spacing: .05em; color: #6b6b6b; font-family: var(--font-display); font-weight: 500; white-space: nowrap; }

.cs-card .card-bg { position: absolute; inset: 0; overflow: hidden; background: var(--accent-blue); z-index: 1; }
.cs-card .card-bg img { width: 100%; height: 100%; object-fit: cover; }

.cs-card .infobar {
  position: absolute; top: 0; left: 0; right: 0; z-index: 3;
  display: flex; gap: 24px; justify-content: space-between; padding: 10px 20px;
  font-size: 12px; color: #2b2b2b;
  background: #fff;
  border-bottom: 1px solid var(--muted);
}
.cs-card .infobar span { text-transform: uppercase; font-family: var(--font-display); font-weight: 600; }
.cs-card .infobar span b { font-weight: 500; text-transform: none; }

.cs-card .card-content {
  position: absolute; top: 56px; left: 20px; right: 20px; bottom: 32px;
  display: flex; flex-direction: column; z-index: 2;
}
.cs-card .tags-row { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 24px; }
.cs-card .pills-group { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; }

/* Issue tag and tag pills are locked to the same 21px height, and their
   colors are fixed black/white regardless of site theme — these cards
   read as a printed page, so they never invert with dark mode. */
.cs-card .issue-tag { display: inline-flex; gap: 4px; align-items: center; font-weight: 700; font-size: 16px; letter-spacing: .04em; text-transform: uppercase; height: 21px; color: #2b2b2b; }
.cs-card .issue-tag b { background: #2b2b2b; color: #fff; height: 100%; padding: 0 5px; border-radius: var(--radius-tag); display: flex; align-items: center; font-weight: 700; box-sizing: border-box; }
.cs-card .tag-pill { border: 0.5px solid #2b2b2b; background: #fbf4ea; color: #2b2b2b; font-size: 16px; height: 21px; padding: 2px 4px 2px 6px; border-radius: var(--radius-tag); font-family: var(--font-display); font-weight: 500; text-transform: capitalize; display: flex; align-items: center; box-sizing: border-box; }
.cs-card h3 { font-size: 32px; margin: 0 0 8px; max-width: 60%; line-height: 1.05; text-transform: capitalize; color: #2b2b2b; }
.cs-card p.cs-desc { font-size: 14px; color: #6b6b6b; margin: 0; max-width: 310px; line-height: 1.3; }
/* Impact Card — shared component, used identically on the homepage
   project covers and the case study page. */
.impact-card {
  display: inline-flex; align-items: center; gap: 4px;
  background: #fbf4ea; border: 0.5px solid #333; border-radius: 4px;
  padding: 4px 6px;
  box-shadow: 0 1px 1px rgba(0,0,0,.1);
  font-family: var(--font-display);
}
.impact-card .impact-label { font-weight: 700; font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: #333; white-space: nowrap; }
.impact-card .impact-stat { font-weight: 500; font-size: 11px; letter-spacing: .05em; color: #333; white-space: nowrap; }
.impact-card .impact-divider { width: 1px; height: 14px; background: #333; opacity: .4; flex-shrink: 0; }

.cs-card .impact-card { align-self: flex-start; margin-top: auto; margin-bottom: 0; }
.cs-card .view-affordance { display: none; } /* shown again on mobile below */


.cs-progress {
  position: absolute; bottom: 40px; left: 300px; right: 60px;
  display: flex; align-items: center; gap: 12px;
  font-size: 13px; font-family: var(--font-display); z-index: 15;
}
.cs-progress .line { flex: 1; height: 1px; background: var(--muted); position: relative; }
.cs-progress .fill { position: absolute; height: 1px; background: var(--fg); width: 40px; top: 0; left: 0; }
.cs-progress span { flex-shrink: 0; width: 20px; }
.cs-progress span:last-child { text-align: right; }

/* ========================================================
   TOOLS TICKER
   ======================================================== */
.ticker-wrap { overflow: hidden; border-top: 1px solid var(--muted); border-bottom: 1px solid var(--muted); padding: 25px 0; margin-top: 100px; }
.ticker-track { display: flex; gap: 64px; width: max-content; animation: tickerScroll 30s linear infinite; padding-left: 96px; }
.ticker-track .tool-tag { display: flex; align-items: center; gap: 13.5px; white-space: nowrap; }
.ticker-track .tool-tag img { height: 50px; width: auto; max-width: 54px; display: block; }
.ticker-track .tool-tag span { font-size: 24px; font-weight: 300; letter-spacing: .02em; color: var(--fg); }
@keyframes tickerScroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* ========================================================
   TESTIMONIALS
   ======================================================== */
.testimonials-wrap { position: relative; height: 400vh; margin-top: 60px; }
.testimonials-pin {
  position: sticky; top: 0; height: 100vh; overflow: hidden;
  display: flex; align-items: center;
}
.testimonials-pin .title-panel { position: absolute; left: 0; top: 0; bottom: 0; }
.testi-track {
  position: relative;
  display: flex; align-items: center; gap: 32px;
  padding-left: 340px;
  transition: transform .1s linear;
}
.testi-card {
  height: 208px; width: auto; max-width: 480px; flex-shrink: 0;
  background: var(--bg-panel); border: 1px solid var(--muted);
  border-radius: 12px 0 12px 0;
  padding: 16px; box-sizing: border-box; overflow: hidden;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px;
}
/* Hover/color-change motion uses plain `ease` per the site's motion
   guidelines — scale is gated to real hover-capable pointers only, since
   an ungated :hover can get visually "stuck" on a card after a tap on
   touch devices. */
@media (hover: hover) and (pointer: fine) {
  .testi-card { transition: transform .25s ease, box-shadow .25s ease; }
  .testi-card:hover { transform: scale(1.05); box-shadow: 0 20px 40px rgba(0,0,0,.15); z-index: 5; }
}
.testi-card:focus-visible { transform: scale(1.05); box-shadow: 0 20px 40px rgba(0,0,0,.15); z-index: 5; transition: transform .25s ease, box-shadow .25s ease; } /* keyboard focus still gets it, regardless of pointer type */
.testi-card .quote { font-size: 16px; line-height: 1.5; text-align: center; margin: 0; letter-spacing: .01em; }
.testi-card .divider { width: 100%; border-top: 1px dashed var(--muted); flex-shrink: 0; }
.testi-card .who { display: flex; align-items: center; gap: 10px; flex-shrink: 0; white-space: nowrap; }
.testi-card .avatar { width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0; object-fit: cover; display: block; }
.testi-card .who .name { font-family: var(--font-display); font-weight: 600; font-size: 16px; }
.testi-card .who .role { font-size: 12px; color: var(--muted-text); font-weight: 300; }

/* Reduced motion gets the same static, stacked treatment as mobile (see
   js/main.js) — the scroll-jacked vertical->horizontal parallax is
   exactly the kind of motion prefers-reduced-motion exists to opt out
   of, and this needs its own rule (rather than relying on the mobile
   media query below) since it applies at any viewport width. */
html.reduce-motion .testimonials-wrap { height: auto !important; margin-top: 40px; }
html.reduce-motion .testimonials-pin { position: static; height: auto; flex-direction: column; align-items: stretch; padding: 0 16px 40px; }
html.reduce-motion .testimonials-pin .title-panel { position: static; }
html.reduce-motion .testi-track { flex-direction: column; align-items: center; padding-left: 0; padding-right: 0; padding-top: 24px; gap: 24px; transform: none !important; }
html.reduce-motion #testimonialsProgress { display: none; }



/* ========================================================
   ABOUT TEASER
   ======================================================== */
.about-section { display: flex; align-items: stretch; min-height: 900px; margin-top: 60px; padding: 60px 24px 60px 0; }
.about-section .title-panel { position: static; height: auto; }
.about-content { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 24px; padding-left: 12px; }
.about-content .about-photo { position: relative; width: 100%; max-width: 1100px; }
.about-content .about-photo img { width: 100%; display: block; }
.about-content .about-copy { max-width: 700px; text-align: center; font-size: 20px; line-height: 1.5; padding: 24px 0; }
.about-content .about-copy p { margin: 0 0 20px; }
.about-content .about-copy p:last-child { margin-bottom: 0; }
.about-content .about-skills-copy { max-width: 774px; text-align: center; font-size: 20px; line-height: 1.5; padding: 24px 0; }
.about-content .about-skills-copy p { margin: 0 0 8px; }
.about-content .about-skills-copy ul { text-align: left; display: inline-block; margin: 8px auto 0; padding-left: 28px; }
.about-content .about-skills-copy li { margin-bottom: 4px; }
.about-content .about-image-placeholder { width: 200px; max-width: 40vw; border: 1px solid var(--muted); border-radius: 8px; overflow: hidden; background: transparent; }
.about-content .about-image-placeholder img { width: 100%; display: block; }
@media (max-width: 1100px) {
  .about-section { flex-direction: column; padding: 40px 20px; margin-top: 0; }
  .about-content { padding-top: 24px; }
  .about-section .title-panel { position: static; height: auto; }
}

/* ========================================================
   FOOTER
   ======================================================== */
.site-footer {
  position: relative; z-index: 1;
  background: #2b2b2b; border-top: 1px solid var(--muted);
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px; flex-wrap: wrap;
  padding: 48px 64px;
}
.site-footer .logo-mark { width: 60px; height: 60px; color: #fff; }
.site-footer .credit { display: flex; gap: 8px; align-items: center; font-family: var(--font-display); font-weight: 700; font-size: 18px; letter-spacing: .05em; color: #fff; flex-wrap: wrap; justify-content: center; text-align: center; }
.site-footer .icon-buttons { display: flex; gap: 16px; align-items: center; }
.site-footer .icon-buttons a { width: 40px; height: 40px; border: 1px solid #fff; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: #fff; }
@media (hover: hover) and (pointer: fine) {
  .site-footer .icon-buttons a { transition: background .15s ease, color .15s ease; }
  .site-footer .icon-buttons a:hover, .site-footer .icon-buttons a:focus-visible { background: #fff; color: #17171a; }
}
@media (max-width: 860px) {
  .site-footer { flex-direction: column; text-align: center; padding: 40px 24px; }
}

/* ========================================================
   THEME SWIPE TRANSITION OVERLAY (js/main.js creates this)
   ======================================================== */
/* Theme switching is instant for now — Mila's going to drop in her own
   transition code here later. */


/* ========================================================
   RESPONSIVE
   ======================================================== */
@media (max-width: 860px) {
  .top-nav .theme-toggle { display: none; }
  .top-nav { top: 16px; left: 16px; right: 16px; padding: 12px 16px; }

  .title-panel {
    position: static; width: 100%; height: auto; padding: 96px 16px 24px;
    border: none; border-bottom: 1px solid var(--muted);
    backdrop-filter: none; -webkit-backdrop-filter: none;
  }
  .case-studies-wrap { height: auto; }
  .case-studies-pin { position: static; height: auto; flex-direction: column; align-items: stretch; padding: 0 16px 40px; }
  .cs-track { flex-direction: column; align-items: stretch; padding-left: 0; padding-top: 24px; gap: 64px; transform: none !important; }
  .testimonials-wrap { height: auto !important; margin-top: 40px; }
  .testimonials-pin { position: static; height: auto; flex-direction: column; align-items: stretch; padding: 0 16px 40px; }
  .testimonials-pin .title-panel { position: static; }
  .testi-track { flex-direction: column; align-items: center; padding-left: 0; padding-right: 0; padding-top: 24px; gap: 24px; transform: none !important; }
  #testimonialsProgress { display: none; }
  .cs-card { width: 98%; margin: 0 auto; aspect-ratio: 370 / 522.7; height: auto; display: flex; flex-direction: column; transform: none !important; filter: none !important; opacity: 1 !important; }
  .cs-card .print-mark { opacity: 1 !important; } /* no hover state on touch, keep marks visible */

  /* Crop marks scale down and sit closer to the edge to match the
     tighter margin on the mobile card. */
  .cs-card .crop { width: 14px; height: 14px; }
  .cs-card .crop.tl { top: -16px; left: -16px; }
  .cs-card .crop.tr { top: -16px; right: -16px; }
  .cs-card .crop.bl { bottom: -16px; left: -16px; }
  .cs-card .crop.br { bottom: -16px; right: -16px; }

  /* Registration bar visually moves after the card content (matching your
     mobile Figma, where it sits inside the card rather than bleeding into
     the margin) using flex order — kept as a sibling of .art-box rather
     than nested inside it, so desktop's overflow:hidden clipping on
     .art-box never affects it. .art-box gets flex:1 so it claims the
     remaining card height (registration bar's height is fixed/content-sized),
     giving the absolutely-positioned card-bg image inside it real space to
     fill instead of collapsing to its in-flow children's height. */
  .art-box { order: 1; flex: 1; min-height: 0; }
  .cs-card .registration-bar { order: 2; position: relative; top: auto; bottom: auto; left: auto; right: auto; padding: 8px; flex-shrink: 0; }

  .cs-card .art-box { position: relative; inset: auto; display: block; }
  .cs-card .infobar { position: absolute; }
  .cs-card .card-content { position: absolute; top: 44px; left: 0; right: 0; bottom: 0; padding: 8px 8px 16px; }
  .cs-card .tags-row { margin-bottom: auto; padding-bottom: 20px; }
  .cs-card h3 { max-width: 100%; font-size: 20px; }
  .cs-card p.cs-desc { max-width: 100%; }
  .cs-card .impact-card { width: 100%; align-self: stretch; margin-top: 16px; margin-bottom: 0; box-sizing: border-box; }
  .cs-card .view-affordance { display: none; }

  /* Info bar: keep Client / File / Date on one line each. Slightly smaller
     type + tighter gaps/padding than desktop so three fields fit a 370px
     mobile card without wrapping. Never below 11px. */
  .cs-card .infobar { top: 0; left: 0; right: 0; gap: 10px; padding: 8px 12px; font-size: 11px; }
  .cs-card .infobar span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .cs-progress { display: none; }

  .panel-split { flex-direction: column !important; }
  .panel-split.reverse { flex-direction: column !important; }
}

/* ========================================================
   CASE STUDY PAGE
   ======================================================== */
.page-progress { position: fixed; top: 0; left: 0; right: 0; height: 4px; background: transparent; z-index: 500; }
.page-progress-fill { height: 100%; width: 0%; background: var(--fg); }

/* Fixed header backdrop: content fades out and disappears
   behind this before it ever reaches the nav pill. */
.header-scrim {
  position: fixed; top: 0; left: 0; right: 0; height: 130px; z-index: 100;
  background: linear-gradient(to bottom, var(--bg) 0%, var(--bg) 40%, transparent 100%);
  backdrop-filter: blur(10px);
  -webkit-mask-image: linear-gradient(to bottom, black 0%, black 55%, transparent 100%);
          mask-image: linear-gradient(to bottom, black 0%, black 55%, transparent 100%);
  pointer-events: none;
}


.panel-split { display: flex; min-height: 100vh; align-items: stretch; }
.panel-split .txt { flex: 1; border-right: 1px solid var(--muted); display: flex; flex-direction: column; justify-content: center; gap: 20px; padding: 120px 64px 64px 32px; }
.panel-split.reverse { flex-direction: row-reverse; }
.panel-split.reverse .txt { border-right: none; border-left: 1px solid var(--muted); padding: 120px 32px 64px 64px; }
.panel-split .art { flex: 1; position: relative; overflow: hidden; background: var(--accent-blue); }
.panel-split .art img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }

/* === Issue badge — Large (web default) & Small (mobile) === */
.tags-row { display: flex; justify-content: space-between; align-items: center; flex-wrap: nowrap; gap: 12px; }
.tags-row .pills-group { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

/* Issue badge */
.tags-row .issue-badge {
  display: flex; gap: 4px; align-items: center;
  font-family: var(--font-display); font-weight: 700;
  font-size: 20px; letter-spacing: .05em; text-transform: uppercase;
  color: #333; white-space: nowrap; flex-shrink: 0;
}
.tags-row .issue-badge b {
  background: #2b2b2b; color: #fff;
  padding: 3.6px 6px; border-radius: var(--radius-tag);
  display: flex; align-items: center;
  font-size: 20px; line-height: 1;
  box-shadow: 0 1px 2px rgba(12,12,13,.05);
}

/* Tag pill — Large (web default) */
.tag-pill {
  border: 0.5px solid #333; background: var(--bg);
  color: #333;
  font-family: var(--font-display); font-weight: 500;
  font-size: 20px; letter-spacing: .02em; text-transform: capitalize;
  padding: 3px 6px;
  line-height: 1.15;
  border-radius: var(--radius-tag);
  display: inline-flex; align-items: center;
  box-shadow: 0 1px 2px rgba(12,12,13,.05);
  box-sizing: border-box; white-space: nowrap; flex-shrink: 0;
}

/* Small variant — used on mobile and inside cs-card */
.tag-pill.tag-pill--sm,
.cs-card .tag-pill {
  font-size: 16px; letter-spacing: .02em;
  padding: 3px 6px;
  line-height: 1.15;
  border-radius: var(--radius-tag);
  border-width: 0.404px;
}
.issue-badge.issue-badge--sm {
  font-size: 16px; letter-spacing: .05em;
}
.issue-badge.issue-badge--sm b {
  font-size: 16px;
  padding: 3.6px 4.8px;
  border-radius: 6.5px 0 6.5px 0;
}

.panel-split h1 { font-size: clamp(36px, 5vw, 56px); margin: 0; line-height: 1.02; }
.panel-split .desc { font-size: 20px; color: var(--muted-text); max-width: 560px; margin: 0; }

.meta-row { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 32px; border-top: 1px solid var(--muted); padding-top: 20px; }
.meta-item .label { font-size: 13px; color: var(--muted-text); font-weight: 300; }
.meta-item .value { font-size: 16px; font-weight: 700; font-family: var(--font-display); }


.panel-centered { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 40px; position: relative; overflow: hidden; padding: 60px 10vw; }
.panel-centered h2 { font-size: clamp(32px, 5vw, 48px); margin: 0; text-align: center; }
.deco-icon { position: absolute; top: 0; bottom: 0; width: 22vw; max-width: 320px; opacity: .5; }
.deco-icon.left { left: 24px; }
.deco-icon.right { right: 24px; }
.deco-icon img { width: 100%; height: 100%; object-fit: contain; }

.quote-block { display: flex; gap: 48px; align-items: center; flex-wrap: wrap; justify-content: center; max-width: 1100px; }
.quote-block .copy { max-width: 560px; }
.quote-block .copy .label { font-size: 18px; font-weight: 700; text-transform: uppercase; margin: 16px 0 8px; }
.quote-block .copy p { font-size: 16px; line-height: 1.6; margin: 0 0 4px; }
.quote-block img { max-width: 380px; }

.goal-row { display: flex; gap: 16px; align-items: center; max-width: 597px; }
.goal-row .num {
  background: #2b2b2b; color: #fff; font-weight: 700; font-size: 32px;
  width: 56px; height: 56px; flex-shrink: 0;
  border-radius: var(--radius-tag);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); letter-spacing: 1px;
}
.goal-row p { font-size: 16px; margin: 0; }
.goals-list { display: flex; flex-direction: column; gap: 28px; }

.bottom-nav { position: sticky; bottom: 0; display: flex; align-items: center; justify-content: space-between; padding: 24px 32px; background: rgba(251,244,234,.9); backdrop-filter: blur(6px); border-top: 1px solid var(--muted); z-index: 90; }
[data-theme="dark"] .bottom-nav { background: rgba(23,23,26,.9); }
.bottom-nav .nav-link { font-size: 18px; font-weight: 500; font-family: var(--font-display); }
.bottom-nav .icon { width: 28px; height: 28px; }

/* ========================================================
   FLIP SIDE — gallery
   The first row (.flip-hero) fills the viewport with no scroll
   of its own — a self-contained opening statement with the
   page intro. Additional rows (.flip-hero--secondary, one per
   batch of 4 items) stack below it, so the page as a whole
   scrolls to reach them — same card component, just without
   the intro block, and a shorter fixed height since there's no
   intro consuming part of it.
   Cards widen + reveal their info panel on hover; siblings
   shrink proportionally so the full row always stays inside
   the viewport width.
   Mobile: every row stacks vertically and scrolls; cards
   default to a short collapsed strip and grow open on tap,
   auto-centering the tapped card.
   All motion is driven by GSAP directly animating layout
   properties in js/flip-gallery.js (see the comment there for
   why) — the values below are just the closed resting state.
   Do NOT add `transition` to these properties; that fights the
   JS timeline and is exactly the bug we moved away from.
   ======================================================== */
.flip-hero {
  height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0 48px;
  box-sizing: border-box;
}
.flip-hero--secondary {
  height: 82vh;
  padding-top: 32px;
}
.flip-hero__intro {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 24px;
  flex-wrap: wrap;
  flex-shrink: 0;
  padding: 128px 0 12px;
}
.flip-hero__lockup.nav-menu-item { flex-direction: row; align-items: center; border-right: 1px solid var(--fg); padding-right: 24px; text-align: left; }
.flip-hero__lockup .content-lockup { text-align: left; }
.flip-hero__lockup .title { text-align: left; }
.flip-hero__tagline { font-size: 20px; max-width: 329px; margin: 0; }

.flip-accordion {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: stretch;
  gap: 16px;
  padding-bottom: 24px;
}

.flip-card {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg-panel);
  border: 1px solid var(--muted);
  border-radius: 8px;
  overflow: hidden;
  cursor: pointer;
  padding: 0;
  appearance: none;
  font: inherit;
  color: inherit;
  text-align: left;
}
.flip-card__media {
  display: block;
  flex: 1 1 100%;
  min-height: 0;
  overflow: hidden;
  background: var(--accent-blue);
  border-bottom: 1px solid transparent;
  transition: border-color .2s ease; /* paint-only, cheap — fine outside the JS timeline */
}
.flip-card.is-active .flip-card__media { border-bottom-color: var(--muted); }
.flip-card__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Moist Mock is a tall studio shot — cover-cropping it hides the box.
   Contain it instead so the full bottle + box is always visible.
   (Currently unused — re-add the class to a card once there's an
   AI-expanded version of the source image that crops better.) */
.flip-card--contain .flip-card__media img { object-fit: contain; }

/* flex-basis/flex-grow, opacity, and padding on .flip-card / .flip-card__media
   / .flip-card__info are all owned by js/flip-gallery.js from here — every
   property that needs to move together lives in ONE animation timeline there.
   Nothing below defines an ".is-active" numeric target for those three
   properties; that's intentional so there's no separate CSS-driven snap
   fighting the JS animation (that was the padding-jump bug). What's below
   is just the closed resting state. */
.flip-card__info {
  display: flex;
  flex-direction: column;
  gap: 16px;
  flex: 0 0 0px;
  min-height: 0;
  opacity: 0;
  padding: 0 12px;
  overflow: hidden;
}
.flip-card__tags { display: flex; align-items: center; }
.flip-card__text { display: flex; flex-direction: column; gap: 8px; min-height: 0; }
.flip-card__title {
  font-size: 20px; font-weight: 700; text-transform: capitalize; color: var(--fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; /* never wraps, so it can't
    flip between 1 and 2 lines mid-animation as the card's width changes */
}
.flip-card__desc { font-size: 14px; color: var(--muted-text); line-height: 1.3; margin: 0; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }

/* Tag pill inside the flip cards, forced to the light-mode palette in both
   themes — unscoped from any breakpoint because the same low-contrast bug
   (dark text on a dark bg-panel/photo) shows up on desktop hover too, not
   just mobile. color-scheme:light is a defensive belt-and-suspenders fix:
   the pill lives inside a native <button>, and some browsers apply their
   own OS-level dark color scheme to form controls independent of our
   manual [data-theme] class, which can silently override these colors
   even with appearance:none set. */
.flip-card__tags .tag-pill {
  background: #fbf4ea !important; border-color: #2b2b2b !important; color: #2b2b2b !important;
  color-scheme: light;
}

@media (max-width: 860px) {
  .flip-hero, .flip-hero--secondary { height: auto; overflow: visible; padding: 0 16px; }
  .flip-hero--secondary { padding-top: 16px; } /* matches the 16px gap already used between cards within a row */
  .flip-hero__intro { padding: 140px 0 24px; flex-direction: column; text-align: center; gap: 12px; }
  .flip-hero__lockup.nav-menu-item { border-right: none; padding-right: 0; }
  .flip-hero__tagline { text-align: center; }

  .flip-accordion { flex-direction: column; padding: 0; gap: 16px; }
  /* End-of-page breathing room belongs on whichever row is actually last —
     not on every row, which would stack up into a big gap between rows. */
  .flip-hero--secondary:last-of-type .flip-accordion { padding-bottom: 60px; }
  .flip-card { flex: none; width: 100%; height: 110px; }

  .flip-card__tags .tag-pill { font-size: 16px; border-width: 0.404px; }

  .goal-row .num { width: 44px; height: 44px; font-size: 24px; }
  .testi-card { max-width: 320px; height: auto; overflow: visible; }
}


/* ========================================================
   PRELOADER
   Plays the MG_Convert logo draw-on animation once, then
   wipes away diagonally (bottom-right -> top-left) to reveal
   the page. Runs fresh on every load/reload (no skip-once
   logic), and locks page scroll while active.
   ======================================================== */
#mg-preloader {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  background: #fff;
  /* Right-triangle with legs along the top/left edges (each extending
     100% past the viewport) and its hypotenuse running from the top-right
     area down to the bottom-left area, positioned so it clears the
     bottom-right corner entirely -> full, solid coverage at rest. */
  clip-path: polygon(-100% -100%, 300% -100%, -100% 300%);
  transition: clip-path .39s cubic-bezier(.65,0,.35,1);
}
#mg-preloader.mg-wipe {
  /* Same triangle translated -120% in both x and y: its diagonal edge
     sweeps across the viewport clearing the bottom-right corner first
     and the top-left corner last, fully uncovering the page. */
  clip-path: polygon(-220% -220%, 180% -220%, -220% 180%);
}
#mg-preloader .mg-logo {
  width: min(340px, 58vw);
  will-change: transform, opacity;
}
#mg-preloader svg { display: block; width: 100%; height: auto; overflow: visible; }
body.mg-loading { overflow: hidden; height: 100%; }

.mg-preloader-inner { display: flex; flex-direction: column; align-items: center; gap: 40px; }
.mg-loading-label {
  display: flex; align-items: center; gap: 4px;
  font-size: 15px; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: #363636;
}
.mg-dots { display: inline-flex; gap: 3px; }
.mg-dots span {
  width: 4px; height: 4px; border-radius: 50%; background: #363636;
  animation: mg-dot-bounce .9s ease-in-out infinite;
}
.mg-dots span:nth-child(2) { animation-delay: .15s; }
.mg-dots span:nth-child(3) { animation-delay: .3s; }
@keyframes mg-dot-bounce {
  0%, 60%, 100% { transform: translateY(0); opacity: .4; }
  30% { transform: translateY(-4px); opacity: 1; }
}

/* ============================================================
   CASE STUDY PAGE (case-study.html) — new components
   Hero text stays fixed #333 (sits on a photographic background in
   both themes). All other section text uses var(--fg) so it inverts
   correctly in dark mode.
   ============================================================ */

.cs-page { background: var(--bg); padding-bottom: 64px; }

/* Hero — contained to the same inset/width as .top-nav, 8px corners,
   image fills to viewport height. Positioned 16px below nav, 16px above
   viewport bottom (nav is ~64px, so wrap starts at ~80px from top). */
.cs-hero-wrap {
  padding: 0 24px;
  /* nav top 24px + ~64px height + 32px gap = 120px */
  margin-top: 120px;
  height: calc(100vh - 120px - 16px);
  min-height: 400px;
  filter: drop-shadow(0 4px 24px rgba(0,0,0,0.10)) drop-shadow(0 1px 4px rgba(0,0,0,0.06));
}
.cs-hero {
  position: relative;
  height: 100%;
  border-radius: 8px;
  display: flex; flex-direction: column;
  gap: 16px;
  padding: 48px 40px 56px;
  overflow: hidden;
  isolation: isolate;
  /* White bg behind the hero image — keeps the light-blue illustration
     readable in dark mode since the web PNG has transparent outer edges.
     Mobile hero uses a fully-opaque baked-in image, no white needed there. */
  background: #ffffff;
}
.cs-hero .cs-hero-bg {
  position: absolute; inset: 0;
  z-index: -1;
  object-fit: cover;
  width: 100%; height: 100%;
  object-position: center center;
}
.cs-hero .tags-row { width: 100%; justify-content: space-between; gap: 15px; }
.cs-hero h1 {
  color: #333; width: 100%; max-width: 900px;
  font-size: clamp(26px, 3vw, 40px);
  text-transform: capitalize;
  margin: 0; line-height: 1.1;
}
.cs-hero .desc {
  color: #333; width: 100%; max-width: 620px;
  font-size: clamp(15px, 1.4vw, 20px); line-height: 1.6; margin: 0;
}
/* Scroll hint: pinned 16px above the hero's bottom edge */
.cs-hero .scroll-hint {
  position: absolute;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  backdrop-filter: blur(2px);
  background: rgba(255,255,255,.1);
  padding: 4px 10px;
  border-radius: 4px;
  border: none; cursor: pointer;
  font-family: inherit;
  white-space: nowrap;
}
.cs-hero .scroll-hint span {
  font-family: var(--font-display); font-weight: 700;
  font-size: 12px; letter-spacing: .1em; text-transform: uppercase;
  color: #6b6b6b;
  display: inline-block;
  animation: scroll-hint-pulse 2.3s ease-in-out infinite;
}
@keyframes scroll-hint-pulse {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0.35; }
}

.cs-section {
  padding: 96px 24px;
  display: flex; flex-direction: column; gap: 48px;
  max-width: 1365px; margin: 0 auto;
  position: relative;
}

.cs-section-title {
  background: rgba(255, 255, 255, 0.5);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border-left: 4px solid var(--fg);
  padding: 12px 16px; display: flex; flex-direction: column; gap: 8px;
  width: 100%; box-sizing: border-box;
}
[data-theme="dark"] .cs-section-title {
  background: var(--panel-tint);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.cs-section-title .cs-title {
  font-family: var(--font-display); font-weight: 700;
  font-size: 24px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--fg); margin: 0;
}
.cs-section-title .cs-desc {
  font-family: var(--font-body); font-weight: 300;
  font-size: 18px; line-height: 26px; color: var(--fg); margin: 0;
}
/* Solution's title uses the same fixed orange highlight as its corner
   tag, so it stands out from the other (theme-reactive) section
   titles — not theme-reactive, deliberately, to match the tag exactly. */
.cs-section-title--solution {
  background: #f1decf; border-left-color: #d1784d;
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
[data-theme="dark"] .cs-section-title--solution { background: #f1decf; }
.cs-section-title--solution .cs-title,
.cs-section-title--solution .cs-desc { color: #333; }

.cs-overview {
  display: flex; flex-wrap: wrap; gap: 24px 48px;
  justify-content: space-between;
  padding: 16px; position: relative; z-index: 1;
}
.cs-overview .cs-col { display: flex; flex-direction: column; gap: 12px; flex: 1 1 0; min-width: 260px; }
.cs-overview .cs-col-label {
  font-family: var(--font-display); font-weight: 700;
  font-size: 20px; letter-spacing: .05em; text-transform: uppercase; color: var(--fg);
}
.cs-overview .cs-col p, .cs-overview .cs-col ul {
  font-family: var(--font-body); font-size: 16px; line-height: 24px; color: var(--fg);
  margin: 0; padding-left: 24px;
}
.cs-overview .cs-col:first-child p { padding-left: 0; }

.cs-decorative-icon {
  position: absolute; top: 0; right: 0; bottom: 0;
  height: 100%; width: auto; opacity: .9;
  pointer-events: none; z-index: 0;
}

.cs-project-role {
  border-bottom: 1px solid var(--muted);
  display: flex; flex-wrap: wrap; gap: 16px 32px;
  justify-content: space-between;
  padding: 24px 16px 17px;
  position: relative; z-index: 1;
}
.cs-project-role .cs-role-block { display: flex; flex-direction: column; gap: 4px; }
.cs-project-role .cs-role-label {
  font-family: var(--font-body); font-weight: 300; font-size: 16px; color: var(--fg);
}
.cs-project-role .cs-role-value {
  font-family: var(--font-display); font-weight: 700; font-size: 18px;
  letter-spacing: .05em; color: var(--fg);
}
.cs-project-role .cs-role-value span:not(:last-child)::after { content: " \00b7 "; font-weight: 400; letter-spacing: 0; }

/* Stacked crew list — for crews too large to read comfortably as one
   inline line (see .cs-role-value above, built for 1-2 people). Reuses
   the same label/value type scale as the rest of .cs-project-role, one
   name-per-line instead. */
.cs-role-block--crew { flex: 1 1 auto; min-width: 220px; }
.cs-crew-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.cs-crew-list li { display: flex; align-items: baseline; gap: 6px; }
.cs-crew-name {
  font-family: var(--font-display); font-weight: 700; font-size: 18px;
  letter-spacing: .05em; color: var(--fg);
}
.cs-crew-sep { font-family: var(--font-display); font-weight: 400; color: var(--fg); }
.cs-crew-title { font-family: var(--font-body); font-weight: 300; font-size: 16px; color: var(--fg); }

.cs-image-block { width: 100%; border-radius: 8px; overflow: hidden; border: 1px solid var(--muted); }
.cs-image-block img { display: block; width: 100%; height: auto; object-fit: cover; }
.cs-image-row { display: flex; gap: 48px; flex-wrap: wrap; }
.cs-image-row .cs-image-block { flex: 1 1 300px; }

/* Flyout image + drag/drop video pair — both fixed to the same width so
   they line up, each "hugging" its own natural height (the video's own
   aspect ratio ends up governing the row's height; align-items:stretch,
   the flex default, makes the image block match it automatically). The
   image itself is just centered inside its block rather than stretched
   to fill it, with a themed block background showing around it and a
   caption centered over the middle. */
.cs-image-row .cs-design-media-block {
  flex: 0 0 633px; width: 633px; max-width: 100%; box-sizing: border-box;
  position: relative; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 8px;
  background: #fff; padding-bottom: 8px;
}
[data-theme="dark"] .cs-image-row .cs-design-media-block { background: #232327; }
.cs-design-media-block video { display: block; width: 100%; height: auto; }
.cs-design-media-block--image img { width: auto; height: auto; max-width: 100%; max-height: 100%; object-fit: contain; }
.cs-design-media-caption {
  font-family: var(--font-body); font-style: italic; font-size: 14px;
  color: var(--muted-text); margin: 0; text-align: center; pointer-events: none;
}
@media (max-width: 860px) {
  .cs-image-row .cs-design-media-block { width: 359px; flex-basis: 359px; }
}

/* Research section — text panel (left) + auto-cycling image carousel
   (right), side by side at 468px each. Reused as a pattern; not yet
   rolled out to the other case studies. */
.cs-research-row { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 24px; }
.cs-research-panel {
  width: 568px; height: 451px; flex: 0 0 568px;
  background: var(--bg-panel); border: 1px solid var(--muted); border-radius: 8px;
  box-sizing: border-box; overflow: hidden;
}
/* Sits between the two panels in place of the plain gap — space-between
   on the row naturally centers it since it's the only item flanked
   equally by two fixed-width siblings. */
.cs-research-arrow { display: flex; align-items: center; padding: 0 8px; flex-shrink: 0; }
.cs-research-arrow img { width: 20px; height: 20px; }
[data-theme="dark"] .cs-research-arrow img { filter: invert(1); }
@media (max-width: 860px) {
  .cs-research-arrow { display: none; } /* panels stack on mobile — no "between" to point at */
}

.cs-research-text { position: relative; padding: 24px; display: flex; flex-direction: column; gap: 24px; }
.cs-research-heading { border-bottom: 1px solid var(--muted); padding-bottom: 12px; position: relative; z-index: 1; }
.cs-research-heading h3 {
  font-family: var(--font-display); font-weight: 700;
  font-size: 24px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--fg); margin: 0;
}
.cs-research-intro {
  font-family: var(--font-body); font-weight: 400; font-size: 16px; line-height: 24px;
  color: var(--fg); margin: 0; position: relative; z-index: 1;
}
.cs-research-details { display: flex; flex-direction: column; gap: 16px; position: relative; z-index: 1; }
/* .cs-case-detail's shared flex:1 1 400px was written for its original
   horizontal usage (Impact section rows), where flex-basis controls
   width. Reused here inside a column-direction flex container,
   flex-basis controls height instead — without this override each item
   inflates to 400px tall. Reset to natural content height. */
.cs-research-details .cs-case-detail { flex: 0 0 auto; }
/* Text reveal here is a typewriter effect driven by JS (see
   case-study.js) — the boxes themselves are visible as soon as the
   panel fades in; only the text content types out, once, the first
   time the panel scrolls into view. */
/* Ghosted watermark — true-centered over the card and sized as a
   percentage of it, so it scales with the card rather than being pinned
   to a fixed pixel size. Opacity is the single explicit 3% here (the
   source SVG is a plain solid fill with no baked-in opacity of its own,
   so this is the only place that number lives). The icon's fill is a
   fixed dark grey, which disappears against the dark-mode panel — invert
   it there so it stays visible in both themes. */
.cs-research-text .cs-decorative-icon {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 60%; height: 60%; max-width: 260px;
  opacity: .02; z-index: 0;
}
/* Case study 3 only — this Research panel's watermark should fill the
   full card rather than the 60%/260px cap the others use. */
[data-case-study="3"] .cs-research-text .cs-decorative-icon {
  top: 0; left: 0; transform: none;
  width: 100%; height: 100%; max-width: none;
}
[data-theme="dark"] .cs-research-text .cs-decorative-icon { filter: invert(1); }

.cs-research-carousel { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 24px 24px 16px; }
.cs-carousel-track { position: relative; width: 100%; height: 370px; }
.cs-carousel-track img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity .6s ease;
}
.cs-carousel-track img.is-active { opacity: 1; }
.cs-carousel-caption {
  font-family: var(--font-body); font-size: 12px; font-style: italic;
  color: var(--muted-text); text-align: center; margin: 8px 0 0;
}
.cs-carousel-dots { display: flex; align-items: center; gap: 10px; }
.cs-carousel-dots button {
  width: 8px; height: 8px; border-radius: 50%; padding: 0; border: none;
  background: var(--muted); cursor: pointer; transition: background-color .2s ease, transform .2s ease;
}
.cs-carousel-dots button.is-active { background: var(--orange-102); transform: scale(1.25); }

@media (max-width: 860px) {
  .cs-research-row { flex-direction: column; gap: 24px; }
  .cs-research-panel { width: 100%; height: auto; flex: none; }
  .cs-research-heading h3 { font-size: 20px; }
  .cs-carousel-track { height: 260px; }
}

/* Solution + Design sections — both driven by the same [data-carousel]
   JS, and (as of this pass) both share the same "image fills the card"
   treatment: object-fit:cover (crop to fill, no letterboxing) with the
   arrows + dot indicator overlaid directly on top of the image, at
   every breakpoint. Design previously used a different contain +
   arrows-outside-the-card treatment; that's been intentionally dropped
   in favor of matching Solution, so don't reintroduce it as a "fix". */
.cs-solution-carousel-wrap, .cs-design-carousel-wrap { position: relative; }

/* Case study 3 only — Lottie embeds above the Design carousel. Each
   variant's aspect-ratio is locked to its own composition's native
   dimensions (web 1316x687, mobile 361x1510) so lottie-web's internal
   scaling has a correctly-proportioned box to fill rather than
   falling back to letterboxing. */
[data-case-study="3"] .cs-lottie-embed {
  width: 100%; margin-bottom: 32px; border-radius: 8px; overflow: hidden;
}
[data-case-study="3"] .cs-lottie-embed svg { display: block; }
[data-case-study="3"] .cs-lottie-embed--web { aspect-ratio: 1316 / 687; }
[data-case-study="3"] .cs-lottie-embed--mobile { aspect-ratio: 361 / 1510; display: none; }
@media (max-width: 860px) {
  [data-case-study="3"] .cs-lottie-embed--web { display: none; }
  [data-case-study="3"] .cs-lottie-embed--mobile { display: block; }
}

.cs-image-block.cs-solution-media { height: 690px; position: relative; overflow: hidden; }
.cs-solution-media .cs-carousel-track,
.cs-design-carousel .cs-carousel-track {
  position: relative; width: 100%; height: 100%;
}
.cs-solution-media .cs-carousel-track img,
.cs-design-carousel .cs-carousel-track img,
.cs-solution-media .cs-carousel-track video,
.cs-design-carousel .cs-carousel-track video {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity .6s ease;
}
.cs-solution-media .cs-carousel-track img.is-active,
.cs-design-carousel .cs-carousel-track img.is-active,
.cs-solution-media .cs-carousel-track video.is-active,
.cs-design-carousel .cs-carousel-track video.is-active { opacity: 1; }
.cs-solution-media .cs-carousel-dots--indicator,
.cs-design-carousel-wrap .cs-carousel-dots--indicator {
  position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 3;
  width: auto; padding: 0;
}

/* Opt-in via .cs-carousel-track--contain (Design carousel + the new
   Research audit carousel) — object-fit:cover crops edges, which cuts
   off real content on screenshots like these (spreadsheet columns,
   annotated diagrams, doc pages) the way it doesn't on the Solution
   carousel's images.
   Real bug last round: .cs-carousel-track--contain img alone has lower
   specificity (0,1,1) than the base cover rule above
   (.cs-design-carousel .cs-carousel-track img, at (0,2,1)) — so cover
   was silently still winning everywhere, on both carousels. It just
   read as "working" on the Design carousel because its images'
   proportions roughly matched its 16:9 box, so the crop wasn't
   obvious; the Research carousel's very differently-proportioned
   images made the same bug plainly visible. Chaining the same two
   ancestor classes in both rules below (0,3,1) guarantees this wins
   regardless of which carousel it's nested in.
   The two carousels are NOT handled identically here on purpose:
   .cs-design-carousel already has its own container-level
   padding:16px (further up, from the original case-study-1 content) —
   adding another 16px inset on the image itself there too would stack
   to 32px total. So Design only needs the object-fit switched to
   contain; Research (.cs-solution-media) has no such container
   padding, so it needs the 16px inset added directly onto the image. */
.cs-design-carousel .cs-carousel-track.cs-carousel-track--contain img,
.cs-design-carousel .cs-carousel-track.cs-carousel-track--contain video {
  object-fit: contain;
}
.cs-solution-media .cs-carousel-track.cs-carousel-track--contain img,
.cs-solution-media .cs-carousel-track.cs-carousel-track--contain video {
  inset: 16px; width: calc(100% - 32px); height: calc(100% - 32px);
  object-fit: contain;
}

/* Dynamic per-slide aspect ratio (Research audit + Design carousels).
   object-fit:contain never crops, but a box ratio that doesn't match
   the CURRENT image still letterboxes it — confirmed with real
   numbers: the research audit carousel's 690px-tall box (inherited
   from the original Solution carousel's own images, ~1.91:1) against
   this carousel's raw-hex-values image (1.4:1) left ~180px of empty
   space on each side, which combined with the 16px inset above read as
   much more empty space than "16px padding" implies. Rather than pick
   one static ratio that only suits some of a carousel's images
   (there's no single ratio that fits 1.79, 1.40, and 1.75 well),
   js/case-study.js measures each slide's own natural width/height as
   it becomes active and writes it here via --contain-ratio, so the box
   always matches whichever image is currently showing — zero
   letterboxing on every slide, not just the ones close to a guess.
   Higher specificity than each carousel's own base sizing rule
   (.cs-solution-media's fixed height:690px / .cs-design-carousel's
   fixed aspect-ratio:16/9) so this cleanly overrides either one. */
.cs-solution-media.cs-carousel-box--contain,
.cs-design-carousel.cs-carousel-box--contain {
  height: auto;
  aspect-ratio: var(--contain-ratio, 1317 / 690);
}

/* Design section — big 4-image carousel with prev/next arrows (autoplay
   off — arrows are the only way through it), a read-only progress-dot
   indicator overlaid on the image, and an "Nth iteration" tag. Reuses
   the same [data-carousel] JS as the Research carousel, just with a
   couple of extra hooks (data-carousel-prev/next, a non-clickable dot
   variant, and a caption target that's a corner tag instead of a
   centered caption line under the image). */
.cs-design-carousel {
  border-radius: 8px; border: 1px solid var(--muted); overflow: hidden;
  background: var(--bg-panel); box-sizing: border-box;
  position: relative; padding: 16px;
  /* 3 of the 4 carousel images are exactly 16:9; the 4th
     (maint-sched-2nd-iteration.png) is 1600x855 (~1.87:1), close enough
     that cover only crops it by ~2% top/bottom — unlike a fixed pixel
     height, this scales correctly at every viewport width instead of
     mismatching badly on mobile specifically. */
  aspect-ratio: 16 / 9;
}
/* Track fills the padded content box automatically (width/height:100%
   is relative to that, not the outer border box) — arrows/dots/tag are
   all position:absolute against .cs-design-carousel itself, so this
   padding doesn't affect them; their insets are still measured from
   the outer edge, not the new inner padding. Small radius on the
   media itself now that it's inset and its corners are visible instead
   of being clipped flush by the card's own rounded corners. */
.cs-design-carousel .cs-carousel-track img,
.cs-design-carousel .cs-carousel-track video { border-radius: 4px; }

/* Corner-tag treatment for Design's "Nth iteration" label (8px from the
   top, flush against the right edge). Solution used to carry a matching
   static "Solution" tag here too, but that's redundant now that the
   section title communicates it, so .cs-design-tag--solution and its
   markup were removed — this positioning is Design-only now. */
.cs-design-tag {
  position: absolute; top: 8px; right: 0; z-index: 2;
  background: var(--panel-tint); border-left: 4px solid var(--fg);
  padding: 12px 16px; margin: 0;
  font-family: var(--font-display); font-weight: 700; font-size: 24px;
  letter-spacing: .02em; text-transform: uppercase; color: var(--fg);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
}
[data-theme="dark"] .cs-design-tag { backdrop-filter: none; -webkit-backdrop-filter: none; }

.cs-carousel-dots--indicator {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  pointer-events: none;
}
.cs-carousel-dots--indicator span {
  display: block; width: 8px; height: 8px; border-radius: 50%;
  background: var(--muted); transition: background-color .2s ease, transform .2s ease;
}
.cs-carousel-dots--indicator span.is-active { background: var(--orange-102); transform: scale(1.25); }

.cs-carousel-arrows { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
.cs-carousel-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 26px; height: 26px; border-radius: 0 8px 0 8px; pointer-events: auto;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-panel); border: 1px solid var(--muted); cursor: pointer; padding: 0;
}
.cs-carousel-arrow img { width: 14px; height: 14px; }
[data-theme="dark"] .cs-carousel-arrow img { filter: invert(1); }
/* Overlaid on the image at a consistent 16px inset, at every breakpoint,
   for both Solution and Design — see the shared-treatment note above. */
/* 8px inset on both sides, matching the expand button's 8px inset
   exactly (see .cs-carousel-expand below) so the arrow and expand
   button edges actually line up instead of sitting at two different
   insets (16px vs 8px). */
.cs-carousel-arrow--prev { left: 8px; }
.cs-carousel-arrow--next { right: 8px; }
@media (hover: hover) and (pointer: fine) {
  .cs-carousel-arrow { transition: background-color .15s ease; }
  .cs-carousel-arrow:hover, .cs-carousel-arrow:focus-visible { background: var(--fg); }
  .cs-carousel-arrow:hover img, .cs-carousel-arrow:focus-visible img { filter: invert(1); }
  [data-theme="dark"] .cs-carousel-arrow:hover img, [data-theme="dark"] .cs-carousel-arrow:focus-visible img { filter: invert(0); }
}

/* Base font-family for Material Symbols ligatures — previously only
   declared scoped to .theme-toggle button, so any other element using
   the .material-symbols-outlined class (like the two below) rendered
   literal text ("fullscreen", "close") instead of an icon. Declaring it
   on the shared class itself instead fixes that for every current and
   future user of the class, not just these two. */
.material-symbols-outlined { font-family: 'Material Symbols Outlined'; font-weight: normal; font-style: normal; }

/* Expand-to-lightbox button — top-left corner (Design's iteration tag
   already owns top-right, dots own bottom-center, so this is the one
   free corner on both carousels). Same button chrome as the prev/next
   arrows for visual consistency. */
.cs-carousel-expand {
  position: absolute; top: 8px; left: 8px; z-index: 3;
  width: 32px; height: 32px; border-radius: 0 8px 0 8px;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-panel); border: 1px solid var(--muted); cursor: pointer; padding: 0;
}
.cs-carousel-expand .material-symbols-outlined { font-size: 18px; color: var(--fg); }
@media (hover: hover) and (pointer: fine) {
  .cs-carousel-expand { transition: background-color .15s ease; }
  .cs-carousel-expand:hover, .cs-carousel-expand:focus-visible { background: var(--fg); }
  .cs-carousel-expand:hover .material-symbols-outlined,
  .cs-carousel-expand:focus-visible .material-symbols-outlined { color: var(--bg-panel); }
}

/* Full-screen image lightbox — one shared instance for every
   [data-carousel-lightbox] carousel. Dark scrim regardless of site
   theme (a photo viewer, not themed page chrome), image letterboxed via
   object-fit:contain so nothing gets cropped at any screen size. */
.cs-lightbox {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  background: rgba(12, 12, 13, .92);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .25s ease;
  padding: 56px 72px; box-sizing: border-box;
}
.cs-lightbox.is-open { opacity: 1; visibility: visible; pointer-events: auto; }
body.cs-lightbox-open { overflow: hidden; }
.cs-lightbox-content {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  width: 100%; height: 100%; min-height: 0;
}
.cs-lightbox-hint {
  display: none; margin: 0 0 16px; font-size: 14px; color: rgba(255, 255, 255, .75);
  text-align: center; flex: none;
}
.cs-lightbox-image { flex: 1 1 auto; min-height: 0; max-width: 100%; max-height: 100%; object-fit: contain; display: block; border-radius: 4px; }
.cs-lightbox-video { flex: 1 1 auto; min-height: 0; max-width: 100%; max-height: 100%; object-fit: contain; display: none; border-radius: 4px; }
.cs-lightbox-close, .cs-lightbox-arrow {
  position: absolute; display: flex; align-items: center; justify-content: center;
  background: rgba(255, 255, 255, .08); border: 1px solid rgba(255, 255, 255, .3);
  border-radius: 0 8px 0 8px; cursor: pointer; padding: 0;
}
.cs-lightbox-close { top: 20px; right: 20px; width: 40px; height: 40px; }
.cs-lightbox-close .material-symbols-outlined { font-size: 22px; color: #fff; }
.cs-lightbox-arrow { top: 50%; transform: translateY(-50%); width: 44px; height: 44px; }
.cs-lightbox-arrow--prev { left: 20px; }
.cs-lightbox-arrow--next { right: 20px; }
.cs-lightbox-arrow img { width: 16px; height: 16px; filter: invert(1); }
@media (hover: hover) and (pointer: fine) {
  .cs-lightbox-close:hover, .cs-lightbox-arrow:hover { background: rgba(255, 255, 255, .18); }
}
@media (max-width: 860px) {
  .cs-lightbox { padding: 16px 56px; }
  .cs-lightbox-close { top: 12px; right: 12px; width: 36px; height: 36px; }
  .cs-lightbox-arrow { width: 36px; height: 36px; }
  .cs-lightbox-arrow--prev { left: 8px; }
  .cs-lightbox-arrow--next { right: 8px; }
}
/* The rotate hint only makes sense in mobile portrait — landscape
   already gives these wide screenshots the room they need, so the
   hint disappears the moment the phone is actually rotated instead of
   nagging regardless of orientation. */
@media (max-width: 860px) and (orientation: portrait) {
  .cs-lightbox-hint { display: block; }
}

@media (max-width: 860px) {
  .cs-design-tag { height: 26px; padding: 8px 4px; font-size: 12px; box-sizing: border-box; display: flex; align-items: center; }
}

/* Solution section — two fixed-size video demos, side by side. See the
   HTML comment above .cs-solution-videos for the full reasoning. */
.cs-solution-videos { display: flex; justify-content: space-between; gap: 24px; flex-wrap: wrap; width: 100%; }
.cs-solution-video-block {
  width: 632px; max-width: 100%; aspect-ratio: 632 / 581;
  background: #F3F3F3; border-radius: 8px; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.cs-solution-video-block video { width: 100%; height: 100%; object-fit: contain; display: block; }
@media (max-width: 860px) {
  .cs-solution-videos { flex-direction: column; gap: 16px; }
  .cs-solution-video-block { width: 100%; }
}

.cs-impact-card {
  background: var(--accent-blue); border: 0.666px solid var(--fg);
  border-radius: 7.357px;
  box-shadow: 0 4px 2px rgba(12,12,13,.1), 0 4px 2px rgba(12,12,13,.05);
  display: flex; flex-wrap: wrap; align-items: center; gap: 16px;
  padding: 8px 12px; width: 100%; box-sizing: border-box;
}
.cs-impact-card .cs-impact-label {
  font-family: var(--font-display); font-weight: 700; font-size: 24px;
  letter-spacing: .05em; text-transform: uppercase; color: var(--fg);
}
.cs-impact-card .cs-impact-stat {
  display: flex; align-items: center; gap: 12px;
}
.cs-impact-card .cs-impact-icon {
  width: 36.78px; height: 36.78px; border-radius: 50%; background: var(--fg);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.cs-impact-card .cs-impact-icon img { width: 60%; height: 60%; filter: brightness(0) invert(1); }
[data-theme="dark"] .cs-impact-card .cs-impact-icon img { filter: brightness(0); }
.cs-impact-card .cs-impact-text {
  font-family: var(--font-display); font-weight: 500; font-size: 24px;
  letter-spacing: .05em; color: var(--fg); white-space: nowrap;
}

.cs-impact-section { display: flex; flex-direction: column; gap: 24px; width: 100%; }
.cs-impact-row {
  display: flex; align-items: center; gap: 24px 32px; flex-wrap: wrap;
}
/* Impact Icon Block, per Figma node 939:1615, now with the animated
   card background from Claude Design (see js/impact-card-animation.js).
   Sized by aspect-ratio rather than a fixed desktop px + a separate
   full-width mobile override — the card's art (blob geometry, dot grid)
   is authored for its native ~190.7:200 proportions, so stretching it
   to a wide/short mobile strip would visibly distort it. aspect-ratio
   keeps it correctly proportioned and lets it shrink fluidly on narrow
   phones instead, capped at its native size via max-width. */
.cs-impact-badge {
  background: var(--bg-panel); border: 1px solid var(--muted); border-radius: 8px;
  width: 191px; max-width: 100%; aspect-ratio: 190.7143 / 200; flex-shrink: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px; padding: 16px; overflow: hidden; position: relative; box-sizing: border-box;
}
/* Fallback for any badge that's still just a bare icon with no circle/
   label markup (case-study-2's placeholder rows currently look like
   this) — keeps them presentable without requiring that page's content
   to be finished first. */
.cs-impact-badge > img { width: 55%; height: 55%; object-fit: contain; opacity: .85; }
[data-theme="dark"] .cs-impact-badge > img { filter: brightness(0) invert(1); opacity: .7; }

.cs-impact-icon-circle {
  width: 53px; height: 53px; border-radius: 50%; background: var(--fg);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  position: relative; z-index: 1;
}
/* Circle just follows --fg like the surrounding text does (light bg
   in light mode, flips to the light cream text color in dark mode —
   no separate override needed). The icon inside inverts to match:
   white on the dark circle, dark on the light one. */
.cs-impact-icon-circle img { width: 55%; height: 55%; filter: brightness(0) invert(1); }
[data-theme="dark"] .cs-impact-icon-circle img { filter: brightness(0); }

.cs-impact-icon-label {
  font-weight: 500; font-size: 18px; letter-spacing: .04em;
  text-align: center; color: var(--fg); margin: 0; line-height: 1.3;
  position: relative; z-index: 1;
}

/* Animated card background — injected by js/impact-card-animation.js
   (a hand-ported version of the Claude Design "Impact Card" export:
   same blob geometry + dot-grid formula, driven by a small
   requestAnimationFrame loop instead of that tool's own React-based
   composition engine, which isn't something this plain HTML/CSS/JS
   site can depend on). Sits behind the icon circle + label at z-index
   0; the card body/border it draws replaces the plain CSS
   background/border above once JS successfully mounts it (see
   .js-animated below) — if JS fails, the plain CSS card is still there
   underneath as a fallback. */
.cs-impact-card-canvas { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.cs-impact-card-canvas svg { display: block; width: 100%; height: 100%; }
/* Once the SVG mounts, it draws its own rounded corners (baked into
   CARD_D's geometry) — leaving the CSS border-radius on top of that
   was clipping the badge's overflow:hidden box at a slightly different
   curve than the path underneath, cutting a visible sliver off the
   corner. The SVG's own rounding is correct on its own; the CSS one
   only needs to stay for the no-JS fallback card (plain background +
   border, no SVG), so it's turned off here rather than removed
   outright. */
.cs-impact-badge.js-animated { background: none; border: none; border-radius: 0; }
.cs-case-detail {
  border-left: 4px solid var(--orange-102, #d1794d);
  padding: 16px; flex: 1 1 400px;
}
/* Impact bullets specifically get a reading-width cap on web — the row
   can stretch quite wide next to the fixed-size icon card, and without
   this the line length made readers turn their head. Research's reuse
   of .cs-case-detail already has its own flex-basis override above, so
   this only needs to target Impact's rows. Removed again on mobile,
   where the row stacks to full width anyway (see breakpoint below). */
.cs-impact-row .cs-case-detail { max-width: 700px; }
.cs-case-detail p {
  font-family: var(--font-body); font-weight: 400; font-size: 20px;
  line-height: 28px; color: var(--fg); margin: 0;
}
.cs-case-detail p strong { font-weight: 700; }
/* Impact's bullets fade/slide in one after another rather than typing
   out — the viewer can read the line the instant it appears instead of
   waiting on it, unlike Research's typewriter reveal. Scoped to
   .cs-impact-section specifically so it doesn't touch Research's
   .cs-case-detail p, which still types out via [data-typewriter]. */
.cs-impact-section .cs-case-detail p {
  opacity: 0; transform: translateY(10px);
  transition: opacity .5s ease, transform .5s ease;
}
.cs-impact-section .cs-case-detail p.is-visible { opacity: 1; transform: translateY(0); }

/* ===================== MY INFLUENCE ===================== */
/* Figma node 958:2391. The one component on the page that deliberately
   inverts against the site's current theme instead of following it —
   see --influence-bg/--influence-fg/--influence-border/--influence-media-bg
   in the theme tokens above for the actual light/dark flip; everything
   here just consumes those variables like normal.
   Structural build only for now — .cs-influence-step is a <button> (so
   it's honest about being interactive later) but has no click handler
   yet, .cs-influence-step-progress has no fill logic yet, and
   .cs-influence-media is an empty panel with nothing decided for it. */
.cs-influence {
  display: flex; align-items: flex-start; gap: 48px; flex-wrap: wrap;
  padding: 48px 32px; border-radius: 8px; overflow: hidden;
  background: var(--influence-bg); border: 1px solid var(--influence-border);
  color: var(--influence-fg);
  position: relative;
}
/* Case study 3 only — this page's 3rd step pushes the info column
   taller than the media panel, so top-aligning (the default above)
   leaves the image looking stranded near the top. Center it against
   the taller column instead. */
[data-case-study="3"] .cs-influence { align-items: center; }
/* Two soft, slowly-drifting glow blobs behind the content — subtle
   motion instead of a flat solid panel, using --influence-glow (same
   hue as the panel's own text color, just near-transparent) so it
   auto-adapts to both theme directions with no extra logic. Pure
   CSS (no canvas/WebGL) to keep this cheap and dependency-free;
   .cs-influence's own overflow:hidden keeps them from ever bleeding
   past the panel's rounded corners.
   (Bumped noticeably more visible than the first pass — 8% opacity
   blurred at 60px worked out to almost nothing perceptible once
   blur spreads that little alpha across a much larger area; 22% at a
   tighter blur actually reads now while still staying soft.) */
.cs-influence::before, .cs-influence::after {
  content: ""; position: absolute; z-index: 0; border-radius: 50%;
  background: var(--influence-glow); filter: blur(45px); pointer-events: none;
}
.cs-influence::before {
  width: 480px; height: 480px; top: -80px; left: -60px;
  animation: cs-influence-drift-1 26s ease-in-out infinite;
}
.cs-influence::after {
  width: 420px; height: 420px; bottom: -100px; right: -40px;
  animation: cs-influence-drift-2 32s ease-in-out infinite;
}
@keyframes cs-influence-drift-1 {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(70px, 40px) scale(1.15); }
}
@keyframes cs-influence-drift-2 {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(-60px, -35px) scale(1.1); }
}
@media (prefers-reduced-motion: reduce) {
  .cs-influence::before, .cs-influence::after { animation: none; }
}
/* Real content sits above the glow layer. */
.cs-influence-info, .cs-influence-media { position: relative; z-index: 1; }
.cs-influence-info {
  flex: 1 1 320px; display: flex; flex-direction: column;
  align-items: flex-start; justify-content: center; gap: 24px; min-width: 0;
}
.cs-influence-header {
  display: flex; flex-direction: column; gap: 16px; width: 100%;
  padding-bottom: 16px; border-bottom: 0.5px solid var(--influence-border);
}
/* Trophy + title now sit in their own row with its own divider (Figma
   node 971:2466), nested inside .cs-influence-header's existing
   divider/padding, which still wraps the whole header block (this row
   plus the description) as before. */
.cs-influence-title-row {
  display: flex; align-items: flex-start; gap: 8px; width: 100%;
  padding-bottom: 8px; border-bottom: 0.5px solid var(--influence-border);
}
/* currentColor so it follows .cs-influence's own themed text color
   automatically (same technique as the stepper's check icon) — the
   source SVG has a hardcoded white fill, which would go invisible
   against the panel once it flips light in dark mode. */
.cs-influence-trophy { width: 25.75px; height: 25.75px; flex-shrink: 0; color: var(--influence-fg); }
.cs-influence-title {
  font-weight: 700; font-size: 36px; letter-spacing: .02em;
  text-transform: uppercase; margin: 0; line-height: 1;
}
.cs-influence-desc {
  font-family: var(--font-body); font-weight: 400; font-size: 20px;
  line-height: 32px; letter-spacing: .01em; margin: 0; max-width: 548px;
}

.cs-influence-steps { display: flex; flex-direction: column; gap: 32px; width: 100%; }
.cs-influence-step {
  display: flex; align-items: flex-start; gap: 8px; width: 100%;
  background: none; border: none; padding: 0; margin: 0;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.cs-influence-step-marker {
  display: flex; flex-direction: column; align-items: center; gap: 11px;
  width: 24px; flex-shrink: 0;
}
/* Color mapping, mirrored from Figma's own "--black"/"--light-grey"
   tokens onto this component's inverted pair: Figma's "--black" is
   always the outer panel's OWN background color (var(--influence-bg)),
   and its "--light-grey"/"--white" is always the outer panel's OWN text
   color (var(--influence-fg)) — regardless of which literal hex that
   resolves to in either site theme. Counter fill + track track both
   use --influence-fg (the "light" one); counter border/number + the
   fill amount both use --influence-bg (the "dark" one) — i.e. the
   opposite pairing from the panel's own text, by design (Figma's
   circle/track are meant to read as a light chip sitting on the dark
   panel, or vice versa once inverted). */
.cs-influence-step-counter {
  width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0;
  background: var(--influence-fg); border: 1.2px solid var(--influence-bg);
  box-shadow: 0 2px 4px rgba(0, 0, 0, .1);
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 17px; letter-spacing: .02em; color: var(--influence-bg);
  position: relative; transition: background-color .3s ease;
}
.cs-influence-step-counter-num { transition: opacity .2s ease; }
.cs-influence-step-counter-check {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  opacity: 0; transform: scale(.5); color: var(--fg);
  transition: opacity .25s ease, transform .25s ease;
}
.cs-influence-step-counter-check svg { width: 12px; height: auto; display: block; }
/* Complete state deliberately switches to the site's NORMAL (non-
   inverted) --fg/--bg-panel pair rather than the panel's inverted
   --influence-fg/--influence-bg — dark mode: dark circle + white check;
   light mode: light circle + dark check. Default (not-yet-reached)
   state keeps the inverted pair, so completing a step visibly reads as
   a distinct "filled in" state rather than two shades of the same
   inverted tone. */
.cs-influence-step.is-complete .cs-influence-step-counter { background: var(--bg-panel); }
.cs-influence-step.is-complete .cs-influence-step-counter-num { opacity: 0; }
.cs-influence-step.is-complete .cs-influence-step-counter-check { opacity: 1; transform: scale(1); }
/* Track + fill are a single element now, not two overlapping ones —
   the fill is a hard-stop background gradient driven by a --cs-fill
   custom property (set in js/influence-stepper.js) rather than a
   separately-sized child box. This was the actual fix for the
   persistent 1px sliver: two overlapping elements will always have a
   seam somewhere from sub-pixel rounding no matter how much they're
   bled past each other's edges, but a single gradient paint has no
   seam to have. Colors use the site's normal --fg/--bg-panel pair
   (not the inverted --influence- pair) per spec: light mode -> dark
   track / white fill; dark mode -> light track / dark fill. */
.cs-influence-step-track {
  width: 4px; height: 48px; border-radius: 4px; overflow: hidden;
  background: linear-gradient(to bottom, var(--bg-panel) var(--cs-fill, 0%), var(--fg) var(--cs-fill, 0%));
}
.cs-influence-step-content {
  display: flex; flex-direction: column; gap: 12px; flex: 1; min-width: 0;
}
.cs-influence-step-title {
  font-weight: 700; font-size: 24px; letter-spacing: .02em; text-transform: capitalize;
  line-height: 1; margin: 0; padding-top: 2px;
}
.cs-influence-step-desc {
  font-family: var(--font-body); font-weight: 400; font-size: 16px;
  line-height: 24px; letter-spacing: .01em; color: var(--influence-fg); opacity: .85;
  max-width: 400px;
}

/* Sizing moved to .cs-influence-media-wrap (added to hold the new
   mobile dot indicator alongside the media box — see below); .cs-
   influence-media itself is just width:100% of that wrapper now.
   Fixed, proportionally-scaling width rather than flex-filling the row
   (per spec: 361px on mobile, exactly 1.73x that — 624.53px — on web).
   max-width:100% is a safety net for viewports narrower than 361px
   itself, same defensive pattern used for the Impact cards/Design
   carousel elsewhere on this page. */
.cs-influence-media-wrap { flex: 0 0 auto; width: 624.53px; max-width: 100%; }
/* Fixed-aspect box (matching the real images' 1444:1201 ratio) with
   each slide's content filling it edge-to-edge via object-fit:cover —
   no inner padding, the image IS the box, not something sitting
   inside it. */
.cs-influence-media {
  width: 100%; aspect-ratio: 1444 / 1201;
  border-radius: 8px;
  position: relative; overflow: hidden;
}
/* Crossfaded via opacity, same technique as the Research/Design/
   Solution carousels elsewhere on the page — every slide absolutely
   fills the same box, so switching slides never changes the panel's
   size. Step 3 layers a video on top of its image the same way (see
   .cs-influence-slide-stack below) rather than stacking them in normal
   flow, which was making the panel grow taller for that slide only. */
.cs-influence-slide {
  position: absolute; inset: 0; opacity: 0; transition: opacity .5s ease;
}
.cs-influence-slide.is-active { opacity: 1; }
.cs-influence-slide-stack { position: absolute; inset: 0; }
.cs-influence-slide-frame {
  display: block; width: 100%; height: 100%; object-fit: cover;
}
/* The video sits absolutely centered over its slide's own image
   (poster covers the load-in gap; underlying image is still there as a
   fallback if the video fails to load) instead of adding its own space
   below it. */
/* Video's native ratio (2160x1030, ~2.1:1) is much wider than this
   panel's fixed 1444:1201 (~1.2:1) box — cover was cropping a lot of
   the interaction off the top/bottom. contain shows the whole video
   letterboxed instead, confirmed as the right call over cover here. */
/* No background here on purpose — the underlying image (also in this
   same slide-stack) shows through the object-fit:contain letterbox
   gaps instead of a solid color bar. */
.cs-influence-slide-stack video.cs-influence-slide-frame { position: absolute; inset: 0; object-fit: contain; }

/* Mobile-only 3-dot indicator, 8px under the media box — desktop
   already shows clear per-step progress via the stepper's own
   counter/track, so this only needs to exist where that's not visible
   at the same time (see .cs-influence-steps-clip below). Driven by
   js/influence-stepper.js, not the generic [data-carousel] system —
   deliberately its own small set of classes rather than reusing
   .cs-carousel-dots--indicator, since that one's positioned for an
   absolute overlay on top of an image, and this needs to sit in normal
   flow underneath one instead. */
.cs-influence-media-dots {
  display: none; margin-top: 8px;
  align-items: center; justify-content: center; gap: 10px;
}
.cs-influence-media-dots span {
  display: block; width: 8px; height: 8px; border-radius: 50%;
  background: var(--influence-fg); opacity: .35;
  transition: opacity .2s ease, transform .2s ease;
}
.cs-influence-media-dots span.is-active { opacity: 1; background: var(--orange-102); transform: scale(1.25); }

/* Mobile-only clipped/sliding step carousel — at desktop
   .cs-influence-steps-clip has no special sizing (the steps list just
   flows normally, all 3 visible), so this only takes effect inside the
   media query below. js/influence-stepper.js sets --cs-step-shift and
   the clip's own height on every step change (both auto-advance and
   click), measured from the actual rendered step elements rather than
   a hardcoded px assumption, so it stays correct regardless of how
   text wraps at a given width. */
@media (max-width: 860px) {
  .cs-influence { flex-direction: column; padding: 16px 20px; gap: 16px; }
  .cs-influence-title { font-size: 28px; }
  .cs-influence-desc { font-size: 16px; line-height: 24px; }
  .cs-influence-step-title { font-size: 20px; }
  .cs-influence-media-wrap { width: 361px; max-width: 100%; align-self: center; }
  .cs-influence-media-dots { display: flex; }
  .cs-influence-steps-clip { overflow: hidden; position: relative; transition: height .5s ease; }
  .cs-influence-steps { transform: translateY(var(--cs-step-shift, 0px)); transition: transform .5s ease; }
}
@media (max-width: 860px) and (prefers-reduced-motion: reduce) {
  .cs-influence-steps-clip, .cs-influence-steps { transition: none; }
}

/* ===================== REFLECTIONS ===================== */
/* Figma node 974:4215. Left panel now follows the site's normal theme
   like every other panel (--bg-panel/--fg); right panel reuses the
   exact same inverted pair as My Influence (--influence-bg/
   --influence-fg) rather than a separate set of tokens, so "invert
   against the site theme" stays defined in exactly one place. */
.cs-reflections { display: flex; align-items: stretch; gap: 48px; width: 100%; }

/* Left panel intentionally has NO fixed/flex-basis width — it just
   grows and shrinks with whatever space is left once the right panel
   (sized to its own content) takes what it needs. That asymmetry is
   deliberate, not a mismatch with the rest of the page's usually-equal
   flex splits. */
.cs-reflections-left {
  flex: 1 1 0; min-width: 0; position: relative; overflow: hidden;
  background: var(--bg-panel); color: var(--fg); border-radius: 8px;
  padding: 24px 32px; display: flex; flex-direction: column; gap: 24px;
}
/* Real bug from last round: height:100% on an absolutely-positioned
   child only resolves against a containing block with an EXPLICIT
   height — .cs-reflections-left's height is content-driven (auto), so
   that 100% was computing to nothing and the image was collapsing to
   zero size in every browser, in both themes. That's why it wasn't a
   color/opacity problem at all. Fixed height in px instead (generous
   enough to span past any panel height its content produces;
   overflow:hidden on the panel clips the extra harmlessly), top+right
   anchored, no bottom needed since height is now explicit.
   Opacity/invert now matches the exact same convention as the
   Research section's decorative icon (.cs-decorative-icon): 2%
   opacity, and the SVG's own fill has no baked-in opacity (reverted
   that back to 1) so this is the one place the number lives — inverted
   in dark mode since the fill is a fixed dark grey that would
   otherwise vanish once the panel goes dark. */
/* Height:100% now that .cs-reflections-left has a definite height via
   align-items:stretch on the parent row (matching .cs-reflections-right)
   — a flex-stretched height counts as "definite" for percentage-height
   children, unlike a plain content-driven auto height, which is why
   this failed before and needed a fixed-px guess instead. Now that the
   parent's height is reliably defined, this tracks it exactly rather
   than guessing — no more bottom-cropping regardless of how tall the
   panel ends up being. */
.cs-reflections-icon {
  position: absolute; top: 0; right: 0; height: 100%; width: auto;
  opacity: .02; pointer-events: none;
}
[data-theme="dark"] .cs-reflections-icon { filter: invert(1); }
.cs-reflections-block { display: flex; flex-direction: column; gap: 12px; position: relative; z-index: 1; }
.cs-reflections-label {
  width: 100%; padding-bottom: 16px; border-bottom: 1px solid var(--muted);
}
.cs-reflections-label h3 {
  font-weight: 700; font-size: 24px; letter-spacing: .05em;
  text-transform: uppercase; margin: 0; color: inherit;
}
.cs-reflections-list {
  list-style: disc; margin: 0; padding-left: 20px; max-width: 560px;
  display: flex; flex-direction: column; gap: 16px;
}
.cs-reflections-list li {
  font-family: var(--font-body); font-size: 16px; line-height: 24px; letter-spacing: .01em;
}
.cs-reflections-list li strong { font-weight: 600; }
.cs-reflections-text {
  font-family: var(--font-body); font-weight: 400; font-size: 16px;
  line-height: 24px; letter-spacing: .01em; max-width: 560px; margin: 0;
}

/* Right panel is sized to its own content (the image's natural width +
   padding) rather than flexing — see .cs-reflections above. Children
   right-align by default (matching Figma's items-end) since the button
   is narrower than the image/label above it; the label and media
   override back to full-width since Figma sizes those explicitly. */
.cs-reflections-right {
  flex: 0 0 auto; width: 396px; max-width: 100%; height: 474px;
  background: var(--influence-bg); color: var(--influence-fg); border-radius: 8px;
  padding: 24px 32px; box-sizing: border-box;
  display: flex; flex-direction: column; align-items: flex-end; gap: 32px;
  overflow: hidden;
}
.cs-reflections-label--dark { border-bottom-color: var(--influence-border); }
.cs-reflections-media {
  width: 100%; aspect-ratio: 332 / 265; border-radius: 4px; overflow: hidden;
  background: var(--influence-media-bg); flex-shrink: 0;
}
.cs-reflections-media img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* CTA colors deliberately use the site's NORMAL (non-inverted) --bg-
   panel/--fg pair rather than --influence-fg/--influence-bg — same
   reasoning as the My Influence stepper's counter/track: this button
   needs to contrast against a panel that's itself already inverted, so
   following the inverted pair a second time would cancel back out to
   looking like the surrounding site instead of standing out. Hover
   flips to the panel's own inverted colors, so the button briefly
   "merges" with the panel it's sitting on. Corner radius is the
   OPPOSITE diagonal from the site's usual --radius-tag (top-right/
   bottom-left): this one rounds top-left/bottom-right, per Figma. */
.cs-reflections-cta {
  display: flex; align-items: center; gap: 8px; flex-shrink: 0;
  background: var(--bg-panel); border: 1px solid var(--fg); color: var(--fg);
  border-radius: 10px 0 10px 0;
  padding: 3px 5px 3px 8px; text-decoration: none;
  box-shadow: 0 1px 2px rgba(12, 12, 13, .05);
}
.cs-reflections-cta span {
  font-weight: 500; font-size: 24px; letter-spacing: .02em; text-transform: capitalize;
}
.cs-reflections-cta img { width: 18px; height: 18px; }
[data-theme="dark"] .cs-reflections-cta img { filter: invert(1); }
@media (hover: hover) and (pointer: fine) {
  .cs-reflections-cta { transition: background-color .15s ease, color .15s ease, border-color .15s ease; }
  .cs-reflections-cta:hover { background: var(--influence-bg); color: var(--influence-fg); border-color: var(--influence-bg); }
  .cs-reflections-cta:hover img { filter: invert(1); }
  [data-theme="dark"] .cs-reflections-cta:hover img { filter: invert(0); }
}

@media (max-width: 860px) {
  .cs-reflections { flex-direction: column; gap: 24px; }
  .cs-reflections-left, .cs-reflections-right { width: 100%; flex: 1 1 auto; padding: 24px 20px; }
  .cs-reflections-right { height: auto; align-items: stretch; }
  .cs-reflections-cta { align-self: flex-end; }
  /* Centered instead of right-pinned on mobile, per spec — height
     capped so the tall 448:618 icon doesn't dominate a now much
     narrower panel. */
  .cs-reflections-icon { top: 0; right: auto; left: 50%; transform: translateX(-50%); height: 220px; width: auto; }
  .cs-reflections-label h3 { font-size: 20px; }
}


/* First bullet highlight in My Role — draws attention to primary contribution */
.cs-overview .cs-col ul li:first-child {
  background: rgba(209, 121, 77, 0.15); /* --orange-102 at 15% — readable light & dark */
  border-left: 3px solid var(--orange-102, #d1794d);
  padding: 4px 8px 4px 10px;
  border-radius: 0 4px 4px 0;
  list-style: none;
  margin-left: -24px; /* pulls back past the ul padding-left */
}
[data-theme="dark"] .cs-overview .cs-col ul li:first-child {
  background: rgba(209, 121, 77, 0.2);
}
.cs-constraints {
  display: flex; gap: 24px; flex-wrap: wrap;
  justify-content: space-between;
  width: 100%;
}
.cs-constraint-card {
  position: relative;
  background: var(--bg-panel, #fff);
  border: 1px dashed var(--muted, #b3b3b3);
  border-radius: 8px;
  padding: 27px;
  flex: 1 1 0; min-width: 260px;
  display: flex; flex-direction: column;
  gap: 32px;
  overflow: hidden;
  box-sizing: border-box;
  min-height: 280px;
}
/* Corner brackets — top-left and bottom-right */
.cs-constraint-card::before,
.cs-constraint-card::after {
  content: '';
  position: absolute;
  width: 22px; height: 22px;
  border-color: var(--fg, #333);
  border-style: solid;
}
.cs-constraint-card::before {
  top: 18px; left: 18px;
  border-width: 2px 0 0 2px;
  border-radius: 2px 0 0 0;
}
.cs-constraint-card::after {
  bottom: 18px; right: 18px;
  border-width: 0 2px 2px 0;
  border-radius: 0 0 2px 0;
}
.cs-constraint-badge {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--font-display); font-weight: 700;
  font-size: 20px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--fg);
  /* Offset to clear the ::before corner bracket (18px from edge + 22px bracket = 40px, +8px gap) */
  margin-left: 8px;
  align-self: flex-start;
}
.cs-constraint-badge b {
  background: var(--fg); color: var(--bg-panel, #fff);
  padding: 3.6px 6px;
  border-radius: var(--radius-tag);
  font-size: 20px; line-height: 1;
  box-shadow: 0 1px 2px rgba(12,12,13,.05);
}
[data-theme="dark"] .cs-constraint-badge b { color: var(--bg); }
.cs-constraint-text {
  flex: 1;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 16px; text-align: center;
  padding: 0 8px;
}
.cs-constraint-title {
  font-family: var(--font-display); font-weight: 700;
  font-size: 28px; letter-spacing: .02em;
  text-transform: capitalize; color: var(--fg);
  margin: 0;
}
.cs-constraint-desc {
  font-family: var(--font-body); font-weight: 400;
  font-size: 20px; line-height: 28px;
  color: var(--fg); margin: 0;
}

@media (max-width: 860px) {
  .cs-constraints { flex-direction: column; gap: 16px; }
  .cs-constraint-card { min-height: 220px; padding: 20px; }
  .cs-constraint-title { font-size: 22px; }
  .cs-constraint-desc { font-size: 16px; line-height: 24px; }
}

/* Bottom navigation */
.cs-bottom-nav {
  position: fixed; left: 0; right: 0; bottom: 0;
  height: 64px; min-height: 64px;
  background: var(--bg);
  backdrop-filter: blur(4px);
  border-top: 1px solid var(--muted);
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 24px;
  z-index: 200;
  transform: translateY(100%);
  transition: transform .35s ease;
}
.cs-bottom-nav.is-visible { transform: translateY(0); }
.cs-bottom-nav .cs-nav-pill {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-display); font-weight: 500; font-size: 20px;
  letter-spacing: .02em; color: var(--fg);
  text-decoration: none;
}
.cs-bottom-nav .cs-nav-pill svg { width: 16px; height: 16px; }
.cs-bottom-nav .cs-nav-pill.cs-prev-none {
  pointer-events: none;
  visibility: hidden; /* reserves layout space so logo stays centered */
}
.cs-bottom-nav .cs-nav-logo {
  width: 28px; height: 28px; color: var(--fg);
  background: none; border: none; padding: 0; cursor: pointer;
}
.cs-bottom-nav .cs-nav-logo svg { width: 100%; height: 100%; display: block; }

/* Dark mode: invert arrow icons so they show on dark nav bg */
[data-theme="dark"] .cs-bottom-nav .cs-nav-pill img {
  filter: invert(1);
}
[data-theme="dark"] .cs-hero .tag-pill {
  background: var(--bg-panel);
  border-color: var(--muted);
  color: var(--fg);
}

/* Impact card: dark mode uses same bg/border as testimonial cards */
[data-theme="dark"] .cs-impact-card {
  background: var(--bg-panel);
  border-color: var(--muted);
}

@media (max-width: 860px) {
  .cs-hero-wrap {
    padding: 0 16px;
    margin-top: 104px;
    height: calc(100svh - 104px - 16px);
    min-height: 600px;
  }
  .cs-hero {
    padding: 16px;
    gap: 16px;
    background-color: var(--bg-panel);
    background-image: url('../assets/images/case-study-hero-images/Case-Study-Hero-Mobile-1.png');
    background-size: 361px 732px;
    background-repeat: no-repeat;
    background-position: center center;
  }
  [data-case-study="2"] .cs-hero {
    background-image: url('../assets/images/case-study-hero-images/Case-Study-Hero-Mobile-2.png');
    background-size: 361px 732px;
    background-repeat: no-repeat;
    background-position: center center;
    --hero-x: 50%;
    --hero-y: 0%;
  }
  [data-case-study="3"] .cs-hero {
    background-image: url('../assets/images/case-study-hero-images/Case-Study-Hero-Mobile-3.png');
    background-size: 361px 732px;
    background-repeat: no-repeat;
    background-position: center center;
  }
  /* Hide the <img> on mobile — background-image handles it */
  .cs-hero .cs-hero-bg {
    display: none;
  }
  .cs-hero h1 {
    font-size: 32px;
    max-width: 100%;
  }
  .cs-hero .desc {
    font-size: 16px;
    max-width: 100%;
  }
  /* Scroll hint stays at same visual position */
  .cs-hero .scroll-hint { bottom: 52px; }

  /* Small badge/pill variant on mobile hero */
  .cs-hero .issue-badge { font-size: 16px; letter-spacing: .05em; }
  .cs-hero .issue-badge b { font-size: 16px; padding: 3.6px 4.8px; }
  .cs-hero .tag-pill { font-size: 14px; padding: 3px 6px; line-height: 1.15; border-width: 0.404px; }
  .cs-hero .pills-group { gap: 8px; }

  .cs-section { padding: 56px 16px; gap: 32px; }
  .cs-section-title .cs-title { font-size: 20px; }
  .cs-section-title .cs-desc { font-size: 16px; line-height: 24px; }
  .cs-overview .cs-col-label { font-size: 20px; }
  .cs-overview { flex-direction: column; gap: 32px; }
  .cs-overview .cs-col { gap: 8px; flex: none; min-width: 0; width: 100%; }
  .cs-impact-card { flex-direction: column; align-items: flex-start; }
  .cs-impact-card .cs-impact-text { white-space: normal; font-size: 16px; }
  /* Row wraps to two lines on mobile (card, then text below) —
     flex-start left-aligns the card by default since it doesn't grow;
     center it explicitly instead. The text block still spans full
     width on its own line regardless, so this only visibly affects
     the card. */
  .cs-impact-row { justify-content: center; }
  .cs-impact-row .cs-case-detail { max-width: none; }
  .cs-image-block.cs-solution-media {
    height: auto;
    /* Matches the actual 2000×1047 screenshots exactly, so cover has
       nothing left to crop and there's no leftover strip to letterbox
       either — the mismatch between a fixed mobile height and these
       images' real (wide, ~1.91:1) ratio was the actual cause of both
       the "not filling" look under cover and the blank bars under
       contain. If a future Solution image ships at a different ratio,
       this value needs to change with it (or the images should be
       pre-cropped to match) — a single shared ratio only works because
       all three current images happen to share one exactly. */
    aspect-ratio: 2000 / 1047;
  }
  .cs-case-detail p { font-size: 16px; line-height: 24px; }
  .cs-decorative-icon { display: none; }
  .cs-project-role { flex-direction: column; gap: 20px; }
  .cs-bottom-nav .cs-nav-pill { min-height: 40px; padding: 0 8px; }
  .cs-bottom-nav .cs-nav-pill span.label-text { font-size: 16px; }
}
