/* ═══════════════════════════════════════════
   TUSHAR JAIN PORTFOLIO — style.css
   Dark theme · Jakarta Sans + IBM Plex Sans
   ═══════════════════════════════════════════ */

/* ─── Design Tokens ─── */
:root {
  /* Backgrounds — white */
  --bg:        #ffffff;
  --bg-2:      #ffffff;
  --bg-3:      #f4f4f6;

  /* Surfaces (glass layers) */
  --surface-1: #f6f6f8;
  --surface-2: #f0f0f3;
  --surface-3: #e9e9ee;
  --surface-glass: rgba(0,0,0,0.035);

  /* Borders */
  --border-1:  rgba(0,0,0,0.10);
  --border-2:  rgba(0,0,0,0.16);
  --border-3:  rgba(0,0,0,0.24);

  /* Text — WCAG AA on #ffffff */
  --text-1:    #111111;   /* 16.8:1 on white — headings, primary body */
  --text-2:    #444448;   /* 9.7:1  on white — secondary, descriptions */
  --text-3:    #6a6a72;   /* 5.36:1 on white, 4.71:1 on --surface-2 — labels, captions, muted.
                             Was #6e6e76, which measured 4.44:1 against the dossier's
                             --surface-2 and failed AA for the 12px meta labels. */

  /* Accent (focus rings) */
  --accent:    #144BFC;
  --accent-h:  #0f3ee0;

  /* Section-title accents */
  --home-accent: #144BFC;
  --proj-accent: var(--text-1);    /* overridden per case-study via [data-project] */

  /* Fonts — unified to Home's spec (Schibsted Grotesk) site-wide, per direct
     instruction: apply Home's font/style to every page rather than leave
     old pages on their original Plus Jakarta Sans/IBM Plex Sans pairing. */
  --f-heading: 'Schibsted Grotesk', system-ui, sans-serif;
  --f-body:    'Schibsted Grotesk', system-ui, sans-serif;

  /* Spacing */
  --s1:  8px;
  --s2:  16px;
  --s3:  24px;
  --s4:  32px;
  --s5:  48px;
  --s6:  64px;
  --s7:  96px;
  --s8:  128px;

  /* Max render height for portrait phone screenshots & videos (keeps aspect ratio) */
  --phone-max-h: 600px;

  /* Radius — crisp modern, 4px or 8px only */
  --r-sm:   4px;
  --r-md:   8px;
  --r-lg:   8px;
  --r-xl:   8px;
  --r-pill: 4px;
  /* Unified radius system (§7 DESIGN_SYSTEM.md) */
  --r-chip:  10px;  /* Icon chips, small badges */
  --r-tile:  18px;  /* Bento tiles, panels, cards, image frames */
  --r-band:  24px;  /* Key-figures/stat bands only */
  --r-phone: 20px;  /* Device frame corners */

  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
}

/* ─── Reset ─── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; scroll-padding-top: 76px; }

/* ─── Focus (WCAG 2.4.7 Focus Visible, AA) ───────────────────────────────────
   Audit found only three :focus-visible rules in the whole stylesheet, and none
   of them on the four home project cards, which ARE the site's primary
   navigation. Their .card-link computed `outline: none 0px`, so a keyboard user
   tabbing the site had no idea where they were.

   Declared once, globally, so a new interactive element is covered by default
   instead of depending on someone remembering. Uses :focus-visible, not :focus,
   so mouse clicks do not paint a ring. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--accent-2);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ─── Line breaking ──────────────────────────────────────────────────────────
   Headings on this site run 34px to 176px and frequently wrap to two lines, so
   a one-word orphan on the last line is both common and very visible at that
   scale. `balance` evens the lines instead. `pretty` on body copy only prevents
   single-word last lines, which is the cheap half of the same problem.
   Both degrade to normal wrapping where unsupported. */
h1, h2, h3, h4,
.cs-hero-title, .cs-section-title, .ch-sub-title, .card-title, .hero-role {
  text-wrap: balance;
}
p, li { text-wrap: pretty; }
body {
  background: var(--bg);
  color: var(--text-1);
  font-family: var(--f-body);
  font-size: 16px;
  line-height: 1.65;
  /* overflow-x moved to .page-clip wrapper — keeps body compositing-clean for backdrop-filter */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
/* overflow-x: clip (not hidden) — `hidden` forces the computed overflow-y to
   `auto`, turning this wrapper into a second nested scroll container and making
   the page scroll hand off between it and the document (jerky, needs an extra
   push). `clip` clips horizontally without ever creating a scroll port. */
.page-clip { overflow-x: clip; }
@keyframes bodyEntry {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Card expand overlay (JS-driven click transition) */
a { color: inherit; text-decoration: none; }
button { background: none; border: none; cursor: pointer; font: inherit; color: inherit; }
ul { list-style: none; }
/* height:auto is the mandatory companion to the intrinsic width/height attributes
   now present on every <img>. The attributes give the browser an aspect ratio to
   reserve space with (killing the 450-680px layout shifts the audit measured),
   but without height:auto a max-width-constrained image would keep its full
   attribute height and distort. Rules that set an explicit height still win. */
img { display: block; max-width: 100%; height: auto; }
::selection { background: rgba(0,113,227,0.18); }

/* Per-project section text accent — AA-safe (≥4.5:1) on dark #0c0e16.
   Hue matches each project's bright card colour (see .project-card inline --card-accent). */
[data-project="dreamplay"]      { --proj-accent: #5a5a62; }
[data-project="social-gameplay"]{ --proj-accent: #5a5a62; }
[data-project="live-activity"]  { --proj-accent: #5a5a62; }
[data-project="tsys"]           { --proj-accent: #5a5a62; }
[data-project="scout-ai"]       { --proj-accent: #5a5a62; }
[data-project="dreamsetgo"]     { --proj-accent: #5a5a62; }

/* ─── Ambient white glows (character on charcoal) ─── */
.glow-field {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}
.glow-field::before,
.glow-field::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  filter: blur(120px);
}
.glow-field::before {
  top: -10%; left: -5%;
  width: 45vw; height: 45vw;
  background: radial-gradient(circle, rgba(0,113,227,0.04) 0%, transparent 70%);
}
.glow-field::after {
  bottom: -15%; right: -10%;
  width: 50vw; height: 50vw;
  background: radial-gradient(circle, rgba(0,113,227,0.03) 0%, transparent 70%);
}

/* ─── Layout ─── */
.container {
  max-width: 1240px;
  margin: 0 auto;
  padding: 0 var(--s4);
}
.section {
  position: relative;
  z-index: 1;
  padding: var(--s7) 0;
}

/* ─── Navigation ─── */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  background: rgba(255,255,255,0.64);
  backdrop-filter: blur(8.5px) saturate(140%);
  -webkit-backdrop-filter: blur(8.5px) saturate(140%);
  border-bottom: 1px solid rgba(0,0,0,0.08);
  transition: border-color 0.3s, box-shadow 0.3s, background 0.3s;
}
.nav.scrolled {
  background: rgba(255,255,255,0.74);
  border-bottom-color: rgba(0,0,0,0.12);
  box-shadow: 0 6px 24px rgba(0,0,0,0.06);
}
.nav-container {
  max-width: 1240px;
  margin: 0 auto;
  padding: 0 var(--s4);
  height: 76px;
  display: flex;
  align-items: center;
  gap: var(--s3);
}

/* TJ Monogram */
.nav-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  background: var(--accent-2);   /* brand blue box */
  border-radius: var(--r-sm);
  font-family: var(--f-heading);
  font-size: 16px;
  font-weight: 800;
  letter-spacing: -0.5px;
  color: #ffffff;                /* white TJ */
  margin-right: auto;
  flex-shrink: 0;
  transition: background-color 0.12s, transform 0.12s;
}
.nav-logo:hover { background-color: #2a39ab; transform: scale(1.05); } /* fast + visible: opacity-only felt delayed/unresponsive */

.nav-links {
  display: flex;
  align-items: center;
  gap: 2px;
}
.nav-link,
.nav-link--btn {
  font-family: var(--f-body);
  font-size: 16px;
  font-weight: 400;
  color: var(--text-2);
  padding: 7px 16px;
  border-radius: 4px; /* was a 9999px pill; matches the site-wide 4px radius convention */
  transition: color 0.2s, background 0.2s;
  display: flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}
.nav-link:hover,
.nav-link--btn:hover {
  color: var(--text-1);
  background: var(--surface-2);
}
.nav-chevron { transition: transform 0.25s var(--ease-out); flex-shrink: 0; }

/* Dropdown */
.nav-item--dropdown { position: relative; }
.nav-item--dropdown:hover .nav-chevron,
.nav-item--dropdown:focus-within .nav-chevron { transform: rotate(180deg); }

.dropdown {
  position: fixed;
  top: 84px;
  min-width: 268px;
  background: rgba(255,255,255,0.76);
  border: 1px solid rgba(0,0,0,0.10);
  border-radius: var(--r-lg);
  box-shadow: 0 15px 45px rgba(0,0,0,0.09), 0 3px 12px rgba(0,0,0,0.045);
  padding: 8px 6px 6px;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transition: visibility 0s 0.18s, opacity 0.18s ease, transform 0.22s var(--ease-out);
  backdrop-filter: blur(8.5px) saturate(140%);
  -webkit-backdrop-filter: blur(8.5px) saturate(140%);
  transform: translateY(-6px);
  z-index: 200;
}
/* dropdown shown/hidden via JS (.dropdown--open class) */
.dropdown--open {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
  transition: visibility 0s 0s, opacity 0.18s ease, transform 0.22s var(--ease-out);
}
.dropdown-inner { display: flex; flex-direction: column; gap: 2px; }

.dropdown-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  border-radius: var(--r-md);
  font-size: 18px;
  font-weight: 400;
  line-height: 1.2; /* single-line menu link — was inheriting body 1.65 */
  color: var(--text-1);
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}
.dropdown-item:hover { background: var(--surface-2); color: var(--accent-2); }
.dropdown-item--soon { opacity: 0.4; pointer-events: none; }
.dropdown-divider { height: 1px; background: var(--border-1); margin: 4px 0; }
.dropdown-all {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 11px 14px;
  border-radius: var(--r-md);
  font-size: 18px;
  font-weight: 500;
  line-height: 1.2; /* single-line menu link — was inheriting body 1.65 */
  color: var(--text-2);
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}
.dropdown-all:hover { background: var(--surface-1); color: var(--accent-2); }

/* Hamburger */
.nav-hamburger {
  display: none;
  flex-direction: column;
  gap: 5px;
  padding: 8px;
  border-radius: var(--r-sm);
  transition: background 0.2s;
}
.nav-hamburger:hover { background: var(--surface-1); }
.nav-hamburger span {
  display: block;
  width: 22px; height: 1.5px;
  background: var(--text-1);
  border-radius: 2px;
  transition: transform 0.3s var(--ease-out), opacity 0.2s;
  transform-origin: center;
}
.nav-hamburger.open span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.nav-hamburger.open span:nth-child(2) { opacity: 0; transform: scaleX(0); }
.nav-hamburger.open span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

/* ─── Mobile Menu ─── */
.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(255,255,255,0.77);
  backdrop-filter: blur(8.5px) saturate(140%);
  -webkit-backdrop-filter: blur(8.5px) saturate(140%);
  display: flex;
  align-items: center;
  padding: var(--s5) var(--s4);
  opacity: 0;
  pointer-events: none;
  /* visibility, not just opacity: opacity alone leaves the three menu links in
     the tab order and the accessibility tree at every width, so a desktop
     keyboard user tabbed through three invisible destinations before reaching
     the page. visibility:hidden removes them from both. Delayed by the fade
     duration on the way out so the transition still reads. (WCAG 2.4.3)
     The dropdown above already does this correctly; this rule was the outlier. */
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0s 0.3s;
}
.mobile-menu.open {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transition: opacity 0.3s ease, visibility 0s 0s;
}
.mobile-menu-inner { width: 100%; }
.mobile-nav {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  margin-bottom: var(--s6);
}
.mobile-link {
  font-family: var(--f-heading);
  font-size: clamp(36px, 8vw, 56px);
  font-weight: 700;
  color: var(--text-3);
  line-height: 1.1;
  transition: color 0.2s;
}
.mobile-link:hover { color: var(--accent-2); }
.mobile-footer {
  display: flex;
  gap: var(--s4);
  font-size: 16px;
  color: var(--text-3);
}
.mobile-footer a:hover { color: var(--text-2); }

/* ─── Buttons ─── */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--f-body);
  font-size: 16px;
  font-weight: 500;
  padding: 12px 24px;
  border-radius: 4px;
  transition: all 0.25s var(--ease-out);
  cursor: pointer;
  white-space: nowrap;
}
.btn-primary {
  background: var(--text-1);   /* ink primary, not brand blue */
  color: #ffffff;
}
.btn-primary:hover { background: #2a2a2e; transform: scale(1.02); box-shadow: 0 4px 16px rgba(0,0,0,0.28); }
.btn-ghost {
  background: transparent;
  color: var(--text-1);
  border: 1px solid var(--border-2);
}
.btn-ghost:hover { background: rgba(0,0,0,0.06); border-color: var(--border-3); transform: scale(1.02); }
.btn-lg { padding: 14px 32px; font-size: 15px; }
/* A secondary way into a project: the DreamPlay Lite deck. Same ghost
   treatment as .btn-ghost at a size that fits a card corner. It sits OUTSIDE
   .card-link, because .card-link wraps the whole card and an anchor inside an
   anchor is invalid; initCardNavigation in js/main.js knows to let it through
   rather than swallowing the click. */
.card-cta {
  position: absolute;
  /* Flush with the description text, not with the body column's box: .card-body
     carries its own left padding, so the CTA has to add it too. */
  left: calc(58% + 16px + var(--s4));
  bottom: 40px;
  z-index: 5;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--f-body);
  font-size: 13px;
  font-weight: 500;
  line-height: 1;
  padding: 9px 16px;
  border-radius: 4px;
  background: transparent;
  color: var(--text-1);
  border: 1px solid var(--border-2);
  transition: all 0.25s var(--ease-out);
}
.card-cta:hover { background: rgba(0,0,0,0.06); border-color: var(--border-3); }

/* The same route out of the case study hero, under the standfirst. */
.cs-hero-cta {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
  margin: var(--s5) 0 0;
}
.cs-hero-cta .btn { font-size: 15px; padding: 11px 22px; }
.cs-hero-cta-note { font-size: 14px; color: var(--text-3); }
@media (max-width: 768px) { .cs-hero-cta { gap: 10px; } }


/* ─── Labels ─── */
.label {
  display: block;
  font-family: var(--f-body);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--home-accent);
  margin-bottom: var(--s2);
}

/* ─── Hero ─── */
.hero {
  position: relative;
  z-index: 1;
  min-height: calc(100svh + 57px);
  display: flex;
  align-items: center;
  overflow: hidden;
  background: var(--bg);
  margin-top: -57px; /* behind nav */
}

/* ─── Shared parallax hero dot-field (§10) — faint blue, drifts on scroll ─── */
.hero-deco {
  position: absolute;
  top: -80px; right: -60px;
  width: 560px; height: 560px;
  background-image: radial-gradient(circle, rgba(53,72,212,0.13) 1.6px, transparent 1.7px);
  background-size: 24px 24px;
  -webkit-mask-image: radial-gradient(circle at 68% 30%, #000 0%, transparent 64%);
          mask-image: radial-gradient(circle at 68% 30%, #000 0%, transparent 64%);
  pointer-events: none;
  z-index: 0;
}
.hero > .container, .hero-content, .about2 > .container, .cs-hero > .container { position: relative; z-index: 1; }
@media (max-width: 768px) { .hero-deco { width: 340px; height: 340px; top: -60px; right: -40px; } }

/* Subtle orange bloom — top right */
.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  background: none;
  pointer-events: none;
  z-index: 0;
}
/* Fade to dark at bottom */
.hero::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, transparent 40%, var(--bg) 100%);
  pointer-events: none;
  z-index: 0;
}

.hero-content {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 1240px;
  margin: 0 auto;
  padding: var(--s8) var(--s4);
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  align-items: start;
  gap: var(--s5);
}
.hero-text {
  align-self: start;
}
.hero-art {
  position: relative;
  display: flex;
  justify-content: flex-end;
  align-items: flex-start;
}
.hero-photo {
  width: 100%;
  max-width: 400px;
  aspect-ratio: 40/43;
  object-fit: cover;
  object-position: center top;
  border-radius: 8px;
  border: 1px solid rgba(0,0,0,0.08);
  box-shadow: 0 24px 60px rgba(0,0,0,0.09), 0 0 0 1px rgba(0,0,0,0.04);
}
.hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--f-body);
  font-size: 16px;
  font-weight: 500;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: var(--s4);
}
.eyebrow-pulse {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: #34c759;
  animation: pulse 2.5s ease infinite;
  flex-shrink: 0;
}
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgba(52,199,89,0.6); }
  70%  { box-shadow: 0 0 0 8px rgba(52,199,89,0); }
  100% { box-shadow: 0 0 0 0 rgba(52,199,89,0); }
}
.hero-name {
  font-family: var(--f-heading);
  font-size: clamp(72px, 13vw, 160px);
  font-weight: 800;
  line-height: 0.88;
  letter-spacing: -0.04em;
  color: #111111;
  margin-bottom: var(--s4);
}
.hero-role {
  font-family: var(--f-heading);
  font-size: clamp(18px, 2.2vw, 24px);
  font-weight: 500;
  color: var(--text-1);
  line-height: 1.5;
  max-width: 560px;
  margin-bottom: 28px;
}
.hero-sub {
  font-size: 16px;
  color: var(--text-2);
  max-width: 440px;
  margin-bottom: var(--s5);
  line-height: 1.6;
}
.hero-actions { display: flex; gap: var(--s2); flex-wrap: wrap; }
@media (min-width: 769px) {
  /* Desktop only: the two CTAs together span the same width as the
     "Design leader..." line (hero-role's 560px cap), edge to edge. */
  .hero-actions { max-width: 560px; }
  .hero-actions .btn { flex: 1; justify-content: center; }
}

/* Scroll indicator */
.hero-scroll {
  position: absolute;
  bottom: var(--s5);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s1);
  z-index: 1;
  transition: opacity 0.5s ease;
}
.hero-scroll.hide { opacity: 0; pointer-events: none; }
.hero-scroll-label {
  font-size: 16px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-3);
}
.hero-scroll-line {
  width: 1.5px; height: 48px;
  border-radius: 2px;
  background: linear-gradient(to bottom, var(--accent-2), #ffffff);
  animation: scrollLine 2s ease infinite;
}
@keyframes scrollLine {
  0%   { transform: scaleY(0); transform-origin: top; opacity: 1; }
  50%  { transform: scaleY(1); transform-origin: top; opacity: 1; }
  100% { transform: scaleY(1); transform-origin: bottom; opacity: 0; }
}

/* ─── Marquee ─── */
.marquee {
  position: relative;
  z-index: 1;
  overflow: hidden;
  border-top: 1px solid var(--border-2);
  border-bottom: 1px solid var(--border-2);
  padding: 11px 0;
  background: var(--surface-glass);
  backdrop-filter: blur(8.5px) saturate(140%);
  -webkit-backdrop-filter: blur(8.5px) saturate(140%);
  box-shadow: none;
}
.marquee-track {
  display: flex;
  gap: var(--s4);
  width: max-content;
  animation: marquee 32s linear infinite;
}
.marquee:hover .marquee-track { animation-play-state: paused; }
.marquee-track span {
  font-family: var(--f-body);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-2);
  white-space: nowrap;
}
/* Was var(--border-2), which composites to 1.45:1 on the marquee's #f6f6f6 and
   fails 1.4.3 as visible text. --text-3 is 4.94:1 on that ground. */
.marquee-track .sep { color: var(--text-3); }
@keyframes marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* ─── Section Separator ─── */
.section-sep {
  border: none;
  border-top: 1px solid var(--border-1);
  max-width: 1240px;
  margin: 0 auto;
  padding: 0 var(--s4);
}

/* ─── Intro Section ─── */
.intro-grid {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: var(--s7);
  align-items: start;
}
.intro-heading {
  font-family: var(--f-heading);
  font-size: clamp(22px, 3vw, 36px);
  font-weight: 700;
  color: var(--text-1);
  line-height: 1.2;
  letter-spacing: -0.02em;
  margin-bottom: var(--s3);
}
.intro-body {
  font-size: 18px;
  color: var(--text-1);
  line-height: 1.75;
  margin-bottom: var(--s4);
}
.text-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 16px;
  font-weight: 500;
  color: var(--accent-2);        /* links carry the accent (§2) */
  text-decoration: underline dotted;
  text-underline-offset: 5px;
  text-decoration-color: var(--accent-2-border);
  transition: gap 0.2s, text-decoration-color 0.2s;
}
.text-link:hover { gap: 12px; text-decoration-color: var(--accent-2); }

/* Stats */
.stats-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s2);
}
.stat {
  background: var(--surface-1);
  border: 1px solid var(--border-1);
  border-radius: var(--r-lg);
  padding: var(--s3);
  transition: border-color 0.3s, background 0.3s;
}
.stat:hover {
  border-color: var(--border-2);
  background: var(--surface-2);
}
.stat-n {
  display: block;
  font-family: var(--f-heading);
  font-size: clamp(30px, 4vw, 44px);
  font-weight: 800;
  color: var(--text-1);
  letter-spacing: -0.03em;
  line-height: 1;
  margin-bottom: 6px;
}
.stat-l {
  display: block;
  font-size: 16px;
  color: var(--text-3);
  line-height: 1.4;
}

/* ─── Projects Section ─── */
.section-hdr {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  margin-bottom: 24px;
  padding: 0 max(20px, calc((100vw - 1200px) / 2));
}
.section-title {
  font-family: var(--f-heading);
  font-size: clamp(36px, 5vw, 56px);
  font-weight: 800;
  letter-spacing: -0.03em;
  color: var(--text-1);
  line-height: 1;
}

/* ─── Vertical scroll progress line (akshaylodha-style) ─── */
.scroll-progress-line {
  position: fixed;
  right: 28px;
  top: 50%;
  transform: translateY(-50%);
  width: 1.5px;
  height: 140px;
  background: rgba(0,0,0,0.10);
  border-radius: 2px;
  z-index: 50;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s ease;
}
.scroll-progress-line.visible { opacity: 1; }
.scroll-progress-fill {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 0%;
  background: rgba(0,0,0,0.50);
  border-radius: 2px;
  transition: height 0.1s linear;
}

/* ─── Project Stack — sticky scroll card stack ─── */
.carousel {
  position: relative;
  z-index: 1;
}
.projects-track {
  display: block;
  padding: 8px max(20px, calc((100vw - 1200px) / 2)) 48px;
}
/* Cards are part of normal page flow; overlap is applied on scroll via JS transforms */
.projects-track .project-card:last-child { margin-bottom: 0; }

/* ─── Project Cards — solid colour + inset image + text panel ─── */
.project-card {
  --card-accent: #e8e8ec;
  --card-ink: #111111;
  position: relative;
  display: block;
  height: 500px;
  margin-bottom: 96px;
  transform-origin: top center;
  border-radius: var(--r-xl);
  overflow: hidden;
  border: 1px solid rgba(255,255,255,1);
  background: rgba(255,255,255,0.20);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  box-shadow: 0 18px 48px rgba(0,0,0,0.21), inset 0 0 0 1px rgba(255,255,255,0.3);
  cursor: pointer;
  transition: box-shadow 0.5s cubic-bezier(0.25,0.46,0.45,0.94);
}
.project-card::before { display: none; }

.project-card:hover {
  box-shadow: 0 30px 68px rgba(0,0,0,0.24);
}
.project-card--soon { filter: saturate(0.6) brightness(0.78); }

/* Card link fills the whole fixed-height card */
.card-link {
  position: absolute;
  inset: 0;
  display: block;
  color: inherit;
  pointer-events: none;
}
/* Focus ring for the card. The obvious version of this (a ring on .card-link)
   DOES NOT WORK and was shipped broken once: .project-card sets
   overflow:hidden, and .card-link is inset ~0.9px inside it, so of the 6px ring
   band (3px offset + 3px width) only 0.9px survives the clip. The ring computed
   correctly and was then almost entirely clipped away, which is worse than no
   rule at all because it looks fixed under a computed-style check.

   An element's own outline is NOT clipped by its own overflow, only its
   children are. So the ring goes on the card and is driven by the child's
   focus state. */
.project-card:has(.card-link:focus-visible) {
  outline: 3px solid var(--accent-2);
  outline-offset: 3px;
  border-radius: var(--r-xl);
}
/* Suppress the global rule's ring on the child, now that the card carries it. */
.card-link:focus-visible { outline: none; }

/* Image — inset 40px from card edges, occupies left ~58% */
.card-img {
  position: absolute;
  top: 40px; left: 40px; bottom: 40px;
  right: calc(42% + 8px);
  z-index: 0;
  overflow: hidden;
  border-radius: var(--r-xl);
}
.card-img-inner { position: absolute; inset: 0; }
.card-img {
  background: #1c1c1e !important;
}
.card-img-photo {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center top;
}

/* Text panel — right side, inset 40px, top-aligned with image */
.card-body {
  position: absolute;
  right: 40px; top: 40px; bottom: 40px;
  width: calc(42% - 56px);
  z-index: 2;
  background: transparent;
  border-radius: 4px;
  padding: 0 0 0 var(--s4);
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  gap: 14px;
}
.project-card:hover .card-body { background: transparent; }

/* Role, company and year, above the project title. This rule existed but no
   markup ever used it; it was styled as a flex row for a badge layout that was
   never built. Repurposed as the single quiet line that tells a reader what
   Tushar actually was on the project, which the card never said before. */
.card-meta {
  font-family: var(--f-heading);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-3);
  line-height: 1.4;
  padding-bottom: 12px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--border-1);
}
@media (max-width: 768px) {
  .card-meta { font-size: 12px; letter-spacing: 0.07em; padding-bottom: 10px; margin-bottom: 12px; }
}
.card-tag {
  font-size: 16px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #111111;
  opacity: 0.85;
}
.card-year {
  font-size: 16px;
  color: #111111;
  opacity: 0.75;
  font-weight: 500;
}
.card-title {
  font-family: var(--f-heading);
  font-size: clamp(22px, 2.4vw, 32px);
  font-weight: 700;
  color: #111111;
  line-height: 1.15;
  letter-spacing: -0.025em;
}
.card-desc {
  font-size: 16px;
  font-weight: 500;
  color: #111111;
  opacity: 0.85;
  line-height: 1.6;
  flex: 1;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* flex:1 grows this box to fill the card, so the overflow window ends up
     taller than the clamp and a partial line paints BELOW the ellipsis
     ("...across Sports,... Events and Travel."). Cap the height to exactly the
     clamped line count so there is no room for the extra line to render.
     1.6em == this element's own line-height. */
  max-height: calc(4 * 1.6em);
}
/* Big thick arrow at lower-right corner (replaces "View case study" text).
   Desktop: sleeker (smaller, thinner stroke) than the original 54px/2.6 —
   the mobile override below keeps its own 36px size, untouched. Turns
   accent blue on hover (was staying black). */
.card-arrow {
  position: absolute;
  right: 28px;
  bottom: 24px;
  width: 40px;
  height: 40px;
  z-index: 4;
  background: #111111;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 12h15M12 5l7 7-7 7' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 12h15M12 5l7 7-7 7' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  transition: transform 0.25s var(--ease-out), background-color 0.2s;
}
.project-card:hover .card-arrow { transform: translateX(5px); background-color: var(--accent-2); }
.card-arrow--muted { opacity: 0.4; }

.soon-badge {
  position: absolute;
  top: 20px; left: 20px;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--card-ink);
  background: rgba(255,255,255,0.85);
  border: 1px solid rgba(0,0,0,0.12);
  padding: 5px 12px;
  border-radius: var(--r-pill);
  z-index: 5;
}

/* ─── CTA Section ─── */
.cta-section {
  position: relative;
  padding-top: 0;
}
.cta-section::before {
  content: '';
  display: block;
  width: 100%;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--border-2) 30%, var(--border-2) 70%, transparent);
  margin-bottom: var(--s7);
}
.cta-card {
  text-align: center;
  max-width: 640px;
  margin: 0 auto;
  padding: 0 var(--s4);
}
.cta-heading {
  font-family: var(--f-heading);
  font-size: clamp(28px, 3.5vw, 48px);
  font-weight: 800;
  letter-spacing: -0.03em;
  color: var(--text-1);
  margin-bottom: var(--s2);
}
.cta-body {
  font-size: 16px;
  color: var(--text-2);
  max-width: 580px;
  margin: 0 auto var(--s4);
  line-height: 1.65;
}
.cta-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  flex-wrap: wrap;
}

/* ─── Footer ─── */
.footer {
  position: relative;
  z-index: 1;
  background: transparent;
  padding: 0 0 var(--s4);
}
.footer-top {
  display: flex;
  align-items: center;
  gap: var(--s5);
  margin-bottom: var(--s4);
}
.footer-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  background: var(--text-1);
  border-radius: 7px;
  font-family: var(--f-heading);
  font-size: 16px;
  font-weight: 800;
  letter-spacing: -0.5px;
  color: var(--bg);
  margin-right: auto;
  flex-shrink: 0;
}
.footer-nav { display: flex; gap: var(--s3); }
.footer-nav a {
  font-size: 16px;
  color: var(--text-3);
  transition: color 0.2s;
}
.footer-nav a:hover { color: var(--text-1); }
.footer-social { display: flex; gap: var(--s3); align-items: center; }
.footer-social a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 16px;
  color: var(--text-3);
  transition: color 0.2s;
}
.footer-social a:hover { color: var(--text-1); }
.footer-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: var(--s3);
  border-top: 1px solid var(--border-1);
  font-size: 16px;
  color: var(--text-3);
}

/* ─── Scroll Reveal ─── */
.reveal {
  visibility: hidden;
  transform: translateY(20px);
  transition: visibility 0s 0.7s, transform 0.7s var(--ease-out);
}
.reveal.revealed {
  visibility: visible;
  transform: translateY(0);
  transition: visibility 0s 0s, transform 0.7s var(--ease-out);
}
.reveal-d1 { transition-delay: 0.08s; }
.reveal-d2 { transition-delay: 0.16s; }
.reveal-d3 { transition-delay: 0.24s; }
.reveal-d4 { transition-delay: 0.32s; }

/* ═══════════════════════════════════════════
   CASE STUDY PAGE STYLES
   ═══════════════════════════════════════════ */

.cs-nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  background: rgba(32, 37, 49, 0.40);
  backdrop-filter: blur(8.5px) saturate(140%);
  -webkit-backdrop-filter: blur(8.5px) saturate(140%);
  border-bottom: 1px solid rgba(255,255,255,0.12);
  padding: 0 var(--s4);
  height: 76px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
}
.cs-back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 16px;
  font-weight: 500;
  color: var(--text-3);
  transition: color 0.2s;
  flex-shrink: 0;
}
.cs-back:hover { color: var(--text-1); }
.cs-nav-title {
  font-family: var(--f-heading);
  font-size: 16px;
  font-weight: 600;
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 40vw;
}

/* Case study hero — full bleed gradient behind nav */
.cs-hero {
  position: relative;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  /* .container is a flex item here. Its `margin: 0 auto` is an AUTO cross-axis
     margin, which disables flex stretch, so it shrink-to-fits (1104px) instead of
     taking its 1240px max-width — putting page content 68px right of the nav logo.
     See `.cs-hero > .container` below for the fix. */
  margin-top: -57px;        /* pull up behind fixed nav */
  padding-top: 180px;       /* nav height + breathing room */
  padding-bottom: var(--s8);
  overflow: hidden;
  background: var(--bg);
}/* Content column must line up with the nav logo: force the flex item to take its
   full max-width instead of shrink-to-fitting (the auto-margin/stretch trap above). */
.cs-hero > .container { width: 100%; }

.cs-hero::before {
  content: '';
  position: absolute;
  inset: 0;
  background: none;
  pointer-events: none;
  z-index: 0;
}
.cs-hero::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, transparent 50%, var(--bg) 100%);
  pointer-events: none;
  z-index: 0;
}
.cs-hero .container {
  position: relative;
  z-index: 1;
}
.cs-hero-label {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--home-accent);
  margin-bottom: var(--s4);
}
/* §1.2 canon: case-study hero eyebrow is accent-2 (Scout/TSYS). Scoped to [data-project]
   so Home/About are never affected (Rule 3). */
[data-project] .cs-hero-label { color: var(--accent-2); }
.cs-hero-title {
  font-family: var(--f-heading);
  font-size: clamp(56px, 9vw, 120px);
  font-weight: 800;
  letter-spacing: -0.04em;
  color: #111111;
  background: none;
  line-height: 0.9;
  margin-bottom: var(--s5);
}
.cs-hero-sub {
  font-size: 18px;
  color: var(--text-2);
  max-width: 760px;
  line-height: 1.65;
  margin-bottom: var(--s6);
}
/* The 64px above is the gap down to the dossier, not the gap between two
   paragraphs of the same lede. When the lede runs to a second paragraph,
   the first one takes a paragraph-sized gap instead. */
.cs-hero-sub--stack { margin-bottom: var(--s3); }
/* Meta bar */
.cs-meta-row {
  display: grid;
  grid-template-columns: repeat(4, auto);
  justify-content: start;
  gap: var(--s3) var(--s6);
  padding-top: var(--s4);
  border-top: 1px solid rgba(0,0,0,0.12);
}
.cs-meta-item { display: flex; flex-direction: column; gap: 6px; }
.cs-meta-item--full { margin-top: var(--s4); }
.cs-meta-label {
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
  opacity: 1;
}
.cs-meta-value {
  font-size: 18px;
  color: var(--text-1);
  font-weight: 500;
  line-height: 1.4;
}
/* Mobile: 18px reads bulky in the narrow 2-col dossier cell where values wrap to
   3 lines — step to 16px with a tighter line-height. Weight stays 500 (medium keeps
   the value distinct from the muted label and the regular body without going heavy).
   Placed after the base so source order holds (§5.1). */
@media (max-width: 768px) {
  .cs-meta-value { font-size: 16px; line-height: 1.35; }
}
/* Duration value: inline "5 years · 2018 – 2023" on desktop, two clean lines on
   mobile (drops the middot). Promoted from TSYS's inline copy (the reference
   implementation) so every case-study dossier shares it (§1.3 registry law). */
.cs-meta-dur-a::after { content: "·"; margin: 0 6px; color: var(--text-3); }
@media (max-width: 900px) {
  .cs-meta-dur { display: flex; flex-direction: column; }
  .cs-meta-dur-a::after { content: none; }
}

/* NDA note */
/* Same role as an image caption (secondary note under a block) — so it carries
   the caption's exact metrics, not a one-off 14px/1.5. */
.cs-nda {
  font-size: 15px;
  color: var(--text-3);
  margin: var(--s4) 0 0;
  font-style: italic;
  line-height: 1.55;
  text-wrap: wrap;
}

/* Cover image */
.cs-cover {
  width: 100%;
  overflow: hidden;
  margin-top: var(--s5);
  border: none;
}
/* No extra styling on images/videos inside a MacBook frame */
.cs-mac-wrap .cs-mac-screen img,
.cs-mac-wrap .cs-mac-screen video {
  border: none !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}

/* Hero tag chips — role/responsibility pills */
.cs-tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 6px;
}
/* CANONICAL responsibility/skill chip (§0 global, §9) — one spec for TSYS,
   Scout, About, all pages. 16px both breakpoints (do not shrink on mobile). */
.cs-tag {
  display: inline-flex;
  align-items: center;
  padding: 8px 18px;
  border-radius: 4px;
  border: none;
  font-size: 16px;
  font-weight: 500;
  line-height: 1.2;
  color: var(--text-2);
  background: rgba(0,0,0,0.05);
  white-space: nowrap;
}
.cs-tag--accent {
  color: var(--text-2);           /* match the About-page chip colour (was ink --text-1) */
  background: rgba(0,0,0,0.05);
}

/* Callout blocks — for objectives / pain points / principles */
.cs-callout-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s2);
  margin-top: var(--s4);
}
.cs-callout {
  background: rgba(0,0,0,0.024);
  border: 1px solid var(--border-1);
  border-left: 3px solid rgba(0,0,0,0.20);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  padding: var(--s3) var(--s3);
}
.cs-callout-title {
  font-size: 14px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-3);
  margin-bottom: 8px;
}
.cs-callout-body {
  font-size: 16px;
  color: var(--text-2);
  line-height: 1.6;
}
.cs-callout-grid--3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 768px) {
  .cs-callout-grid, .cs-callout-grid--3 { grid-template-columns: 1fr; }
}

/* Pivotal highlight callout (promoted from Scout's inline .sc-quotes/.sc-prompt to the global
   registry so any page can use it, not just Scout): blue-tint surface, accent-bordered lines,
   generous rhythm. Originally built for quoted customer questions; the container/typography
   works equally well for a short set of stated facts/constraints (e.g. a project brief) — drop
   the literal quotation marks in the content when not quoting someone. */
.cs-quotes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: var(--s4) var(--s5);
  margin-top: var(--s5);
  background: var(--accent-2-surface);
  border-radius: 24px;
  padding: var(--s5);
}
.cs-prompt {
  margin: 0; max-width: none; font-size: 18px; line-height: 1.45;
  color: var(--text-1); font-style: italic;
  padding-left: 16px; border-left: 2px solid var(--accent-2);
}
@media (max-width: 768px) {
  .cs-quotes { grid-template-columns: 1fr; border-radius: 18px; padding: var(--s4); gap: var(--s3); }
}

/* Full-bleed pull-quote band (TSYS `.tsys-quote` canon, promoted to the registry):
   the one statement a chapter turns on, given its own full-width section with a
   giant parallax quote glyph behind it. Inside the accent-tint `.cs-band` it flips
   to white, per the tint-band canon (§2) — tint on tint would vanish. */
.cs-quoteband { position: relative; width: 100vw; max-width: none; margin-left: calc(50% - 50vw);
  margin-top: var(--s7); margin-bottom: var(--s7); padding: var(--s7) var(--s4);
  background: var(--accent-2-tint); text-align: center; overflow: hidden; }
.cs-band .cs-quoteband { background: #ffffff; }
.cs-quoteband blockquote { position: relative; z-index: 1; max-width: 940px; margin: 0 auto;
  padding-left: 0; border-left: none; color: var(--text-1);
  font-family: var(--f-display); font-size: clamp(38px, 4.4vw, 60px); line-height: 1.18; font-weight: 400; font-style: normal; }
.cs-quoteband figcaption { position: relative; z-index: 1; text-align: center; margin: 0 0 var(--s3);
  color: var(--accent-2); font-family: var(--label-caps-family); font-weight: var(--label-caps-weight);
  letter-spacing: var(--label-caps-tracking); text-transform: uppercase; font-size: 13px; line-height: 1.35; }
@media (max-width: 768px) {
  .cs-quoteband { padding: var(--s6) var(--s3); margin-top: var(--s6); margin-bottom: var(--s6); }
  .cs-quoteband blockquote { font-size: 30px; line-height: 1.2; }
}

/* Screen caption below mac frame */
.cs-mac-caption {
  font-size: 14px;
  color: var(--text-3);
  text-align: center;
  margin-top: var(--s2);
  font-style: italic;
}

/* Body */
.cs-body { padding: calc(var(--s7) * 1.5) 0; }
.cs-section { margin-bottom: 200px; }
.cs-section:last-child { margin-bottom: 0; }

/* ═══ CHAPTER / SUB-SECTION SYSTEM (§0 global, §4/§6) — governs every case study
   (TSYS, Scout, …) identically. Values are the TSYS canonical. Explicit
   desktop/mobile so the two pages can never drift again. ═══ */
.tsys-block, .sc-block { margin-bottom: 160px; }            /* chapter gap: desktop (§4) */
/* Last chapter in the page needs no chapter-gap of its own — .cs-body's own bottom padding
   (144px) already provides the gap to the related-projects rail. Scout and TSYS each carry this
   exact rule in their own inline <style> block; it was never in the shared global sheet, so any
   page (DreamPlay) that has no inline <style> of its own never got it — double-counted margin +
   padding produced too much gap after the last chapter. Adding it here is a no-op for Scout/TSYS
   (identical declaration already applies) and fixes every other page that shares this file. */
.tsys-block:last-child, .sc-block:last-child { margin-bottom: 0; }
.ch-sub {
  display: flex; align-items: baseline; gap: var(--s3);
  padding-top: 0; border-top: none;                          /* NO separator line (§6) — space divides */
  margin: 112px 0 var(--s3);                                 /* sub-section gap 112px / head→content 24px */
}
/* ch-sub owns the 24px gap to its content; zero the component's own margin-top so it doesn't
   inflate the gap to 32 (Scout does this, scout-ai.html:211). Scoped to DreamPlay so it never
   touches Scout/TSYS (Rule 4). */
[data-project="dreamplay"] .ch-sub + .cs-cards, [data-project="dreamplay"] .ch-sub + .cs-split2,
[data-project="dreamplay"] .ch-sub + .cs-phone-row, [data-project="dreamplay"] .ch-sub + .cs-video-row,
[data-project="dreamplay"] .ch-sub + .cs-swatch-grid, [data-project="dreamplay"] .ch-sub + .cs-type-grid,
[data-project="dreamplay"] .ch-sub + .cs-timeline, [data-project="dreamplay"] .ch-sub + .cs-bullets,
[data-project="dreamplay"] .ch-sub + .cs-body-text, [data-project="dreamplay"] .ch-sub + .cs-quotes,
[data-project="social-gameplay"] .ch-sub + .cs-cards, [data-project="social-gameplay"] .ch-sub + .cs-split2,
[data-project="social-gameplay"] .ch-sub + .cs-phone-row, [data-project="social-gameplay"] .ch-sub + .cs-video-row,
[data-project="social-gameplay"] .ch-sub + .cs-timeline, [data-project="social-gameplay"] .ch-sub + .cs-bullets,
[data-project="social-gameplay"] .ch-sub + .cs-body-text, [data-project="social-gameplay"] .ch-sub + .cs-quotes,
[data-project="social-gameplay"] .ch-sub + .cs-flat { margin-top: 0; }
@media (max-width: 768px) {
  .tsys-block, .sc-block { margin-bottom: 72px; }            /* chapter gap: mobile */
  .ch-sub { margin: 72px 0 var(--s3); flex-direction: column; gap: 6px; } /* sub-section gap 72px */
}

/* ═══ CANONICAL IMAGE FRAME (§0 global, §6/§8.6) — annotated/artifact figures.
   Same 1px border, 18px radius, and lift shadow as TSYS's screenshot frame. ═══ */
.sc-flat img, .sc-tall img {
  border: 1px solid var(--border-1);
  border-radius: var(--r-tile);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.07);
}

.cs-section-label {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--home-accent);
  margin-bottom: 10px;
}
.cs-section-title {
  font-family: var(--f-heading);
  font-size: clamp(24px, 3.2vw, 38px);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text-1);
  line-height: 1.2;
  margin-bottom: var(--s3);
  max-width: 760px;
}
/* Body / running text — ONE size everywhere (§3.1): 17px desktop / 16px mobile,
   --text-2, line-height 1.5. This is the global source; pages must NOT re-declare it
   inline (the earlier 18px/1.75 default forced every page to override, and pages that
   forgot to — DreamPlay, live-notifications, social-gameplay — rendered oversized). */
.cs-body-text {
  font-size: 17px;
  color: var(--text-2);
  line-height: 1.5;
  max-width: 760px;
}
@media (max-width: 768px) { .cs-body-text { font-size: 16px; } }
.cs-body-text p { margin-bottom: 18px; }
.cs-body-text p:last-child { margin-bottom: 0; }
.cs-body-text strong { color: var(--text-1); font-weight: 600; }
/* The one-loud-moment inline highlight (Scout .cs-hl, TSYS .tsys-hl) — canonical global rule so
   every page's usage renders consistently at the same weight as strong (600), not a separate
   value. Scout's own inline override previously set 500; TSYS's own inline .tsys-hl already used
   600 correctly. DreamPlay had no rule at all for .cs-hl (rendered unstyled, weight 400). */
.cs-hl, .tsys-hl { color: var(--text-1); font-weight: 600; }

/* Outcome metrics — promoted from Scout's inline .sc-metrics/.sc-metric (exact spec, not a
   paraphrase): a 3-part structure per tile (number / short label / explanatory note), not the
   2-part .cs-highlight this replaces on DreamPlay. .cs-highlight-grid stays defined below,
   unchanged, for social-gameplay.html and live-notifications.html (untouched, out of scope). */
.sc-metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s4); margin-top: var(--s4); }
.sc-metric { background: var(--surface-1); border: 1px solid rgba(0,0,0,0.06); border-bottom: 1px solid var(--border-1);
  border-radius: var(--r-lg); padding: var(--s4); }
.sc-metric-num { font-family: var(--f-heading); font-size: clamp(30px, 3vw, 42px); font-weight: 700;
  letter-spacing: -0.02em; line-height: 1.05; color: var(--text-1); }
.sc-metric-what { font-size: 15px; font-weight: 600; color: var(--text-2); margin: 10px 0 6px; line-height: 1.45; }
.sc-metric-note { font-size: 14px; color: var(--text-3); line-height: 1.55; margin: 0; }
@media (max-width: 768px) { .sc-metrics { grid-template-columns: 1fr; } .sc-metric-note { font-size: 17px; } }

/* Stats highlight grid */
.cs-highlight-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s2);
  margin-top: var(--s4);
}
.cs-highlight {
  background: var(--surface-1);
  border: 1px solid var(--border-1);
  border-radius: var(--r-lg);
  padding: var(--s4);
  transition: border-color 0.2s, background 0.2s;
}
.cs-highlight:hover { border-color: var(--border-2); background: var(--surface-2); }
.cs-highlight-n {
  font-family: var(--f-heading);
  font-size: clamp(30px, 3vw, 42px);
  font-weight: 700;
  color: var(--text-1);
  letter-spacing: -0.02em;
  line-height: 1.05;
  margin-bottom: 10px;
}
.cs-highlight-l {
  font-size: 16px;
  color: var(--text-2);
  line-height: 1.5;
}

/* Image styles */
.cs-image {
  width: 100%;
  height: auto;
  border-radius: var(--r-lg);
  display: block;
  border: none;
}
.cs-image-row {
  display: flex;
  gap: var(--s2);
  margin: var(--s4) 0;
  align-items: flex-start; /* prevent vertical stretch / aspect-ratio distortion */
}
.cs-image-row .cs-image {
  flex: 1 1 0;
  min-width: 0;            /* allow equal-width distribution */
  width: auto;
  height: auto;
}
/* Rows of portrait phones — size to capped height & centre instead of filling width */
.cs-image-row:has(.cs-image--phone) { justify-content: center; }
.cs-image-row .cs-image--phone { flex: 0 1 auto; }
/* Portrait phone screenshots — cap height, keep aspect ratio, centre, iOS device frame */
.cs-image--phone,
.cs-video--phone {
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: var(--phone-max-h);
  margin-left: auto;
  margin-right: auto;
  /* iPhone device frame: thin black bezel + light titanium rim + depth */
  border: 7px solid #0a0a0c;
  border-radius: 44px;
  background: #0a0a0c;
  box-shadow:
    0 0 0 2px #cdcbd6,
    0 0 0 3.5px #9b99a6,
    0 16px 36px rgba(0,0,0,0.304);
}
@media (max-width: 768px) {
  .cs-image--phone, .cs-video--phone {
    box-shadow:
      0 0 0 2px #cdcbd6,
      0 0 0 3.5px #9b99a6,
      0 13px 29px rgba(0,0,0,0.243);
  }
}

/* Video */
.cs-video {
  width: 100%;
  height: auto;
  border-radius: var(--r-lg);
  border: none;
  display: block;
  background: var(--bg-3);
}
/* legacy flex .cs-video-row removed — dead CSS, zero pages used .cs-image-row/.cs-video, and it was silently overriding the current grid .cs-video-row's mobile gap by source order (§5.1 trap). */
/* Portrait phone video — cap height, keep aspect ratio, centre */
.cs-video--phone {
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: var(--phone-max-h);
  margin-left: auto;
  margin-right: auto;
}

/* ─── Video play badge: centered overlay while a video is paused/unstarted ───
   js/main.js wraps every <video> in a .video-shell and appends the badge; the
   shell copies the video's own margins/align-self at bind time so wrapping is
   layout-neutral. Badge hides while playing, returns on pause/end. */
.video-shell {
  position: relative;
  display: inline-block;
  vertical-align: top;
  max-width: 100%;
}
/* GLOBAL VIDEO RULE: no overlay on any video, on any page, at any breakpoint.
   The custom centre play badge that used to live here is gone — every video
   now carries native `controls` permanently (see js/main.js), and those
   controls are the only affordance. No badge, no scrim, no hover state. */
/* Every video gets this shell + badge (main.js wraps them all, site-wide), so the native
   big center "start playback" button must be hidden universally too, not per-page — otherwise
   it sits directly on top of our custom badge (Chrome/Android: overlay-play-button; Safari has
   a SEPARATE pseudo-element, start-playback-button, that Chrome's rule never touched). */
.video-shell video::-webkit-media-controls-overlay-play-button,
.video-shell video::-webkit-media-controls-start-playback-button {
  display: none !important;
  -webkit-appearance: none;
}

/* ─── Media lightbox (click to expand & zoom) ─── */
.cs-image, .cs-video {
  cursor: zoom-in;
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s5);
  background: rgba(0, 0, 0, 0.92);
  backdrop-filter: blur(8px) saturate(120%);
  -webkit-backdrop-filter: blur(8px) saturate(120%);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.28s var(--ease-out), visibility 0s 0.28s;
}
.lightbox.lightbox--open {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.28s var(--ease-out);
}
.lightbox-media {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  border-radius: var(--r-lg);
  box-shadow: 0 30px 90px rgba(0,0,0,0.53);
  transform: scale(0.96);
  transition: transform 0.28s var(--ease-out);
  cursor: zoom-in;
  object-fit: contain;
  user-select: none;
  -webkit-user-drag: none;
}
.lightbox--open .lightbox-media { transform: scale(1); }
.lightbox-media.is-zoomed {
  cursor: grab;
  max-width: none;
  max-height: none;
  border-radius: 0;
}
.lightbox-media.is-zoomed:active { cursor: grabbing; }
/* A board with a composited embed (.cs-annotated-embed) needs a positioning
   context sized like the img in the flex overlay — .lightbox-media-wrap takes
   over the flex-item role (max-size/transform/zoom, via the shared
   .lightbox-media class) and the img inside it just fills that box, since the
   img's own max-width/max-height:100% would otherwise fight the wrap's. */
.lightbox-media-wrap { position: relative; display: block; }
.lightbox-media-inner {
  display: block; width: 100%; height: 100%;
  object-fit: contain; user-select: none; -webkit-user-drag: none;
}
.lightbox-embed { box-shadow: none; }

.lightbox-close {
  position: fixed;
  top: 20px;
  right: 24px;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  background: var(--surface-2);
  border: 1px solid var(--border-1);
  color: var(--text-1);
  font-size: 22px;
  line-height: 1;
  /* Stay above the zoomed image (which is later in the DOM and would otherwise
     cover the button when scaled up) so it never gets hidden or feels displaced. */
  z-index: 2;
  transition: background 0.2s, transform 0.2s var(--ease-out);
}
.lightbox-close:hover { background: var(--surface-3); transform: scale(1.06); }
.lightbox-hint {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 14px;
  line-height: 1.3;
  letter-spacing: 0.04em;
  color: var(--text-3);
  pointer-events: none;
  z-index: 2; /* above the zoomed image, same as the close button */
}
@media (max-width: 768px) {
  .lightbox { padding: var(--s3); }
  .lightbox-hint { display: none; }
}

/* ── Pre-tap zoom affordance on dense diagrams (mobile). These are 2352px
   composites rendering in a ~335px column, so their labels are unreadable
   until zoomed — but the only zoom cue was the in-overlay hint, which is
   hidden until the overlay is already open. On touch there was no cue at all.
   A persistent corner badge on the thumbnail states it up front. */
[data-project="dreamplay"] .cs-flat { position: relative; }
@media (max-width: 768px) {
  [data-project="dreamplay"] .cs-flat::after {
    content: "Tap to zoom";
    position: absolute;
    top: 10px; right: 10px;
    padding: 5px 10px;
    font-size: 12px; font-weight: 600; letter-spacing: 0.04em;
    color: #ffffff; background: rgba(17,17,17,0.72);
    border-radius: 100px;
    pointer-events: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .lightbox, .lightbox-media { transition: none; }
}

/* ─── MacBook frame — desktop screenshots & prototype video ─── */
.cs-mac-wrap {
  position: relative;
  width: 100%;
  max-width: 960px;
  margin-left: auto;
  margin-right: auto;
  margin-top: 32px;
  padding: 2.8% 2.8% 0;
  background: #d8d8da;
  border-radius: 12px 12px 0 0;
  box-shadow:
    0 0 0 1.5px #c0c0c4,
    0 0 0 2.5px #e8e8ec,
    0 24px 60px rgba(0,0,0,0.09);
}
/* Screen area */
.cs-mac-wrap .cs-mac-screen {
  position: relative;
  width: 100%;
  border-radius: 4px 4px 0 0;
  overflow: hidden;
  background: #f4f4f4;
}
.cs-mac-wrap .cs-mac-screen img,
.cs-mac-wrap .cs-mac-screen video {
  width: 100%;
  height: auto;
  object-fit: unset;
  display: block;
  border-radius: 0;
}
/* Notch / camera dot */
.cs-mac-wrap::before {
  content: "";
  position: absolute;
  top: 0.9%;
  left: 50%;
  transform: translateX(-50%);
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #b8b8bc;
  z-index: 1;
}
/* Hinge bar + base */
.cs-mac-base {
  width: 100%;
  max-width: 960px;
  margin: 0 auto 32px;
  height: 18px;
  background: linear-gradient(to bottom, #c8c8cc, #d8d8da);
  border-radius: 0 0 2px 2px;
  box-shadow:
    0 0 0 1.5px #c0c0c4,
    0 3px 12px rgba(0,0,0,0.06);
}
/* Make images inside the frame lightbox-clickable */
.cs-mac-wrap .cs-mac-screen img  { cursor: zoom-in; }
.cs-mac-wrap .cs-mac-screen video { cursor: auto; }
@media (max-width: 768px) {
  .cs-mac-wrap { padding: 3% 3% 0; border-radius: 8px 8px 0 0; }
  .cs-mac-wrap::before { width: 5px; height: 5px; }
  .cs-mac-base { height: 12px; }
}

/* Social Gameplay — objectives cover (4 areas) */
.sg-objectives {
  background:
    radial-gradient(120% 130% at 50% -10%, rgba(33,230,115,0.08), transparent 55%),
    rgba(0,0,0,0.02);
  border: none;
  border-radius: var(--r-xl);
  padding: clamp(20px, 3.4vw, 44px);
}
.sg-obj-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(12px, 1.6vw, 20px);
}
.sg-obj-item {
  position: relative;
  background: rgba(0,0,0,0.024);
  border: 1px solid var(--border-1);
  border-radius: var(--r-lg);
  padding: clamp(18px, 2.2vw, 28px);
  overflow: hidden;
  transition: border-color 0.3s ease, transform 0.3s ease;
}
.sg-obj-item::before {
  content: "";
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 3px;
  background: var(--obj);
}
.sg-obj-item:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--obj) 45%, var(--border-1));
}
.sg-obj-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px; height: 46px;
  border-radius: 13px;
  background: color-mix(in srgb, var(--obj) 16%, transparent);
  color: var(--obj);
  margin-bottom: 16px;
}
.sg-obj-icon svg { width: 23px; height: 23px; }
.sg-obj-title {
  font-family: var(--f-heading);
  font-size: clamp(20px, 2.1vw, 26px);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text-1);
  margin-bottom: 8px;
}
.sg-obj-desc {
  font-size: 15px;
  color: var(--text-2);
  line-height: 1.6;
}
@media (max-width: 768px) {
  .sg-obj-grid { grid-template-columns: 1fr; }
}

/* Divider */
.cs-divider {
  height: 0;
  background: none;
  margin: 100px 0;
}

/* Quote */
.cs-quote {
  font-family: var(--f-heading);
  font-size: clamp(16px, 2vw, 22px);
  font-weight: 600;
  color: var(--text-1);
  line-height: 1.5;
  letter-spacing: -0.01em;
  border-left: 2px solid var(--accent);
  padding: var(--s2) 0 var(--s2) var(--s3);
  margin: var(--s5) 0;
  max-width: 720px;
}

/* Phase label pill */
.cs-phase-label {
  display: inline-block;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-2);
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-radius: var(--r-pill);
  padding: 4px 14px;
  margin-bottom: var(--s3);
}

/* Feature list */
.cs-feature-list {
  list-style: disc;
  padding-left: 20px;
  margin: var(--s2) 0;
}
.cs-feature-list li {
  font-size: 16px;
  color: var(--text-2);
  line-height: 1.7;
  margin-bottom: 8px;
}
.cs-feature-list li strong { color: var(--text-1); font-weight: 600; }

/* Deprecated notice */
.cs-deprecated {
  background: rgba(234, 179, 8, 0.06);
  border: 1px solid rgba(234, 179, 8, 0.20);
  border-radius: var(--r-md);
  padding: 12px var(--s3);
  font-size: 14px;
  color: #f59e0b;
  margin-top: var(--s3);
  line-height: 1.6;
}

/* Impact box */
.cs-impact-box {
  background: var(--surface-1);
  border: 1px solid var(--border-1);
  border-radius: var(--r-md);
  padding: var(--s3);
  margin-top: var(--s3);
}
.cs-impact-box p { font-size: 14px; color: var(--text-2); line-height: 1.65; margin: 0; }
.cs-impact-box strong { color: var(--text-1); font-weight: 600; }

/* Archetype grid */
.cs-archetype-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--s2);
  margin: var(--s3) 0;
}
.cs-archetype {
  background: var(--surface-1);
  border: 1px solid var(--border-1);
  border-radius: var(--r-md);
  padding: var(--s2) var(--s3);
  font-size: 14px;
  font-weight: 500;
  color: var(--text-2);
}

/* Icon row (Social Gameplay pillars) */
.cs-icon-row {
  display: flex;
  gap: var(--s4);
  align-items: flex-start;
  margin: var(--s4) 0;
  flex-wrap: wrap;
}
.cs-icon-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s1);
  text-align: center;
  flex: 1;
  min-width: 80px;
}
.cs-icon-item img {
  width: 64px; height: 64px;
  object-fit: contain;
  filter: brightness(0) invert(0.6);
}
.cs-icon-label { font-size: 14px; font-weight: 600; color: var(--text-2); }

/* Goal / Status grids */
.cs-goal-grid,
.cs-status-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--s2);
  margin: var(--s3) 0;
}
.cs-goal-item,
.cs-status-item {
  background: var(--surface-1);
  border: 1px solid var(--border-1);
  border-radius: var(--r-md);
  padding: var(--s2) var(--s3);
}
.cs-goal-num, .step {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: 6px;
}
.cs-goal-text { font-size: 14px; color: var(--text-2); line-height: 1.5; }
.cs-status-item { font-size: 14px; color: var(--text-1); font-weight: 500; line-height: 1.4; }
.cs-status-item .step { color: var(--text-3); }

/* Principle grid */
.cs-principle-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s2); /* 16px — uniform card-grid gap, matches process/techrow (§4) */
  margin: var(--s3) 0;
}
.cs-principle {
  font-size: 16px;
  color: var(--text-2);
  line-height: 1.6;
  padding: var(--s3);
  border: 1px solid var(--border-1);
  border-radius: var(--r-tile); /* Unified to 18px per §7 */
}
.cs-principle:last-child {
  border: 1px solid var(--border-1);
}
@media (max-width: 768px) {
  .cs-principle-grid { grid-template-columns: 1fr 1fr; gap: var(--s2); } /* 16px stacked-card gap, matches process/techrow (§4) */
  .cs-principle { padding: var(--s2); }             /* tighter on mobile: 24 → 16px */
  .cs-principle .tsys-chip { margin-bottom: 12px; }  /* 14 → 12px, denser card */
}
@media (max-width: 480px) {
  .cs-principle-grid { grid-template-columns: 1fr; }
}
.cs-principle strong { display: block; margin-bottom: 8px; } /* type comes from the shared card-tile-title rule below */
/* Chip icon inside a principle card: the card is white, so the chip takes the
   surface tint (TSYS `.tsys-chip` on white cards) rather than the white it uses
   on filled tiles. */
.cs-principle .cs-card-icon { width: 38px; height: 38px; border-radius: 10px;
  background: var(--surface-1); color: var(--text-1); margin-bottom: 14px; }
.cs-principle .cs-card-icon svg { width: 20px; height: 20px; }
/* Illustration block inside a principle card: no well at all — the artwork sits
   directly on the white card, so nothing frames or tints it. */
.cs-principle .cs-card-illus { background: none; border: none; padding: 0; }

/* ═══ CANONICAL CARD-TILE TITLE (§0 global component, §3.1 / §9) ═══
   The title inside any content tile/card — process steps, principle cards,
   Scout cards & video captions. ONE spec for all pages, defined here so the
   inline per-page copies can be deleted. Body face, 18px/16px, semibold. */
.tsys-step-name, .cs-principle strong, .sc-card-name, .sc-video-name, .sc-item-name,
.cs-card-name, .cs-mediacap-title, .cs-type-name {
  font-family: var(--f-body);
  font-size: 18px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--text-1);
}
@media (max-width: 768px) {
  .tsys-step-name, .cs-principle strong, .sc-card-name, .sc-video-name, .sc-item-name,
  .cs-card-name, .cs-mediacap-title, .cs-type-name { font-size: 16px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   SHARED CASE-STUDY COMPONENTS (global, §0) — clean generic media/card/layout
   patterns usable by any case study (DreamPlay uses these exclusively so its
   page carries zero manual overrides). Typography routes through the global
   body rule (.cs-body-text / .cs-mediacap) and the card-tile-title rule above.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Caption / media-supporting text — body-sized, muted (§3.1 + §8.6) */
.cs-mediacap { font-size: 17px; line-height: 1.5; color: var(--text-3); margin: var(--s2) 0 0; } /* consistent media→caption gap (16px) on every frame type, matches Scout .sc-flat-caption */
.cs-mediacap--frame { margin-top: var(--s2); max-width: 760px; }
/* A caption owns the structural gap to a paragraph that follows it (32px, the
   paragraph→component step) — otherwise it sits flush against that paragraph and
   reads as part of it. Scout carries the same rule as `.sc-flat + .cs-body-text`. */
.cs-mediacap + .cs-body-text, .cs-flat + .cs-body-text { margin-top: var(--s4); }
/* Titled caption — Scout `.sc-phone-cell` / `.sc-video-cell` canon, exact values.
   Scout: the media carries a 24px bottom gap, then the name and description sit 0
   apart as one block (`.sc-cell-head`), identical at both breakpoints. Same here:
   the TITLE owns the 24px media gap (margin-top: s3) and the description follows at
   0. Never put a flex `gap` on the cell — each element's own margin owns its gap,
   or the two spacings drift apart the way they did before Entry 105. */
.cs-mediacap-title { margin: var(--s3) 0 0; }
.cs-mediacap-title + .cs-mediacap, .cs-mediacap-title + ul.cs-mediacap { margin-top: 0; }
.cs-video-cell .cs-mediacap-title, .cs-phone-cell .cs-mediacap-title { width: 280px; max-width: 100%; }
@media (max-width: 768px) { .cs-mediacap-title { margin-top: var(--s2); } } /* 24px → 16px on mobile only */
@media (min-width: 769px) { .cs-mediacap-title + .cs-mediacap, .cs-mediacap-title + ul.cs-mediacap { margin-top: 4px; } } /* title → caption gap, desktop only */
/* List variant: when a caption reads better as scannable points than a sentence.
   Same type and colour as the plain caption, with the standard bullet dot. */
ul.cs-mediacap { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 6px; }
/* A bulleted caption is a list of short points, not prose — the body 1.5 leading
   makes the items float apart and stop reading as one group. Tighten to 1.35. */
ul.cs-mediacap li { position: relative; padding-left: 16px; line-height: 1.35; }
ul.cs-mediacap li::before { content: ""; position: absolute; left: 0; top: 9px;
  width: 4px; height: 4px; border-radius: 50%; background: currentColor; opacity: 0.85; }
@media (max-width: 768px) { ul.cs-mediacap li::before { top: 8px; } }
@media (max-width: 768px) { .cs-mediacap { font-size: 16px; } }

/* Icon / numbered card grid — strategy, challenges, approaches (Scout sc-item look) */
/* Card = Scout .sc-item canon (§1.2): OPEN card, top hairline only — no box/fill/radius.
   Icon 38px, 1px border, no fill, text-2 stroke, r-md. Num inline 13px Plus Jakarta 600 text-3. */
/* Card = Scout .sc-item BOXED card, replicated exactly (§0.0 rule 0): surface-1 fill, 18px
   radius, s4 padding, num absolute top-right, white 48px icon chip, desc 17/16 text-2. */
/* margin-top bumped 32->48px: body text into a 3-card grid into a full-width
   image read as cramped at this rhythm — flagged directly on the live page. */
.cs-cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s2); margin-top: var(--s5); }
.cs-cards--3 { grid-template-columns: repeat(3, 1fr); }
.cs-cards + .cs-body-text,
.sc-metrics + .cs-body-text { margin-top: var(--s4); } /* component → paragraph step (32px), matches the rest of the rhythm; was 0 (touching) */
.cs-card { position: relative; background: var(--surface-1); border-radius: var(--r-tile); padding: var(--s4); }
.cs-card-icon { width: 48px; height: 48px; display: flex; align-items: center; justify-content: center;
  background: #ffffff; border-radius: var(--r-md); color: var(--accent-2); margin-bottom: 14px; }
.cs-card-icon svg { width: 25px; height: 25px; display: block; }
/* Large icon: for a card that leads on the icon rather than a screenshot — sized to
   carry the card the way an illustration would, without needing artwork. */
.cs-card-icon--lg { width: 72px; height: 72px; border-radius: var(--r-md); margin-bottom: 18px; }
.cs-card-icon--lg svg { width: 38px; height: 38px; }
@media (max-width: 768px) { .cs-card-icon--lg { width: 60px; height: 60px; } .cs-card-icon--lg svg { width: 32px; height: 32px; } }
.cs-card-num { position: absolute; top: var(--s4); right: var(--s4); font-family: var(--f-heading); font-weight: 600;
  letter-spacing: 0.08em; font-size: 14px; line-height: 1.2; color: var(--text-3); }
.cs-card-name { margin: 8px 0 6px; }        /* type from card-tile-title rule above */
.cs-card-desc { font-size: 17px; line-height: 1.5; color: var(--text-2); margin: 0; }
@media (max-width: 768px) { .cs-card-desc { font-size: 16px; } }
@media (max-width: 768px) { .cs-cards, .cs-cards--3 { grid-template-columns: 1fr; gap: var(--s2); } }
/* --bento: mixed-width card mosaic (Scout .sc-grid bento thinking). Spans are set
   per-card in markup (.cs-span2/3/4 on a 6-col grid) so odd counts never orphan.
   Mobile: 2-col mosaic — span3/span4 go full-width, span2 becomes a half tile. */
.cs-cards--bento { grid-template-columns: repeat(6, 1fr); }
.cs-cards--bento .cs-span2 { grid-column: span 2; }
.cs-cards--bento .cs-span3 { grid-column: span 3; }
.cs-cards--bento .cs-span4 { grid-column: span 4; }
/* Outline variant: the `.cs-eco-node` card treatment applied to a card grid — white
   card, hairline, r-md and the same soft lift. The icon keeps the standard tinted
   square chip (white-on-white would disappear). Layout is untouched, so it composes
   with --bento and its spans. */
.cs-cards--outline .cs-card { background: #ffffff; border: 1px solid var(--border-1);
  border-radius: var(--r-md); box-shadow: 0 2px 10px rgba(16,24,64,0.05); }
.cs-cards--outline .cs-card-icon { background: var(--surface-1); }
@media (max-width: 768px) {
  .cs-cards--bento { grid-template-columns: repeat(2, 1fr); gap: 8px; }
  .cs-cards--bento .cs-span3, .cs-cards--bento .cs-span4 { grid-column: span 2; }
  .cs-cards--bento .cs-span2 { grid-column: span 1; }
  .cs-cards--bento .cs-card { padding: 16px; }
  /* --bento-even: for an ODD card count. Mobile collapses every card to one
     column so the grid packs 2-up with no ragged holes, and the trailing odd
     card spans the full width instead of leaving a half-empty row. */
  .cs-cards--bento-even .cs-card { grid-column: span 1; }
  .cs-cards--bento-even .cs-card:last-child:nth-child(odd) { grid-column: span 2; }
}
/* Statband unit suffix (x, %, +): optically centered against the numerals, and
   free of the numeral's wide tracking (TSYS suffix rule). Scoped to this page. */
/* Was scoped to social-gameplay only, and the size/weight (0.5em/600) lived as
   a hardcoded inline style repeated 8 times across 4 pages (`style="font-
   size:0.5em;font-weight:600"`) instead of a class — bypassing the design
   system entirely. That's also why the suffix's tracking looked disproportionate:
   letter-spacing inherits as an absolute computed length, not a re-computed
   em value, so the parent's tracking (set for a much larger font-size) applied
   at full strength to a suffix rendered at half the size. Consolidated into one
   class, universal across all pages, with the same letter-spacing:0 reset TSYS
   already had — now the fix everyone gets, not just one page. */
/* vertical-align:middle centred the suffix on the numeral's x-height, sitting
   it 4.6px above the numeral baseline. Both sit on the same baseline. */
.cs-statband-suffix { font-size: 0.5em; font-weight: 600; vertical-align: baseline; letter-spacing: 0; margin-left: 2px; }
/* --m3: keeps a 3-up row on mobile too (for short illustrated cards that read as one
   strip, e.g. the Social Gameplay pillars). Placed after the 1-col collapse so source
   order wins (§5.1). Type stays at the 14px floor. */
@media (max-width: 768px) {
  .cs-cards--m3 { grid-template-columns: repeat(3, 1fr); gap: 8px; }
  .cs-cards--m3 .cs-card { padding: 12px; }
  .cs-cards--m3 .cs-card-illus { padding: 6px; margin-bottom: 10px; }
  .cs-cards--m3 .cs-card-illus img { height: 56px; }
  .cs-cards--m3 .cs-card-name { font-size: 14px; line-height: 1.3; }
  .cs-cards--m3 .cs-card-desc { font-size: 14px; line-height: 1.4; }
}

/* ─── Transition band: a full-bleed statement that hands off between chapters ───
   Same construction as TSYS's .tsys-quote (100vw accent-tint band, centred display
   type, s7 vertical padding) but with no quote glyph — it is a narrator line, not
   a quotation. Full border-free by design; no single-edge rules (§ Border rule). */
.cs-transition { position: relative; width: 100vw; max-width: none;
  margin-left: calc(50% - 50vw); margin-top: var(--s7); margin-bottom: var(--s7);
  padding: var(--s7) var(--s4); background: var(--accent-2-tint); text-align: center; overflow: hidden; }
/* Same Bebas-tuned tracking as the headline group above (+0.04em on mixed-case
   text) — same fix, weight up + tracking negative for Schibsted Grotesk. */
.cs-transition p { max-width: 760px; margin: 0 auto;
  font-family: var(--f-display); font-weight: 600;
  font-size: clamp(22px, 2.4vw, 30px); letter-spacing: -0.015em; line-height: 1.35;
  color: var(--text-1); }
@media (max-width: 768px) { .cs-transition { padding: var(--s6) var(--s3); } }

/* ─── Brainstorm board: challenges → principles → approach ───
   Reads like the output of a working session: three labelled clusters of
   notes on a clean board, with arrows showing how one cluster fed the next.
   Notes are orthogonal (no tilt) and carry a soft lift off the surface.
   NOTE (§ rule): every box here uses a FULL border — no single-edge accent
   bars anywhere. Stacks to one column on mobile with the arrows turned down. */
.cs-flowmap { border: 1px solid var(--border-1); border-radius: var(--r-tile); background: #ffffff;
  padding: var(--s5) var(--s4); margin-top: var(--s4);
  display: grid; grid-template-columns: 1fr 96px 1fr 96px 1fr; align-items: start; }
.cs-flowcol { display: flex; flex-direction: column; min-width: 0; }
/* Cluster label: plain small-caps title, no chip */
.cs-flowcol-head { align-self: flex-start; color: var(--text-1);
  display: flex; align-items: center; gap: 8px; margin-bottom: 16px; }
.cs-flowcol-head .step { flex: none; display: block; margin: 0; letter-spacing: 0; line-height: 1;
  font-family: var(--f-heading); font-weight: 700; font-size: 14px; color: var(--accent-2); }
.cs-flowcol-head .t { font-family: var(--f-heading); font-weight: 600; font-size: 12px;
  letter-spacing: 0.1em; text-transform: uppercase; line-height: 1.3; }
.cs-flowcol-head .count { font-family: var(--f-heading); font-weight: 600;
  font-size: 14px; color: var(--text-3); white-space: nowrap; }
.cs-flowcol-items { flex: 1; display: flex; flex-direction: column; gap: 12px; }
/* Arrows between clusters */
.cs-flowarrow { display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; padding: 0 4px; align-self: center; }
.cs-flowarrow .chev { flex: none; width: 40px; height: 40px; border-radius: 50%;
  background: #ffffff; border: 1px solid var(--border-2);
  display: flex; align-items: center; justify-content: center; }
.cs-flowarrow svg { width: 20px; height: 20px; color: var(--accent-2); flex: none; }
.cs-flowarrow .lbl { font-family: var(--f-heading); font-weight: 600; font-size: 12px;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-3); }
/* Sticky notes — one shared treatment, three surfaces; alternating tilt */
.cs-flowchal-item, .cs-flowprin-item, .cs-flowappr-item {
  border: 1px solid var(--border-1); border-radius: var(--r-md); padding: 16px;
  box-shadow: 0 1px 2px rgba(0,0,0,0.05), 0 6px 14px rgba(0,0,0,0.05); }
.cs-flowchal-item { background: var(--surface-1); }
.cs-flowprin-item { background: var(--surface-2); }
.cs-flowappr-item { background: #ffffff; }
.cs-flowchal-item .name, .cs-flowprin-item .name, .cs-flowappr-item .name {
  font-family: var(--f-heading); font-weight: 600; font-size: 15px; line-height: 1.35;
  letter-spacing: -0.01em; color: var(--text-1); display: block; }
.cs-flowchal-item .desc, .cs-flowprin-item .desc, .cs-flowappr-item .desc {
  font-size: 14px; line-height: 1.45; color: var(--text-2); margin: 2px 0 0; }
.cs-flowappr-item ul { margin: 8px 0 0; display: flex; flex-direction: column; gap: 5px; }
.cs-flowappr-item li { position: relative; padding-left: 15px; font-size: 14px; line-height: 1.45; color: var(--text-2); }
.cs-flowappr-item li::before { content: ''; position: absolute; left: 0; top: 0.55em;
  width: 5px; height: 5px; border-radius: 50%; background: var(--text-3); }
@media (max-width: 900px) {
  .cs-flowmap { grid-template-columns: 1fr; padding: var(--s3); }
  .cs-flowarrow { flex-direction: row; padding: 14px 0; }
  .cs-flowarrow svg { transform: rotate(90deg); }
  .cs-flowcol-items { justify-content: flex-start; }
}

/* ─── Bet map: research hub → branches, mindmap-style (deck quality) ───
   Solid accent hub on the left, a branching spine in the gutter, and one
   branch card per bet with an accent number chip. Spine + stubs hide on
   mobile where the layout stacks. */
.cs-betmap { margin-top: var(--s4);
  display: grid; grid-template-columns: 220px 64px minmax(0, 1fr); align-items: center; }
.cs-betmap-hub { background: var(--surface-2); color: var(--text-1); border-radius: var(--r-md);
  border: 1px solid var(--border-2); padding: 22px 20px; }
.cs-betmap-hub .t { font-family: var(--f-heading); font-weight: 700; font-size: 15px;
  letter-spacing: 0.06em; text-transform: uppercase; line-height: 1.35; display: block; }
.cs-betmap-hub .s { font-size: 14px; line-height: 1.5; color: var(--text-2); margin: 6px 0 0; }
/* Gutter: only the hub → spine horizontal; the vertical is chained per item so it
   starts exactly at item 1's centre and ends exactly at the last item's centre */
.cs-betmap-spine { position: relative; align-self: stretch; }
.cs-betmap-spine::after { content: ''; position: absolute; top: 50%; transform: translateY(-50%);
  left: 0; right: 50%; height: 2px; background: var(--border-2); }
.cs-betmap-items { display: flex; flex-direction: column; gap: 10px; }
.cs-betmap-item { position: relative; background: #ffffff; border: 1px solid var(--border-1);
  border-radius: var(--r-md); padding: 14px 16px; display: grid; grid-template-columns: 26px minmax(0, 1fr);
  column-gap: 12px; align-items: start; }
/* Elbow: horizontal stub at the item's centre; non-last items also carry the
   down-leg (border-left) from their centre to their bottom edge */
.cs-betmap-item::before { content: ''; position: absolute; left: -32px; top: 50%; width: 32px; height: 2px;
  background: var(--border-2); }
.cs-betmap-item:not(:last-child)::before { height: calc(50% + 2px); background: none;
  border-top: 2px solid var(--border-2); border-left: 2px solid var(--border-2); }
/* Up-leg on non-first items: from the top edge (through the gap) down to the centre */
.cs-betmap-item:not(:first-child)::after { content: ''; position: absolute; left: -32px; bottom: 50%;
  width: 2px; height: calc(50% + 10px); background: var(--border-2); }
.cs-betmap-item .num { width: 26px; height: 26px; border-radius: 50%; background: var(--surface-2); color: var(--accent-2);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--f-heading); font-weight: 700; font-size: 14px; margin-top: 1px; }
.cs-betmap-item .name { font-family: var(--f-heading); font-weight: 700; font-size: 15px; line-height: 1.35;
  color: var(--text-1); display: block; }
.cs-betmap-item .desc { font-size: 14px; line-height: 1.45; color: var(--text-2); margin: 2px 0 0; }
@media (max-width: 900px) {
  .cs-betmap { grid-template-columns: 1fr; padding: var(--s3); }
  .cs-betmap-hub { margin-bottom: 14px; }
  .cs-betmap-spine { display: none; }
  /* both connector legs must go, not just ::before — the ::after up-leg would
     otherwise leave stray vertical lines beside the stacked cards */
  .cs-betmap-item::before,
  .cs-betmap-item::after { display: none; }
}

/* .cs-pillars/.cs-pillar (boxed principle strip) retired: the one instance on
   scout-ai.html was converted to .sc-grid/.sc-item, the canonical ruled index,
   so the boxed variant is no longer used anywhere and has been removed. */

/* Flow map on mobile: the HTML graphic is replaced by a captured image (framed,
   lightbox-zoomable via the global cs-flat binding) — the 3-column map is
   unreadable stacked at phone width. */
.cs-flowmap-mobile { display: none; }
@media (max-width: 900px) {
  .cs-flowmap { display: none; }
  .cs-flowmap-mobile { display: block; }
}

/* Two-column text + media split (Scout .sc-split look; collapse on mobile) */
.cs-split2 { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: var(--s5) var(--s6);
  align-items: start; margin-top: var(--s4); }
[data-project="dreamplay"] .cs-split2 > *, [data-project="social-gameplay"] .cs-split2 > * { margin-top: 0; }
/* ...and the media column adds no top padding either. A gallery row keeps its 32px
   padding when it stands alone, but inside a split that padding pushes the frame
   below the first line of the text beside it. Top edges must line up. */
[data-project="dreamplay"] .cs-split2 > .cs-video-row, [data-project="social-gameplay"] .cs-split2 > .cs-video-row,
[data-project="dreamplay"] .cs-split2 > .cs-phone-row, [data-project="social-gameplay"] .cs-split2 > .cs-phone-row { padding-top: 0; }
/* ...and the LAST child adds no bottom margin. A `.cs-split2` is a grid container,
   so margins inside it do not collapse: a media row's own 32px bottom margin (from
   the legacy `.cs-video-row` shorthand) stacked on the next component's 32px top
   margin and made the gap 64px after a split but 32px after a bare row. The split
   owns the gap to whatever follows it. */
[data-project="dreamplay"] .cs-split2 > :last-child, [data-project="social-gameplay"] .cs-split2 > :last-child { margin-bottom: 0; } /* top-align the two columns: a child's own top margin (e.g. .cs-bullets 24px) must not offset it from the grid top. Scoped to beat .cs-bullets' own margin at equal base specificity. */
/* The media column is sized to what it holds, so the frame ends flush with the
   section's right edge. A 480px column around a 280px frame left 200px of dead
   space and the media read as floating short of the edge, while every other
   component on the page ran edge to edge. --wide holds a 2-frame row (592px). */
.cs-split2--wide { grid-template-columns: minmax(0, 1fr) 592px; }
/* One-off: the DreamPlay home-ecosystem composite reads better narrower than
   the full 592px --wide column it sits in — 0.8x, desktop only (mobile fills
   the column at 100% like every other --wide media item). */
@media (min-width: 901px) {
  .dp-home-ecosystem-img { max-width: 474px; }
}
@media (max-width: 900px) {
  .cs-split2, .cs-split2--wide { grid-template-columns: 1fr; row-gap: var(--s3); }
  /* .cs-split2 collapses to one column here, but the video/phone frame
     full-width fix elsewhere only fires at 768px — between 769-900px the
     column is already full width while every frame inside it is still
     capped at 280px, leaving dead space and a caption that stops short of
     the container edge. Sync the two breakpoints. */
  .cs-split2 .cs-vframe, .cs-split2 .cs-vframe-letterbox, .cs-split2 .cs-phone,
  .cs-split2 .cs-video-cell .cs-mediacap-title, .cs-split2 .cs-video-cell .cs-mediacap,
  .cs-split2 .cs-phone-cell .cs-mediacap-title, .cs-split2 .cs-phone-cell .cs-mediacap { max-width: 100%; width: 100%; }
}

/* Phone-frame screenshots — consistent width, aspect preserved, device frame, lightbox */
/* Media galleries sit in ONE grey container per section (replicated from Scout .sc-phone-trio:
   surface-1, 18px radius, 32px padding, cells transparent) — not each phone on bare white. */
.cs-phone-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s4); margin-top: var(--s4); align-items: start; background: var(--surface-1); border-radius: var(--r-tile); padding: var(--s4); }
.cs-phone-row--2 { grid-template-columns: repeat(2, 1fr); }
.cs-phone-cell { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }
/* Phone frame = Scout .cs-image--phone canon (§1.2): 280px, 7px #0a0a0c device bezel (6px mobile),
   dark bg, r-phone, soft shadow. Mobile keeps the 280px STANDARD width (never full-bleed),
   height by aspect ratio — the documented mobile-frame mandate. */
.cs-phone { width: 280px; max-width: 100%; height: auto; display: block; align-self: flex-start;
  border: 7px solid #0a0a0c; background: #0a0a0c; border-radius: var(--r-phone);
  box-shadow: 0 0 0 2px #cdcbd6, 0 0 0 3.5px #9b99a6, 0 16px 36px rgba(0,0,0,.304); /* §7 canonical phone-frame lift, matches .cs-vframe */
  cursor: zoom-in; margin-bottom: 0; }
/* No hover fade — removed on every zoomable image, phones included. cursor:zoom-in
   is the affordance; the lightbox is the interaction. */
.cs-phone-cell .cs-mediacap { width: 280px; max-width: 100%; }
@media (max-width: 768px) { /* grid-template-columns/gap for .cs-phone-row is set once, with .cs-video-row, further down */
  .cs-phone { border-width: 6px; box-shadow: 0 0 0 2px #cdcbd6, 0 0 0 3.5px #9b99a6, 0 13px 29px rgba(0,0,0,.243); } }
/* structural step from a principle grid to a following paragraph (§4) */
.cs-principle-grid + .cs-body-text { margin-top: var(--s5); }

/* Phone-frame video — reuses the global play-badge/video-shell; adds the device frame */
.cs-video-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s4); margin-top: var(--s4); align-items: start; background: var(--surface-1); border-radius: var(--r-tile); padding: var(--s4); }
.cs-video-row--3 { grid-template-columns: repeat(3, 1fr); }
/* On the accent-tint Product band, the grey gallery would clash with the tint; use white (Scout .sc-dark pattern) */
.cs-band .cs-phone-row, .cs-band .cs-video-row { background: #ffffff; }
.cs-band .cs-card { background: #ffffff; } /* Scout tint-band canon (§2): tiles inside the accent-tint band flip to white; grey-on-tint clashes */
.cs-band .ch-eyebrow { background: #ffffff; } /* same rule, missed on the pill: --surface #f4f4f6 on --accent-2-tint #F2F5FF is a ~1% lightness difference, functionally invisible */
.cs-video-cell { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }
/* Flush variant: no gallery background/padding — used when the row sits directly
   on the accent-tint band. Caption goes dark ink for contrast without the white
   card (var(--text-3) is tuned for a white background, not the tint). */
.cs-band .cs-video-row--flush, .cs-video-row--flush,
.cs-band .cs-phone-row--flush, .cs-phone-row--flush { background: none; padding: 0; border-radius: 0; }
.cs-video-row--flush .cs-mediacap, .cs-phone-row--flush .cs-mediacap { color: var(--text-1); }
/* Without the gallery card's background, the standard 32px component gap reads as
   too tight — there's no boundary line to separate this from what precedes it.
   Bump to the next spacing step. When flush is used as the media column inside a
   .cs-split2 (paired with its text partner), the higher-specificity
   `[data-project="dreamplay"] .cs-split2 > *` rule (0,1,1,0 beats 0,0,1,0 here)
   still wins and zeroes this back out, so the two-column top-alignment is untouched. */
.cs-video-row--flush, .cs-phone-row--flush { margin-top: var(--s5); }
.cs-vframe { width: 100%; max-width: 280px; height: auto; align-self: flex-start; display: block;
  border: 7px solid #0a0a0c; border-radius: var(--r-phone); background: #0a0a0c;
  box-shadow: 0 0 0 2px #cdcbd6, 0 0 0 3.5px #9b99a6, 0 16px 36px rgba(0,0,0,0.304); margin-bottom: 0; }
.cs-video-cell .cs-mediacap { width: 280px; max-width: 100%; }
/* Mobile stacking gap (§ mobile media rule): when a media row collapses to one
   column, the gap between one cell's caption and the next cell's frame is 48px
   (--s5), fixed, on every page. This was drifting — .cs-phone-row rendered 48px
   correctly but .cs-video-row rendered 40px because of a dead legacy rule (removed
   above) that won the cascade by source order alone. Both rows now share one
   value from one place. */
@media (max-width: 768px) {
  .cs-video-row, .cs-video-row--3, .cs-phone-row, .cs-phone-row--2 { grid-template-columns: 1fr; gap: 60px; /* bespoke — no design token equals 60px */ }
  .cs-vframe, .cs-vframe-letterbox { border-width: 6px; box-shadow: 0 0 0 2px #cdcbd6, 0 0 0 3.5px #9b99a6, 0 13px 29px rgba(0,0,0,0.243); }
  /* The 280px max-width on .cs-vframe/.cs-vframe-letterbox/.cs-phone exists so
     multiple cells fit side-by-side on desktop. At this breakpoint the row is
     already a single column, so the cap just leaves dead, asymmetric space to
     the right of every frame instead of filling the column. Site-wide fix —
     every case-study page uses these same classes. */
  .cs-vframe, .cs-vframe-letterbox, .cs-phone,
  .cs-video-cell .cs-mediacap-title, .cs-video-cell .cs-mediacap,
  .cs-phone-cell .cs-mediacap-title, .cs-phone-cell .cs-mediacap { max-width: 100%; width: 100%; }
}
/* Desktop row geometry — Scout `.sc-phone-trio` behaviour, made count-independent.
   THREE frames is the base unit: they sit end to end, first flush left, last flush
   right, so the row spans its full measure. The gap that falls out of that
   ( (100% - 3x280) / 2 ) is then the FIXED gap for every row, so a 2-frame row uses
   the same spacing and starts from the left rather than spreading to fill.
   max() floors it at the standard 32px so narrow desktops never go negative.
   Row-gap stays 32px for rows that wrap. */
@media (min-width: 769px) {
  .cs-video-row, .cs-video-row--3, .cs-phone-row, .cs-phone-row--2 {
    grid-template-columns: repeat(auto-fit, 280px);
    justify-content: start;
    column-gap: max(var(--s4), calc((100% - 840px) / 2));
    row-gap: var(--s4);
  }
  /* Opt-in exception: a row of two device frames plus a flat image. The image is a
     wide composite, not a phone screen, so a 280px column strands it. It takes the
     row's leftover width instead, at the standard 32px gap. Used deliberately, per
     section — the end-to-end rule above stays the default for every other row. */
  .cs-video-row--2plus1 {
    grid-template-columns: 280px 280px minmax(280px, 1fr);
    column-gap: var(--s4);
  }
  .cs-video-row--2plus1 .cs-flat--incell { width: 100%; }
}

/* Letterboxed video frame: for a clip whose native aspect ratio differs from its
   row siblings. Matches .cs-vframe's border/radius/shadow exactly but on a wrapper
   sized to the SHARED aspect ratio, with the video itself object-fit:contain inside
   — full frame, no distortion, no crop (unlike object-fit:cover, which would crop). */
.cs-vframe-letterbox { width: 100%; max-width: 280px; align-self: flex-start; border: 7px solid #0a0a0c;
  border-radius: var(--r-phone); background: #0a0a0c;
  box-shadow: 0 0 0 2px #cdcbd6, 0 0 0 3.5px #9b99a6, 0 16px 36px rgba(0,0,0,0.304); overflow: hidden; }
/* aspect-ratio lives on this inner, border-free layer so it sizes off the CONTENT
   box, not the bordered outer box — matching how the sibling videos' own intrinsic
   ratio (also computed on their content box) ends up producing the target height. */
.cs-vframe-letterbox-inner { width: 100%; aspect-ratio: 720 / 1558; }
/* js/main.js wraps every <video> in a .video-shell. Without a height here the
   shell sizes to its content, the video's `height: 100%` resolves against an
   auto-height parent and falls back to the clip's own intrinsic ratio — which
   stretches the inner past its aspect-ratio and defeats the whole letterbox.
   Symptom: a frame visibly taller than the .cs-phone stills beside it in a row. */
.cs-vframe-letterbox-inner > .video-shell { width: 100%; height: 100%; }
.cs-vframe-letterbox video { width: 100%; height: 100%; object-fit: contain; display: block; }
@media (max-width: 768px) {
  .cs-vframe-letterbox { box-shadow: 0 0 0 2px #cdcbd6, 0 0 0 3.5px #9b99a6, 0 13px 29px rgba(0,0,0,0.243); }
}

/* Wide full-width artifact image, and tall image — plain frame + lightbox (§8.6).
   Matches TSYS's actual annotated-screenshot treatment (.tsys-screen:not(.tsys-screen-video)
   .cs-mac-wrap in tsys.html, which every other TSYS reference image on the page uses):
   a plain 1px border-1 line, 18px radius, soft 6px/18px shadow at 7% opacity. */
.cs-flat { margin-top: var(--s5); } /* bumped 32->48px, same rhythm fix as .cs-cards above */
.cs-flat img, .cs-tall img { display: block; height: auto;
  border: 1px solid var(--border-1);
  border-radius: var(--r-tile);
  box-shadow: 0 6px 18px rgba(0,0,0,0.07);
  cursor: zoom-in; }
.cs-flat img { width: 100%; }
/* Narrow variant: squarish or two-frame composites that would render oversized at
   full container width. Caps the frame (and its caption) at the 760px body measure. */
.cs-flat--narrow { max-width: 760px; }
/* Mat variant: for a source image whose artwork runs to its own edges. The frame
   gets white safe space so the screens are not pinched against the border. Padding
   sits on the img (border-box) so the artwork is matted, never cropped. */
.cs-flat--mat img { background: #ffffff; padding: 24px; box-sizing: border-box; }
/* A flat image used as one cell of a media row: it sits beside device frames, so it
   drops the standalone block's top margin and takes the row's 280px column width. */
.cs-flat--incell { margin-top: 0; width: 280px; max-width: 100%; }
.cs-flat--incell img { padding: 10px; }
@media (max-width: 768px) { .cs-flat--mat img { padding: 12px; } }
/* No opacity fade on these. .cs-flat/.cs-tall hold the page's large composite
   diagrams (up to 2352x1846). Transitioning opacity on an image that big forces
   a full repaint of the layer every frame of the fade — visible jank on hover.
   cursor:zoom-in already signals the affordance, and the lightbox is the real
   interaction. The small 280px .cs-phone frames keep their fade: cheap to
   repaint, smooth. */
.cs-tall { margin-top: var(--s4); }
.cs-tall img { width: 100%; max-width: 320px; }

/* Colour swatches (TSYS .tsys-swatch spec: 14px name/role) */
/* Colour system: two tiers, matching the brand hierarchy, not a flat grid.
   Primary/brand colours are full-width bars (the ones that carry the product);
   accent colours (utility: info, warning, success) sit in a smaller 2-up row
   below. Text sits ON the swatch, colour picked per-swatch for AA (4.5:1
   normal text) rather than assuming one text colour works on every hue. */
.cs-color-system { margin-top: var(--s4); display: flex; flex-direction: column; gap: var(--s4); }
.cs-swatch-group-label { font-size: 12px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-3, #6e6860); margin-bottom: 10px; }
.cs-swatch-primary-row { display: flex; flex-direction: column; gap: 10px; }
.cs-swatch-primary { display: flex; align-items: center; justify-content: space-between;
  height: 60px; border-radius: var(--r-md); padding: 0 var(--s4); }
.cs-swatch-primary .cs-swatch-name { font-size: 16px; font-weight: 600; }
/* No opacity: it reduces effective contrast below the ratio the colour pair
   measures at, invisibly to any check that reads `color` alone. */
.cs-swatch-primary .cs-swatch-role { font-size: 13px; font-weight: 600; }
.cs-swatch-accent-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.cs-swatch-accent { display: flex; flex-direction: column; align-items: flex-start; justify-content: center;
  gap: 2px; height: 68px; border-radius: var(--r-md); padding: 0 var(--s3); }
.cs-swatch-accent .cs-swatch-name { font-size: 14px; font-weight: 600; }
/* 12px floor: was 11px, the only sub-12px text on the site and a break of the
   documented minimum. */
.cs-swatch-accent .cs-swatch-role { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.1em; }
.cs-swatch-name, .cs-swatch-role { line-height: 1.3; white-space: nowrap; }
@media (max-width: 768px) {
  .cs-swatch-primary { height: 56px; padding: 0 var(--s3); }
  .cs-swatch-primary .cs-swatch-name { font-size: 15px; }
  .cs-swatch-accent-row { grid-template-columns: repeat(2, 1fr); }
  .cs-swatch-accent { height: 56px; }
  .cs-swatch-accent .cs-swatch-role { display: none; } /* name alone at this width; role still in the a11y label */
}

/* Type specimen (registry §1.3): typography shown SEPARATELY from the colour palette. Open cards, top hairline. */
.cs-type-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s5); margin-top: var(--s4); }
.cs-type-spec { padding-top: var(--s3); border-top: 1px solid var(--border-1); }
.cs-type-aa { font-family: var(--f-display); font-size: 64px; line-height: 0.9; color: var(--text-1); letter-spacing: 0.02em; margin-bottom: 12px; display: block; }
.cs-type-aa--body { font-family: var(--f-body); font-size: 56px; letter-spacing: 0; }
.cs-type-aa--tomorrow { font-family: 'Tomorrow', sans-serif; font-weight: 700; letter-spacing: 0; }
.cs-type-aa--inter { font-family: 'Inter', sans-serif; font-weight: 700; letter-spacing: 0; }
.cs-type-name { display: block; margin-bottom: 6px; } /* type from the global card-tile-title rule */
.cs-type-role { font-size: 16px; color: var(--text-2); line-height: 1.6; margin-bottom: 8px; }
.cs-type-weights { font-size: 16px; color: var(--text-3); line-height: 1.6; }
@media (max-width: 768px) { .cs-type-grid { grid-template-columns: 1fr; gap: var(--s4); } .cs-type-aa { font-size: 52px; } .cs-type-role, .cs-type-weights { font-size: 16px; } }

/* Canonical bullet list (registry §1.3). Follows the body canon 17/16 text-2 1.5 (§14) — NOT
   TSYS's inline 18/text-1 drift. Ink dot (§2.4). Never `display:flex` on li (breaks <strong> wrap). */
.cs-bullets { list-style: none; padding: 0; margin: var(--s3) 0 0; display: flex; flex-direction: column; gap: 12px; max-width: 820px; }
.cs-body-text + .cs-bullets { margin-top: var(--s4); } /* paragraph -> structural component step (32px); ch-sub + .cs-bullets keeps its own 24px sub-head proximity */
/* ...and the reverse, which was missing: a list followed by a paragraph had no
   step at all, so the two ran together. Same 32px, so the rhythm is symmetric
   whichever way round the pair appears. */
.cs-bullets + .cs-body-text { margin-top: var(--s4); }
/* ...and the NESTED case, which is the one that actually recurs. A list and a
   paragraph are usually siblings INSIDE a single .cs-body-text, not two sibling
   components, so the rule above never matched them. .cs-bullets carries
   margin: 24px 0 0 (top only), which left a paragraph after a list with zero
   step and the two ran together. Covers ul and ol so the rule holds if the
   markup changes. */
.cs-body-text .cs-bullets + p,
.cs-body-text ul + p,
.cs-body-text ol + p { margin-top: var(--s4); }
.cs-bullets li { position: relative; padding-left: 18px; font-size: 17px; color: var(--text-2); line-height: 1.5; }
.cs-bullets li::before { content: ""; position: absolute; left: 0; top: 10px; width: 5px; height: 5px; border-radius: 50%; background: var(--text-1); }
.cs-bullets strong { color: var(--text-1); font-weight: 600; }
@media (max-width: 768px) { .cs-bullets li { font-size: 16px; } .cs-bullets li::before { top: 9px; } }

/* Milestone timeline — horizontal dotted track, stacks on mobile */
.cs-timeline { position: relative; display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: var(--s2); margin-top: var(--s4); }
/* Connecting rail behind the dots (the milestones must read as one connected track, not floating points). */
.cs-timeline::before { content: ""; position: absolute; top: 9px; left: 5px; right: 5px; height: 2px; background: var(--border-2); z-index: 0; }
.cs-ms { position: relative; padding-top: 26px; z-index: 1; }
.cs-ms::before { content: ""; position: absolute; top: 4px; left: 0; width: 12px; height: 12px; border-radius: 50%; background: var(--accent-2); box-shadow: 0 0 0 3px #fff; }
.cs-ms--end::before { background: var(--text-3); }
.cs-ms-date { font-family: var(--label-caps-family); font-weight: 700; letter-spacing: 0.06em; font-size: 14px; color: var(--accent-2); }
.cs-ms--end .cs-ms-date { color: var(--text-3); }
.cs-ms-txt { font-size: 17px; color: var(--text-2); line-height: 1.45; margin-top: 6px; }
@media (max-width: 768px) { .cs-ms-txt { font-size: 16px; } }
@media (max-width: 768px) { .cs-timeline { grid-auto-flow: row; gap: var(--s4); } .cs-ms { padding-top: 0; padding-left: 26px; } .cs-ms::before { top: 4px; left: 0; }
  .cs-timeline::before { top: 9px; bottom: 9px; left: 5px; right: auto; width: 2px; height: auto; } }

/* ── Horizontal continuous timeline (.cs-timeline-h) ──
   A multi-year release timeline read left to right on ONE continuous rail.
   Registry component (§1.3). Year chips sit on the rail; each year column
   carries an optional phase note (tinted callout) and month rows. The whole
   track scrolls horizontally on every breakpoint (the one intentional
   horizontal-scroll pattern, like the related rail) so the rail never breaks. */
.cs-timeline-h { margin-top: var(--s4); overflow-x: auto; -webkit-overflow-scrolling: touch; touch-action: pan-x pan-y; padding-bottom: var(--s2); }
.cs-th-track { position: relative; display: flex; gap: var(--s4); min-width: max-content; padding-top: 4px; }
/* the continuous rail, drawn behind the year chips */
.cs-th-track::before { content: ""; position: absolute; top: 20px; left: 0; right: 0; height: 0; border-top: 2px dashed var(--accent-2-line); z-index: 0; }
.cs-th-year { position: relative; z-index: 1; width: 300px; flex: 0 0 auto; }
.cs-th-chip { display: inline-block; font-family: var(--label-caps-family); font-weight: 700; font-size: 15px; line-height: 1.2; letter-spacing: 0.06em; color: #ffffff; background: var(--accent-2); border-radius: var(--r-chip); padding: 8px 16px; }
.cs-th-note { margin-top: var(--s2); background: var(--accent-2-surface); border: 1px solid var(--accent-2-border); border-radius: var(--r-md); padding: 12px 14px; font-size: 15px; line-height: 1.45; color: var(--text-2); }
.cs-th-items { list-style: none; margin: var(--s2) 0 0; padding: 0; }
.cs-th-items li { display: grid; grid-template-columns: 44px 1fr; gap: 10px; padding: 10px 0; border-top: 1px solid var(--border-1); font-size: 15px; line-height: 1.45; color: var(--text-2); }
.cs-th-items li:first-child { border-top: none; }
.cs-th-items .m { font-family: var(--label-caps-family); font-weight: 700; font-size: 13px; line-height: 1.6; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-1); }
@media (max-width: 768px) { .cs-th-year { width: 264px; } .cs-th-items li, .cs-th-note { font-size: 14px; } }

/* ── Card illustration (.cs-card-illus) ──
   Optional artwork block at the top of a .cs-card, used when a card's source
   content ships with real illustration art (Social Gameplay pillar and
   building-block illustrations). White plate so the artwork reads cleanly on
   the grey card fill; full border per §6 (no one-edge borders). */
.cs-card-illus { background: #ffffff; border: 1px solid var(--border-1); border-radius: var(--r-md); padding: 10px; margin-bottom: 16px; }
.cs-card-illus img { display: block; width: 100%; height: 132px; object-fit: contain; }
@media (max-width: 768px) { .cs-card-illus img { height: 116px; } }

/* Full-bleed opaque hero banner (a case study opening on a key visual). The
   `--banner` modifier cancels the base gradient-hero pull-up so the image sits
   flush below the fixed nav (77px = nav height). */
.cs-hero--banner { min-height: auto; margin-top: 0; padding-top: 77px; padding-bottom: 160px; } /* matches Scout/TSYS's own inline hero padding-bottom = the standard chapter gap; was var(--s8)=128px, 32px short */
.cs-hero--banner + .cs-body { padding-top: 0; }

/* Full-bleed accent-tint band for the ONE pivotal chapter (registry §1.3), replicated from
   Scout .sc-dark: 100vw tint, s7 padding, chapter-gap as external margin-bottom, no double
   border (the band edge is the boundary). Wrap: .cs-band > .container > .sc-block. */
.cs-band { width: 100vw; margin-left: calc(50% - 50vw); background: var(--accent-2-tint); padding: var(--s7) 0; margin-bottom: 160px; }
.cs-band > .container { max-width: 1240px; margin: 0 auto; padding: 0 var(--s4); }
/* At 0,2,0 the rule above beats the mobile `.container { padding: 0 20px }`
   (0,1,0) in its media query, so every chapter wrapped in .cs-band kept the
   32px desktop inset on a 375px screen — content sat 12px short of the page
   edge on each side while every other chapter ran to 20px. Restore parity. */
@media (max-width: 768px) { .cs-band > .container { padding: 0 20px; } }
[data-project="dreamplay"] .cs-band .ch-head, [data-project="social-gameplay"] .cs-band .ch-head { border-top: none; padding-top: 0; }
/* Inside the band, the block must NOT carry the 160px chapter margin — the band's own s7/s6
   padding is the bottom spacing (Scout .sc-dark). Otherwise the band bottom gaps by 160+96. */
[data-project="dreamplay"] .cs-band .sc-block, [data-project="social-gameplay"] .cs-band .sc-block { margin-bottom: 0; }
.cs-band + .cs-divider { display: none; } /* band owns its own bottom spacing (margin-bottom) */
@media (max-width: 768px) { .cs-band { padding: var(--s6) 0; margin-bottom: 72px; } }
/* Keep the banner flush under the fixed 77px nav on mobile too — the generic mobile
   `.cs-hero { padding-top:140px }` (text-hero breathing room) otherwise leaves a white gap. */
@media (max-width: 768px) { .cs-hero.cs-hero--banner { padding-top: 77px; padding-bottom: 72px; } } /* mobile chapter gap, matches Scout/TSYS inline (was falling through to generic .cs-hero's 64px) */
.cs-hero-banner { width: 100vw; margin-left: calc(50% - 50vw); }
.cs-hero-banner img { width: 100%; height: auto; display: block; }
/* breathing room between the full-bleed banner and the intro label/title below it */
.cs-hero-banner + .container { padding-top: var(--s6); }
@media (max-width: 768px) { .cs-hero-banner + .container { padding-top: var(--s5); } }
@media (min-width: 769px) {
  /* Extends up behind the fixed nav (77px) on first load, bottom edge unchanged:
     margin-top pulls the banner up by the nav's height, height grows by the same
     amount, and object-position: bottom keeps the crop coming off the top only.
     Height is vw-based (not vh) so the visible/hidden ratio of the source image
     stays constant across every desktop width — a vh-driven crop hides a growing
     fraction of the image as screens get wider, which broke composing against it. */
  .cs-hero-banner { height: calc(32.3vw + 77px); margin-top: -77px; overflow: hidden; }
  .cs-hero-banner img { height: 100%; object-fit: cover; object-position: center bottom; }
}

/* Key-figures stat band — the hero's impact numbers on the ambient blue surface */
.cs-statband { position: relative; overflow: hidden; display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s4);
  margin-top: var(--s5); background: var(--accent-2-surface); border-radius: var(--r-band); padding: var(--s5) var(--s4); } /* meta-row → stats = 48px, matches TSYS's tsys-stats (was 64px) */
.cs-statband > [role="listitem"] { position: relative; z-index: 1; }
/* Parallax dot-field drifting behind the numbers — replicated exactly from TSYS .tsys-stats-deco.
   User instruction: keep padding and parallax on this band the same as TSYS. */
.cs-statband-deco { position: absolute; top: -60px; right: -40px; width: 420px; height: 300px;
  background-image: radial-gradient(circle, rgba(53,72,212,0.14) 1.6px, transparent 1.7px);
  background-size: 22px 22px;
  -webkit-mask-image: radial-gradient(circle at 70% 40%, #000 0%, transparent 68%);
          mask-image: radial-gradient(circle at 70% 40%, #000 0%, transparent 68%);
  pointer-events: none; z-index: 0; will-change: transform; }
/* Stat number = Scout/TSYS *-stat-num RENDERED spec (measured 1280+375): Bebas 400, text-1,
   clamp(48,5vw,72) — 48 at 375, 64 at 1280 (same on Scout and TSYS). Band padding --s5 --s4 (48/32)
   and the parallax deco match TSYS per the user's instruction (Scout's band uses 64/32 and no deco). */
/* 48-72px was the flagged "too big and too loud" stat number. A calibrated
   34-44px version already existed behind data-page="case" — applying it. */
.cs-statband-num { font-size: clamp(34px, 3.6vw, 44px); line-height: 1; color: var(--text-1); }
.cs-statband-label { font-size: 16px; color: var(--text-2); line-height: 1.5; margin-top: 10px; max-width: 220px; }
@media (max-width: 768px) { .cs-statband { grid-template-columns: 1fr 1fr; padding: var(--s5) var(--s3); }
  .cs-statband-deco { width: 260px; height: 200px; } }

/* ─── Related projects — horizontal scroll cards ─── */
.related {
  padding: 0 0 var(--s7);
  position: relative;
  z-index: 1;
}
/* Matches Home's canonical label spec (12px/600/0.1em) — was 17px/700 via the
   old Bebas-era caps group, reading as a section title instead of a label. */
.related .label { font-size: 12px; font-weight: 600; letter-spacing: 0.1em; color: var(--text-3); }
@media (max-width: 768px) {
  .related .label { font-size: 12px; }
}
.related .container::before {
  content: '';
  display: block;
  border-top: 1px solid var(--border-1);
  margin-bottom: var(--s7);
}
.related-scroll {
  /* full-bleed: extend to both viewport edges from within any container */
  width: 100vw;
  margin-left: calc(50% - 50vw);
  /* --inset = distance from viewport left to container content edge */
  --inset: max(var(--s4), calc((100vw - 1240px) / 2 + var(--s4)));
  padding-left: var(--inset);
  scroll-padding-left: var(--inset);
  scroll-padding-right: var(--inset);
  overflow-x: auto;
  overflow-y: visible;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  /* pan-x pan-y (was pan-x): pan-x alone blocks the vertical touch gesture over
     this full-width band, so scrolling the page needed an extra push here. Both
     axes let the vertical gesture pass to the page; horizontal drag still works
     (native pan-x + the JS drag handler). */
  touch-action: pan-x pan-y;
  overscroll-behavior-x: contain;
  /* proximity (not mandatory) — Chrome's mandatory snap fights trackpad/wheel
     momentum and feels janky; proximity scrolls freely and smoothly like Safari
     while still snapping to a card when you land near one */
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  /* vertical clearance for hover scale + shadow */
  padding-top: 160px;
  padding-bottom: 220px;
  margin-top: calc(var(--s3) - 160px);
  margin-bottom: -192px;
}
.related-scroll::-webkit-scrollbar { display: none; }
.related-rail {
  display: flex;
  gap: 36px; /* desktop card gap (was 52px, −16); mobile overrides to 24px */
  overflow: visible;
  width: max-content;
}
.related-rail::-webkit-scrollbar { display: none; }
.polaroid { scroll-snap-align: start; }
.polaroid:last-child {
  scroll-snap-align: end;
  margin-right: var(--inset);
}
.polaroid {
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  /* Desktop cards scaled to 0.8× (500 → 400px cap, calc term ×0.8). Mobile keeps its
     own full-width rule below. Padding, image aspect ratio, and internal gaps stay
     the same absolute values — only the card footprint shrinks. */
  width: min(400px, calc((50vw - var(--inset) / 2 - 26px) * 0.8));
  height: auto;
  /* Matched to Home's .project-card (--r-xl). Was 4px — the shadow already
     matched Home exactly, only the radius was off. */
  border-radius: var(--r-xl);
  overflow: hidden;
  border: 1px solid rgba(255,255,255,1);
  background: rgba(255,255,255,0.20);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  /* box-shadow (not filter: drop-shadow) — a `filter` on the element makes Chrome
     treat it as its own backdrop root, which silently disables backdrop-filter and
     leaves the card looking too transparent. box-shadow keeps the frosted glass
     rendering identically in Chrome and Safari. The rail's tall vertical padding
     (160/220px) keeps the shadow inside the scroll clip box. */
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.3), 0 18px 48px rgba(0,0,0,0.21);
  transition: box-shadow 0.5s cubic-bezier(0.25,0.46,0.45,0.94),
              transform 0.4s cubic-bezier(0.25,0.46,0.45,0.94);
  /* GPU-composite the hover lift so Chrome doesn't repaint every frame */
  will-change: transform;
  color: #111111;
  text-decoration: none;
  padding: 20px;
}
.polaroid:hover {
  transform: scale(1.03) translateY(-4px);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.3), 0 30px 68px rgba(0,0,0,0.24);
}
.polaroid:active {
  transform: scale(1.03) translateY(-4px);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.3), 0 30px 68px rgba(0,0,0,0.24);
}
.polaroid:focus-visible { outline: 3px solid var(--text-1); outline-offset: 3px; }
.polaroid-img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  /* Matched to Home's .card-img (--r-xl). Was 2px. */
  border-radius: var(--r-xl);
  background: #2a2a2e;
  margin-bottom: 24px;
  display: block;
  flex-shrink: 0;
}
.polaroid-cap {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex-shrink: 0;
}
.polaroid-name {
  font-family: var(--f-heading);
  font-size: 20px;
  font-weight: 600;
  color: #111111;
  line-height: 1.2;
  margin: 0;
}
.polaroid-desc {
  font-size: 16px;
  color: #444448;
  opacity: 1;
  line-height: 1.5;
  margin: 0;
}

/* About page */
.about-hero { padding-top: 120px; padding-bottom: var(--s7); }
.about-grid {
  display: grid;
  grid-template-columns: 1fr 1.3fr;
  gap: var(--s7);
  align-items: start;
}
.about-photo-wrap { position: relative; }
.about-photo {
  width: 100%;
  aspect-ratio: 3/4;
  object-fit: cover;
  border-radius: var(--r-xl);
  display: block;
  border: 1px solid var(--border-1);
}
.about-photo-placeholder {
  width: 100%;
  aspect-ratio: 3/4;
  background: var(--bg-3);
  border: 1px solid var(--border-1);
  border-radius: var(--r-xl);
  display: flex;
  align-items: center;
  justify-content: center;
}
.about-deco {
  position: absolute;
  bottom: -24px; right: -24px;
  width: 100px; height: 100px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(0,0,0,0.04) 0%, transparent 70%);
  z-index: -1;
}
.about-content { padding-top: var(--s2); }
.about-name {
  font-family: var(--f-heading);
  font-size: clamp(28px, 4vw, 44px);
  font-weight: 800;
  letter-spacing: -0.03em;
  color: var(--text-1);
  margin-bottom: var(--s1);
}
.about-role { font-size: 15px; color: var(--text-2); margin-bottom: var(--s4); }
.about-bio { font-size: 14px; color: var(--text-2); line-height: 1.75; margin-bottom: var(--s4); }
.about-section-title {
  font-family: var(--f-heading);
  font-size: 16px;
  font-weight: 700;
  color: var(--text-1);
  letter-spacing: -0.01em;
  margin-bottom: var(--s2);
}
.about-tags { display: flex; flex-wrap: wrap; gap: var(--s1); margin-bottom: var(--s4); }
.about-tag {
  font-size: 16px;
  font-weight: 500;             /* canonical chip weight (§9) */
  line-height: 1.2;
  color: var(--text-2);
  background: rgba(0,0,0,0.05);
  border: none;
  border-radius: 4px;
  padding: 8px 18px;
}
.about-cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 500;
  color: var(--text-1);
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-radius: var(--r-pill);
  padding: 12px 24px;
  transition: background 0.2s, transform 0.2s, border-color 0.2s;
}
.about-cta:hover { background: var(--surface-3); border-color: var(--border-3); transform: scale(1.02); }

/* ─── About v2 (editorial, akshaylodha-style) ─── */
.about2 {
  position: relative;
  z-index: 1;
  padding: 180px 0 var(--s7);
  min-height: 100svh;
  margin-top: -57px;
  background: var(--bg);
  overflow: hidden;
}
.about2::before {
  content: '';
  position: absolute;
  inset: 0;
  background: none;
  pointer-events: none;
  z-index: 0;
}
.about2::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, transparent 35%, var(--bg) 100%);
  pointer-events: none;
  z-index: 0;
}
.about2 .container { position: relative; z-index: 1; }
.about2-eyebrow {
  font-size: 14px; font-weight: 700; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--home-accent); margin-bottom: var(--s3);
}
/* Matched to Home's .hero-name — the same role (the person's name as the page's
   one display line). Was a Bebas-era slab: 84px/800/uppercase, plus a
   word-spacing hack that only existed to un-crowd Bebas caps. Schibsted at
   weight 400 needs none of that, and Home is not uppercase. */
.about2-title {
  font-family: var(--f-display);
  font-size: clamp(44px, 6.4vw, 84px);
  font-weight: 400; line-height: 1.0; letter-spacing: -0.042em;
  color: var(--text-1);
  margin-bottom: var(--s3);
}
.about2-top {
  display: grid;
  grid-template-columns: 340px 1fr;
  gap: var(--s6);
  align-items: end;
  margin-bottom: var(--s7);
}
.about2-intro { padding-bottom: 0; }
.about2-portrait {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 5;
  border-radius: var(--r-lg);
  overflow: hidden;
  border: 1px solid var(--border-1);
  background: var(--bg-3);
}
.about2-portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }
.about2-portrait::after {
  content: '';
  position: absolute; inset: 0;
  box-shadow: inset 0 0 45px rgba(0,0,0,0.23);
  pointer-events: none;
}

/* Two-image portrait scroller (headshot + sailor illustration) */
.about2-scroller {
  width: 100%;
  max-width: 360px;
  display: flex;
  gap: var(--s2);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  cursor: grab;
  border-radius: var(--r-lg);
}
.about2-scroller::-webkit-scrollbar { display: none; }
.about2-scroller.dragging { cursor: grabbing; }
.about2-slide {
  flex: 0 0 100%;
  scroll-snap-align: center;
  aspect-ratio: 4 / 5;
  border-radius: var(--r-lg);
  overflow: hidden;
  border: 1px solid var(--border-1);
  background: var(--bg-3);
  position: relative;
}
.about2-slide img { width: 100%; height: 100%; object-fit: cover; display: block; }
.about2-slide--art { background: var(--bg-2); }
/* sailor line-art → white lines on the page */
.about2-slide--art img {
  object-fit: contain;
  padding: 8%;
  filter: invert(1) contrast(1.05) brightness(1.5);
  mix-blend-mode: screen;
}
.about2-dots { display: flex; gap: 6px; justify-content: center; margin-top: var(--s2); }
.about2-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--border-3); }
.exp-date { font-size: 14px; font-weight: 500; color: var(--text-3); margin-top: 6px; } /* metadata: smaller + grey, steps down from role */
/* Body-scale lede for both about2-lead and about2-mission, matching Home's
   own [data-page="home"] .hero-role exactly (19px/400/1.6 desktop, 17px/1.62
   mobile) rather than the un-overridden base .hero-role clamp — about.html
   carries no [data-page] attribute, so it never picked up Home's own
   page-specific size and rendered a step smaller than the "Head of Design…"
   line it's meant to match. */
.about2-lead, .about2-mission {
  font-family: var(--f-body);
  font-size: 19px;
  font-weight: 400; line-height: 1.6; letter-spacing: normal;
  color: var(--text-2);
}
@media (max-width: 768px) {
  .about2-lead, .about2-mission { font-size: 17px; line-height: 1.62; }
}
.about2-lead { max-width: none; }
.about2-lead .accent { color: var(--accent-2); }
.accent { color: var(--accent-2); } /* the one loud accent moment (§2) — was dead ink via --home-accent */

/* stat row */
.about2-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s4);
  padding: var(--s5) 0;
  border-top: 1px solid var(--border-1);
  border-bottom: 1px solid var(--border-1);
  margin-bottom: var(--s6);
}
/* Stat labels: grey small-caps (§2/§3.1) — separated in tone from the ink value */
/* Canonical label spec (12/600/0.1em), same as every other label on the site.
   Was 14/700/0.08em, a Bebas-era caps value. */
.about2-stat-k { font-family: var(--label-caps-family); font-size: 12px; font-weight: 600; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 8px; }
.about2-stat-v { font-family: var(--f-heading); font-size: 18px; font-weight: 500; color: var(--text-1); letter-spacing: -0.02em; }

/* mission — a multi-sentence reading paragraph; line-height 1.5 for accessibility
   (WCAG 1.4.8), esp. at the 18px mobile size where it reads as prose. */
.about2-mission {
  /* type from the shared .about2-lead, .about2-mission rule above */
  max-width: 900px; margin-bottom: var(--s7);
}
/* The mission runs as two paragraphs: what I do, then how I lead. Consecutive
   ones sit a paragraph apart (24/32px), not a section apart, so they read as
   one thought in two beats; the block-level --s7 gap only applies after the
   last one. Two earlier attempts at this both stacked a second gap on top of
   .about2-mission's own margin-bottom instead of replacing it (once via a
   wrong calc, once via padding-top added alongside the existing margin) —
   the actual fix is to zero the FIRST paragraph's margin-bottom and let the
   normal flow gap do the work once. */
.about2-mission:has(+ .about2-mission) { margin-bottom: var(--s3); }
@media (min-width: 901px) {
  .about2-mission:has(+ .about2-mission) { margin-bottom: var(--s4); }
}
.about2-mission .accent { color: var(--accent-2); font-weight: 600; } /* the one loud accent moment (§2) */

/* experience */
.about2-h { font-family: var(--label-caps-family); font-size: 12px; font-weight: 600; color: var(--text-3); letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: var(--s2); }
.about2-exp { display: flex; flex-direction: column; }
.exp-row {
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: var(--s7);
  padding: var(--s4) 0;
  border-top: 1px solid var(--border-1);
  align-items: start;
}
.exp-row:last-child { border-bottom: 1px solid var(--border-1); }
/* Logo as-is, no wrapper chip/background — just a fixed height so the row of six
   wildly different source assets (transparent, flat white, brand-colour squares)
   lines up on one baseline; width follows each logo's own aspect ratio. */
.exp-logo { height: 40px; margin-bottom: var(--s2); display: block; }
.exp-logo img { height: 100%; width: auto; display: block; }
/* Clear 3-step hierarchy: company (ink bold) → role (ink medium) → date (grey meta) */
/* 600 is the site's ceiling weight (Home uses nothing heavier). Was 700. */
.exp-co { font-family: var(--f-heading); font-size: 20px; font-weight: 600; color: var(--text-1); letter-spacing: -0.015em; }
.exp-role { font-size: 16px; font-weight: 500; color: var(--text-1); margin-top: 6px; }
/* One tier below the lead/mission group (clamp 16-18px): exp-desc was 18px
   desktop, matching or exceeding them, so all four read as one level instead
   of two. Fixed at 16px, a full step down the site's 15/16/17/18px scale. */
.exp-desc { font-size: 16px; font-weight: 400; color: var(--text-2); line-height: 1.65; letter-spacing: 0.005em; }
/* Desktop only: the left column starts with the 40px logo + its s2 margin
   before the company name, so the row's own top edge (where exp-desc sits,
   grid-aligned to start) is 56px above exp-co — the two text blocks read as
   misaligned even though the columns themselves are top-aligned. Push
   exp-desc down to meet the company name, not the logo above it. Scoped to
   the 2-column desktop grid only; mobile stacks to 1fr and this would just
   add unwanted gap above the already-flowing description. */
@media (min-width: 769px) {
  .exp-desc { margin-top: calc(40px + var(--s2)); }
}
.about2-block { margin-top: var(--s7); }

/* ══ Shared label/title/tag line-heights (§1.1 / §3.1) — these classes were
   inheriting body's 1.5–1.65 and inflating their boxes on the About & Home pages
   (which have no inline styles). Placed after the base declarations so it wins. ══ */
.label, .hero-scroll-label, .about-tag,
.about2-stat-k, .about2-stat-v, .about2-h,
.exp-co, .exp-role, .exp-date { line-height: 1.25; }

@media (max-width: 768px) {
  .about2-top { grid-template-columns: 1fr; gap: var(--s4); margin-bottom: var(--s4); }
  .about2-portrait { max-width: 180px; }
  .about2-intro { padding-top: 0; }
  /* lead + mission share the clamp (20px on mobile) — no per-element size override */
  .about2-stats { grid-template-columns: 1fr; gap: 24px; text-align: left; margin-bottom: 0; padding: var(--s5) 0; } /* mobile-only: +8px between stat blocks (was var(--s2)=16px) */
  /* The paired second paragraph (.about2-mission + .about2-mission) already
     gets its gap from the :has() rule above (24px, via the FIRST paragraph's
     margin-bottom) — it must not also carry its own margin-top, or the two
     collapse to max(24, 48) = 48px. Third time this exact stacking pattern
     (T7) has hit this component; targeted directly this time, not inferred
     via :not(). */
  .about2-mission { margin-top: var(--s5); margin-bottom: var(--s5); }
  .about2-mission + .about2-mission { margin-top: 0; }
  .about2-block { margin-top: var(--s5); }
  /* (chips stay at the canonical 16px on mobile too — §9; the old 14px reduction removed) */
  .about2-stat-k { margin-bottom: 6px; } /* label-to-value gap: unchanged, back to original */
  .exp-row { grid-template-columns: 1fr; gap: 22px; }
  .exp-role { margin-top: 4px; }
  .exp-date { margin-top: 4px; }
}

/* ─── Reduced Motion ─── */
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  .marquee-track { animation: none; }
  .eyebrow-pulse, .hero-scroll-line { animation: none; }
  .hero-photo { animation: none; }
}

/* ─── Responsive ─── */
@media (max-width: 900px) {
  .nav-links { display: none; }
  .nav-hamburger { display: flex; }
  .intro-grid { grid-template-columns: 1fr; gap: var(--s5); }
  .intro-heading .accent { display: block; }
  .intro-body { font-size: 16px; }
  .cs-body-text { font-size: 16px; }
  .cs-feature-list li { font-size: 16px; }
  .exp-desc { font-size: 15px; }
  .about-grid { grid-template-columns: 1fr; }
  .cs-highlight-grid { grid-template-columns: 1fr 1fr; }
  /* hero photo moves above name on mobile */
  .hero-content { grid-template-columns: 1fr; gap: var(--s3); } /* photo->name gap tightened 48->24px so the scroll nudge fits on-screen */
  .hero-art { position: static; opacity: 1; justify-content: flex-start; order: -1; }
  .hero-photo { max-width: 200px; aspect-ratio: 1/1; border-radius: 50%; }
  .hero-name { margin-bottom: var(--s2); } /* name->role gap tightened 32->16px, same reason */
}

@media (max-width: 768px) {
  .container { padding: 0 20px; }
  .nav-container { padding: 0 20px; }
  .marquee-track span { font-size: 14px; }
  .hero-content { padding-left: 20px; padding-right: 20px; padding-top: 160px; padding-bottom: 130px; } /* tightened 190→130 to leave real breathing room before the fixed scroll indicator, which was only 6px below "About me" */
  .hero-role { margin-bottom: 20px; } /* role→actions gap tightened 28→20px, same reason */
  .cta-card { padding-left: 20px; padding-right: 20px; }
  /* Sleeker CTA buttons on mobile only — less chunky padding/height */
  .btn { padding: 10px 20px; }
  .btn-lg { padding: 11px 24px; font-size: 15px; }
  /* unified section rhythm on mobile: 64px top & bottom across all pages */
  .section { padding: var(--s6) 0; }
  .cs-body { padding: var(--s6) 0 0; }
  .cs-section { margin-bottom: 72px; }
  /* Scoped to .cs-body only (matches the desktop-scoped rule) — this was
     unscoped and also zeroed the ONE divider that's supposed to keep its
     margin on mobile (the hero→chapter-1 hairline), leaving it glued flush
     against the NDA line above it with no visible gap. */
  .cs-body .cs-divider { margin: 0; }
  .related { padding: 0; }
  /* On mobile .cs-body has no bottom padding, so this section-break hairline sat
     flush against the last chapter (0px above, 56px below) — the two sections read
     as overlapping. Make it symmetric so the divider gets equal air on both sides. */
  .related .container::before { margin-top: 56px; margin-bottom: 56px; }
  .cs-hero { padding-top: 140px; padding-bottom: var(--s6); }
  .about2 { padding-top: 160px; padding-bottom: var(--s6); min-height: unset; } /* matches Home's mobile top padding (was 140, leaving only 6px below the nav) */
  /* About-only: .cta-section::before's margin-bottom (var(--s7)=128px) was never given a
     mobile reduction, and stacked on top of .about2's own 64px bottom padding this left a
     ~190px dead gap before the CTA card. Scoped to `.about2 + .cta-section` so Home's cta-section
     (which follows different markup) is untouched. */
  .about2 + .cta-section::before { margin-bottom: var(--s5); }
  .about2-stat-k { margin-bottom: 6px; } /* label-to-value gap: unchanged, back to original */
  .section-hdr { padding: 0 20px; }
  .projects-track { padding: 8px 20px 0; }
  .projects.section { padding-bottom: 0; }
  /* mobile — normal flow, not the absolute two-pane split desktop uses.
     Image is a fixed 3:2 box (not a % of card height) so nothing in it gets
     cropped; text below is un-clamped so nothing gets truncated — the card
     grows to fit real content instead of hiding the excess. Arrow anchors to
     the card's own bottom-right, which now moves with the card's real
     height instead of a fixed percentage split. */
  .project-card {
    position: relative;
    top: auto;
    height: auto;
    min-height: 0;
    margin-bottom: 40px;
  }
  .projects-track .project-card:last-child { margin-bottom: 0; }
  /* card-img/card-body/card-arrow are all children of .card-link, not of
     .project-card directly. .card-link is the base absolute/inset:0 overlay
     (sized by .project-card, itself height:auto) — with both ends of that
     chain depending on the other, the card collapses to ~0px. Taking
     .card-link out of absolute positioning on mobile breaks the loop: its
     children drive its height in normal flow, and .project-card (whose only
     child it is) inherits that real height. */
  .card-link {
    position: relative;
    inset: auto;
    display: block;
    padding-bottom: 56px; /* reserves room for the arrow below the text */
  }
  .card-img {
    position: relative;
    top: auto; left: auto; right: auto; bottom: auto;
    margin: 20px 20px 0;
    width: auto;
    aspect-ratio: 3 / 2;
    border-radius: var(--r-xl);
  }
  .card-img-inner { position: absolute; inset: 0; padding-bottom: 0; }
  .card-body {
    position: relative;
    top: auto; right: auto; left: auto; bottom: auto;
    width: auto;
    height: auto;
    margin: 20px 20px 0;
    padding: 0;
    border-radius: 0;
  }
  .card-desc { font-size: 16px; -webkit-line-clamp: unset; max-height: none; padding-right: 0; }
  .card-title { font-size: clamp(20px, 6vw, 28px); }
  .card-arrow { width: 36px; height: 36px; right: 20px; bottom: 20px; }
  .card-cta { left: 20px; bottom: 20px; font-size: 12px; padding: 8px 14px; }
  .cta-card { padding: 0 var(--s2); }
  .footer-top { flex-wrap: wrap; gap: var(--s2); }
  .footer-logo { margin-right: 0; }
  .footer-bottom { flex-direction: column; gap: 8px; text-align: center; padding-top: 28px; font-size: 14px; }
  .hero-name { font-size: clamp(60px, 16vw, 100px); }
  /* No scroll indicator on mobile: neither the SCROLL label nor the animated
     line. On a phone the gesture needs no prompting, and the indicator had to
     be position:fixed here (the hero is taller than the viewport, so anchoring
     it to the hero's own bottom edge put it below the fold), which left it
     floating over the content until the first scroll. Removing it is simpler
     than the workaround it required. Desktop keeps it, animation and all. */
  .hero-scroll { display: none; }
  .cs-image--phone, .cs-video--phone {
    width: 60%;
    max-width: 60%;
    max-height: none;
    display: block;
    margin-left: auto !important;
    margin-right: auto !important;
    border-width: 6px;
    border-radius: 38px;
    /* shadow spread + darkness reduced 20% from desktop for a lighter mobile feel */
    box-shadow:
      0 0 0 2px #cdcbd6,
      0 0 0 3.5px #9b99a6,
      0 16px 36px rgba(0,0,0,0.304);
  }
  .cs-nav-title { max-width: 50vw; }
  .cs-meta-row {
    grid-template-columns: 1fr 1fr;
    gap: var(--s4) var(--s4);
  }
  .cs-meta-item--full {
    grid-column: 1 / -1;
    margin-top: var(--s3);
  }
  .cs-highlight-grid { grid-template-columns: 1fr 1fr; }

  /* Related: mobile inset = 20px (container padding on mobile) */
  .related .container > .label { margin-bottom: 4px; }
  .related-scroll {
    --inset: 20px;
    padding-top: 160px;
    /* padding-bottom reserves card-shadow room; margin-bottom pulls the footer up
       into it. Sized so the footer defines the page bottom (no dead reserved space)
       while leaving a 56px gap between the cards and the footer separator. */
    padding-bottom: 156px;
    margin-top: calc(var(--s3) - 160px);
    margin-bottom: -100px;
  }
  .related-rail { gap: 24px; }
  .polaroid {
    /* card fills viewport minus left inset and right peek */
    width: calc(100vw - var(--inset) - 56px);
    max-width: none;
    height: auto;
    padding: 20px;
    flex-shrink: 0;
    /* box-shadow (not filter) so backdrop-filter keeps rendering the frosted glass in Chrome */
    /* shadow spread + darkness reduced 20% from desktop for a lighter mobile feel */
    box-shadow: inset 0 0 0 1px rgba(255,255,255,0.3), 0 14px 38px rgba(0,0,0,0.168);
  }
  .polaroid-img { margin-bottom: 20px; }
  .polaroid-name { font-size: 19px; }
  .polaroid-desc { font-size: 14px; }
}

@media (max-width: 480px) {
  .cs-highlight-grid { grid-template-columns: 1fr; }
  .hero-actions { flex-direction: column; }
  .hero-actions .btn { width: 100%; justify-content: center; }
  /* .cs-hero-title's mobile floor now lives in the single unguarded clamp()
     rule further down (was dead code here — that rule always won via source
     order, so this override was silently unreachable). */
}

/* ═══════════════════════════════════════════
   WHITE THEME TYPE SYSTEM
   Bebas Neue for titles & captions · ink-only
   hierarchy through weight, not colour
   ═══════════════════════════════════════════ */
:root {
  --f-display: 'Schibsted Grotesk', system-ui, sans-serif;
  --home-accent: #111111;   /* labels & eyebrows are ink, not blue */
  --accent:      #111111;
  --accent-h:    #333338;
}
::selection { background: rgba(17,17,17,0.14); }

/* Titles — was Bebas Neue (condensed caps face, needed +0.045em tracking at
   weight 400 to carry any presence). Now Schibsted Grotesk everywhere, a
   normal-width grotesque: positive tracking on mixed-case text at this size
   reads as loose, amateur kerning, not intentional style (design-review
   skill's own rule: "No letterspacing on lowercase text"). Retuned using
   Home's own already-approved Schibsted calibration as the reference —
   hero-scale stays weight 400 (size carries presence) with tight negative
   tracking; section/card-scale steps up to weight 600 for definition, since
   400 alone doesn't read as a headline in a normal-width face the way it did
   in a condensed one. */
.hero-name, .cs-hero-title {
  font-family: var(--f-display);
  font-weight: 400;
  letter-spacing: -0.03em;
}
.section-title, .cta-heading, .cs-section-title,
.card-title, .cs-nav-title {
  font-family: var(--f-display);
  font-weight: 600;
  letter-spacing: -0.02em;
}
/* .ch-sub-title is NOT a once-per-page headline like the classes above — it's a
   sub-section title that repeats 8-19x on a single case-study page (measured:
   tsys 8, dreamplay/scout-ai 14, social-gameplay 19). Home's weight-600
   calibration for section/card titles was tuned for elements that appear once
   or a handful of times; applying that same weight to a high-frequency
   recurring element makes the whole page read as uniformly bold/loud when
   scrolled through, even though any single instance matches Home's per-
   instance values (owner feedback: "on dreamplay everything is too loud").
   Stepping down one weight (500) keeps it clearly a heading — heavier than
   500 body text isn't used up here — while letting the true chapter titles
   (.cs-section-title, still 600) carry the actual emphasis. */
.ch-sub-title {
  font-family: var(--f-display);
  font-weight: 500;
  letter-spacing: -0.02em;
}
.polaroid-name { font-family: var(--f-display); font-weight: 600; letter-spacing: -0.015em; }
.hero-name      { font-size: clamp(84px, 14vw, 176px); }
/* min lowered 64→36: this rule is unguarded (no media query) so it sat after,
   and clobbered, the `@media (max-width: 480px) { .cs-hero-title { clamp(36px,
   12vw,64px) } }` override below — the case-study hero title (SCOUT / TSYS
   Prime) was stuck at a 64px floor on every phone. */
/* Was clamp(36px, 10vw, 140px) — 140px is nearly double Home's own hero-name
   max (94px). Same recurring bug: a calibrated 44-72px version already
   existed for this exact rule, gated behind data-page="case", an attribute
   these pages don't carry. Applied directly, matching the same fix pattern
   as chapter titles/sub-titles/stat numbers above. */
.cs-hero-title  { font-size: clamp(44px, 6.4vw, 72px); font-weight: 500; letter-spacing: -0.04em; line-height: 1.02; }
.cs-section-title { font-size: clamp(30px, 3.8vw, 46px); }
/* 36-58px was reading too big/loud against Home's 28-38px section-title scale
   (direct feedback). A calibrated, smaller version of this exact rule already
   existed at 26-36px but was gated behind data-page="case", an attribute none
   of these pages carry — applying that proven value here instead of guessing
   a new one. */
/* Floor is 30px, not 26px, and it must stay >= the <=768px value below (30px).
   With a 26px floor the title got SMALLER as the viewport grew: 30px at 768,
   then 2.9vw = 26.1px at 900, then back to 36px at 1440. A heading must never
   shrink on a wider screen. 2.9vw only overtakes 30px at ~1035px, so the floor
   carries the whole 769-1035 band. */
.ch-head .cs-section-title { font-size: clamp(30px, 2.9vw, 36px); line-height: 1.16; }
/* Same fix, same reasoning: a calibrated 21-26px version already existed
   behind data-page="case". Size reduced here; weight stays 500 (set in the
   separate rule above) since this element also repeats 8-19x per page. */
.ch-sub-title   { font-size: clamp(21px, 2.1vw, 26px); line-height: 1.25; }
.section-title  { font-size: clamp(44px, 6vw, 72px); }
.cta-heading    { font-size: clamp(44px, 6vw, 72px); }
.card-title     { font-size: clamp(30px, 3vw, 42px); }
.polaroid-name  { font-size: 20px; }
/* Display titles were inheriting body line-height — tighten (§3). polaroid-name
   (Home project cards) deliberately excluded. */
.hero-name, .section-title, .cta-heading,
.cs-hero-title, .cs-section-title, .cs-nav-title, .card-title { line-height: 1.05; }
@media (min-width: 769px) {
  .hero-name { line-height: 0.9; } /* desktop-only: tighter Bebas leading on the home hero name */
}

/* Small-caps labels & titles — Plus Jakarta Sans (§3: moved OFF Bebas), semibold,
   tight tracking. Screen/panel titles + numbers override size/case where needed. */
.label, .cs-hero-label, .cs-section-label, .cs-meta-label,
.ch-eyebrow, .ch-sub-num,
.tsys-screen-label, .sc-screen-label,
.tsys-techcol-label, .sc-panel-title, .sc-journey-label,
.tsys-step-num, .sc-item-num, .tsys-standout-title {
  font-family: var(--label-caps-family);
  font-weight: 700;
  letter-spacing: 0.08em;
  font-size: 14px;
  line-height: 1.25;
}

/* Key figures & ghost numerals — same Bebas-tuned tracking as the headline
   group (+0.035em, up to +0.07em on .cs-statband-num specifically — wider
   still). A case-scoped override already existed with the correct Schibsted
   calibration (weight 600 / -0.03em) but only ever applied to pages carrying
   data-page="case", which none of these restored pages have. Fixing the
   base rule directly with that same, already-proven calibration instead of
   relying on a scope that doesn't reach them. */
.tsys-stat-num, .sc-stat-num, .sc-metric-num, .ch-num, .cs-statband-num {
  font-family: var(--f-display);
  font-weight: 600;
  letter-spacing: -0.03em;
}
.ch-num { font-size: clamp(64px, 8vw, 104px); }

/* Chapter head — grid layout + hairline separator (the chapter boundary, §6).
   Scout/TSYS carry an identical copy inline; defined globally here so a page
   with no inline block (DreamPlay) gets the same layout, separator and sizes.
   Scoped under .ch-head so it wins over the generic .ch-num / .cs-section-title. */
/* SCOPED to DreamPlay only. Scout/TSYS (and any page with its own inline ch-head) must render
   from their own CSS — an unscoped global here regressed TSYS's eyebrow (Rule 4 violation). */
[data-project="dreamplay"] .ch-head, [data-project="social-gameplay"] .ch-head {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px var(--s3);
  align-items: start;
  padding-top: var(--s4);
  border-top: 1px solid var(--border-2);
  margin-bottom: var(--s5);
}
[data-project] .ch-head .ch-num {
  grid-row: 1 / 3;
  font-size: clamp(56px, 6vw, 70px);
  font-weight: 400;
  line-height: 0.85;
  letter-spacing: -0.03em;
  color: var(--accent-2);
  user-select: none;
}
[data-project] .ch-head .ch-eyebrow { margin: 4px 0; color: var(--text-3); text-transform: uppercase; }
[data-project] .ch-head .cs-section-title {
  font-size: clamp(30px, 2.9vw, 36px); /* 30px floor matches the <=768px value so the title never shrinks as the viewport grows — see the note on .ch-head .cs-section-title above. Was 28-40px before that; the weight/tracking fix in the comment below never included size. */
  /* Was weight 800 / letter-spacing +0.045em — a leftover Bebas Neue-era value
     the base-class Schibsted retune (see .cs-section-title above) never
     reached, because this project-scoped rule (0,3,0) sits later in the file
     and wins on both specificity and source order. Positive tracking on
     mixed-case Schibsted at this size is the exact "loose, amateur kerning"
     problem the retune fixed everywhere else; weight 800 on a chapter title
     that repeats once per chapter (9x on dreamplay, 8x on social-gameplay)
     compounded it into visible loudness across the whole page. Matched to
     Home's own chapter-title calibration (600 / -0.02em), same as the
     already-correct [data-project="dreamplay"] .dp-head .cs-section-title
     rule further down this file. */
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.12;
  max-width: 900px;
  margin-bottom: 0;
}
@media (max-width: 768px) {
  [data-project="dreamplay"] .ch-head, [data-project="social-gameplay"] .ch-head { grid-template-columns: 1fr; gap: 2px; }
  [data-project] .ch-head .ch-num { grid-row: auto; font-size: 56px; margin-bottom: 10px; }
}

/* ═══════════════════════════════════════════
   DESIGN SYSTEM TOKENS — additive, not yet wired up
   See DESIGN_SYSTEM.md for full documentation.
   Nothing below is referenced by existing selectors yet;
   the follow-up cleanup pass migrates real usage onto these.
   ═══════════════════════════════════════════ */
:root {
  /* Accent — Electric Indigo. Sparing use only: highlights, small
     details, single-word emphasis. Never a dominant fill. Replaces
     the dead --accent/--home-accent tokens above once migrated. */
  --accent-2:         #3548D4;   /* ~6:1 on white — safe for text */
  --accent-2-tint:    #F2F5FF;   /* light blue band (pull-quote highlight) */
  --accent-2-surface: #F0F3FF;   /* light blue container fill (tiles, panels) */
  --accent-2-border:  #D5DBF7;   /* blue container / chip border */
  --accent-2-line:    #C3CAF1;   /* blue separator / hairline */
  --img-frame:        #605D64;   /* neutral grey image-frame border (2px) */

  /* Small-caps label treatment — replaces Bebas Neue for anything
     12–17px (eyebrows, labels, captions, stat labels), where an
     all-caps condensed display face hurts legibility at small sizes. */
  --label-caps-family:  var(--f-heading);   /* Plus Jakarta Sans */
  --label-caps-weight:  600;
  --label-caps-tracking: 0.08em;

  /* Typographic size scale — names the sizes already in use across
     the site today (see DESIGN_SYSTEM.md §3). Nothing visually
     changes yet; this is the reference scale future headings/body/
     captions should pull from instead of one-off clamp() values. */
  --fs-display-1: clamp(84px, 14vw, 176px);  /* home hero name */
  --fs-display-2: clamp(64px, 10vw, 140px);  /* case-study hero title */
  --fs-h1:        clamp(44px, 6vw, 72px);    /* section/cta heading */
  --fs-h2:        clamp(30px, 3.8vw, 46px);  /* cs-section-title */
  --fs-h2-chapter: clamp(26px, 2.9vw, 36px); /* chapter head title — unused token, corrected for reference only */
  --fs-h3:        clamp(21px, 2.1vw, 26px);  /* ch-sub-title — unused token, corrected for reference only */
  --fs-h4:        clamp(30px, 3vw, 42px);    /* project card title */
  --fs-body-lg:    20px;   /* ch-lede / intro paragraphs */
  --fs-body:       18px;   /* standard case-study body */
  --fs-body-sm:    16px;   /* dense bullet lists, mobile body */
  --fs-caption:    14px;   /* small-caps labels/eyebrows at current size */
  --fs-label:      17px;   /* current Bebas-Neue label size, pre-migration */
}

/* ═══════════════════════════════════════════════════════════════════════════
   SOCIAL GAMEPLAY components (registry §1.3). Built on the shared tokens; no
   page-local values. Each is documented where it deviates from an existing role.
   ═══════════════════════════════════════════════════════════════════════════ */



/* ── Impact block (.cs-impact) ──
   Outcomes stated after a feature area. Tinted, labelled and bulleted so results
   are scannable and never read as another body paragraph. */
.cs-impact { border: 1px solid var(--accent-2-border); border-radius: var(--r-md); background: var(--accent-2-surface);
  padding: var(--s3); margin-top: var(--s4); }
/* Inside the accent-tint band the tinted fill would sit tint-on-tint, so it flips to
   white — the same tint-band canon (§2) already applied to .cs-card and the galleries. */
.cs-band .cs-impact { background: #ffffff; }
.cs-impact-label { display: flex; align-items: center; gap: 8px; font-family: var(--label-caps-family); font-weight: 700;
  font-size: 13px; line-height: 1.2; letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent-2); margin-bottom: 10px; }
.cs-impact-label svg { width: 15px; height: 15px; }
.cs-impact ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.cs-impact li { position: relative; padding-left: 18px; font-size: 16px; line-height: 1.5; color: var(--text-2); }
.cs-impact li::before { content: ""; position: absolute; left: 0; top: 0.62em; width: 5px; height: 5px;
  border-radius: 50%; background: var(--accent-2); }
.cs-impact li strong { color: var(--text-1); font-weight: 600; }
@media (max-width: 768px) { .cs-impact li { font-size: 15px; } }

/* Learnings variant (.cs-impact--learn). Same geometry, type and bullet rhythm as
   .cs-impact so the two read as a matched pair at the end of a chapter: one states
   what worked, one states what we got wrong and changed. It differentiates on
   neutral tone rather than a new hue — the palette has exactly one accent, and
   spending it on failures would compete with the outcome blocks it sits beside. */
.cs-impact--learn {
  --learn-ink:     #9A5B12;  /* 5.13:1 on the tint below — AA for the 13px label */
  --learn-surface: #FFF7ED;
  --learn-border:  #F2DFC4;
  background: var(--learn-surface);
  border-color: var(--learn-border);
}
.cs-band .cs-impact--learn { background: #ffffff; }
.cs-impact--learn .cs-impact-label { color: var(--learn-ink); }
.cs-impact--learn li::before { background: var(--learn-ink); }

/* Inline note (.cs-note). A single paragraph that needs to read as a status
   change rather than more body copy — a feature being sunset, a constraint
   arriving. Sits inside .cs-body-text and inherits its measure.

   .cs-note--neg is the "this went away" tone. It borrows the .cs-impact--learn
   construction deliberately: tinted surface plus a full 1px border, never a
   single-edge rule, which is a banned pattern site-wide. Muted rather than
   alarming — a deprecation is a fact, not an error state. */
.cs-note {
  margin-top: var(--s3);
  padding: 14px 16px;
  border: 1px solid transparent;
  border-radius: var(--r-card, 10px);
}
/* Matches .cs-impact li (16/1.5), not the 20px lede it usually sits inside —
   both are the same kind of thing: a short callout, not body prose. Needs
   [data-project] .cs-body-text.ch-lede p's own specificity (0,3,1) to actually
   win; `.cs-note` alone (0,1,0) lost to it silently even though it comes
   later in the file — the note rendered at 20px until this was raised. */
[data-project] .cs-body-text.ch-lede p.cs-note,
[data-project] p.cs-note { font-size: 16px; line-height: 1.5; }
@media (max-width: 768px) {
  [data-project] .cs-body-text.ch-lede p.cs-note,
  [data-project] p.cs-note { font-size: 15px; }
}
.cs-note--neg {
  --neg-ink:     #A03027;  /* 6.59:1 on the tint below — AA and AAA for body text */
  --neg-surface: #FDF4F3;
  --neg-border:  #F0DCDA;
  background: var(--neg-surface);
  border-color: var(--neg-border);
}
.cs-note--neg strong { color: var(--neg-ink); }
.cs-band .cs-note--neg { background: #ffffff; }

/* Media↔caption alignment law (§5). The frame and its caption share one left edge —
   the cell's. Enforced globally on .cs-phone-cell / .cs-video-cell above; this last
   rule is needed because js/main.js wraps each <video> in .video-shell and COPIES the
   video's computed align-self onto the shell, which would otherwise re-center it. */
.cs-video-cell > .video-shell, .cs-phone-cell > .video-shell { align-self: flex-start !important; }

/* Media in a row is TOP-aligned: every frame starts at the row's top edge and its
   caption follows a uniform 24px below it. Do NOT bottom-align or subgrid these —
   both were tried and both make the media→title gap vary from cell to cell, which
   reads as broken spacing. Every frame in every row is the same 280px at the same
   32px gap, whether the row holds 2, 3 or 4 — including a flat image used as a
   cell, which takes the frame width rather than a wider column of its own. */
[data-project="social-gameplay"] .cs-flat--narrow img { max-width: 560px; }
/* Hero → chapter 01 gap: the ONE chapter gap (160/72), per §6. Without this the
   plain .cs-hero's own padding-bottom (s8=128) STACKS on .cs-body's 144px top
   padding for 272px of dead space. TSYS/Scout fix this inline; this is the same
   fix for the pages that carry no inline block. */
[data-project="social-gameplay"] .cs-hero { padding-bottom: 160px; }
[data-project="social-gameplay"] .cs-hero + .cs-body { padding-top: 0; }
@media (max-width: 768px) {
  [data-project="social-gameplay"] .cs-hero { padding-bottom: 72px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   RULED CARD INDEX — canonical card treatment for case studies
   ───────────────────────────────────────────────────────────────────────────
   Replaces the boxed card (surface-1 fill, 18px radius, 32px padding) that was
   repeated across nine different components and made every page read the same.
   Cards are now a ruled index: hairline structure, no fill, no radius, an icon
   in ink rather than a tinted chip, and an accent rule that wipes across the
   top edge on hover.

   Separator sits on the TOP of each card, never the bottom, so the line between
   two rows belongs to the row below. That means the last row ends without a
   trailing rule at any card count; a bottom-border approach would need the
   count hardcoded per grid to know which cards sit in the final row.

   Applies to the three equivalent components across the case studies:
     .cs-card      DreamPlay, Social Gameplay
     .sc-item      Scout
     .tsys-step    TSYS
   ═══════════════════════════════════════════════════════════════════════════ */
[data-project] .cs-cards {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: 24px;
  row-gap: 0;
  margin-top: 40px;
}
[data-project] .cs-cards > .cs-card,
[data-project] .sc-grid > .sc-item,
[data-project] .tsys-process > .tsys-step,
[data-project] .cs-principle-grid > .cs-principle {
  background: none;
  border: none;
  border-radius: 0;
  box-shadow: none;
  border-top: 1px solid var(--border-1);
  padding: 24px 20px 28px 0;
  position: relative;
}
[data-project] .cs-cards > .cs-card { grid-column: span 4; }
[data-project] .cs-cards--3 > .cs-card { grid-column: span 4; }

/* First row carries the heavier rule so the component opens on a definite edge */
[data-project] .cs-cards > .cs-card:nth-child(-n+3),
[data-project] .sc-grid > .sc-item:nth-child(-n+3),
[data-project] .tsys-process > .tsys-step:nth-child(-n+3),
[data-project] .cs-principle-grid > .cs-principle:nth-child(-n+3) { border-top-color: var(--border-2); }

/* The only motion: an accent rule revealing the grid structure already present */
[data-project] .cs-cards > .cs-card::before,
[data-project] .sc-grid > .sc-item::before,
[data-project] .tsys-process > .tsys-step::before,
[data-project] .cs-principle-grid > .cs-principle::before {
  content: ""; position: absolute; left: 0; top: 0;
  width: 0; height: 2px; background: var(--accent-2);
  transition: width 0.35s var(--ease-out);
}
[data-project] .cs-cards > .cs-card:hover::before,
[data-project] .cs-cards > .cs-card:focus-within::before,
[data-project] .sc-grid > .sc-item:hover::before,
[data-project] .tsys-process > .tsys-step:hover::before,
[data-project] .cs-principle-grid > .cs-principle:hover::before { width: 100%; }
@media (prefers-reduced-motion: reduce) {
  [data-project] .cs-cards > .cs-card::before,
  [data-project] .sc-grid > .sc-item::before,
  [data-project] .tsys-process > .tsys-step::before,
  [data-project] .cs-principle-grid > .cs-principle::before { transition: none; }
}

/* Icon: ink line-work, no chip */
/* Icon keeps the accent blue it carried before the ruled pass. In ink it read
   as another piece of body text; the accent is what makes it register as an
   icon and ties the card back to the project's colour. */
[data-project] .cs-card-icon,
[data-project] .sc-item-icon,
[data-project] .tsys-chip {
  width: 28px; height: 28px;
  background: none; border: none; border-radius: 0;
  color: var(--accent-2); margin-bottom: 20px; display: block;
}
[data-project] .cs-card-icon svg,
[data-project] .sc-item-icon svg,
[data-project] .tsys-chip svg { width: 28px; height: 28px; stroke-width: 1.25; }

/* Number sits in the TOP-RIGHT corner, opposite the icon. Placing it inline
   between the icon and the title interrupted the read (icon, number, title,
   description) and pushed the title down; in the corner it indexes the card
   without entering the reading order. One placement for all three components. */
[data-project] .cs-card-num,
[data-project] .sc-item-num,
[data-project] .tsys-step-num {
  position: absolute; top: 24px; right: 20px;
  display: block; margin: 0;
  font-size: 12px; letter-spacing: 0.14em;
  color: var(--text-3);
}
@media (max-width: 900px) {
  [data-project] .cs-card-num,
  [data-project] .sc-item-num,
  [data-project] .tsys-step-num { top: 18px; right: 0; }
}
[data-project] .cs-card-name,
[data-project] .sc-item-name,
[data-project] .tsys-step-name {
  font-size: 17px; font-weight: 600; line-height: 1.25;
  color: var(--text-1); margin: 0 0 6px;
}
[data-project] .cs-card-desc,
[data-project] .sc-item-desc,
[data-project] .tsys-step-desc {
  font-size: 15px; line-height: 1.5; color: var(--text-2); margin: 0;
}

/* Bento: declared by POSITION, not by per-card span classes. The legacy
   .cs-spanN classes were written against a 6-column grid, so remapping them
   onto 12 made every combination ragged. Row one leads at 6 + 3 + 3, row two
   settles into 4 + 4 + 4. */
[data-project] .cs-cards--bento > .cs-card { grid-column: span 4; }
[data-project] .cs-cards--bento > .cs-card:nth-child(1) { grid-column: span 6; }
[data-project] .cs-cards--bento > .cs-card:nth-child(2),
[data-project] .cs-cards--bento > .cs-card:nth-child(3) { grid-column: span 3; }
/* No type bump on the lead card. Cards in one grid are peers and must share a
   single title/description size; scaling the first card made two titles in the
   same row read as different components. The bento's wider first column is
   already the emphasis. */
/* Odd counts keep an even grid rather than orphaning the last card */
[data-project] .cs-cards--bento-even > .cs-card,
[data-project] .cs-cards--bento-even > .cs-card:nth-child(1),
[data-project] .cs-cards--bento-even > .cs-card:nth-child(2),
[data-project] .cs-cards--bento-even > .cs-card:nth-child(3) { grid-column: span 4; }

/* Inside the accent-tint band the ground is supplied by the band itself */
[data-project] .cs-band .cs-card { background: none; }

/* Mobile: every grid collapses to one card per row.
   These use :nth-child(n) purely to match the specificity of the desktop bento
   rules above. Without it, .cs-cards--bento-even > .cs-card:nth-child(1) (0,3,1)
   outranks a plain .cs-cards--bento-even > .cs-card (0,2,1), so the desktop
   spans kept winning on mobile and the grid stayed three across at 375px. */
@media (max-width: 900px) {
  [data-project] .cs-cards > .cs-card:nth-child(n),
  [data-project] .cs-cards--3 > .cs-card:nth-child(n),
  [data-project] .cs-cards--bento > .cs-card:nth-child(n),
  [data-project] .cs-cards--bento-even > .cs-card:nth-child(n) { grid-column: span 12; }
  [data-project] .cs-cards > .cs-card,
  [data-project] .sc-grid > .sc-item,
  [data-project] .tsys-process > .tsys-step,
  [data-project] .cs-principle-grid > .cs-principle { padding: 18px 0 20px; }
  [data-project] .cs-card-icon,
  [data-project] .sc-item-icon,
  [data-project] .tsys-chip { margin-bottom: 14px; }
}

/* Legacy .cs-spanN neutralised on non-bento grids.
   Those classes were authored against a 6-column grid; left active on the
   12-column field they produced unequal card widths in a grid whose items are
   peers (Play / Consume / Discover all carry the same weight). Bento keeps its
   deliberate 6+3+3 / 4+4+4 pattern; every other grid is even at every width. */
/* Desktop only. Unscoped, this outranked the mobile collapse on specificity
   (five classes vs four) and kept every grid three across at 375px. */
@media (min-width: 901px) {
  [data-project] .cs-cards:not(.cs-cards--bento) > .cs-card.cs-span2,
  [data-project] .cs-cards:not(.cs-cards--bento) > .cs-card.cs-span3,
  [data-project] .cs-cards:not(.cs-cards--bento) > .cs-card.cs-span4,
  [data-project] .cs-cards:not(.cs-cards--bento) > .cs-card { grid-column: span 4; }
}


/* --4: sanctioned exception, DESKTOP ONLY. Four peer cards on one row rather
   than wrapping 3 + 1, which leaves a ragged orphan. Mobile still collapses to
   one per row like every other grid. Use only when the four items are genuinely
   equal in weight and their copy is short enough to hold a quarter column.
   Declared LAST: it has the same specificity as the general span-4 rule above,
   so source order is what makes it win. */
@media (min-width: 901px) {
  [data-project] .cs-cards--4 > .cs-card:nth-child(n) { grid-column: span 3; }
}

/* --impact: MANUAL placement, desktop only. Deliberate exception.
   A two-column split leaves dead space under a short text column while the
   media column runs tall. This modifier pulls the chapter's Impact block into
   that space: it becomes the third grid child, so auto-placement drops it at
   row 2, column 1, directly under the bullets it belongs to.

   No `order` juggling is needed. The impact block sits LAST in the DOM, so when
   the split stacks on mobile the reading order is still text, media, impact.
   Desktop grid placement and mobile source order agree by construction. */
@media (min-width: 901px) {
  /* EXPLICIT placement, not auto-placement. Relying on auto-flow put the impact
     block in row 2, and row 1 is as tall as the MEDIA column, so it landed 597px
     below the text instead of tucked under it. The media now spans both rows and
     the impact is pinned to column 1 row 2, directly beneath the bullets. */
  [data-project] .cs-split2--impact {
    align-items: start;
    grid-template-rows: auto 1fr;
    /* The split's 48px row-gap would stack on top of the impact's own 32px
       margin, putting 80px between a bullet list and the outcome that belongs
       to it. Row 2 exists only to hold the impact, so the gap is owned solely
       by the impact's margin. */
    row-gap: 0;
  }
  [data-project] .cs-split2--impact > :first-child { grid-column: 1; grid-row: 1; }
  [data-project] .cs-split2--impact > :nth-child(2) { grid-column: 2; grid-row: 1 / span 2; }
  /* Doubled `.cs-split2.cs-split2--impact` on purpose. The margin-reset block
     near the end of this file names `[data-project] .cs-split2 > .cs-impact`
     at the same (0,3,0,0) specificity but LATER in source order, so it won
     and flattened this gap to 0 — the bullets sat flush against the box.
     That reset is right for every other split2 (the grid gap owns spacing),
     but `--impact` sets row-gap:0 and delegates the gap to this margin, so it
     needs to outrank it. */
  [data-project] .cs-split2.cs-split2--impact > .cs-impact {
    grid-column: 1; grid-row: 2;
    align-self: start;
    margin-top: var(--s4);
  }
}

/* TSYS process + principle grids adopt the same 12-column ruled field.
   Both were still 2-up / 3-up boxed tiles because they use their own container
   classes. `.tsys-step-head` laid the icon and number side by side in a flex
   row, which fights the ruled stack (icon, then number, then name), so it is
   unwrapped here. */
[data-project] .tsys-process,
[data-project] .cs-principle-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: 24px;
  row-gap: 0;
  margin-top: 40px;
  background: none;
}
[data-project] .tsys-process > .tsys-step,
[data-project] .cs-principle-grid > .cs-principle { grid-column: span 4; }

/* The head wrapper carries the icon-to-title gap, because tsys.html zeroes the
   chip's own margin (`.tsys-step-head .tsys-chip { margin-bottom: 0 }`) and
   that page rule wins. Without this the gap measured 0px against the canonical
   20px, so TSYS cards were visibly tighter than every other card on the site. */
[data-project] .tsys-step-head {
  display: block;
  background: none;
  border: none;
  padding: 0;
  margin: 0 0 20px;
}
[data-project] .tsys-step-head .tsys-step-num {
  position: absolute; top: 24px; right: 20px;
  display: block; margin: 0;
  font-size: 12px; letter-spacing: 0.14em;
  color: var(--text-3);
}
/* .cs-principle uses a bare <strong> as its title rather than a titled element */
[data-project] .cs-principle-grid > .cs-principle strong {
  display: block;
  font-size: 17px; font-weight: 600; line-height: 1.25;
  color: var(--text-1); margin: 0 0 6px;
}
[data-project] .cs-principle-grid > .cs-principle {
  font-size: 15px; line-height: 1.5; color: var(--text-2);
}
@media (max-width: 900px) {
  [data-project] .tsys-process > .tsys-step:nth-child(n),
  [data-project] .cs-principle-grid > .cs-principle:nth-child(n) { grid-column: span 12; }
}

/* ── Ruled index inside a TINT BAND: flips to white containers ──
   Tint-band canon (§2): a component sitting on the accent tint cannot rely on
   hairline rules, because a 10%-alpha line over a tinted fill has almost no
   contrast and the structure disappears. Inside `.sc-dark` / `.cs-band` the
   ruled index therefore becomes what the rest of the system already does on a
   band: a white container. Ruled hairlines on white, white cards on tint. */
[data-project] .sc-dark .sc-grid,
[data-project] .cs-band .cs-cards,
[data-project] .cs-band .cs-principle-grid {
  row-gap: var(--s2);
}
/* Tinted-band card variant: the WHITE FILL is the variant. Padding and radius
   are not — those come from the one card spec, or the band's cards read as a
   different component from every other card on the site. */
[data-project] .sc-dark .sc-grid > .sc-item,
[data-project] .cs-band .cs-cards > .cs-card,
[data-project] .cs-band .cs-principle-grid > .cs-principle {
  background: #ffffff;
  border: none;
  border-top: none;
  border-radius: var(--r-tile);
  padding: 22px 20px 24px;
}
/* The accent wipe would clip the rounded corner, so it is retired on the band */
[data-project] .sc-dark .sc-grid > .sc-item::before,
[data-project] .cs-band .cs-cards > .cs-card::before,
[data-project] .cs-band .cs-principle-grid > .cs-principle::before { display: none; }
/* Number returns inside the padded box */
[data-project] .sc-dark .sc-grid > .sc-item .sc-item-num,
[data-project] .cs-band .cs-cards > .cs-card .cs-card-num {
  top: var(--s4); right: var(--s4);
}
@media (max-width: 900px) {
  [data-project] .sc-dark .sc-grid > .sc-item,
  [data-project] .cs-band .cs-cards > .cs-card,
  [data-project] .cs-band .cs-principle-grid > .cs-principle { padding: 18px 18px 20px; }
  [data-project] .sc-dark .sc-grid > .sc-item .sc-item-num,
  [data-project] .cs-band .cs-cards > .cs-card .cs-card-num { top: var(--s3); right: var(--s3); }
}

/* ── Cards in a row share baselines (subgrid) ──
   Titles wrap to different line counts, so a one-line title and a two-line
   title pushed their descriptions to different heights and the row read as
   ragged. Each card becomes a subgrid spanning three rows (icon, title,
   description), so the row lines are shared across every card in that row and
   all three elements align regardless of how the copy wraps.
   Guarded by @supports: without subgrid the cards simply keep flowing as
   before, which is the current behaviour, so nothing regresses. */
/* .cs-split2 stacked on mobile: cap the inherited row-gap.
   The desktop `gap: var(--s5) var(--s6)` sets a 48px row-gap that is invisible
   while the two columns sit side by side, but becomes the vertical gap between
   the text and its media once the grid stacks. §4 already documents this rule
   for `.sc-split`; `.cs-split2` never had it applied, so mobile showed a 48px
   hole between a bullet list and the media it belongs to. */
@media (max-width: 900px) {
  [data-project] .cs-split2 { row-gap: var(--s3); }
  [data-project] .cs-split2--impact > .cs-impact { margin-top: var(--s3); }
  /* social-gameplay: unify board and video-row spacing (see the other
     [data-project="social-gameplay"] .cs-split2 override near .cs-split2's
     own mobile rule) — restated here because this rule appears later in the
     file and would otherwise win the cascade at equal specificity. */
  [data-project="social-gameplay"] .cs-split2 { row-gap: var(--s5); }
  [data-project="social-gameplay"] .cs-split2--impact > .cs-impact { margin-top: var(--s5); }
}

@supports (grid-template-rows: subgrid) {
  @media (min-width: 901px) {
    [data-project] .cs-cards > .cs-card,
    [data-project] .sc-grid > .sc-item,
    [data-project] .tsys-process > .tsys-step,
    [data-project] .cs-principle-grid > .cs-principle {
      display: grid;
      grid-template-rows: subgrid;
      grid-row: span 3;
      row-gap: 0;
      align-content: start;
    }
    /* The description takes the remaining row so cards end level */
    [data-project] .cs-cards > .cs-card > .cs-card-desc,
    [data-project] .sc-grid > .sc-item > .sc-item-desc,
    [data-project] .tsys-process > .tsys-step > .tsys-step-desc { align-self: start; }
    /* Absolutely-positioned numerals must not consume a grid row */
    [data-project] .cs-card-num,
    [data-project] .sc-item-num,
    [data-project] .tsys-step-num { grid-row: 1; }
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   HOME HERO, quieter
   ───────────────────────────────────────────────────────────────────────────
   The name was 176px Bebas in all-caps across two lines, which read as a slab
   rather than an introduction: "too bold". It also sat above three abstract
   nouns that said nothing.

   Now: the name is set in the heading face at a light weight and sentence
   case, at roughly half the size, so the first thing that carries weight on
   the page is the SENTENCE, not the surname. Uniform weight throughout, no
   partial emphasis on one half of the name.

   Declared after the unguarded display-type rule near line 3096, which would
   otherwise win on source order and put Bebas back.
   ═══════════════════════════════════════════════════════════════════════════ */
.hero-name {
  font-family: var(--f-heading);
  font-size: clamp(42px, 5.4vw, 72px);
  font-weight: 300;
  letter-spacing: -0.035em;
  line-height: 1.02;
  color: var(--text-1);
  margin-bottom: 22px;
}
/* The lead is the line that should actually stop someone. */
.hero-lead {
  font-family: var(--f-heading);
  font-size: clamp(24px, 2.9vw, 36px);
  font-weight: 500;
  line-height: 1.26;
  letter-spacing: -0.02em;
  color: var(--text-1);
  max-width: 20ch;
  margin-bottom: 18px;
}
/* Role drops to supporting weight so it sits under the lead, not against it. */
.hero-role {
  font-size: clamp(16px, 1.5vw, 18px);
  font-weight: 400;
  color: var(--text-2);
  line-height: 1.62;
  max-width: 48ch;
  margin-bottom: 32px;
}
@media (max-width: 768px) {
  .hero-lead { max-width: 100%; }
  .hero-role { max-width: 100%; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   HOME TYPE SYSTEM  ([data-page="home"] only)  ·  Schibsted Grotesk
   ───────────────────────────────────────────────────────────────────────────
   Chosen from rendered specimens rather than from a font name, which is the
   step whose absence got two previous typefaces rejected after they had
   already been built.

   ONE family for everything. That means hierarchy cannot lean on a face
   change; it has to come from size, weight and ink tone alone. Which is the
   discipline that was missing before, when the page ran Bebas, Plus Jakarta
   and IBM Plex at once with no rule about which did what.

   Before this block, measured at 1440:
     .hero-name      Plus Jakarta 72px/300   <- tied for largest with...
     .section-title  Bebas Neue   72px/400   <- ...this
     .hero-lead      Plus Jakarta 36px/500   <- same size, different weight...
     .intro-heading  Plus Jakarta 36px/700   <- ...as this

   The ladder now, with a real step between every rung:
     94   name          400   his page, so his name is the largest thing on it
     32   lead          500   the sentence that should stop someone
     38   section       600   PROJECTS, the CTA
     30   intro head    600
     26   card title    600
     19   role          400   text-2
     18   body          400   text-2
     16   card desc     400   text-2
     12   ALL labels    600   uppercase, 0.1em, text-3

   Schibsted Grotesk ships 400..900, so 400 is the lightest available. At 94px
   that still reads calm, which was the "too bold" note on the 176px slab.

   Scoped to [data-page="home"]: .section-title and .cta-heading are shared
   with pages that are out of scope this pass, and the 0,2,0 specificity also
   lets these beat the mobile block at the end of the file without reordering.
   ═══════════════════════════════════════════════════════════════════════════ */
[data-page="home"] {
  --f-heading: 'Schibsted Grotesk', system-ui, sans-serif;
  --f-body:    'Schibsted Grotesk', system-ui, sans-serif;
  --f-display: 'Schibsted Grotesk', system-ui, sans-serif;
}

/* His page. Presence comes from SIZE, not weight. */
[data-page="home"] .hero-name {
  font-size: clamp(46px, 6.6vw, 94px);
  font-weight: 400;
  letter-spacing: -0.042em;
  line-height: 1;
  color: var(--text-1);
  margin-bottom: 20px;
  text-transform: none;
}
[data-page="home"] .hero-lead {
  font-size: clamp(22px, 2.5vw, 32px);
  font-weight: 500;
  line-height: 1.24;
  letter-spacing: -0.024em;
  color: var(--text-1);
  max-width: 21ch;
  margin-bottom: 18px;
}
[data-page="home"] .hero-role {
  font-size: 19px;
  font-weight: 400;
  line-height: 1.6;
  color: var(--text-2);
  max-width: 46ch;
  margin-bottom: 34px;
}
[data-page="home"] .section-title,
[data-page="home"] .cta-heading {
  font-size: clamp(28px, 2.9vw, 38px);
  font-weight: 600;
  letter-spacing: -0.026em;
  line-height: 1.12;
  text-transform: none;
}
[data-page="home"] .intro-heading {
  font-size: clamp(24px, 2.4vw, 30px);
  font-weight: 600;
  letter-spacing: -0.022em;
  line-height: 1.28;
}
[data-page="home"] .card-title {
  font-size: clamp(22px, 1.9vw, 26px);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin-bottom: 10px;
  text-transform: none;
}
[data-page="home"] .card-desc  { font-size: 16px; font-weight: 400; line-height: 1.6;  color: var(--text-2); }
[data-page="home"] .intro-body { font-size: 18px; font-weight: 400; line-height: 1.72; color: var(--text-2); }

/* ── Mobile hero hierarchy ──────────────────────────────────────────────────
   At 375 the name and the lead read at the SAME level, and the measurements
   show why it was not a size problem:

     .hero-name  46px / w400 / 1 line  -> block 46px tall
     .hero-lead  22px / w500 / 2 lines -> block 55px tall

   The lead was physically taller than the name, heavier than it, and the same
   ink. Half the type size but more visual mass, so the two tied.

   Three fixes, because all three caused it: the name goes up, the lead drops
   to the same weight as the body so it stops out-weighting the name, and it
   loses a step of size so its two lines stay shorter than the name's one. */
@media (max-width: 768px) {
  /* First pass equalised the weight but left both blocks at exactly 53px, so
     they still tied: the lead's two lines matched the name's one. Size alone
     could not fix that, so the name takes more of it and the lead gives some
     back. Ink tone is the third lever and stays on --text-1: dropping the lead
     to --text-2 would have merged it into the role line underneath. */
  [data-page="home"] .hero-name { font-size: clamp(58px, 15.5vw, 68px); letter-spacing: -0.048em; }
  [data-page="home"] .hero-lead { font-size: 19px; font-weight: 400; line-height: 1.3; max-width: 27ch; }
  [data-page="home"] .hero-role { font-size: 17px; line-height: 1.62; }
}

/* Desktop: the portrait was 400px wide against a 1.1fr text column, which made
   it compete with the name for attention rather than accompany it. Smaller,
   and the grid gives the reclaimed width back to the words. */
@media (min-width: 901px) {
  [data-page="home"] .hero-photo { max-width: 300px; }
  [data-page="home"] .hero-content { grid-template-columns: 1.35fr 0.65fr; }
}

/* ONE label treatment. Was three: .label 14/700, .card-meta 12/600 and the
   marquee 16/600, all uppercase, all slightly different. */
[data-page="home"] .label,
[data-page="home"] .card-meta,
[data-page="home"] .marquee-track span {
  font-family: var(--f-heading);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
}

/* ═══════════════════════════════════════════════════════════════════════════
   CASE-STUDY TYPE SYSTEM  ([data-page="case"])  ·  Schibsted Grotesk
   ───────────────────────────────────────────────────────────────────────────
   Same family and same logic as the home ladder, retuned for long-form: a
   case study is read, not scanned, so the steps are tighter and the body
   sizes up rather than the headings.

   Bebas Neue is gone here too. It was carrying chapter numbers, section
   titles, sub-heads, stat figures and card titles, i.e. five different jobs
   in a display face with one weight, which is why hierarchy inside a chapter
   depended entirely on size.

     72   hero title
     44   chapter number     text-3, it indexes, it does not shout
     36   section title
     26   sub-section title
     19   lede               text-1, the one paragraph that carries a chapter
     18   body
     44   stat figure
     12   ALL labels         eyebrow, meta label, caption, tag

   Body goes UP from 17 to 18 and the reading column comes DOWN, because the
   audit measured 92 characters per line against an ideal band of 45 to 75.
   ═══════════════════════════════════════════════════════════════════════════ */
/* Through .ch-head: the page-level rule is
   [data-project="dreamplay"] .ch-head .cs-section-title at 0,3,0, which beat a
   plain [data-page="case"] .cs-section-title at 0,2,0. Matching the specificity
   and relying on source order, since this block sits later in the file. */
/* The chapter number indexes the page. It should be findable, not loud. */
/* Reading column: 92 characters per line measured, ideal is 45 to 75. At 18px
   Schibsted Grotesk, 660px lands near 70. */

/* ONE label treatment across the whole page. */
/* .cs-statband-label and .cs-mediacap-title are deliberately NOT in this group.
   Uppercase is for labels of three words or fewer (DESIGN_SYSTEM §3). Stat
   labels run to nine ("daily CEA, held for weeks with no marketing") and
   caption titles are sentences; setting those uppercase made them unreadable
   and broke the site's own label-register rule. */

/* ── Highlighted phrases ────────────────────────────────────────────────────
   Measured before: highlight #111/600 against body #444448/400 at the same
   size. A 1.95:1 tonal step and one weight step, which is a difference you can
   see when you stop and compare, and cannot see at all when you scan. On a
   19,000px page the whole point of a highlight is that it survives scanning.

   Hue is the strongest pre-attentive cue available, so the highlight moves to
   the portfolio's indigo. That is also precisely the use the token is reserved
   for in DESIGN_SYSTEM §2: "highlights, small details, single-word emphasis,
   never a dominant fill". Two highlights on this page, so it stays sparing.

   #3548D4 on #ffffff is 6.03:1, clear of AA for normal text. */
/* ── Chapter head: tighter, and the number becomes a real index marker ──────
   The number was 44px in --text-3, which is the same grey as a caption, so it
   read as decoration and the eye skipped it. On a 19,000px page with no other
   wayfinding, the chapter number IS the wayfinding. In the portfolio indigo it
   becomes the thing you scan for.

   #3548D4 on #ffffff is 6.03:1, clear of AA.

   Layout tightened: the head was a 32px column gap and a 48px bottom margin,
   which pushed the title away from its own number and away from the lede it
   introduces. 20px and 32px close both. */
/* Consolidated: this used to override just margin-bottom (2px), which beat the
   pill's own 20px margin-bottom (.ch-eyebrow, ~line 4288) purely on specificity
   (0,3,0 vs 0,2,0), collapsing the pill-to-title gap to 2px and, via the
   [data-project="dreamplay"] .ch-head .ch-eyebrow rule earlier in the file,
   leaving the pill on --text-3 (4.56:1) instead of the intended --text-2
   (8.83:1, measured on --surface). Restated in full here — the one place that
   wins the cascade for both properties — instead of adding a third override. */
@media (max-width: 768px) {
}

/* The closing line. It lands by flatness: after eight chapters of visible care,
   one short sentence with no adjective in it, and then the page ends. Given
   room and set at lede size so it reads as a full stop rather than a footnote.
   Nothing goes between it and the project rail. */
/* The existing smooth-scroll handler lands targets under the 76px fixed nav. */
.sc-block[id] { scroll-margin-top: 100px; }

/* ── Milestones: mobile text fallback ────────────────────────────────────
   dp-milestones.png is a 3840px-wide dense composite — even at .cs-flat--hero
   (full-bleed, the widest treatment on the page) it renders at ~0.085x scale
   on a 375px viewport, illegible, measured. No amount of scaling fixes a
   16:9 image this dense at phone width, so mobile gets the same data as a
   readable list instead; desktop keeps the image. */
[data-project="dreamplay"] .dp-milestones-list { display: none; }
@media (max-width: 768px) {
  [data-project="dreamplay"] .dp-milestones-img { display: none; }
  [data-project="dreamplay"] .dp-milestones-list {
    display: block;
    list-style: none;
    margin: var(--s4) 0 0;
    padding: 0;
  }
  [data-project="dreamplay"] .dp-milestones-list li {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 14px 0;
    border-bottom: 1px solid var(--border-2);
    font-size: 15px;
    line-height: 1.5;
    color: var(--text-1);
  }
  [data-project="dreamplay"] .dp-milestones-list li:last-child { border-bottom: none; }
  [data-project="dreamplay"] .dp-milestones-date {
    font-family: var(--f-heading);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--accent-2);
  }
}


/* Bolded phrases inside bullets and body stay ink: they are emphasis WITHIN a
   read, not a scan target, and colouring every one of them would spend the
   accent until it stopped meaning anything. */
/* Sentence case, because these are phrases rather than labels. */
@media (max-width: 768px) {
}

/* ═══════════════════════════════════════════════════════════════════════════
   FIELD AND OBJECT  ·  [data-page="case"]
   ───────────────────────────────────────────────────────────────────────────
   CORRECTION. The previous version of this block made the whole page grey.
   That was a misread on my part: the reference images are screenshots of
   different sites, so the grey around them was the screenshot, not the design.
   I read the frame as the artwork.

   The page is WHITE. Grey is a deliberate device used ON it, which is what
   most of the references actually do: LUXURA sets grey cards on near-white,
   "From concept to launch" sets grey pricing cards on white, "Everything you
   need" sets its tag pills on white.

   The real principle underneath is unchanged, and it is still the thing this
   site was missing: an object has to read as an object. That comes from a
   tone step between field and object, in EITHER direction, plus radius and
   space. Not from a 1px hairline drawn on a flat white sheet.

     page (field)   #ffffff
     surface        #f4f4f6    grey object on white
     surface-deep   #ececf0    a second step, for nesting
     radius         20px panel, 12px inner
     shadow         used sparingly; the tone step does most of the work

   Grey is used for: media grounds, grouped card sets, process blocks, spec
   panels. NOT for whole chapters. A chapter stays on white so the page keeps
   its air, and the grey blocks inside it become the objects the eye lands on.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Chapters sit on white and are separated by space, not by a rule and not by
   a panel. Space is the separator; that is what "breathing room" means. */

/* Media gets a grey ground so the artwork reads as placed, not floating, and
   so a screenshot with white edges does not bleed into the page. */

/* Mobile spacing hierarchy: chapter gap, sub-section gap and media-row gap
   measured at 64px / 72px / 60px — within 12px of each other, so nothing read
   as "chapter" at 375px. Re-set as a clean ratio: chapter (96) = 2x
   sub-section (48) = 1.5x media-row (32). .ch-sub and .cs-video-row/.cs-phone-row
   are shared components (TSYS/Scout/Social Gameplay also use them), so these
   overrides are scoped to [data-page="case"], which only ever matches this
   page — the other pages' shared 112px/72px .ch-sub and 60px row-gap rules are
   untouched. */
@media (max-width: 768px) {
}

/* ═══════════════════════════════════════════════════════════════════════════
   CASE-STUDY COMPONENTS, from the reference direction
   See DESIGN_SYSTEM.md §4. Page is WHITE; grey #f4f4f6 is a device on it.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Pill label ────────────────────────────────────────────────────────────
   The references open a section with a small grey pill, not a bare uppercase
   word floating on white. The pill gives the label an object to sit in, which
   is the same field/object idea at label scale. */

/* ── Two-tone chapter title ────────────────────────────────────────────────
   "Trusted by fashion connoisseurs worldwide" sets the first clause in ink and
   the rest in grey, in one sentence. It creates hierarchy inside a single line
   without a second type size, which is why the references read calm. Wrap the
   trailing clause in <span class="dim">. */

/* ── Big-number card ───────────────────────────────────────────────────────
   Figure large and LIGHT, small grey label under it, no border, sitting on a
   grey ground. The references use these as the anchors the eye lands on. */
@media (max-width: 900px) { [data-page="case"] .cs-figures { grid-template-columns: repeat(2, 1fr); } }

/* ── Dossier row: the flat "label above value" bar had one weight throughout,
   no separators between items, and the same tight gap before Responsibilities
   as between the label and its own value — so the whole block read as one
   undifferentiated line, not two levels of a hierarchy. Vertical rules give
   the four items real edges (matching how the figure cards above already
   read as distinct objects); label drops a step further from the value
   (11px vs 20px, not 14px vs 18px) so the eye can separate "field name" from
   "field content" without reading either; Responsibilities gets its own top
   border and real air above it, so it reads as a second block, not a fifth
   column. No top rule on the row itself — the figure cards directly above
   already read as a distinct grey-ground object, so a second separator line
   4px under them read as a stray, unexplained mark rather than a boundary.
   Scoped to dreamplay only — the shared .cs-meta-row/.cs-tag rules stay
   untouched for TSYS/Scout/Social Gameplay. */
/* Hero dossier: meta + responsibilities + stats unified into one left-aligned
   surface-tint panel (tone-shift, per the reference guideline's "section rhythm
   via background shift, not a bordered panel" rule) instead of two separate
   blocks — the grey-blue stat band arriving cold after a bare meta grid. Stats
   sit as white tiles inside the panel; no pill on the eyebrow. */
[data-project] .cs-hero-dossier {
  /* DESIGN SYSTEM: hero dossier sits on the SECOND surface step, not the first.
     It contains white stat tiles, so it needs enough tone separation from them
     to read as a container rather than a tint. */
  background: var(--surface-2);
  /* --r-panel is declared only inside the dead [data-page="case"] block, so it
     resolved to nothing and the panel rendered square-cornered on desktop while
     the 768px override (using --r-tile) gave it corners on mobile only. */
  border-radius: var(--r-tile);
  padding: var(--s5);
  margin-top: var(--s6);
}
[data-project] .cs-hero-dossier .cs-statband,
[data-project] .cs-hero-dossier .sc-stats,
[data-project] .cs-hero-dossier .tsys-stats {
  background: #ffffff;
  border-radius: var(--r-lg, 24px);
  margin-top: var(--s4);
}
[data-project] .cs-hero-dossier .cs-meta-row {
  border-top: none;
  border-bottom: none;
  padding-top: 0;
  padding-bottom: 0;
  margin-top: 0;
  /* Row-gap only (column-gap keeps --s6): the Company/Role/Team/Duration row
     and Responsibilities read as one dense band at the base 24px row-gap.
     --full's own margin-top is zeroed a few rules down (T7 sweep) so this
     grid gap is the ONLY thing setting that distance — raising it here is
     the whole fix, across all four case-study pages that share this markup. */
  row-gap: var(--s5);
}
[data-project="dreamplay"] .cs-meta-item {
  gap: 10px;
  padding-left: 24px;
  border-left: 1px solid rgba(0,0,0,0.1);
}
[data-project="dreamplay"] .cs-meta-item:first-child {
  padding-left: 0;
  border-left: none;
}
[data-project="dreamplay"] .cs-meta-label {
  font-size: 12px;
  letter-spacing: 0.1em;
  color: var(--text-3);
}
[data-project="dreamplay"] .cs-meta-value {
  font-size: 20px;
  font-weight: 600;
  color: var(--text-1);
  letter-spacing: -0.01em;
}
[data-project="dreamplay"] .cs-meta-item--full {
  /* No border-top: the other three dossiers (Scout, TSYS, Social Gameplay)
     separate Responsibilities from the grid above it with the row-gap alone,
     no rule. This page had picked up its own divider that the other three
     never got, so the same section read as two different components
     depending which case study you were on. */
  grid-column: 1 / -1;
  padding-left: 0;
  border-left: none;
}
@media (max-width: 768px) {
  [data-project] .cs-hero-dossier { padding: var(--s4); border-radius: var(--r-tile); }
  [data-project="dreamplay"] .cs-meta-row { padding-top: 0; }
  [data-project="dreamplay"] .cs-meta-item { padding-left: 18px; }
  /* 2-col grid at this width: the desktop rule only strips the border/indent
     from the literal first child, but every left-column item (odd position)
     is visually "first in its row" here — item 3 (Team) kept a stray
     border-left + indent that made it read as misaligned under Company. */
  [data-project="dreamplay"] .cs-meta-item:nth-child(odd) { padding-left: 0; border-left: none; }
  [data-project="dreamplay"] .cs-meta-value { font-size: 17px; }
}

/* Every other gap in a lede→cards→flat→phone-row chapter runs on the same
   48px rhythm (measured live: 48/48/48). The shared [data-project] .cs-cards
   rule sets margin-top:40px (a value tuned for Scout/TSYS's plain ruled-index
   list), an 8px outlier right at the one spot flagged as reading tight. */
[data-project="dreamplay"] .cs-cards { margin-top: var(--s5); }

/* ch08 "The Look" tried a tighter 64px gap (.ch-sub--grouped) on Colour and
   Communication design to signal pairing with the sub-head before each. Numerically
   exact every time it rendered, but the same 64px read as inconsistent next to
   visually denser vs. sparser content above it (bento cards vs. a one-line type
   spec vs. a swatch bar) — removed; every ch-sub now uses the one standard gap. */

/* ── Dense stacked-media chapters (lede → cards → flat diagram → phone/video row,
   e.g. ch04, ch05): flagged repeatedly as reading tight even after the 40px
   outlier was fixed to match the site's 48px component rhythm. 48px is the
   right gap for lighter chapters, but this page's heaviest chapters stack four
   dense components in a row and need more air between them. Bump to 64px for
   dreamplay's card/flat/phone-row/video-row components specifically. */
[data-project="dreamplay"] .cs-cards,
[data-project="dreamplay"] .cs-flat,
[data-project="dreamplay"] .cs-video-row--flush,
[data-project="dreamplay"] .cs-phone-row--flush {
  margin-top: var(--s6);
}
/* The 64px rhythm above is a desktop-only fix — at 375px it's a much larger
   proportion of the viewport and reads as breaking the paragraph-to-media
   association rather than adding air. Back to the standard 48px on mobile. */
@media (max-width: 768px) {
  [data-project="dreamplay"] .cs-cards,
  [data-project="dreamplay"] .cs-flat,
  [data-project="dreamplay"] .cs-video-row--flush,
  [data-project="dreamplay"] .cs-phone-row--flush {
    margin-top: var(--s5);
  }
}
/* .cs-split2 collapses to a single column at 900px and applies its own s3
   (24px) row-gap between the stacked children. When the media child is
   .cs-video-row--flush/.cs-phone-row--flush, its own 48px margin-top (the
   mobile rhythm rule just above) stacks on top of that grid gap instead of
   being replaced by it — 24+48=72px, roughly double the intended gap, and
   exactly what breaks the paragraph-to-media association. The grid gap alone
   is the correct spacing here; zero the child's own margin. */
@media (max-width: 900px) {
  [data-project="dreamplay"] .cs-split2 > .cs-video-row--flush,
  [data-project="dreamplay"] .cs-split2 > .cs-phone-row--flush {
    margin-top: 0;
  }
}

/* ── Two DIFFERENT relationships were sharing one gap value, which is why the
   same rhythm read as "too tight" in one place and "breaks association" in
   another. They are not the same thing:
     paragraph → the media it describes   = one unit, should read TIGHT
     component → the next component       = a step, should read SEPARATE
   The 64/48px rhythm above is correct for component→component. When a body
   paragraph or bullet list is immediately followed by its own media, that
   larger gap orphans the media from the sentence introducing it. Measured
   live: the same relationship rendered at 24px inside .cs-split2 but 48-64px
   at chapter top level. Normalise the paragraph→media case to the tighter
   value; component→component keeps the wider rhythm. */
[data-project="dreamplay"] .cs-body-text + .cs-flat,
[data-project="dreamplay"] .cs-body-text + .cs-video-row--flush,
[data-project="dreamplay"] .cs-body-text + .cs-phone-row--flush,
[data-project="dreamplay"] .cs-bullets + .cs-flat,
[data-project="dreamplay"] .cs-bullets + .cs-video-row--flush,
[data-project="dreamplay"] .cs-bullets + .cs-phone-row--flush {
  margin-top: var(--s3);
}
/* ── DESIGN SYSTEM RULE: content-above-media, desktop.
   The 24px above is the MOBILE value — correct on a 375px screen where the
   media follows immediately under the sentence. On desktop the same 24px
   clumps the sentence and the frames into one mass against a page whose own
   component rhythm is 64px. 48px is the right desktop value: a half-step
   below the section rhythm, because these are captioned continuations of the
   sentence above, not independent components.
   Applies wherever a text block sits directly ABOVE a media row/frame. */
@media (min-width: 901px) {
  [data-project="dreamplay"] .cs-body-text + .cs-flat,
  [data-project="dreamplay"] .cs-body-text + .cs-video-row--flush,
  [data-project="dreamplay"] .cs-body-text + .cs-phone-row--flush,
  [data-project="dreamplay"] .cs-bullets + .cs-flat,
  [data-project="dreamplay"] .cs-bullets + .cs-video-row--flush,
  [data-project="dreamplay"] .cs-bullets + .cs-phone-row--flush {
    margin-top: var(--s5);
  }
  /* Side-by-side case is the opposite requirement: inside a .cs-split2 the
     media column must TOP-ALIGN with the first line of text beside it. The
     24px above was pushing it exactly 24px low while align-items:start held
     the text at 0. Zero it here (this is the "frames not top aligned to the
     content on the left" report). */
  [data-project="dreamplay"] .cs-split2 > .cs-video-row--flush,
  [data-project="dreamplay"] .cs-split2 > .cs-phone-row--flush,
  [data-project="dreamplay"] .cs-split2 > .cs-video-cell { margin-top: 0; }
}

/* ── Tablet dead space. The desktop row geometry (min-width:769px) uses fixed
   280px columns — correct at 1440px where 3x280 spans the measure, but at
   ~800px the row renders 592px inside a 736px container, so every 2-up row
   stops ~72px short of the right edge while the left is flush. Two rows on
   the page already fill correctly, so the page contradicts itself. In the
   769-900 band, let the columns flex and the frames fill them. */
/* ── DESIGN SYSTEM RULE: a phone frame never exceeds its 2-up width.
   A .cs-split2 collapses to one column at 900px, so a media cell inside it
   inherits the full container width. At 375px that is correct (335px reads as
   a real phone). At 700-900px it is not: the frame balloons to 700-840px, a
   phone screen rendered wider than a laptop window. Cap it at the width it
   WOULD have if two frames sat side by side in that same container.

   ONLY applies in the tablet band, where the split has actually collapsed to
   one column. Above 900px the split is already two columns and the frame is
   already 280px — halving it there would shrink frames to ~124px. */
@media (min-width: 769px) and (max-width: 900px) {
  [data-project="dreamplay"] .cs-split2 > .cs-video-cell .cs-vframe,
  [data-project="dreamplay"] .cs-split2 > .cs-video-cell .cs-vframe-letterbox,
  [data-project="dreamplay"] .cs-split2 > .cs-video-cell .cs-phone,
  [data-project="dreamplay"] .cs-split2 > .cs-phone-cell .cs-phone {
    max-width: calc((100% - var(--s4)) / 2);
  }
}

@media (min-width: 769px) and (max-width: 900px) {
  /* TABLET BAND. Column counts for this band are declared in the matching
     tablet block near the end of this file — do NOT re-declare them here, the
     later rule wins on source order and the two silently fight. This block
     only lifts the 280px frame cap so the columns actually get filled. */
  [data-project="dreamplay"] .cs-vframe,
  [data-project="dreamplay"] .cs-vframe-letterbox,
  [data-project="dreamplay"] .cs-phone,
  [data-project="dreamplay"] .cs-video-cell .cs-mediacap-title,
  [data-project="dreamplay"] .cs-video-cell .cs-mediacap,
  [data-project="dreamplay"] .cs-phone-cell .cs-mediacap-title,
  [data-project="dreamplay"] .cs-phone-cell .cs-mediacap { max-width: 100%; width: 100%; }
}

/* ── Cards + icons, per references/CONCEPT_GUIDELINE.md (the project's own
   synthesized reference spec), which this page was violating on both counts:

   1. Bento flavour. The guideline picks the gapped/Image-7 flavour — "real
      visible gaps between cards, each card independently rounded and grounded
      (its own radius, its own fill), NOT sharing hairline borders inside one
      container" — and explicitly rejects the seamless/Image-8 flavour. The
      cards were rendering as the rejected version: transparent, square, with
      a shared top hairline.
   2. Icons. The guideline names thin-line-art-with-no-tile as a prior
      violation by name, and specifies a rounded-square tile at accent-2-tint,
      ~44-48px. The icons were 28px, stroke-width 1.25, no tile, no fill.

   UNIVERSAL: applies to every project page, not just dreamplay. Bebas-era
   card and icon treatments are not per-page choices. Scout's .sc-item and
   TSYS's .tsys-step are the same role under a page-local name — they are in
   the list too, so one card treatment covers every page. */
/* REMOVED Aug 2026: this block re-filled every card as a grey tile, opting all
   four project pages out of the ruled treatment above — but it never listed
   `.cs-principle`, so TSYS's principle grid kept the rule and the same role
   rendered two different ways on the same site. One card treatment now covers
   every page, which is what the comment above already claimed.
   To restore filled tiles, put this block back. */
[data-project] .cs-cards { column-gap: var(--s3); row-gap: var(--s3); }
[data-project] .cs-card-icon,
[data-project] .sc-item-icon,
[data-project] .tsys-chip {
  width: 44px; height: 44px;
  background: var(--accent-2-tint);
  border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 16px;
}
[data-project] .cs-card-icon svg,
[data-project] .sc-item-icon svg,
[data-project] .tsys-chip svg { width: 22px; height: 22px; stroke-width: 2; }

/* Sub-section titles were 500 — deliberately stepped down from Home's 600 to
   stop a high-frequency element reading loud, but at 21-26px that lands too
   light to hold as a heading against a bold body lead-in beneath it. 700 with
   the size unchanged: reads as a heading again without adding scale. */
[data-project] .ch-sub-title { font-weight: 700; }

/* ── Captions "not extending to the end of their container". The box was
   always the right width — `text-wrap: pretty` (inherited from the global
   `p, li` rule) was pulling the last word down to avoid a short final line,
   leaving up to ~100px of unused track beside the media's right edge. The
   ink stopped early even though the box did not. Plain wrapping fills the
   measure. */
[data-project] .cs-mediacap,
[data-project] .cs-mediacap-title { text-wrap: wrap; }

/* ── .cs-vframe-letterbox stayed capped at 280px on mobile while its plain
   .cs-vframe siblings filled the 335px column: the letterbox base rule is
   declared AFTER the mobile full-width override at equal specificity, so it
   won on source order alone (same bug class as the .cs-band container inset).
   Re-assert here, late enough in the file that nothing defeats it.

   Scoped to [data-project] rather than one page on purpose: the source-order
   defeat is in the base rules, so every case study using a letterbox hit it.
   Social Gameplay's autocontest clip sat at 280px beside 639px phone images in
   the same row until this was widened. */
@media (max-width: 768px) {
  [data-project] .cs-vframe-letterbox { max-width: 100%; width: 100%; }
}

/* ── Typography-only coherence pass vs Home (font-size/weight/tracking/line-height
   ONLY — no layout, spacing, color or structural changes). The shared caps group
   above (.label, .ch-eyebrow, .cs-hero-label, .cs-meta-label) is 14px/700/0.08em,
   a Bebas-era value heavier and larger than Home's actual label spec
   (12px/600/0.1em). UNIVERSAL across every project page — the Bebas-era label
   value is not a per-page choice. */
[data-project] .ch-eyebrow,
[data-project] .cs-hero-label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  line-height: 1.25;
}
[data-project] .cs-meta-label { font-size: 12px; font-weight: 600; letter-spacing: 0.1em; line-height: 1.25; }
[data-project="dreamplay"] .cs-meta-value { font-size: 18px; font-weight: 500; }
@media (max-width: 768px) {
  [data-project="dreamplay"] .cs-meta-value { font-size: 17px; font-weight: 500; }
  /* Hero label→title gap: 32px against a 44px title reads as too much air on a
     375px screen — the label almost looks disconnected from its own title. */
  [data-project="dreamplay"] .cs-hero-label { margin-bottom: 12px; }
  /* Sub-heading → its own content: 24px on a short heading like "The ask" reads
     loose on a narrow screen, weakening the heading-to-list association. */
  [data-project="dreamplay"] .ch-sub { margin-bottom: var(--s2); }
  /* Chapter title (26px/600/ink) vs sub-title (21px/500/ink) is only a 5px,
     one-weight step at this scale — not enough to read as a hierarchy drop on
     a 375px screen. Push the sub-title back with color as well as size, the
     same way .cs-body-text sits at text-2 rather than full ink. */
  /* Earlier attempt pushed this to --text-2, which is EXACTLY the body-text
     colour — the heading then read as lighter than the bold lead-in beneath
     it, i.e. below its own paragraph in the hierarchy. Wrong lever. Keep full
     ink and separate by weight+size instead, and widen the chapter→sub step
     (it compressed from 1.38x at desktop to 1.24x here). */
  [data-project] .ch-sub-title { color: var(--text-1); font-weight: 700; font-size: 18px; }
  [data-project] .ch-head .cs-section-title { font-size: 30px; }
}
/* Requested removal: "The Product" chapter sat inside .cs-band, a full-bleed
   accent-tint (light blue) background used elsewhere to mark one chapter as
   visually pivotal. Not wanted here — flatten to plain white, matching every
   other chapter. Layout/padding/margin structure is untouched, only the tint. */
/* Blue band restored on chapter 07 (was briefly flattened to white). */
[data-project="dreamplay"] .cs-band .ch-eyebrow { background: none; }
/* Chapter index numeral: sized to match the combined height of the eyebrow +
   title stack on a single-line chapter title (measured live: eyebrow 15px +
   tightened 4px gap + title line ~29-40px ≈ 48-59px), not an arbitrary value.
   Two-line titles will run taller than the numeral — expected, matches the
   brief (matched for the single-line case specifically). */
[data-project="dreamplay"] .ch-head .ch-num {
  font-size: clamp(56px, 6vw, 70px);
  font-weight: 400;
}
/* Eyebrow → title: was 10px, reads loose against the tightened numeral pairing. */
[data-project="dreamplay"] .ch-head .ch-eyebrow {
  margin: 4px 0 4px;
}
/* Card name/desc: bring the negative tracking every other Schibsted title on
   the page carries, and raise desc off the site's body-copy floor (16/1.6). */
[data-project="dreamplay"] .cs-card-name { letter-spacing: -0.012em; }
[data-project="dreamplay"] .cs-card-desc { font-size: 16px; line-height: 1.6; }
/* Media caption: was equal to or smaller than its own title with no mobile
   hierarchy step. */
[data-project] .cs-mediacap { font-size: 15px; line-height: 1.55; color: var(--text-3); }
/* Stat caption: was body-copy size, reading as a peer to the number instead
   of an annotation under it. */
[data-project="dreamplay"] .cs-statband-label { font-size: 14px; line-height: 1.45; }

/* DreamPlay's diagram/composite images are raw Figma-frame exports that already
   carry their own near-white canvas background baked into the pixels — the
   generic [data-page="case"] .cs-flat grey mat around them reads as a second,
   redundant frame nested inside the first. Drop the mat for this page only;
   everything else about .cs-flat (image border-radius included) stays as-is. */
[data-project="dreamplay"] .cs-flat {
  background: none;
  padding: 0;
}
/* [data-page="case"] .cs-flat img also strips the border and swaps the radius
   token — an unrequested side effect of activating that block. Restore the
   original border/radius this page always had. */
[data-project="dreamplay"] .cs-flat img,
[data-project="dreamplay"] .cs-flat picture img {
  border: 1px solid var(--border-1);
  border-radius: var(--r-tile);
}
/* ── Numbered process, vertical ────────────────────────────────────────────
   "From concept to launch": number left, title and one line right. Replaces
   prose for anything sequential.
   Was flat white with hairlines — the one process block on the page NOT
   using the grey-ground device, contradicting the §4 colour rule
   that process blocks are one of the four legitimate grey "devices." Moved
   onto --surface with the same 20px panel radius .cs-flat uses; the internal
   step dividers stay (a sequence still needs row separation) but move to
   --border-2 for legibility on grey, and the container's own border-top/last
   row's border-bottom drop since the panel edge now does that job. */
/* Narrowed: the description column ran to ~145 characters per line at 1440px,
   roughly double a comfortable measure for a component whose job is fast
   scanning, not reading. 480px lands it in the 60-80 char band instead. */

/* ── Icon tile ─────────────────────────────────────────────────────────────
   Solid glyph in a rounded square, replacing thin line icons floating loose. */

/* ═══════════════════════════════════════════════════════════════════════════
   DREAMPLAY REDESIGN — media weight scale, chapter-scoped bento components,
   tablet 2-up media rows, mobile nav tap targets.
   All rules below are scoped to [data-project="dreamplay"] (or the page-
   exclusive [data-page="case"]), per the brief to move away from the shared
   6/12-col `.cs-cards--bento` machinery where it serves this page, without
   touching TSYS/Scout/Social Gameplay, which share several of the base
   classes being extended here (.cs-flat, .cs-cards--4, .cs-phone-row, the
   nav itself).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Media weight scale (finding #6) ─────────────────────────────────────────
   36 media items on the page rendered at just two widths, 280px or 1176px.
   .cs-flat--hero is a full-bleed opt-in modifier, assigned to every dense
   multi-state composite (entry flow, matchmaking, network errors, closing
   milestones) so each scales to fit the full viewport width instead of a
   uniform 307px shrink — no image ever requires a scroll gesture to see in
   full. The messaging showcase (fewer, larger states — safe to shrink
   further) uses the existing .cs-flat--narrow, unchanged. */
[data-project="dreamplay"] .cs-flat--hero {
  width: 100vw;
  max-width: none;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  padding: 0 var(--s4);
  background: none;
}
[data-project="dreamplay"] .cs-flat--hero img { border-radius: var(--r-tile); }
@media (max-width: 768px) {
  [data-project="dreamplay"] .cs-flat--hero { padding: 0 var(--s3); }
}

/* ── Ludo payoff row: 3-up wide (finding #6) ─────────────────────────────────
   .cs-phone/.cs-vframe cap at 280px globally (Scout/TSYS/Social Gameplay all
   depend on that), which left this specific row's three cells stranded at
   280px inside a ~349px track. Scoped modifier stretches the cells to fill
   their grid track instead of touching the shared 280px cap. */
[data-project="dreamplay"] .cs-phone-row--full .cs-phone,
[data-project="dreamplay"] .cs-phone-row--full .cs-vframe { max-width: 100%; width: 100%; }

/* ── Card index mark, shared by both #ch01 bentos ────────────────────────────
   First pass sized these at 32-44px/400, nearly matching .ch-num's own
   30-44px/300 — a card index and the page's own chapter index read as the
   same hierarchy level, which is confusing exactly where the page most needs
   a reader to tell "which chapter am I in" from "which card in this chapter."
   Shrunk well below chapter-number territory and set as a small tabular tag,
   not a dominant "big light number" moment. */
[data-project="dreamplay"] .cs-card-bignum {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--f-heading);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--accent-2);
  background: var(--accent-2-tint);
  width: 26px;
  height: 26px;
  border-radius: 7px;
  margin-bottom: 14px;
}

/* ── #ch01 "the ask": 4 claims + 1 video, custom bento (finding #10) ────────
   5 items don't divide evenly into the generic 6+3+3/4+4+4 bento pattern
   without orphaning a row, so this is a dedicated chapter-scoped grid: the two
   identity-defining claims paired at half width, the two shorter claims and
   the video (a peer card now, not a stranded 280px aside) at a third each. */
[data-project="dreamplay"] #ch01 .cs-ask-bento {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  /* align-items: start, not the grid default (stretch) — stretch forced the
     two short text cards in row 2 to match the tall video card's height,
     leaving ~500px of empty grey space in each. Cards now size to their own
     content; the video card is simply taller, which is fine in a bento. */
  align-items: start;
  gap: var(--s3);
  margin-top: var(--s4);
}
[data-project="dreamplay"] #ch01 .cs-ask-bento > * { grid-column: span 12; }
[data-project="dreamplay"] #ch01 .cs-ask-bento > :nth-child(1),
[data-project="dreamplay"] #ch01 .cs-ask-bento > :nth-child(2) { grid-column: span 6; }
[data-project="dreamplay"] #ch01 .cs-ask-bento > :nth-child(3),
[data-project="dreamplay"] #ch01 .cs-ask-bento > :nth-child(4),
[data-project="dreamplay"] #ch01 .cs-ask-bento > :nth-child(5) { grid-column: span 4; }
@media (max-width: 768px) {
  [data-project="dreamplay"] #ch01 .cs-ask-bento { grid-template-columns: 1fr; }
  [data-project="dreamplay"] #ch01 .cs-ask-bento > :nth-child(n) { grid-column: span 1; }
}

/* ── #ch04 async-scoring 4-card grid: one insight, three consequences ───────
   The generic [data-project] .cs-cards--4 rule gives all four cards an equal
   3/12 span. Card 1 ("an animation buys server time") is the async-scoring
   insight — the server-verification model everything else in the grid
   supports — so it gets the wide slot; the other three stay small, equal
   supporting consequences. */
[data-project="dreamplay"] .cs-async-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--s3); margin-top: var(--s4); }
[data-project="dreamplay"] .cs-async-grid > .cs-card:nth-child(1) { grid-column: span 6; }
[data-project="dreamplay"] .cs-async-grid > .cs-card:nth-child(2),
[data-project="dreamplay"] .cs-async-grid > .cs-card:nth-child(3),
[data-project="dreamplay"] .cs-async-grid > .cs-card:nth-child(4) { grid-column: span 2; }
@media (max-width: 768px) {
  [data-project="dreamplay"] .cs-async-grid { grid-template-columns: 1fr 1fr; }
  [data-project="dreamplay"] .cs-async-grid > .cs-card:nth-child(1) { grid-column: 1 / -1; }
}

/* ── #ch05 "The Look": design-system showcase, asymmetric (finding #10) ─────
   Five heterogeneous items (type spec, colour system, illustration, identity,
   communication) used to stack full-width, none using .cs-card. Explicit
   3-row composition — not the generic nth-child pattern, which only fits
   counts of exactly 3 or a multiple of 3: type+colour paired as the two
   "fundamentals," illustration full-width as the singular hero, identity+
   communication paired as matched application examples. Wrapping the three
   images in .cs-card also gives them the grey ground DESIGN_SYSTEM.md §4 asks
   media to sit on, as a side effect of the bento conversion. */
[data-project="dreamplay"] #ch05 .cs-look-bento {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--s3);
  margin-top: var(--s4);
}
/* min-width: 0 overrides the grid item's implicit minmax(auto,1fr) sizing —
   without it, a 1fr track still sizes to its content's max-content width
   (the classic CSS grid "blowout"), which is exactly what broke this at
   mobile: the single 1fr column computed to 632.57px inside a 335px
   container because a child (the type specimen / an image) had a wider
   intrinsic min-width than the viewport. */
[data-project="dreamplay"] #ch05 .cs-look-bento > .cs-look-card { grid-column: span 12; min-width: 0; }
[data-project="dreamplay"] #ch05 .cs-look-bento > .cs-look-card:nth-child(1) { grid-column: span 5; }
[data-project="dreamplay"] #ch05 .cs-look-bento > .cs-look-card:nth-child(2) { grid-column: span 7; }
[data-project="dreamplay"] #ch05 .cs-look-bento > .cs-look-card:nth-child(3) { grid-column: span 12; }
[data-project="dreamplay"] #ch05 .cs-look-bento > .cs-look-card:nth-child(4),
[data-project="dreamplay"] #ch05 .cs-look-bento > .cs-look-card:nth-child(5) { grid-column: span 6; }
/* Type specimen must stack (not stay 2-col) inside the narrower 5/12 card —
   at that width, 2 columns squeezes the 64px "Aa" glyphs. */
[data-project="dreamplay"] #ch05 .cs-look-bento > .cs-look-card:nth-child(1) .cs-type-grid {
  grid-template-columns: 1fr;
  gap: var(--s4);
}
/* .cs-look-media also carries .cs-flat in markup (unchanged) so the image
   keeps matching the lightbox script's ".cs-flat img" selector — losing that
   class would silently drop click-to-zoom on these three images. This just
   neutralises .cs-flat's own grey-ground background/padding, which would
   otherwise double up inside the already-grey .cs-card. */
[data-project="dreamplay"] .cs-look-card .cs-flat.cs-look-media {
  background: none;
  padding: 0;
  border-radius: 0;
}
[data-project="dreamplay"] .cs-look-media { margin-top: var(--s3); }
[data-project="dreamplay"] .cs-look-media img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r-md);
  box-shadow: 0 6px 18px rgba(0,0,0,0.07);
  cursor: zoom-in;
}
[data-project="dreamplay"] .cs-look-media .cs-mediacap { margin-top: 12px; }
@media (max-width: 768px) {
  /* Heterogeneous panel types (spec block, swatch block, 3 images) — a full
     1-column stack instead of the generic 2-col mobile bento collapse, which
     would squeeze the type/colour panels illegibly. */
  [data-project="dreamplay"] #ch05 .cs-look-bento { grid-template-columns: 1fr; gap: var(--s3); }
  [data-project="dreamplay"] #ch05 .cs-look-bento > .cs-look-card:nth-child(n) { grid-column: span 1; }
  /* The illustration showcase is a dense composite, but no image on this
     page may require a scroll gesture to view in full (explicitly rejected
     earlier in this project) — scale to fit the viewport instead, breaking
     out of the card's own padding for maximum width, same as the other
     full-bleed composites elsewhere on the page. */
  [data-project="dreamplay"] #ch05 .cs-look-bento > .cs-look-card:nth-child(3) {
    margin-left: calc(-1 * var(--s3));
    margin-right: calc(-1 * var(--s3));
    padding-left: var(--s3);
    padding-right: var(--s3);
  }
  [data-project="dreamplay"] #ch05 .cs-look-bento > .cs-look-card:nth-child(3) .cs-look-media img {
    width: 100%;
    height: auto;
  }
}

/* ── Tablet 2-up (finding #11) ────────────────────────────────────────────
   Every video/phone row collapsed to 1 column at exactly the range (700-899px
   viewport, ~640-830px container) that comfortably fits 2-up (280+32+280=592px)
   — the max-width:768px mobile tier and the min-width:769px desktop tier
   (which already gives 2-up via auto-fit) leave a gap at the real iPad-
   portrait width of 768px itself. Higher specificity than the shared
   max-width:768px rule wins regardless of the media query, per the
   file's own specificity note, so this applies even where the ranges overlap. */
@media (min-width: 769px) and (max-width: 900px) {
  [data-project="dreamplay"] .cs-video-row,
  [data-project="dreamplay"] .cs-phone-row--2 {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--s4);
  }
  /* 3-cell rows keep 3 columns in this band. Forcing them to 2 orphaned the
     third cell alone on a second row beside a half-empty gap — 384px of dead
     space measured at 800px. */
  [data-project="dreamplay"] .cs-video-row--3,
  [data-project="dreamplay"] .cs-phone-row {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--s3);
  }
}

/* ── 901-999px. The desktop row geometry uses FIXED 280px columns via auto-fit.
   Three of those plus their gaps needs ~968px of viewport, so between 901 and
   ~968 only two fit and the third cell orphans onto a second row beside a
   half-empty gap. Keep 3-cell rows on three flexible columns until the fixed
   280px track genuinely fits; above 999 the desktop rule takes over unchanged. */
@media (min-width: 901px) and (max-width: 999px) {
  [data-project="dreamplay"] .cs-video-row--3,
  [data-project="dreamplay"] .cs-phone-row {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    column-gap: var(--s3);
  }
  [data-project="dreamplay"] .cs-video-row--3 .cs-vframe,
  [data-project="dreamplay"] .cs-video-row--3 .cs-vframe-letterbox,
  [data-project="dreamplay"] .cs-phone-row .cs-phone,
  [data-project="dreamplay"] .cs-phone-row .cs-vframe { max-width: 100%; width: 100%; }
}

/* ── Mobile nav tap targets — WCAG 2.5.5, 44x44 minimum.
   Hamburger measured 38x31, logo 34x34.

   This was previously scoped to [data-project="dreamplay"], with a comment
   reasoning that the nav is shared so the override should be narrow. That
   reasoning was backwards and it is the exact T12 pattern: the nav being
   shared is precisely why the fix must be GLOBAL. Scoped, it left the same
   accessibility failure live on Scout, TSYS, Social Gameplay, Home and About
   — every page except the one it was tested on. An accessibility minimum is
   never per-page. No [data-project] here, deliberately. */
@media (max-width: 768px) {
  .nav-logo { width: 44px; height: 44px; font-size: 18px; }
}
.nav-hamburger {
  width: 44px;
  height: 44px;
  padding: 0;
  justify-content: center;
  align-items: center;
}

/* ═══════════════════════════════════════════════════════════════════════════
   DREAMPLAY — GROUND-UP REBUILD (redesign brief, this pass)
   ───────────────────────────────────────────────────────────────────────────
   New, self-contained `.dp-*` classes. Deliberately NOT built on `.ch-head`/
   `.cs-cards`/`.cs-card-icon` — those are shared with TSYS/Scout/Social
   Gameplay and carry the "ruled index" treatment (`[data-project] .cs-cards >
   .cs-card`, `[data-project] .cs-card-icon`) that strips card fills and turns
   icons into thin ink line-work sitewide. Patching inside that shared,
   already-overridden system is exactly the incremental-patch trap this brief
   says to stop; a page-local component set sidesteps it instead of fighting
   it. `.cs-card`/`.cs-card-name`/`.cs-card-desc` are still reused directly
   (their OWN base rules keep real fills/sizing as long as the parent isn't
   named `.cs-cards`) — only the grid wrapper and the icon get new classes.
   Everything here is scoped to [data-project="dreamplay"].
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Chapter head: centred pill + two-tone title + one support line ─────────
   Replaces `.ch-head`'s left-aligned number+eyebrow rail for this page only;
   `.ch-head` itself is untouched so Social Gameplay keeps its own layout.
   The chapter number stays in the pill (order genuinely matters in a case
   study read start to finish), not as a big free-standing numeral. */
[data-project="dreamplay"] .dp-head { text-align: center; max-width: 700px; margin: 0 auto var(--s5); }
/* Background+border both used the pale --accent-2-surface/-border tint pair —
   measured contrast against the white page: 1.11:1 fill, 1.37:1 border, both
   functionally invisible (the exact "pill not even visible" bug flagged
   twice) — first fix, a full-strength --accent-2 border, was rejected: no
   reference actually uses a solid-colour ring like that. Re-checked
   REFERENCE_ANALYSIS.md directly: Image 6's pill is white-fill-plus-a-soft-
   shadow (no border at all); Image 5 doesn't use a pill shape; Image 9's
   pill has a thin accent-colour outline as a one-off. Shadow, not a border,
   is the actual reference device for defining a pale shape on a light page —
   matched here: a soft ambient shadow plus a real (not near-invisible) tint
   fill using the existing --accent-2-line token. */
[data-project="dreamplay"] .dp-pill {
  display: inline-flex; align-items: center; gap: 8px;
  /* #CDD5F5, not a design-system token: computed to balance two constraints
     that pull opposite ways — fill-vs-page-white needs to be dark enough to
     read as a shape (1.46:1, was 1.11:1), text-vs-fill needs to stay above
     the 4.5:1 AA floor for 12px bold (4.76:1, computed; --accent-2-line's
     4.30:1 fell just short). */
  background: #CDD5F5;
  box-shadow: 0 1px 2px rgba(53,72,212,0.06), 0 4px 10px rgba(53,72,212,0.08);
  color: var(--accent-2); border-radius: 999px; padding: 8px 16px;
  font-family: var(--f-heading); font-size: 12px; font-weight: 700;
  letter-spacing: 0.09em; text-transform: uppercase; margin-bottom: 20px;
}
[data-project="dreamplay"] .dp-head .cs-section-title {
  max-width: none; margin: 0 auto; font-weight: 600; letter-spacing: -0.02em; line-height: 1.16;
}
[data-project="dreamplay"] .dp-support { font-size: 18px; line-height: 1.62; color: var(--text-2); margin: 18px auto 0; max-width: 600px; }
@media (max-width: 768px) {
  [data-project="dreamplay"] .dp-head { margin-bottom: var(--s4); }
  [data-project="dreamplay"] .dp-pill { font-size: 11px; padding: 7px 14px; margin-bottom: 16px; }
  [data-project="dreamplay"] .dp-support { font-size: 16px; }
}
/* Everything after the centred head reads as a normal left-aligned column again */
[data-project="dreamplay"] .dp-head + * { text-align: left; }

/* ── Bento grid + solid icon tile ─────────────────────────────────────────
   `.dp-grid` is a plain 12-col grid — never named `.cs-cards`, so its
   `.cs-card` children keep their real `background: var(--surface-1)` fill
   instead of the sitewide ruled-index override. `.dp-icon` is a new element
   (not `.cs-card-icon`) precisely so it escapes the equally sitewide
   `[data-project] .cs-card-icon { background:none; border:none; }` override
   and can carry a solid accent tile, per DESIGN_SYSTEM.md §9. */
[data-project="dreamplay"] .dp-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--s3); margin-top: var(--s4); align-items: start; }
[data-project="dreamplay"] .dp-grid > .cs-card { grid-column: span 4; }
/* Same fix as .dp-pill, same reasoning: a solid-colour border was rejected
   as not matching any reference (Image 2's tile, the closest match, is a
   plain pale-grey fill with no border). --accent-2-tint on a .cs-card
   (#f6f6f8) measured 1.01:1 — invisible. --accent-2-line as fill measures
   1.65:1, a real colour step from the card, backed by a soft shadow for
   edge definition instead of a ring. The icon glyph itself only needs 3:1
   (non-text), so this fill is well clear for that; text-contrast rules
   don't apply here since there's no text on the tile. */
[data-project="dreamplay"] .dp-icon {
  width: 44px; height: 44px; border-radius: var(--r-chip);
  background: var(--accent-2-line); color: var(--accent-2);
  box-shadow: 0 1px 2px rgba(53,72,212,0.08), 0 3px 8px rgba(53,72,212,0.1);
  display: flex; align-items: center; justify-content: center; margin-bottom: 16px;
}
[data-project="dreamplay"] .dp-icon svg { width: 22px; height: 22px; display: block; }
[data-project="dreamplay"] .cs-card--accent { background: var(--accent-2); }
[data-project="dreamplay"] .cs-card--accent .cs-card-name,
[data-project="dreamplay"] .cs-card--accent .cs-card-desc { color: #ffffff; }
[data-project="dreamplay"] .cs-card--accent .cs-card-desc { color: rgba(255,255,255,0.82); }
/* The base .dp-icon's indigo fill+shadow would be invisible here since this
   card's own background IS --accent-2 (1:1 contrast). Translucent white
   fill instead, no shadow needed (the card's own edge already separates it
   from the page). */
[data-project="dreamplay"] .cs-card--accent .dp-icon { background: rgba(255,255,255,0.2); box-shadow: none; color: #ffffff; }
@media (max-width: 768px) {
  [data-project="dreamplay"] .dp-grid { grid-template-columns: 1fr; gap: var(--s2); }
  [data-project="dreamplay"] .dp-grid > .cs-card { grid-column: 1 / -1 !important; }
}
/* #ch01 "why it made sense": one wide accent claim + two supporting, 6+3+3 */
[data-project="dreamplay"] #ch01 .dp-grid--why > .cs-card:nth-child(1) { grid-column: span 6; }
[data-project="dreamplay"] #ch01 .dp-grid--why > .cs-card:nth-child(2),
[data-project="dreamplay"] #ch01 .dp-grid--why > .cs-card:nth-child(3) { grid-column: span 3; }
/* #ch06 Ludo features: four equal peers, no card is more important than another */
[data-project="dreamplay"] #ch06 .dp-grid--ludo > .cs-card { grid-column: span 3; }
@media (max-width: 900px) {
  [data-project="dreamplay"] #ch06 .dp-grid--ludo { grid-template-columns: 1fr 1fr; }
  [data-project="dreamplay"] #ch06 .dp-grid--ludo > .cs-card { grid-column: span 1; }
}
@media (max-width: 768px) {
  [data-project="dreamplay"] #ch01 .dp-grid--why > .cs-card:nth-child(n) { grid-column: 1 / -1; }
}

/* ── Horizontal numbered flow: challenges → principles → approach ───────────
   Wires up the dormant `.cs-flowmap`/`.cs-flowmap-mobile` pair (built for
   this exact chapter, never inserted into markup) rather than building a
   parallel component — its sticky-note board IS the NordCats-style
   "numerals + arrow" horizontal flow pattern the system asks for,
   applied to real content instead of a decorative index. */
[data-project="dreamplay"] .ch-sub + .cs-flowmap,
[data-project="dreamplay"] .ch-sub + .cs-flowmap-mobile { margin-top: 0; }

/* ── ch06: unwrapped from `.cs-band` (finding: whole-chapter tint, the exact
   mistake DESIGN_SYSTEM.md §13 calls out by name). The chapter now sits on white
   like every other chapter; grey stays local to the cards inside it. */
[data-project="dreamplay"] #ch06 { padding-top: 0; }

/* ── Chapter rhythm: dp-head replaces the old top hairline + s4 padding
   `.ch-head` carried, so re-declare the vertical gap between chapters here
   instead of relying on the removed rule. */
[data-project="dreamplay"] .sc-block { padding-top: var(--s4); }
[data-project="dreamplay"] .sc-block:first-child { padding-top: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE OVERRIDES, RE-ASSERTED (must stay LAST in this file)
   ───────────────────────────────────────────────────────────────────────────
   A media query adds NO specificity. Four mobile rules declared earlier in this
   file were therefore silently defeated by later, unguarded rules of equal
   specificity, purely on source order. They were dead at every viewport width.
   Measured at 375px before this fix:

     .hero-name             rendered 84px,   intended 60px   (40% oversized)
     .card-title            rendered 30px,   intended 22.5px (33% oversized)
     .polaroid-name         rendered 26px,   intended 19px
     .cs-vframe-letterbox   kept the DESKTOP ring shadow and 7px border, so the
                            0.8-spread mobile shadow was never applied to
                            letterboxed video frames.

   Re-declared here, after every competing rule, so source order now works FOR
   the mobile intent instead of against it. The original declarations are left
   in place and commented, so the intent is documented where it is authored.

   RULE FOR FUTURE EDITS: nothing may be appended below this block. A new
   unguarded rule added at the end of the file would defeat these again, which
   is exactly how they died the first time.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
  /* Retuned for the lighter hero: the old 60-100px range was sized for the
     176px Bebas slab this replaced, and looked oversized at weight 300. */
  .hero-name     { font-size: clamp(38px, 10vw, 54px); }
  .card-title    { font-size: clamp(20px, 6vw, 28px); }
  .polaroid-name { font-size: 19px; }
  /* Phone/video frame: 6px border and the ring shadow at 0.8 of the desktop
     distance, per the frame spec in DESIGN_SYSTEM.md §7. */
  .cs-vframe, .cs-vframe-letterbox {
    border-width: 6px;
    box-shadow: 0 0 0 2px #cdcbd6, 0 0 0 3.5px #9b99a6, 0 13px 29px rgba(0,0,0,0.243);
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   UNIVERSAL CORRECTIONS — owner review pass. Last in the file on purpose:
   these are the single values for every project page and must not be
   re-scoped or re-declared per page. See DESIGN_SYSTEM.md §1 and §9.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Chapter head: one grid on every page. TSYS had drifted to a 688/464
   two-column split that put the eyebrow to the RIGHT of the title instead of
   above it, and gave the numeral a full 688px column. Reference: a narrow
   numeral column, the numeral spanning both rows, eyebrow then title stacked
   beside it. */
[data-project] .ch-head {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  align-items: start;
  gap: 4px 24px;
}
[data-project] .ch-head .ch-num { grid-column: 1; grid-row: 1 / 3; }
[data-project] .ch-head .ch-eyebrow { grid-column: 2; grid-row: 1; }
[data-project] .ch-head .cs-section-title { grid-column: 2; grid-row: 2; }

/* ── A tinted band replaces the rule. Owner rule: where a chapter sits on a
   colour band, the band itself is the boundary — no separator line above the
   chapter head, and no padding standing in for one. Reverses an earlier
   change that restored the rule inside .sc-dark. */
[data-project] .cs-band .ch-head,
[data-project] .sc-dark .ch-head {
  border-top: none;
  padding-top: 0;
}

/* ── The eyebrow is text, never a chip. The white pill was a leftover from a
   band-tint workaround. */
[data-project] .ch-eyebrow,
[data-project] .cs-band .ch-eyebrow,
[data-project] .sc-dark .ch-eyebrow { background: none; padding-left: 0; padding-right: 0; }

/* ── Hero dossier: the meta items are divided by hairlines, the way the
   reference hero reads them as a set of four. Only dreamplay had them. */
[data-project] .cs-hero-dossier .cs-meta-item { border-left: 1px solid rgba(0, 0, 0, 0.1); padding-left: 24px; }
[data-project] .cs-hero-dossier .cs-meta-item:first-child { border-left: none; padding-left: 0; }
[data-project] .cs-hero-dossier .cs-meta-item--full { border-left: none; padding-left: 0; }
@media (max-width: 900px) {
  /* No vertical rules below desktop. They exist to read four items as ONE
     row; once the grid drops to two columns the items stack into pairs and a
     hairline between them divides nothing — it just adds noise and forces an
     indent that misaligns the left column against everything above it. The
     horizontal rule above Responsibilities stays: that IS a real division. */
  [data-project] .cs-hero-dossier .cs-meta-item { border-left: none; padding-left: 0; }
}

/* ── Pull quotes: ONE spec. The three pages had 60/400/normal, 56/400/+0.04em
   and 56/500/-0.04em — three different sizes, two weights, and one of them
   the only positive tracking on display type anywhere in the system. All were
   also running larger than the hero title, which no secondary element should.
   Display rules apply: weight 500, negative tracking, tight leading. */
[data-project] .cs-quoteband blockquote,
[data-project] .tsys-quote blockquote,
[data-project] .sc-quote blockquote {
  font-family: var(--f-display);
  font-size: clamp(28px, 3.2vw, 40px);
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 1.2;
  color: var(--text-1);
}
@media (max-width: 768px) {
  [data-project] .cs-quoteband blockquote,
  [data-project] .tsys-quote blockquote,
  [data-project] .sc-quote blockquote { font-size: 26px; line-height: 1.25; }
}

/* ── Big numerals: the stat band numeral is the largest number on the page.
   Scout's metric numeral was running 58px against the band's 44px. */
[data-project] .sc-metric-num,
[data-project] .sc-stat-num,
[data-project] .tsys-stat-num,
[data-project] .cs-statband-num { font-size: clamp(34px, 3.6vw, 44px); }

/* ── Numeral units and suffixes are one size, whatever they are called:
   "wk", "mo", "s", "+", "%", "x". Scout's "+" was rendering full size. */
[data-project] .cs-statband-suffix,
[data-project] .sc-metric-num .unit,
[data-project] .sc-stat-num .unit {
  font-size: 0.5em; font-weight: 600; vertical-align: baseline;
  letter-spacing: 0; margin-left: 2px;
}

/* ── Caption TITLES: one spec. The caption body was already identical
   everywhere (15/400/--text-3), but its title was 16px/700 on the page-local
   forks against the reference 18px/600. A smaller, heavier title over
   identical body copy is exactly what reads as "wrong typeface" — the pair,
   not the face, was wrong. */
[data-project] .cs-mediacap-title,
[data-project] .sc-cell-name,
[data-project] .sc-video-name,
[data-project] .tsys-screen-name {
  font-size: 18px; font-weight: 600; line-height: 1.3;
  letter-spacing: normal; color: var(--text-1); text-wrap: wrap;
}
@media (max-width: 768px) {
  [data-project] .cs-mediacap-title,
  [data-project] .sc-cell-name,
  [data-project] .sc-video-name,
  [data-project] .tsys-screen-name { font-size: 16px; }
}

/* ── Quote attribution: label ABOVE the quote, with real air under it. It sets
   the line up rather than footnoting it. TSYS had margin-bottom 0, so a 13px
   caps label sat flush against a 40px display quote and the two read as one
   run-on block. One spec for all three quote components. */
[data-project] .cs-quoteband figcaption,
[data-project] .tsys-quote figcaption,
[data-project] .sc-quote figcaption {
  order: -1;
  margin: 0 0 var(--s3);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.1em;
  line-height: 1.35;
  text-transform: uppercase;
  color: var(--accent-2);
}
[data-project] .cs-quoteband,
[data-project] .tsys-quote,
[data-project] .sc-quote { display: flex; flex-direction: column; }

/* ── A figcaption on a quote plays ONE OF TWO ROLES, and they are not
   interchangeable:

   1. ATTRIBUTION (default, above the quote) — names the source or the reason
      the quote matters: "The core research insight that reframed the
      redesign". It is a label: caps, 13px, accent. It sets the quote up.

   2. STATEMENT (.quote-statement, below the quote) — a full sentence that
      CONTINUES the argument: "Customers don't need more options. They need an
      expert to help them choose." Setting this in uppercase caps mangles it —
      a sentence is not a label, and forcing it into label styling makes it
      unreadable and misrepresents what it is.

   Statements read below the quote, in sentence case, at body scale. */
[data-project] .cs-quoteband figcaption.quote-statement,
[data-project] .tsys-quote figcaption.quote-statement,
[data-project] .sc-quote figcaption.quote-statement {
  order: 1;
  margin: var(--s3) 0 0;
  font-size: 18px;
  font-weight: 400;
  letter-spacing: normal;
  line-height: 1.6;
  text-transform: none;
  color: var(--text-2);
  max-width: 760px;
}
@media (max-width: 768px) {
  [data-project] .cs-quoteband figcaption.quote-statement,
  [data-project] .tsys-quote figcaption.quote-statement,
  [data-project] .sc-quote figcaption.quote-statement { font-size: 17px; }
}

/* ── Responsive line break utility. A <br class="br-desktop"> breaks the line
   at desktop only and collapses to nothing below 901px, where the narrower
   measure already breaks the line for you and a forced break would strand a
   short orphan line. Use it to control where a display line breaks — never to
   fake spacing. */
br.br-desktop { display: none; }
@media (min-width: 901px) { br.br-desktop { display: inline; } }

/* ── Hero: NO vertical rules on mobile or tablet. A vertical hairline exists
   to read a row of four items as ONE set. Below 901px the grid drops to two
   columns and the items stack into pairs — a line between them divides
   nothing, adds noise, and forces an indent that misaligns the left column
   against the label and title above it. This is a rule for the whole hero,
   not just the meta row: nothing inside .cs-hero carries a left or right
   border below 901px. Horizontal rules stay — those mark real divisions. */
@media (max-width: 900px) {
  [data-project] .cs-hero *,
  [data-project] .cs-hero-dossier * { border-left: none; border-right: none; }
  [data-project] .cs-hero-dossier .cs-meta-item { padding-left: 0; }
}

/* ── Stat band radius: the band is a large panel and takes the panel radius,
   not the 8px --r-xl used for small elements. It was rendering square-ish at
   every breakpoint against an 18px dossier wrapping it. */
[data-project] .cs-hero-dossier .cs-statband,
[data-project] .cs-hero-dossier .sc-stats,
[data-project] .cs-hero-dossier .tsys-stats { border-radius: 24px; }

/* ── Cards, mobile only: tighter. At 375 the cards stack into one column, so
   the 24px row gap and the 22/20/24 padding that read correctly as a 3-up
   grid on desktop turn into a lot of dead vertical space in a single stack —
   the icon, name and description end up further apart than the cards are
   from each other. Desktop is untouched. */
@media (max-width: 768px) {
  [data-project] .cs-cards > .cs-card,
  [data-project] .sc-grid > .sc-item,
  [data-project] .tsys-process > .tsys-step,
  [data-project] .cs-principle-grid > .cs-principle { padding: 18px 0 20px; }
  [data-project] .cs-cards,
  [data-project] .sc-grid,
  [data-project] .tsys-process { column-gap: var(--s2); row-gap: var(--s2); }
  [data-project] .cs-card-icon,
  [data-project] .sc-item-icon,
  [data-project] .tsys-chip { width: 40px; height: 40px; margin-bottom: 12px; }
  [data-project] .cs-card-icon svg,
  [data-project] .sc-item-icon svg,
  [data-project] .tsys-chip svg { width: 20px; height: 20px; }
}

/* ═══ CTA CARD — one component, one spec, every page ════════════════════════
   The CTA card is shared by Home, About and any future page. Its heading and
   its label were both specced only under [data-page="home"], with a comment
   reasoning that the classes are shared so the override should stay narrow.
   Same inverted reasoning as the nav tap-target bug (T12): shared is exactly
   why it must NOT be scoped. About was left rendering the heading at
   clamp(44px, 6vw, 72px) — 72px against Home's 38px, nearly double — and the
   label at 14/700/ink against Home's 12/600/--text-3.

   Declared on the component, not the page. Unscoped, deliberately. */
.cta-heading {
  font-family: var(--f-display);
  font-size: clamp(28px, 2.9vw, 38px);
  font-weight: 600;
  letter-spacing: -0.026em;
  line-height: 1.12;
  text-transform: none;
}
.cta-card .label {
  font-family: var(--f-heading);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  line-height: 1.25;
  text-transform: uppercase;
  color: var(--text-3);
}

/* ── Chapter lede: the opening paragraph of a chapter, one step up from body
   so the chapter opens with a slightly larger voice. Scout and TSYS each
   declared it page-locally (20px / 21px) and each was then defeated by a
   later equal-specificity page-local body-text rule, so the lede rendered
   identical to body copy on both. Declared once, here, where nothing later
   competes. */
[data-project] .ch-lede p,
[data-project] .cs-body-text.ch-lede p { font-size: 20px; line-height: 1.6; }
@media (max-width: 768px) {
  [data-project] .ch-lede p,
  [data-project] .cs-body-text.ch-lede p { font-size: 17px; line-height: 1.6; }
}

/* ── T7 sweep: where a grid already owns the gap, the child must not add its
   own margin. Two real double-counts remained after the earlier --flush fix:

   1. .cs-split2 children other than --flush variants. At 375 the split
      collapses to one column, so row-gap 24 applies AND the child's own
      margin-top 24 applies = 48px where 24 was intended.
   2. .cs-meta-item--full (Responsibilities). It marks a real division with a
      rule + 20px padding-top, but also carried margin-top 24 on top of the
      row's 24-32px gap, so the divider floated 56-76px below the meta items.
      The rule and its padding are the division; the gap is the grid's. */
[data-project] .cs-split2 > * + * { margin-top: 0; }
/* `> * + *` is only (0,1,0) on the child, so any rule naming the child by
   class outranks it. Name them explicitly to match that specificity. */
[data-project] .cs-split2 > .cs-video-row,
[data-project] .cs-split2 > .cs-phone-row,
[data-project] .cs-split2 > .cs-video-cell,
[data-project] .cs-split2 > .cs-phone-cell,
[data-project] .cs-split2 > .cs-impact,
[data-project] .cs-split2 > .cs-flat,
[data-project] .cs-split2 > .cs-cards,
[data-project] .cs-split2 > .cs-body-text { margin-top: 0; }
[data-project] .cs-hero-dossier .cs-meta-item--full { margin-top: 0; }

/* ═══ FLOW DIAGRAM — a continuous track with nodes ON it ════════════════════
   NOT cards in a row with arrows between them. That is still a card grid,
   just rearranged, and it reads as four objects that happen to be adjacent.
   A flow diagram is one continuous line that the eye follows, with the steps
   sitting ON that line. The line is the component; the steps are marks on it.

   Structure: a 2px track runs marker-centre to marker-centre. Each node is a
   44px tinted disc sitting on the track with a white ring, so the track reads
   as passing behind it. Numeral, name and description hang off the node.
   There is no card, no fill, no radius — nothing to make a step look like a
   separate object, and no outer frame: the diagram sits directly on the page,
   so the track is the only line in it.

   RESPONSIVE BY ROTATION, not by reflow. The same diagram turns 90 degrees:
     min-901   horizontal track, nodes left to right, text below each node
     max-900   vertical track, nodes top to bottom, text right of each node
   Nothing re-columns, nothing squashes, no arrangement is invented for a
   middle band. A flow that becomes a 2x2 grid at tablet stops being a flow.

   Node label width is 205px at 1024 and 240px (capped) from ~1200 up, so the
   description runs 3-4 lines on the widest step. That is correct for a
   diagram: these are labels on a track, not card copy, and they are read
   glancing along the line rather than as prose. It is also why the rotation
   threshold is 900 and not lower: below that the horizontal columns would
   drop under ~180px and the labels would start breaking mid-phrase.

   The arrowhead sits at the END of the track, once, marking direction for the
   whole diagram rather than repeating between every pair. */
[data-project] .cs-flowline {
  list-style: none;
  margin: var(--s4) 0 0;
  padding: 0;
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  column-gap: var(--s3);
}
/* The track. Inset to the first and last marker centres so it never overshoots
   the diagram. 12.5% is half of a quarter-width column. */
[data-project] .cs-flowline::before {
  content: "";
  position: absolute;
  left: 12.5%; right: 12.5%;
  top: 22px;
  height: 2px;
  background: var(--border-2);
}
/* Direction, stated once at the end of the run. */
[data-project] .cs-flowline::after {
  content: "";
  position: absolute;
  right: calc(12.5% - 1px);
  top: 22px;
  width: 8px; height: 8px;
  border-top: 2px solid var(--border-2);
  border-right: 2px solid var(--border-2);
  transform: translate(0, -50%) rotate(45deg);
}
[data-project] .cs-flowline-node {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  min-width: 0;
}
/* The node sits ON the track. The white ring is what makes the track read as
   continuous behind it rather than butting into it. */
[data-project] .cs-flowline-marker {
  position: relative;
  z-index: 1;
  flex: none;
  width: 44px; height: 44px;
  border-radius: 50%;
  background: var(--accent-2-tint);
  box-shadow: 0 0 0 6px #ffffff;
  color: var(--accent-2);
  display: flex; align-items: center; justify-content: center;
}
[data-project] .cs-flowline-marker svg { width: 22px; height: 22px; stroke-width: 2; }
[data-project] .cs-flowline-body { margin-top: var(--s3); max-width: 240px; }
[data-project] .cs-flowline-num {
  display: block;
  font-family: var(--f-heading);
  font-size: 12px; font-weight: 600;
  letter-spacing: 0.1em; line-height: 1.25;
  text-transform: uppercase;
  color: var(--accent-2);
  margin-bottom: 6px;
}
[data-project] .cs-flowline-name {
  font-size: 16px; font-weight: 600; line-height: 1.3;
  color: var(--text-1); margin: 0 0 6px;
}
[data-project] .cs-flowline-desc {
  font-size: 15px; font-weight: 400; line-height: 1.55;
  color: var(--text-3); margin: 0; text-wrap: wrap;
}

/* ── ROTATED: the same diagram, vertical. Track runs down the marker column,
   text sits to the right of each node. Below 901 there is never enough width
   for four readable columns — at 1024 the description already wraps to four
   lines in a 203px column, and it only gets worse. */
@media (max-width: 900px) {
  [data-project] .cs-flowline {
    grid-template-columns: 44px minmax(0, 1fr);
    column-gap: var(--s3);
    row-gap: var(--s4);
  }
  [data-project] .cs-flowline::before {
    left: 21px; right: auto;
    top: 22px; bottom: 22px;
    width: 2px; height: auto;
  }
  [data-project] .cs-flowline::after {
    left: 18px; right: auto;
    top: auto; bottom: 18px;
    transform: rotate(135deg);
  }
  [data-project] .cs-flowline-node {
    display: contents;                 /* marker and body become grid items */
  }
  [data-project] .cs-flowline-body {
    margin-top: 0;
    max-width: none;
    text-align: left;
    padding-top: 2px;
  }
}
@media (max-width: 480px) {
  [data-project] .cs-flowline { column-gap: var(--s2); }
}

/* ── .cs-annotated ────────────────────────────────────────────────────────────
   An annotated component board: one app screen (or multi-frame flow) with
   labelled callouts and dotted leaders, shipped as a flat PNG.

   An earlier `.cs-annotated-keys` mirror-`<ol>` component (repeating every
   callout as real text below the figure) has been removed entirely — it was
   legacy from the old scratchpad/board-gen.html pipeline, where callout copy
   was small/generator-drawn and unreadable once the board downscaled ~14x on
   a 375px phone. Every board on social-gameplay.html today is instead
   exported directly from Tushar's own Figma file, at a scale where titles/
   chips/captions are already large and legible baked into the PNG, so the
   mirror list was pure duplicate content, not an accessibility aid. It was
   asked removed repeatedly (four separate boards, one session) before being
   deleted outright — do not reintroduce it. */
[data-project] .cs-annotated { margin: 0; }
[data-project] .cs-annotated img {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--border-1); border-radius: 18px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.07);
  cursor: zoom-in;
}
/* Compositing motion into a board: a small clip positioned over the exact
   screen it replaces, using the source Figma frame's own rect (as a % of the
   board so it tracks the image at any width) rather than a guessed overlay. */
/* Percentage height on an absolutely positioned child only resolves against a
   containing block with a DEFINITE height; .cs-annotated-media's height is
   otherwise auto (set by the img), which silently zeroes .cs-annotated-embed.
   aspect-ratio is set inline per board (the img's own natural ratio) so this
   stays generic across boards instead of hardcoding one board's dimensions. */
[data-project] .cs-annotated-media { position: relative; }
/* js/main.js wraps every <video> in a .video-shell with its own `position:
   relative`, which becomes the embed's containing block instead of this div —
   and the shell, being an unsized inline-block around an absolutely
   positioned child, collapses to 0x0, zeroing every percentage on the embed.
   display:contents un-boxes the shell here so .cs-annotated-media (this div)
   is the containing block, as intended; the shell's is-playing class toggle
   has no visual effect site-wide (the play-badge it targeted was removed). */
[data-project] .cs-annotated-media .video-shell { display: contents; }
[data-project] .cs-annotated-embed {
  position: absolute; display: block;
  object-fit: cover;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.06);
}
/* Reads as a looping GIF, not a video: no scrub bar/controls. main.js forces
   `controls = true` on every <video> site-wide for WCAG 1.4.2 (a way to pause
   autoplaying media); this component is muted with no audio to pause, so the
   requirement doesn't apply — main.js explicitly skips this class. */
[data-project] .cs-annotated-embed::-webkit-media-controls { display: none !important; }

/* ── Board placement rule ─────────────────────────────────────────────────────
   A board is a media frame. It takes the same gap to the content above it as
   any other frame in a sub-section, and it fits the device width at every size.

   `.cs-boardrow` pairs a board with a live video. The video cannot be baked into
   the PNG, so it is a real element in a second grid column. Both cells scale
   with the row, so the pair fits any width without a fixed pixel anywhere. */
[data-project] figure.cs-annotated { margin: var(--s5) 0 0; }
[data-project] .ch-sub + figure.cs-annotated,
[data-project] .cs-body-text + figure.cs-annotated,
[data-project] ul.cs-bullets + figure.cs-annotated { margin-top: var(--s5); }
[data-project] figure.cs-annotated + .cs-impact,
[data-project] figure.cs-annotated + .cs-video-row,
[data-project] figure.cs-annotated + .cs-phone-row { margin-top: var(--s5); }

[data-project] .cs-boardrow {
  display: grid;
  grid-template-columns: 1fr var(--boardrow-video, 24%);
  gap: var(--s4);
  align-items: start;
  margin: var(--s5) 0 0;
}
[data-project] .cs-boardrow > figure.cs-annotated { margin: 0; min-width: 0; }
[data-project] .cs-boardrow .cs-boardrow-video { min-width: 0; }
/* the clip fills its column and keeps its own aspect, so it scales with the row
   instead of being pinned to a pixel width */
[data-project] .cs-boardrow .cs-boardrow-video video {
  display: block; width: 100%; height: auto;
  border-radius: 14px; box-shadow: 0 0 0 1px rgba(0,0,0,.10);
}
@media (max-width: 900px) {
  /* one column: the board scales to the viewport and the video follows it */
  [data-project] .cs-boardrow { grid-template-columns: 1fr; gap: var(--s5); }
}

/* .cs-boardrow-video keeps its own aspect and does not stretch to fill a row it
   no longer shares with a board (Privacy board went full-width, per Tushar:
   do not split the settings screen, show it whole). */
[data-project] .cs-boardrow-video video { max-height: none; }


/* ═══════════════════════════════════════════════════════════════════
   Social Gameplay: archetype cards + ecosystem landscape.
   Both replace earlier components on that page — the bento archetype
   grid and `.cs-ecomap`. They share one language: white card, hairline
   ring plus hue-tinted shadow, colour carried by a tinted icon chip.

   Colour note: this page is otherwise grey by design (`--proj-accent`
   is #5a5a62). These two components are the deliberate exception, and
   the five hues below are each measured ≥4.5:1 on white. Red is
   excluded — it reads as an error state everywhere else on the site.
   ═══════════════════════════════════════════════════════════════════ */

/* ══════════════ Behaviour field ══════════════ */
.cs-arch{
  --t1:#111111; --t2:#444448; --t3:#6a6a72;
  --rule:#E6E6EC;
  font-family:'Schibsted Grotesk',system-ui,sans-serif;
  color:var(--t1);
}
.cs-arch-lede{font-size:15px;line-height:1.55;color:var(--t2);max-width:60ch;margin:0 0 32px;}
.cs-arch-lede strong{font-weight:600;color:var(--t1);}

/* Rails removed: each card now states its own two coordinates, so a shared
   axis frame was saying the same thing twice. */
.cs-arch-plot{display:block;position:relative;}

/* ─── True 3D coverflow ────────────────────────────────────────────────
   Not a scroll track: the cards are absolutely stacked on one centre point
   and pushed back in Z by their distance from the focused card, so the
   neighbours sit BEHIND it rather than beside it. Exactly one card is
   visible either side; anything further is faded out and taken out of the
   tab order by main.js. The ring wraps, so next from the last card is the
   first. Perspective lives on the stage, not the cards, so all five share
   one vanishing point. */
.cs-arch-stage{
  position:relative;
  perspective:1600px;
  perspective-origin:50% 45%;
  height:var(--arch-h,420px);
  margin:0 auto;
  /* Not clipped. Off-ring cards are already opacity:0 and inert, so there is
     nothing left for a clip to hide — all it ever did was cut the shadows off
     the focused card and its two neighbours. */
  overflow:visible;
}
.cs-arch-grid{
  position:relative;
  height:100%;
  transform-style:preserve-3d;
  list-style:none;margin:0;padding:0;
}
/* `.cs-arch-grid >` on purpose: the base `.cs-arch-card` rule below declares
   position:relative at equal (0,1,0) specificity and sits LATER in the file,
   so a bare `.cs-arch-card` here loses on source order and the whole 3D
   stack silently collapses back into normal flow. */
.cs-arch-grid > .cs-arch-card{
  position:absolute;
  /* Inset, not flush: the stage clips overflow, and at top:0 the card's own
     shadow (and the rotated neighbours' top corners) got cut off. */
  top:24px;
  left:50%;
  width:min(400px,80vw);
  margin-left:calc(min(400px,80vw) / -2);   /* true centre, independent of width */
  transform-origin:50% 50%;
  backface-visibility:hidden;
  transition:transform .55s cubic-bezier(.22,1,.36,1),
             opacity   .55s cubic-bezier(.22,1,.36,1),
             filter    .55s cubic-bezier(.22,1,.36,1),
             box-shadow .35s ease;
  will-change:transform,opacity,filter;
}
/* main.js writes the composed transform straight onto style.transform, rather
   than assembling it here from custom properties. The var() version silently
   computed to `none` on this page — one invalid substitution voids the whole
   declaration, and a transform that fails this way leaves no trace in devtools
   beyond an identity matrix. Writing the finished string is deterministic. */
.cs-arch-card[aria-hidden="true"]{pointer-events:none;}
.cs-arch-stage.is-dragging > .cs-arch-grid > .cs-arch-card{transition:none;}
.cs-arch-grid > .cs-arch-card[data-focus="1"]{
  box-shadow:
    0 0 0 1px rgba(17,17,20,.14),
    0 2px 4px rgba(17,17,20,.07),
    0 24px 48px -12px rgba(17,17,20,.20);
}

/* Controls */
.cs-arch-nav{
  display:flex;align-items:center;justify-content:center;gap:16px;margin-top:20px;
}
.cs-arch-arrow{
  width:40px;height:40px;flex:none;border-radius:50%;
  display:grid;place-items:center;cursor:pointer;
  background:#fff;color:#3548D4;border:0;padding:0;
  box-shadow:0 0 0 1px rgba(17,17,20,.10), 0 2px 8px -2px rgba(17,17,20,.14);
  transition:box-shadow .2s ease, transform .2s ease;
}
.cs-arch-arrow:hover{transform:translateY(-1px);
  box-shadow:0 0 0 1px rgba(53,72,212,.35), 0 4px 12px -2px rgba(53,72,212,.28);}
.cs-arch-arrow:focus-visible{outline:2px solid #3548D4;outline-offset:3px;}
.cs-arch-arrow svg{width:18px;height:18px;fill:none;stroke:currentColor;
  stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.cs-arch-dots{display:flex;gap:0;}
/* The painted dot stays 7px; the *button* is 24px so it meets WCAG 2.5.8.
   Drawing the dot in ::before is what lets the target grow without the
   indicator growing with it. */
.cs-arch-dot{
  width:24px;height:24px;min-width:24px;min-height:24px;
  display:grid;place-items:center;
  background:none;border:0;padding:0;cursor:pointer;
}
.cs-arch-dot::before{
  content:"";width:7px;height:7px;border-radius:50%;
  /* #8A8A96 is 3.18:1 on white. The old #D8D8E0 was 1.42:1 — invisible as an
     indicator, and a 1.4.11 failure. */
  background:#8A8A96;
  transition:background-color .25s ease,transform .25s ease;
}
.cs-arch-dot[aria-current="true"]::before{background:#3548D4;transform:scale(1.35);}
.cs-arch-dot:focus-visible{outline:2px solid #3548D4;outline-offset:-2px;border-radius:4px;}

/* Card: no border, no grey fill. Two-layer shadow — a tight contact shadow plus
   a wide soft one — tinted with the card's own hue so the colour reads even in
   the shadow rather than sitting on the card as a patch. */
.cs-arch-card{
  position:relative;background:#fff;border-radius:var(--r-tile);padding:20px;
  display:flex;flex-direction:column;gap:6px;
  box-shadow:
    0 0 0 1px rgba(17,17,20,.14),
    0 1px 2px rgba(17,17,20,.06),
    0 10px 28px -6px var(--hue-shadow, rgba(17,17,20,.13));
}
/* No hover/focus lift, no click outline: on both breakpoints, clicking or
   tapping a card does nothing (desktop's layout() ignores it entirely; the
   mobile front card's own click handler is a guaranteed no-op, and its
   neighbours are pointer-events:none). A tile that visually promises an
   action it can't deliver is worse than one that promises nothing. */

/* Icon chip carries the hue as a field rather than a dot, so the colour is
   legible at a glance without tinting the card itself. Same construction as
   the site's .cs-card-icon: 24-unit line icon, 1.6 stroke, round caps. */
.cs-arch-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:2px;}
.cs-arch-ico{
  width:48px;height:48px;flex:none;border-radius:var(--r-md);
  display:flex;align-items:center;justify-content:center;
  background:var(--hue-tint);color:var(--hue);
}
.cs-arch-ico svg{width:25px;height:25px;display:block;
  fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;}
.cs-arch-num{
  font-size:12px;font-weight:700;letter-spacing:.1em;line-height:1;
  font-variant-numeric:tabular-nums;color:var(--hue);
}
/* min-height reserves 2 lines on the name and 3 on the desc — the longest
   either runs to across the five archetypes. margin-top:auto on the meters
   (below) only pins them to the card's bottom; it doesn't guarantee equal
   BREATHING ROOM above them. Without a floor, a short one-line card left 51px
   of air before its meters while "Collaborative Influencer" (2-line name,
   3-line desc) was left 6px — same meter position, wildly different rhythm
   getting there. Reserving the space up front means every card's meters sit
   the same distance below its actual text, not just the same distance from
   the card edge. */
.cs-arch-name{margin:0;font-size:17px;font-weight:600;line-height:1.3;color:var(--t1);min-height:calc(1.3em * 2);}
.cs-arch-desc{margin:0;font-size:15px;line-height:1.5;color:var(--t2);min-height:calc(1.5em * 3);}

/* Position meters. Shown on every card at every width — with the axis
   rails gone these are the only thing carrying each archetype's coordinates. */
/* margin-top:auto, not a fixed value: cards are equal height (grid stretch on
   desktop), but name/desc wrap to different line counts, so a fixed gap left
   the meters starting at a different y on every card. Pinning to the bottom
   of the flex column puts them at the same baseline regardless. */
.cs-arch-meters{margin-top:auto;padding-top:16px;display:flex;flex-direction:column;gap:18px;}
.cs-arch-meter{display:flex;flex-direction:column;gap:6px;}
.cs-arch-mlab{font-size:12px;line-height:1;color:var(--t3);display:flex;justify-content:space-between;gap:16px;}
.cs-arch-track{position:relative;height:3px;border-radius:3px;background:#EDEDF2;}
.cs-arch-track::after{
  content:"";position:absolute;top:50%;width:11px;height:11px;border-radius:50%;
  background:var(--hue);transform:translate(-50%,-50%);
  box-shadow:0 0 0 3px #fff;
}
.cs-arch-lo::after{left:8%} .cs-arch-mid::after{left:50%} .cs-arch-hi::after{left:92%}

.cs-arch-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;}

/* ── hues. Every one measured ≥4.5:1 on #ffffff (readout below the plot).
   No red: it reads as an error state everywhere else on this site. The five
   walk the wheel from the site's blue through teal, violet, amber and green. ── */
/* One system blue across all five, not a rainbow — the archetypes are
   variations on one system, and --accent-2 is this site's only accent. */
.cs-arch--h1,.cs-arch--h2,.cs-arch--h3,.cs-arch--h4,.cs-arch--h5{
  --hue:#3548D4;--hue-tint:#EEF0FE;--hue-shadow:rgba(53,72,212,.20);
}

/* 768, not an invented 640 — §3 has exactly two real boundaries. */
@media (max-width:768px){
  .cs-arch-stage{--arch-h:460px;perspective:1100px;}
  .cs-arch-card{padding:20px;}
}

/* ── Desktop: one static portrait row, not a coverflow ──────────────────
   The 3D stack reads well on a phone, where only one card can be in view at
   a time anyway. On a desktop-width screen there's room to show the whole
   set, and a mechanism that hides four of five archetypes stops being a
   space-saver and starts being an obstacle. Same cards, same markup — main.js
   detects this width and stops writing the coverflow transform/opacity/filter
   per card, so the grid below is what actually places them. */
@media (min-width:769px){
  .cs-arch-nav{display:none;}
  .cs-arch-stage{perspective:none;height:auto;}
  .cs-arch-grid{
    display:grid;
    grid-template-columns:repeat(5,minmax(0,1fr));
    gap:var(--s3);
    transform-style:flat;
  }
  .cs-arch-grid > .cs-arch-card{
    position:static;
    top:auto;left:auto;width:auto;margin-left:0;
    height:100%;
  }
  /* Portrait proportions at 5-up: the icon stacks above the copy instead of
     sharing a row with it, so a ~200px column reads as a tall card rather
     than a squeezed-flat one. */
  .cs-arch-head{flex-direction:column;align-items:flex-start;gap:14px;}
  .cs-arch-card{padding:18px;}
}
/* Reduced motion keeps the 3D arrangement (it carries the "one in focus"
   meaning) and only drops the animation between states. */
@media (prefers-reduced-motion:reduce){
  /* Must out-specify `.cs-arch-grid > .cs-arch-card` (0,2,0), which declares
     the transitions later in the file. At (0,1,0) this rule did nothing and
     reduced-motion users still got the full 3D spin. */
  .cs-arch-grid > .cs-arch-card{transition:none;}
  /* Blur is a motion-adjacent effect for this audience and hurts legibility
     more than it helps once the animation is gone. */
  .cs-arch-grid > .cs-arch-card:not([data-focus="1"]){filter:none !important;}
}

/* ================= cs-audio-toast — one-time "turn audio on" nudge =================
   Fired from js/main.js the first time a muted, audio-capable video scrolls into
   view on a project page. Floating pill, not a full-width bar — sits below the
   nav rather than covering the logo, dismissible, auto-hides on its own. */
.cs-audio-toast{
  position:fixed; top:92px; left:50%;
  transform:translateX(-50%) translateY(-12px);
  z-index:250; /* above .dropdown (200), below .lightbox (1000) */
  display:flex; align-items:center; gap:10px;
  max-width:min(420px, calc(100vw - 32px));
  background:var(--accent-2); color:#fff;
  border-radius:8px;
  padding:12px 14px 12px 16px;
  box-shadow:0 10px 28px -6px rgba(17,17,20,.32), 0 1px 2px rgba(17,17,20,.1);
  font-size:14px; line-height:1.4;
  opacity:0; pointer-events:none;
  transition:opacity .22s var(--ease-out,ease), transform .22s var(--ease-out,ease);
}
.cs-audio-toast[data-open="1"]{ opacity:1; transform:translateX(-50%) translateY(0); pointer-events:auto; }
.cs-audio-toast-icon{ flex:0 0 auto; width:18px; height:18px; display:block; color:#fff; }
.cs-audio-toast-icon svg{ width:100%; height:100%; display:block; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
.cs-audio-toast-msg{ flex:1 1 auto; }
.cs-audio-toast-close{
  flex:0 0 auto; width:28px; height:28px; display:grid; place-items:center;
  background:rgba(255,255,255,.16); border:none; border-radius:50%;
  color:#fff; font-size:16px; line-height:1; cursor:pointer;
}
.cs-audio-toast-close:hover{ background:rgba(255,255,255,.28); }
@media (max-width:768px){
  .cs-audio-toast{ top:84px; padding:10px 10px 10px 14px; font-size:14px; }
  /* 44px matches the site's touch-target convention (see .lightbox-close) —
     transparent background keeps the visual size unchanged from desktop. */
  .cs-audio-toast-close{ width:44px; height:44px; background:transparent; }
}
@media (prefers-reduced-motion:reduce){
  .cs-audio-toast{ transition:opacity .01s linear; transform:translateX(-50%); }
  .cs-audio-toast[data-open="1"]{ transform:translateX(-50%); }
}

