
/* ============================================================
   RESET
============================================================ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:focus-visible {
  outline: 2px solid rgba(var(--accent-rgb), 0.9);
  outline-offset: 3px;
}

:root {
  /* ── The ground ──
     This was #161616, a deliberately NEUTRAL near-black: the old blob carried
     jade and violet, and any hue in the page fought it. The blob's palette is
     now the sky reference's — cornflower and apricot, and high-key — so a
     neutral black reads as a hole cut behind it rather than as a ground it
     belongs to.

     So the page takes the blob's own hue (about 212 degrees) at the far dark
     end. Two things fall out of that: the blue half of the blob now sits on a
     related colour rather than on nothing, and the apricot half reads as warm
     light against a cool ground instead of as a stray orange. It stays dark
     enough that body text holds 14.7:1 — the neutral it replaced was 15.2:1,
     so nothing is traded away for it. */
  /* Lifted again, from #495867. This is as far as it goes without changing
     something else: --muted below rides directly on this ground at 4.62:1, and
     one more step of this size puts it under the 4.5:1 that body text needs.
     Past here the scheme has to give something up — a lighter muted that stops
     reading as muted, a different secondary colour, or a flip to dark-on-light. */
  --bg:      #50606f;   /* slate, lifted further toward white */
  --text:    #ecebe7;   /* near-neutral off-white */
  /* Lifted from 0.55 as the ground rose. Secondary text rides on the
     background, so every lift eats its contrast; at 0.88 against this ground it
     lands on 4.62:1, clearing AA with about one step to spare. Raising the
     alpha further is not really available — it is already close enough to full
     text that the muted/full distinction is nearly gone. */
  --muted:   rgba(236, 235, 231, 0.88);
  --border:  #7991a9;   /* the ground lifted, so rules read as part of it */
  --nav-h:   80px;
  --px:      clamp(20px, 5vw, 80px);
  --ease:    cubic-bezier(0.16, 1, 0.3, 1);
  --nav-bg:  rgba(85, 102, 118, 0.68);
  /* ── Type system ── see DESIGN.md
     Two families with separate jobs. Serif is DISPLAY only: page titles,
     section headings, greetings, project titles and big numbers. Everything
     read (body, lists, subheads, card copy, captions) and all interface is
     DM Sans: at reading size on the slate ground the serif was the hard part
     to read, and the sans is the face the nav already uses.
     Ten sizes, and nothing outside them. */
  --serif:   'Source Serif 4', Georgia, 'Times New Roman', serif;
  --sans:    'DM Sans', -apple-system, BlinkMacSystemFont, sans-serif;

  --fs-display: clamp(40px, 6vw, 80px);    /* page greetings */
  --fs-h1:      clamp(32px, 4.5vw, 56px);  /* page + case-study titles */
  --fs-h2:      clamp(26px, 3vw, 38px);    /* section headings */
  --fs-h3:      clamp(20px, 2vw, 26px);    /* subsections, project titles */
  --fs-lead:    clamp(17px, 1.5vw, 20px);  /* intros, subtitles */
  --fs-body:    clamp(16px, 1.2vw, 18px);  /* reading copy */
  --fs-h4:      18px;                      /* card titles */
  --fs-sm:      15px;                      /* card copy, meta, UI text */
  --fs-xs:      13px;                      /* captions, table cells, links */
  --fs-label:   12px;                      /* uppercase labels, tags */
  /* Home hero only: the one place the type is allowed past the scale. */
  --fs-hero:     clamp(52px, 6.4vw, 120px);
  --fs-hero-sub: clamp(18px, 1.6vw, 26px);
  --tracking-label: 0.08em;
  --accent-rgb: 190, 224, 253;   /* the blob's cornflower, lifted clear */
  --subtitle-size: var(--fs-lead);
}

html { scroll-behavior: smooth; -webkit-font-smoothing: antialiased; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
  overflow-x: hidden;
}

body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 9997;
  pointer-events: none;
  opacity: 0.05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}

body.is-loading { overflow: hidden; }
body { transition: opacity 0.22s ease; }
body.page-exit { opacity: 0; pointer-events: none; }

/* ── Cross-document View Transitions ──────────────────────────────────────
   Opt both source and destination pages into the View Transitions API.
   Ignored by browsers that don't support it; main.js fade handles those.
   TO REVERT: remove view-transitions.js script tags from HTML pages.
   ──────────────────────────────────────────────────────────────────────── */
@view-transition { navigation: auto; }

/* Case study cover always carries this name so it can morph from the card */
.cs-cover img { view-transition-name: project-cover; }

/* Speed up the morph slightly and use the site's easing */
::view-transition-group(project-cover) {
  animation-duration: 0.42s;
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
/* Softer default crossfade for nav-link transitions (non-project pages) */
::view-transition-old(root), ::view-transition-new(root) {
  animation-duration: 0.28s;
}

/* ── Hover video preview ────────────────────────────────────────────────── */
.project-image { position: relative; }

.project-video-preview {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.4s ease;
  pointer-events: none;
}
body.has-cursor, body.has-cursor * { cursor: none !important; }

/* ── Custom cursor ──
   It used to be painted in --text, a near-white, on the assumption the page
   behind it is always dark. It isn't: measured against the site's own imagery,
   ACM's cover sits at luma 240 against a cursor at 235, and Flowstate's cover
   and the hi-fi phone screens are close behind — the cursor effectively
   disappeared over them. That mattered more than it sounds, because
   `cursor: none` is forced on everything, so there is no system pointer
   underneath to fall back on.

   difference blending makes it work its own colour out against whatever is
   behind it: light on the dark page, dark on a cream cover, with nothing
   needing to know what it is over. White is the correct source colour, since
   |backdrop - 255| is a full inversion. */
#cursor-dot {
  position: fixed;
  width: 14px;
  height: 14px;
  background: #fff;
  mix-blend-mode: difference;
  border-radius: 50%;
  pointer-events: none;
  z-index: 99999;
  transform: translate(-50%, -50%);
  transition: width 0.25s var(--ease), height 0.25s var(--ease), opacity 0.3s;
}
/* The size jump is the only hover signal now that the ring is gone. It stays
   white rather than picking up the accent: under difference blending a
   coloured dot becomes a different hue on every background. */
#cursor-dot.hover {
  width: 26px;
  height: 26px;
}
#cursor-dot.hidden { opacity: 0; }

/* Reading progress bar */
#read-progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 2px;
  width: 0%;
  background: rgba(var(--accent-rgb), 0.85);
  z-index: 99997;
  pointer-events: none;
  transition: width 0.08s linear;
}

/* Skill chips */
.about-skills {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 28px;
}
.skill-chip {
  font-family: var(--sans);
  font-size: var(--fs-label);
  letter-spacing: 0.06em;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 6px 14px;
  transition: border-color 0.2s, color 0.2s;
}
.skill-chip:hover {
  border-color: rgba(162, 178, 224, 0.5);
  color: rgba(236, 235, 231, 0.75);
}

a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }

/* ============================================================
   LOADER
============================================================ */
#loader {
  position: fixed;
  inset: 0;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  z-index: 9999;
  transition: opacity 0.6s ease, visibility 0.6s ease;
}

#loader.done { opacity: 0; visibility: hidden; pointer-events: none; }

.loader-label {
  font-size: var(--fs-label);
  letter-spacing: var(--tracking-label);
  color: var(--muted);
  text-transform: uppercase;
}

.loader-dots { display: flex; gap: 8px; }

.loader-dots span {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text);
  animation: dotBounce 1.1s ease-in-out infinite;
}
.loader-dots span:nth-child(2) { animation-delay: 0.16s; }
.loader-dots span:nth-child(3) { animation-delay: 0.32s; }

@keyframes dotBounce {
  0%, 80%, 100% { opacity: 0.15; transform: scale(0.75); }
  40%           { opacity: 1;    transform: scale(1); }
}

/* ============================================================
   NAV
============================================================ */
#nav {
  position: fixed;
  inset: 12px 0 auto 0;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 clamp(20px, 3vw, 48px);
  background: transparent;
  border-bottom: 1px solid transparent;
  z-index: 100;
  transition: transform 0.3s var(--ease), background 0.3s ease, border-color 0.3s ease;
}

#nav.scrolled {
  background: var(--nav-bg);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom-color: rgba(255,255,255,0.06);
}

@media (min-width: 769px) {
  #nav.scrolled {
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-bottom-color: transparent;
  }
}

#nav.hide { transform: translateY(-100%); }

/* Left: name + subtitle */
.nav-left {
  display: flex;
  flex-direction: column;
  gap: 0px;
  min-width: 140px;
}

.nav-name {
  font-size: 22px;
  font-weight: 700;
  font-family: var(--serif);
  color: var(--text);
  letter-spacing: -0.01em;
  transition: opacity 0.2s;
}
.nav-name:hover { opacity: 0.45; }


/* Right: all nav links */
.nav-right {
  display: flex;
  align-items: center;
  gap: 28px;
  justify-content: flex-end;
  background: rgba(32, 28, 24, 0.42);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid rgba(236, 235, 231, 0.07);
  border-radius: 999px;
  padding: 12px 28px;
}

.nav-right a {
  display: flex;
  align-items: center;
  gap: 3px;
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--text);
  transition: opacity 0.2s, transform 0.45s cubic-bezier(0.23, 1, 0.32, 1);
  letter-spacing: 0.04em;
}
.nav-right a:hover { opacity: 0.45; }

.ext-icon { flex-shrink: 0; }

.burger {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 18px 11px;
}
.burger span {
  display: block;
  width: 24px;
  height: 1.5px;
  background: var(--text);
  transition: opacity 0.2s, transform 0.3s;
}

/* ============================================================
   MOBILE MENU
============================================================ */
.mobile-menu {
  position: fixed;
  inset: 0;
  background: rgba(17, 16, 16, 0.92);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  z-index: 500;
  padding: var(--nav-h) var(--px) 48px;
  display: flex;
  flex-direction: column;
  transform: translateX(100%);
  transition: transform 0.45s var(--ease);
}
.mobile-menu.open { transform: translateX(0); }

.mobile-close {
  position: absolute;
  top: 20px;
  right: var(--px);
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text);
  padding: 4px;
  opacity: 0.6;
  transition: opacity 0.2s;
}
.mobile-close:hover { opacity: 1; }

.mobile-menu-body {
  display: flex;
  flex-direction: column;
  gap: 48px;
  margin-top: 48px;
}

.mm-label {
  display: block;
  font-family: var(--sans);
  font-size: var(--fs-label);
  letter-spacing: var(--tracking-label);
  color: var(--muted);
  text-transform: uppercase;
  margin-bottom: 16px;
}

.mm-link {
  display: block;
  font-family: var(--serif);
  font-size: var(--fs-h1);
  font-weight: 400;
  line-height: 1.15;
  padding: 10px 0;
  border-bottom: 1px solid rgba(236, 235, 231, 0.08);
  color: var(--text);
  transition: opacity 0.2s;
}
.mm-link:hover { opacity: 0.45; }

/* ============================================================
   HERO
============================================================ */

/* Bulb */
.bulb-wrap {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  transform-origin: top center;
  width: clamp(170px, 22vw, 300px);
  pointer-events: none;
  z-index: 0;
}

.bulb-wrap.swinging {
  animation: bulbSwing 0.5s ease-out forwards;
}

@keyframes bulbSwing {
  0%   { transform: translateX(-50%) rotate(-8deg); }
  40%  { transform: translateX(-50%) rotate(5deg); }
  70%  { transform: translateX(-50%) rotate(-2deg); }
  100% { transform: translateX(-50%) rotate(0deg); }
}

.bulb-img {
  width: 100%;
  height: auto;
  display: block;
  opacity: 0;
}

.bulb-img.flickering {
  animation: bulbFlicker 1.2s ease-out forwards;
}

.bulb-img.on {
  opacity: 1;
}

@keyframes bulbFlicker {
  0%   { opacity: 0; }
  8%   { opacity: 0.9; }
  12%  { opacity: 0; }
  22%  { opacity: 0.85; }
  26%  { opacity: 0.05; }
  38%  { opacity: 1; }
  43%  { opacity: 0; }
  55%  { opacity: 1; }
  60%  { opacity: 0.1; }
  72%  { opacity: 1; }
  100% { opacity: 1; }
}

/* Glow — centered on the bulb globe */
.bulb-glow-inner {
  position: absolute;
  top: 54%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 320%;
  aspect-ratio: 1;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  z-index: -1;
}

/* Natural diffuse glow like the reference — bright core, wide soft halo */
.bulb-glow-inner::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(255, 255, 255, 0.75)  0%,
    rgba(255, 255, 255, 0.45) 10%,
    rgba(255, 255, 255, 0.18) 25%,
    rgba(255, 255, 255, 0.06) 45%,
    rgba(255, 255, 255, 0.02) 65%,
    transparent 80%
  );
  filter: blur(18px);
}


.bulb-glow-inner.flickering {
  animation: bulbFlicker 1.2s ease-out forwards;
}

.bulb-glow-inner.on {
  opacity: 1;
  transition: opacity 1.2s ease;
}

.bulb-glow-inner.breathing {
  animation: bulbBreath 3.5s ease-in-out infinite;
}

@keyframes bulbBreath {
  0%, 100% { filter: brightness(1); }
  50%       { filter: brightness(0.72); }
}

/* Hero text — sits inside hero section */
.hero-text-wrap {
  position: relative;
  z-index: 5;
  text-align: left;
}

.hero-text-wrap.lit .hero-greeting {
  animation: heroFadeUp 1s cubic-bezier(0.16, 1, 0.3, 1) 0.1s both;
}

.hero-text-wrap.lit .hero-headline {
  animation: heroFadeUp 1.1s cubic-bezier(0.16, 1, 0.3, 1) 0.35s both;
}

@keyframes heroFadeUp {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

.hero-greeting {
  font-family: var(--serif);
  font-size: var(--fs-display);
  font-weight: 400;
  margin-bottom: 18px;
  color: var(--text);
  letter-spacing: -0.025em;
  line-height: 1;
  opacity: 0;
}

.hero-headline {
  font-family: var(--sans);
  font-size: var(--subtitle-size);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: -0.01em;
  margin: 0;
  color: rgba(236, 235, 231, 0.62);
  opacity: 0;
}
.tw-cursor {
  display: inline;
  color: rgba(var(--accent-rgb), 0.7);
  animation: tw-blink 0.75s step-end infinite;
}
@keyframes tw-blink {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0; }
}

.hero-clock {
  font-family: var(--sans);
  font-size: var(--fs-label);
  letter-spacing: 0.07em;
  color: var(--muted);
  margin-bottom: 28px;
  opacity: 0;
  animation: heroFadeUp 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0s both;
}
.hero-text-wrap.lit .hero-clock {
  animation: heroFadeUp 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0s both;
}

/* 100vw, not 100%. For a fixed element width:100% resolves against the
   client width, which EXCLUDES the scrollbar — so the canvas stopped 15px short
   of the right edge and the body's flat --bg showed through in that strip,
   beside a canvas the orbs had tinted. A fixed seam with the page scrolling
   past it, which is exactly what it looked like. body already sets
   overflow-x:hidden, so the extra width costs nothing. */
#bgCanvas {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100%;
  z-index: -1;
  display: block;
}

/* Swipe CTA */
.swipe-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  user-select: none;
}

.swipe-text {
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
}

.swipe-arrow {
  color: var(--muted);
  animation: arrowBounce 2s ease-in-out infinite;
}

@keyframes arrowBounce {
  0%, 100% { transform: translateY(0); }
  50%       { transform: translateY(5px); }
}

/* ============================================================
   WORK
============================================================ */
.work-header {
  padding: calc(var(--nav-h) + 72px) clamp(48px, 8vw, 120px) 56px;
}

.work-header-title {
  font-family: var(--serif);
  font-size: var(--fs-h1);
  font-weight: 400;
  letter-spacing: -0.025em;
  line-height: 1.05;
  margin-bottom: 16px;
  color: var(--text);
}

.work-header-sub {
  font-family: var(--sans);
  font-size: var(--subtitle-size);
  font-weight: 400;
  /* Was 0.62, which held up on a flat dark page but fell to ~3.2:1 once the
     blob could be lifted behind it by a hovered project. */
  color: rgba(236, 235, 231, 0.8);
  line-height: 1.5;
  letter-spacing: -0.01em;
  margin-top: 12px;
}

.work-page {
  padding: 0 clamp(24px, 9vw, 120px);
}

/* "Selected Work": a label that opens the grid, not a heading competing
   with it. A rule the width of the card column with the label under it,
   in the site's label style. */
.work-eyebrow {
  border-top: 1px solid rgba(236, 235, 231, 0.32);
  padding-top: 14px;
  margin: 72px 0 32px;
}
.work-eyebrow-label {
  font-family: var(--sans);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text);
  margin: 0;
}

/* Project card */
.project {
  display: block;
  padding-bottom: 48px;
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
  color: inherit;
  text-decoration: none;
}
.project.visible { opacity: 1; transform: translateY(0); }
.project.visible:hover { transform: translateY(-5px); }

/* Image */
.project-image {
  width: 100%;
  aspect-ratio: 5 / 2.7;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  transition: filter 0.35s ease, transform 0.4s var(--ease);
}

.project-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 30%;
  display: block;
}

.project-image img.img-centered {
  object-position: center center;
}

.project-image-placeholder {
  width: 100%;
  height: 100%;
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
}

.project-image-placeholder span {
  font-size: var(--fs-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
}

.project.visible:hover .project-image {
  filter: brightness(0.88);
  transform: scale(1.012);
}
.project.visible:hover .project-video-preview { opacity: 1; }

.img-placeholder {
  font-size: var(--fs-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: rgba(255,255,255,0.5);
}

/* Tags row */
.project-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 20px;
}

.tag {
  font-family: var(--sans);
  font-size: var(--fs-label);
  font-weight: 400;
  color: var(--muted);
  background: rgba(236, 235, 231, 0.06);
  border: 1px solid rgba(236, 235, 231, 0.12);
  border-radius: 100px;
  padding: 5px 14px;
  letter-spacing: 0.03em;
}

/* Footer row: title+desc left, arrow right */
.project-footer {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 32px;
  margin-top: 12px;
}

.project-body { flex: 1; }

.project-title {
  font-family: var(--serif);
  font-size: var(--fs-h3);
  font-weight: 400;
  letter-spacing: -0.025em;
  line-height: 1.1;
  margin-bottom: 8px;
}

.project-desc {
  font-size: var(--fs-sm);
  font-weight: 400;
  color: var(--muted);
  line-height: 1.75;
}

/* Arrow button */
.project-arrow {
  width: 52px;
  height: 52px;
  border: 1px solid var(--border);
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--text);
  transition: background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
}
.project.visible:hover .project-arrow {
  background: var(--text);
  color: var(--bg);
  border-color: var(--text);
}

@media (max-width: 768px) {
  .project-footer { align-items: flex-start; }
  .project-arrow { display: none; }
}


/* ============================================================
   FOOTER
============================================================ */
.footer {
  /* The hero canvas lives inside .page-fade, which has a transform and so
     forms a stacking context; that whole context paints in the positioned
     phase, AFTER a plain block like this one. The footer was therefore drawn
     underneath the canvas and read through a translucent film of whatever the
     blob was tinted to — which is why hovering a project washed it out and no
     amount of text colour could reach it. Claiming a layer puts it back on
     top, where its own contrast applies. */
  position: relative;
  z-index: 1;
  border-top: 1px solid var(--border);
  padding: 56px clamp(48px, 8vw, 120px);
}

.footer-inner {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 48px;
}

.footer-left {
  display: flex;
  flex-direction: column;
}

.footer-logo {
  font-family: var(--serif);
  font-size: var(--fs-h2);
  font-weight: 400;
  letter-spacing: -0.02em;
  margin-bottom: 10px;
}

.footer-note {
  font-size: var(--fs-sm);
  color: var(--muted);
  margin-bottom: 40px;
}

.footer-copy {
  font-size: var(--fs-label);
  color: rgba(236, 235, 231, 0.72);
}

.footer-cols {
  display: flex;
  gap: clamp(32px, 6vw, 80px);
}

.footer-col {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.footer-col-label {
  font-family: var(--sans);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 4px;
}

.footer-col a {
  font-size: var(--fs-xs);
  font-weight: 400;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: rgba(236, 235, 231, 0.9);
  transition: opacity 0.2s;
}

/* Footer text sits on the page's own ground (the footer claims its own layer
   above the hero canvas), so it only needs to be white. The layered dark
   text-shadow halo that used to sit here read as a smudge around the type. */
.footer-logo,
.footer-note,
.footer-copy,
.footer-col-label,
.footer-col a {
  color: #fff;
}
.footer-col a:hover { opacity: 0.45; }

/* ============================================================
   HERO — full-screen on home (no work section below)
============================================================ */
.hero {
  min-height: 100svh;
  position: relative;
  display: flex;
  align-items: center;
  padding: calc(var(--nav-h) + 40px) var(--px) 80px;
}


.swipe-cta {
  position: absolute;
  bottom: 48px;
  left: 50%;
  transform: translateX(-50%);
  opacity: 0;
  animation: swipeFadeIn 0.8s ease 1.1s forwards;
}

@keyframes swipeFadeIn {
  to { opacity: 1; }
}

/* Make swipe-cta a proper link on the home page */
a.swipe-cta {
  display: flex;
  align-items: center;
  gap: 14px;
  width: fit-content;
  transition: opacity 0.2s;
}
a.swipe-cta:hover { opacity: 0.5; }

/* ============================================================
   WORK PAGE
============================================================ */
.work-page {
  padding-top: 0;
}

/* ============================================================
   INFO PAGE
============================================================ */
.info-hero {
  padding-top: var(--nav-h);
  border-top: 1px solid var(--border);
}

.info-statement {
  padding: 56px var(--px) 72px;
  border-bottom: 1px solid var(--border);
}

.info-headline {
  font-size: var(--fs-h1);
  font-weight: 400;
  letter-spacing: -0.03em;
  line-height: 1.1;
  max-width: 820px;
  font-family: var(--serif);
}

/* Story blocks */
.info-story {
  padding: 0 var(--px);
}

.story-block {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 48px;
  padding: 56px 0;
  border-bottom: 1px solid var(--border);
}

.story-label {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
  padding-top: 4px;
}

.story-text p {
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.8;
  color: var(--text);
  margin-bottom: 20px;
  max-width: 620px;
  font-family: var(--sans);
}
.story-text p:last-child { margin-bottom: 0; }
.story-text em { font-style: italic; }

/* Experience */
.info-experience {
  padding: 0 var(--px) 0;
}

.exp-list {
  padding: 0;
}

.exp-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  padding: 28px 0;
  border-bottom: 1px solid var(--border);
}

.exp-left {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.exp-role {
  font-size: var(--fs-h4);
  font-weight: 600;
  font-family: var(--sans);
}

.exp-place {
  font-size: var(--fs-xs);
  color: var(--muted);
}

.exp-date {
  font-size: var(--fs-label);
  color: var(--muted);
  white-space: nowrap;
  padding-top: 3px;
}

/* Contact */
.info-contact {
  padding: 0 var(--px) 80px;
}

.contact-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
  padding: 8px 0;
}

.contact-block {
  padding: 32px 0;
  border-bottom: 1px solid var(--border);
  padding-right: 48px;
}
.contact-block:nth-child(odd) { border-right: 1px solid var(--border); }

.contact-label {
  display: block;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 10px;
}

.contact-block a,
.contact-block p {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 400;
  line-height: 1.8;
  transition: opacity 0.2s;
}
.contact-block a:hover { opacity: 0.45; }

/* ============================================================
   ABOUT PAGE
============================================================ */
.about-page {
  border-top: 1px solid var(--border);
  padding: 0 clamp(32px, 8vw, 140px);
}

.about-intro {
  display: grid;
  grid-template-columns: 1fr clamp(300px, 32vw, 480px);
  gap: clamp(40px, 5vw, 80px);
  align-items: center;
  min-height: 100svh;
  padding: calc(var(--nav-h) + 60px) 0 80px;
  border-bottom: 1px solid var(--border);
}

.about-intro-text { display: flex; flex-direction: column; }

.about-greeting {
  font-family: var(--serif);
  font-size: var(--fs-display);
  font-weight: 400;
  letter-spacing: -0.025em;
  line-height: 1;
  margin-bottom: 28px;
  color: var(--text);
}

.about-bio {
  font-family: var(--sans);
  font-size: var(--subtitle-size);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: -0.01em;
  margin-bottom: 16px;
  /* Was its own rgba(…, 0.62), which put it at 3.45:1 on the old ground and
     3.16:1 on this one — under the 4.5:1 it needs, since at 23px it is not
     large text (that starts at 24px). It was failing before the ground moved;
     the lift only made it more obviously wrong. Pointed at the shared token so
     it tracks the ground from now on instead of drifting on its own. */
  color: var(--muted);
}
.about-bio:last-child { margin-bottom: 0; }

.about-intro-photo {
  width: 100%;
  aspect-ratio: 2 / 3;
  border-radius: 12px;
  overflow: hidden;
}

.about-intro-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center top;
  display: block;
}

/* Outside section */
.about-outside {
  border-bottom: 1px solid var(--border);
}

/* Activities section */
.about-activities {
  padding: 72px 0;
  border-bottom: 1px solid var(--border);
}

.about-activities-label {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 16px;
}

.about-activities-heading {
  font-family: var(--serif);
  font-size: var(--fs-h2);
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1.2;
  margin-bottom: 40px;
}

.about-activities-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}

.about-activity-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.about-activity-img {
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: 12px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--border);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.about-activity-img span {
  font-size: var(--fs-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
}

.about-activity-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.4s var(--ease);
}
.about-activity-card:hover .about-activity-img img { transform: scale(1.03); }

.about-activity-title {
  font-family: var(--sans);
  font-size: var(--fs-h4);
  font-weight: 600;
  color: var(--text);
}

.about-activity-sub {
  font-family: var(--sans);
  font-size: var(--fs-sm);
  font-weight: 400;
  color: var(--muted);
  margin-top: -6px;
}

.about-activity-desc {
  font-family: var(--sans);
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--muted);
  margin-top: 8px;
  line-height: 1.5;
}

@media (max-width: 768px) {
  .about-activities-grid { grid-template-columns: 1fr; }
}

.about-outside-heading {
  font-family: var(--serif);
  font-size: var(--fs-h2);
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1.2;
  /* No padding of its own any more. It is a grid cell sitting beside the
     collage and centred against it, so a 64px top pad here just pushed the
     text off that centre; the section's own spacing lives on the wrapper. */
  padding: 0;
  margin: 0;
}

/* ── Outside of designing ──
   Three photographs as a collage that resolves to a square.

   It has been three things this week and the first two were both wrong in the
   same direction. As alternating full-width rows it ran 1,589px — 47% of the
   page — to say three things of about 24 characters each. As three equal
   columns of a 4:3 image with the caption beneath, it was a copy of the
   activities grid directly above it, so the page had two interchangeable
   blocks in a row. A square of unequal tiles is neither: nothing else on the
   site is square, and the tiles being different sizes is what makes it read as
   a collage rather than as another grid. */
/* Column widths, not 1fr-and-a-cap. With the heading column free to take
   whatever was left, it took 456px to hold a 404px line and the square settled
   at 540 — leaving 425px of dead height beside it, since two lines of heading
   cannot fill a column as tall as a square is wide. Pinning the text column to
   roughly what the words need lets the square take the rest: the heading wraps
   to three lines instead of two, and the collage grows by a third. */
.about-outside-inner {
  display: grid;
  /* Fractions, not fixed pixels. Pinned at 336px the text column kept its full
     width as the page narrowed and took it out of the square instead — at a
     900px viewport that left a 363px collage next to a 336px column of mostly
     air, which is the problem this was meant to fix, arriving from the other
     side. As a ratio both shrink together and the square keeps its two thirds
     at every width. */
  grid-template-columns: minmax(0, 0.336fr) minmax(0, 0.664fr);
  gap: clamp(28px, 4.7vw, 60px);
  align-items: center;
  padding: 56px 0 68px;
}

/* The square. One tall tile down the left, two stacked to its right — tiled,
   never overlapping, so the outer edge stays exactly 1:1 whatever the column
   is worth. The 1.32/1.12 splits are the collage: an even 2x2 would just be a
   grid with a missing line. */
.about-interest-collage {
  display: grid;
  grid-template-columns: 1.32fr 1fr;
  grid-template-rows: 1.12fr 1fr;
  gap: 10px;
  aspect-ratio: 1 / 1;
  width: 100%;
}
.about-interest:nth-child(1) { grid-row: span 2; }

.about-interest {
  position: relative;
  margin: 0;
  border-radius: 12px;
  overflow: hidden;
  min-height: 0;
}

.about-interest img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.5s var(--ease);
}
.about-interest:hover img { transform: scale(1.04); }

/* The scrim. Captions sit on the photographs to keep the square unbroken, and
   these photographs are busy — forest, rock, a shelf of cards — so the text
   needs its own ground rather than luck. Measured against the worst pixel
   under each label, not against an average. */
.about-interest::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  /* Capped in px, not left at a bare percentage. The tiles are very different
     heights — 540 against 250 — and 62% of the tall one darkened half the
     photograph to light a caption sitting in its bottom 30px. The cap gives
     every caption the same ground and gives the tall tile its picture back. */
  height: min(62%, 168px);
  background: linear-gradient(to top, rgba(14, 18, 24, 0.82) 0%,
                                      rgba(14, 18, 24, 0.52) 38%,
                                      rgba(14, 18, 24, 0) 100%);
  pointer-events: none;
}

/* Italic sans, where the activity cards above are upright serif. These three
   finish the sentence the heading starts — "I'll likely be… traveling to new
   places" — so they want to read as speech, and italic is the one thing in the
   type system that does that. */
.about-interest-label {
  position: absolute;
  left: clamp(12px, 1.4vw, 18px);
  right: clamp(12px, 1.4vw, 18px);
  bottom: clamp(10px, 1.2vw, 15px);
  z-index: 1;
  font-size: var(--fs-sm);
  font-style: normal;
  font-weight: 500;
  line-height: 1.3;
  color: #fff;
  margin: 0;
  font-family: var(--sans);
}

@media (max-width: 860px) {
  .about-outside-inner {
    grid-template-columns: 1fr;
    gap: 28px;
    align-items: start;
  }
  /* The square is the point, so it survives the stack — it just stops being
     beside the heading and sits under it, capped so it cannot fill a phone. */
  .about-interest-collage { max-width: 460px; }
}

@media (max-width: 700px) {
  .about-interest-collage { gap: 7px; }
  .about-interest-label { font-size: var(--fs-xs);
  font-family: var(--sans);
  font-weight: 500;
}
}

/* Contact on about page */
.about-contact {
  padding-bottom: 80px;
}

/* Active nav link */
.nav-active {
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
}
.mm-active { opacity: 0.45; }

/* Page fade-in */
.page-fade {
  animation: pageFadeIn 0.5s ease forwards;
}
@keyframes pageFadeIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ============================================================
   RESPONSIVE
============================================================ */
@media (max-width: 768px) {
  .nav-right { display: none; }
  .burger    { display: flex; }

  .project-content   { grid-template-columns: 1fr; }

  .about-intro       { grid-template-columns: 1fr; }
  .about-intro-photo { max-width: 320px; }
  .story-block       { grid-template-columns: 1fr; gap: 16px; }
  .contact-grid      { grid-template-columns: 1fr; }
  .contact-block:nth-child(odd) { border-right: none; }

  .exp-item { flex-direction: column; gap: 4px; }
  .exp-date { align-self: flex-start;
  font-size: var(--fs-label);
}
}

/* ============================================================
   CASE STUDY PAGES
============================================================ */
.cs-wrap {
  padding-top: calc(var(--nav-h) + 60px);
}

.cs-inner {
  width: min(1360px, 90vw);   /* the same frame as the home page's work grid, so margins match */
  margin: 0 auto;
  padding: 0 0 120px;
}

/* Back button */
.cs-back {
  position: fixed;
  top: calc(var(--nav-h) + 20px);
  left: clamp(20px, 4vw, 60px);
  z-index: 90;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: var(--fs-xs);
  font-weight: 500;
  /* A solid pill, not glass. It is fixed, so it slides over whatever the
     page scrolls under it, including white screenshots, where 60% white text
     on a 7% white wash vanished. The dark fill keeps it ~11:1 on any image. */
  color: #fff;
  background: rgba(38, 48, 58, 0.92);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22);
  padding: 10px 18px 10px 14px;
  border-radius: 999px;
  transition: background 0.2s, color 0.2s;
  text-decoration: none;
}
.cs-back:hover { background: rgba(58, 72, 86, 0.96); color: #fff; }
.cs-back svg { flex-shrink: 0; }

/* Hero */
.cs-title {
  font-family: var(--serif);
  font-size: var(--fs-h1);
  font-weight: 400;
  letter-spacing: -0.025em;
  line-height: 1.05;
  margin-bottom: 16px;
  padding-top: 40px;
}

.cs-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 40px;
}

/* Intro: overview text left, meta stack right */
.cs-intro {
  display: grid;
  grid-template-columns: 1fr 280px;
  gap: 64px;
  margin-bottom: 72px;
}

.cs-intro-text p {
  font-family: var(--sans);
  font-size: var(--subtitle-size);
  font-weight: 400;
  line-height: 1.75;
  color: var(--text);
  margin-bottom: 20px;
}
.cs-intro-text p:last-child { margin-bottom: 0; }

/* Right meta stack */
.cs-meta {
  display: flex;
  flex-direction: column;
  background: var(--cs-surface, #556677);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 8px 24px;
}

.cs-meta-item {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 16px 0;
  border-top: 1px solid var(--border);
}
.cs-meta-item:first-child {
  border-top: none;
}

.cs-meta-label {
  font-family: var(--sans);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
}

.cs-meta-value {
  font-family: var(--sans);
  font-size: var(--fs-sm);
  font-weight: 400;
  color: var(--text);
  line-height: 1.6;
}

@media (max-width: 680px) {
  .cs-intro { grid-template-columns: 1fr; gap: 40px; }
}

/* Cover image */
/* The cover shows the whole image at its own shape. A fixed 16:9 box
   cropped the 4:3 ACM cover, cutting off the top of the site it shows. */
.cs-cover {
  width: 100%;
  border-radius: 10px;
  overflow: hidden;
  margin-bottom: 56px;
}

.cs-cover img {
  width: 100%;
  height: auto;
  display: block;
}

.cs-cover--dark {
  background: var(--cs-cover-dark, #43515e);
  display: flex;
  align-items: center;
  justify-content: center;
}

.cs-cover-inset {
  width: 72%;
  border-radius: 12px;
  overflow: hidden;
}

.cs-cover-inset img {
  width: 100%;
  height: auto;
  display: block;
}

/* Sections — two-column: label left, content right */
.cs-section {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 56px 0;
  border-top: 1px solid var(--border);
}

.cs-section--no-border {
  border-top: none;
  padding-top: 0;
}

.cs-label-lg {
  font-size: var(--fs-h2) !important;
}

.cs-mid-label {
  font-family: var(--sans);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 40px;
}

.cs-mid-label:first-child {
  margin-top: 0;
}

.cs-section-body {
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

.cs-section-body .cs-img-placeholder {
  width: calc(100% + 160px + 48px);
  margin-left: calc(-160px - 48px);
  align-self: stretch;
}

.cs-section-body .cs-img-placeholder:first-child {
  margin-top: 64px;
}

.cs-section-label {
  font-family: var(--serif);
  font-size: var(--fs-h2);
  font-weight: 400;
  letter-spacing: -0.02em;
  text-transform: none;
  color: var(--text);
}

.cs-section-label--xl {
  font-size: var(--fs-h2);
}

.cs-section-body p {
  font-family: var(--sans);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.7;
  color: rgba(255,255,255,0.8);
  margin-bottom: 20px;
}
.cs-section-body p:last-child { margin-bottom: 0; }

/* Labels are sans, even when they are a <p> inside section copy. Without
   this, .cs-section-body p (one element more specific than a bare class)
   repainted every label in the body serif at reading size. */
.cs-section-body :is(.cs-problem-category, .cs-problem-img-label,
                     .cs-mob-caption, .cs-mid-label) {
  font-family: var(--sans);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  line-height: 1.4;
}
.cs-section-body :is(.cs-problem-category, .cs-problem-img-label,
                     .cs-mob-caption, .cs-mid-label) { font-size: var(--fs-label); }

/* Story bridge (solid medium weight, not italic: DM Sans italic isn't
   loaded): the line that hands off from one part of a case study to the
   next when the telling is not chronological ("Here's how we got there"). */
.cs-section-body .cs-story-bridge {
  margin-top: 40px;
  padding-left: 18px;
  border-left: 2px solid rgba(var(--accent-rgb), 0.7);
  font-size: var(--fs-lead);
  font-weight: 500;
  color: var(--text);
}

/* Small heading inside a block of copy (e.g. "Mentor Feedback", "Closet").
   Doubled class so it outranks .cs-section-body p. */
.cs-section-body .cs-mini-heading {
  font-family: var(--sans);
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.35;
  color: var(--text);
  margin: 0 0 8px;
}

/* Bulleted copy, for sections that were a wall of prose. Same type as body
   paragraphs so it reads as part of the same voice, just easier to scan. */
.cs-list {
  list-style: none;
  margin: 0 0 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.cs-list:last-child { margin-bottom: 0; }
.cs-list li {
  position: relative;
  padding-left: 22px;
  font-family: var(--sans);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.65;
  color: rgba(255,255,255,0.8);
}
.cs-list li::before {
  content: '';
  position: absolute;
  left: 4px;
  top: 0.72em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(var(--accent-rgb, 236,235,231), 0.85);
}

.cs-section-body strong {
  font-weight: 600;
  color: var(--text);
}

.cs-subsection-label {
  font-family: var(--sans);
  font-size: var(--fs-h3);
  font-weight: 500;
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--text);
  margin: 36px 0 10px;
}
.cs-subsection-label:first-child { margin-top: 0; }

/* ── Reusable phone mockup for dense mobile-screen grids ──
   Namespaced cs-mob-* (not cs-phone-*) to avoid colliding with the
   existing .cs-phone-wrap/.cs-phone-frame/.cs-phone-screen system used
   elsewhere, which silently overrode this block when names matched. ── */
.cs-mob-mockup {
  position: relative;
  width: 260px;
  flex-shrink: 0;
  background: #000;
  border-radius: 46px;
  padding: 10px;
  box-shadow: 0 0 0 1px rgba(255,255,255,0.08), 0 32px 80px rgba(0,0,0,0.5);
}
/* Outer: clips to rounded corners, no scroll here */
.cs-mob-screen-outer {
  position: relative;
  background: #fff;
  border-radius: 38px;
  overflow: hidden;
}
/* Bottom fade signals there's more to scroll to, without a visible
   scrollbar. Only shown when the screen actually overflows (see the
   .cs-mob-fits toggle in main.js) so screens that already fit stay clean. */
.cs-mob-screen-outer::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 36px;
  background: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.55));
  pointer-events: none;
  z-index: 5;
}
.cs-mob-screen-outer.cs-mob-fits::after { display: none; }
/* Inner: scrolls, no border-radius here — keeping scroll and corner-clip
   on separate elements avoids a rendering bug where combining
   overflow-y:auto with border-radius on the same element can fail to
   composite child content correctly. Fixed height so every phone in a
   row is the same overall size; scrollbar is hidden (cross-browser) so
   it doesn't visually clash with the phone frame, while scroll itself
   still works via touch/trackpad/wheel. */
.cs-mob-screen-inner {
  overflow-y: auto;
  height: 520px;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.cs-mob-screen-inner::-webkit-scrollbar { display: none; }
.cs-mob-screen-inner img { width: 100%; display: block; }
.cs-mob-caption {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--muted);
  text-align: center;
  margin: 10px 0 0;
}
.cs-mob-row {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
  margin-top: 20px;
}


/* Full-width image inside section body */
.cs-full-img {
  width: calc(100% + 160px + 48px);
  margin-left: calc(-160px - 48px);
  display: block;
  border-radius: 10px;
  margin-top: 16px;
}

.cs-centered-img {
  display: block;
  max-width: 100%;
  margin: 16px auto;
  border-radius: 10px;
}

.cs-centered-img--sm {
  width: 24%;
  margin-top: 40px;
}

.cs-centered-img--nobg {
  mix-blend-mode: multiply;
}

.cs-widgets-img {
  width: 80% !important;
  max-width: none !important;
}

/* Fitty market overview */
.fitty-market-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
  margin-bottom: 40px;
}
.fitty-market-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 28px 24px;
  background: var(--cs-surface, #556677);
  border: 1px solid var(--border);
  border-radius: 14px;
}
.fitty-market-num {
  font-family: var(--serif);
  font-size: var(--fs-h2);
  font-weight: 400;
  letter-spacing: -0.02em;
  color: var(--text);
}
.fitty-market-label {
  font-family: var(--sans);
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--muted);
  line-height: 1.4;
}

/* Fitty competitor cards */
.fitty-competitors {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
  margin-bottom: 40px;
}
.fitty-competitor {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  text-align: center;
}
.fitty-competitor-logo {
  width: 100%;
  aspect-ratio: 1;
  border-radius: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.fitty-competitor-name {
  font-family: var(--sans);
  font-size: var(--fs-h4);
  font-weight: 600;
  color: var(--text);
}
.fitty-competitor-users {
  font-family: var(--sans);
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--muted);
}

.cs-sm-img {
  display: block;
  max-width: 40%;
  margin: 16px auto;
  border-radius: 10px;
}

.cs-two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-top: 16px;
}

.cs-two-col img {
  width: 100%;
  border-radius: 10px;
  display: block;
}

.cs-vector-side {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 40px;
  align-items: center;
}

.cs-vector-three {
  display: grid;
  grid-template-columns: 400px 300px 300px;
  gap: 10px;
  align-items: center;
}

.cs-vector-three img {
  width: 100%;
  border-radius: 10px;
  display: block;
}

.cs-vector-side > p {
  margin-bottom: 0 !important;
}

.cs-vector-grid {
  margin-top: 0;
  display: flex;
  justify-content: center;
}

.cs-vector-inner {
  width: 38%;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.cs-vector-inner > img {
  width: 100%;
  border-radius: 10px;
  display: block;
}

.cs-vector-bottom {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.cs-vector-bottom img {
  width: 100%;
  border-radius: 10px;
  display: block;
}

.cs-three-col {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin-top: 24px;
  max-width: 75%;
  margin-left: auto;
  margin-right: auto;
}

.cs-three-col--hifi {
  /* Was 94%, which was compensating for the old frame PNG's transparent
     margins — the phone occupied well under the full image width. The CSS
     mockup fills its column edge to edge, so the same number rendered them
     oversized. */
  max-width: 68%;
}

.cs-three-col > img {
  width: 100%;
  border-radius: 10px;
  display: block;
}

/* Problem highlight section */
/* ── Research: side-by-side row ── */
.cs-research-row {
  display: grid;
  grid-template-columns: 1fr 1.6fr;
  gap: 32px;
  align-items: start;
  margin-bottom: 48px;
}

.cs-research-desc {
  margin-bottom: 0;
  padding-top: 4px;
}

/* ── Research: Stat Grid ── */
.cs-stat-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  background: var(--cs-surface, #556677);
  border: 1px solid var(--border);
  border-radius: 16px;
  overflow: hidden;
  margin-bottom: 48px;
}

.cs-stat-card {
  padding: 28px 24px;
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

.cs-stat-card:nth-child(3),
.cs-stat-card:nth-child(6) {
  border-right: none;
}

.cs-stat-card:nth-child(4),
.cs-stat-card:nth-child(5),
.cs-stat-card:nth-child(6) {
  border-bottom: none;
}

.cs-stat-num {
  font-family: var(--serif);
  font-size: var(--fs-h1);
  font-weight: 400;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--text);
  margin-bottom: 12px;
}

.cs-stat-desc {
  font-family: var(--sans);
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--muted);
  line-height: 1.6;
}

/* Smaller stat grid variant */
.cs-stat-grid--sm .cs-stat-card {
  padding: 18px 16px;
}

.cs-stat-grid--sm .cs-stat-num {
  font-size: var(--fs-h2);
  margin-bottom: 8px;
}

.cs-stat-grid--sm .cs-stat-desc {
  font-size: var(--fs-label);
}

@media (max-width: 700px) {
  .cs-research-row {
    grid-template-columns: 1fr;
  }
}

/* ── Research: Bar Chart ── */
.cs-bar-chart {
  display: flex;
  align-items: flex-start;
  gap: 24px;
  margin-bottom: 40px;
  padding: 0;
  width: 70%;
  align-self: center;
}

.cs-bar-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.cs-bar-pct {
  position: absolute;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  font-size: var(--fs-sm);
  font-weight: 600;
  /* Ink. This label sits ON the bar, and the bars are pale blues — near-white
     on them measured 1.68:1, 1.29:1 and 1.04:1, the last being very nearly
     invisible. It was never legible; the bars are light on a dark page, so this
     one label has to run the other way from everything around it. 8.07:1 on the
     darkest bar, 12.95:1 on the lightest. */
  color: #16203a;
  letter-spacing: -0.01em;
  text-shadow: 0 1px 3px rgba(0,0,0,0.35);
}

/* Fixed-height track so every bar shares the same baseline,
   regardless of how many lines its caption below wraps to. */
.cs-bar-track {
  width: 100%;
  height: 200px;
  display: flex;
  align-items: flex-end;
}

.cs-bar {
  position: relative;
  width: 100%;
  height: var(--bar-h);
  border-radius: 6px 6px 0 0;
}

/* Re-laddered with the ground. Bars encode data, so each has to clear 3:1
   against what it sits on — the darkest was at 3.34:1 before and would have
   fallen to 2.96:1 on the lighter ground. Lifting only that one squeezed it
   against its neighbour, so the middle moved too: the three now step 0.156 and
   0.162 in luminance, more evenly than the 0.116 / 0.247 they had before. */
.cs-bar--1 { background: #a1b8e5; }   /* 3.24:1 */
.cs-bar--2 { background: #bcd1f8; }   /* 4.20:1 */
.cs-bar--3 { background: #dce7fa; }   /* 5.20:1 */

.cs-bar-label {
  font-family: var(--sans);
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--muted);
  text-align: center;
  line-height: 1.5;
  padding: 14px 8px 0;
  width: 100%;
}

@media (max-width: 700px) {
  .cs-stat-grid {
    grid-template-columns: 1fr 1fr;
  }
  .cs-stat-card:nth-child(2),
  .cs-stat-card:nth-child(4) {
    border-right: none;
  }
  .cs-stat-card:nth-child(3) {
    border-right: 1px solid var(--border);
  }
  .cs-stat-card:nth-child(5),
  .cs-stat-card:nth-child(6) {
    border-bottom: none;
  }
}

/* Problem section big headline */
/* The problem in one line, under the section headline. At --fs-h2 serif it
   matched the headline above it, so the two competed; as a sans lead at
   --fs-h3 it reads as the first thing said in the section. */
.cs-problem-headline {
  font-family: var(--sans);
  font-size: var(--fs-h3);
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.3;
  margin-bottom: 20px;
  color: var(--text);
}

/* Numbered card grid (Overview / Problem / Solution) */
.cs-num-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin: 40px 0;
}

.cs-num-card {
  background: var(--cs-surface, #556677);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 28px 24px;
}

.cs-num-card-header {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 16px;
}

.cs-num {
  font-size: var(--fs-label);
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0.04em;
  flex-shrink: 0;
}

.cs-num-title {
  font-family: var(--sans);
  font-size: var(--fs-h4);
  font-weight: 600;
  color: var(--text);
  line-height: 1.2;
}

.cs-num-card-desc {
  font-family: var(--sans);
  font-size: var(--fs-sm);
  font-weight: 400;
  color: var(--muted);
  line-height: 1.7;
}

@media (max-width: 700px) {
  .cs-num-grid {
    grid-template-columns: 1fr;
  }
}

/* HMW Call-out Block */
.cs-hmw-block {
  background: var(--cs-hmw, #5f7385);
  border: 1px solid rgba(120, 100, 220, 0.3);
  border-radius: 20px;
  padding: clamp(28px, 4vw, 48px);
  margin: 40px 0;
}

/* The callout's label reads as a subtitle, in the result-title style
   (sans --fs-h4, 600, the project accent lifted toward white), so it
   matches "Closet", the takeaway titles and the rest. No icon. */
.cs-hmw-label,
.cs-section-body .cs-hmw-label {
  font-family: var(--sans);
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: normal;
  text-transform: none;
  color: color-mix(in srgb, rgb(var(--accent-rgb)) 65%, #fff);
  margin-bottom: 16px;
}

.cs-hmw-text {
  font-family: var(--sans);
  font-size: var(--fs-h3);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -0.02em;
  color: var(--text);
}

.cs-problem-highlight {
  padding: 48px 0;
  border-top: 1px solid var(--border);
}

.cs-highlight-title {
  font-family: var(--serif);
  font-size: var(--fs-h2);
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1.1;
  margin-bottom: 28px;
}

.cs-highlight-body {
  font-family: var(--sans);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.75;
  color: var(--text);
  margin-bottom: 14px;
}

.cs-highlight-body strong {
  font-weight: 600;
}

.cs-highlight-cards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-top: 32px;
}

.cs-highlight-card {
  background: var(--cs-surface, #556677);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 28px;
}

.cs-highlight-card-title {
  font-family: var(--sans);
  font-size: var(--fs-h4);
  font-weight: 600;
  color: var(--text);
  margin-bottom: 12px;
}

.cs-highlight-card-desc {
  font-family: var(--sans);
  font-size: var(--fs-sm);
  font-weight: 400;
  color: var(--muted);
  line-height: 1.65;
}

/* ── Design System side by side ── */
.cs-design-system-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  margin: 24px 0;
  align-items: stretch;
}

.cs-ds-panel {
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 40px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  background: var(--cs-surface, #556677);
}

.cs-ds-panel-title {
  font-size: var(--fs-label) !important;
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 4px !important;
}

/* Color swatches */
.cs-ds-color-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.cs-ds-swatch {
  flex: 1;
  height: 96px;
  border-radius: 10px;
  display: flex;
  align-items: flex-end;
  padding: 10px 14px;
}

/* The hex labels sit ON the swatches, and the swatches are ACM's own brand
   set — #522D14 through #FFD260. Neither white nor ink works across that range:
   white measured 2.31:1 on #FE8D3C and 1.88:1 on #FFAC30, and ink fails just as
   badly at the dark end. A soft shadow is not a ground, it is a blur. A chip
   is: white on it clears 4.77:1 even where the swatch underneath is pure white,
   and more everywhere else. */
.cs-ds-hex {
  font-size: var(--fs-label);
  font-weight: 600;
  color: #fff;
  font-family: var(--sans);
  background: rgba(0, 0, 0, 0.55);
  padding: 2px 6px;
  border-radius: 4px;
  text-shadow: none;
}

.cs-ds-swatches-group {
  flex: 1;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
}

.cs-ds-swatches-group .cs-ds-swatch {
  flex: none;
  height: 96px;
}

.cs-ds-color-name {
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--text);
  white-space: nowrap;
  width: 130px;
  flex-shrink: 0;
  text-align: right;
}

/* Typography */
.cs-ds-fonts {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  flex: 1;
}

.cs-ds-font-card {
  background: var(--cs-font-card, #6e859b);
  border-radius: 12px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.cs-ds-font-preview {
  background: var(--cs-font-prev, #829eb8);
  padding: 48px 16px;
  font-size: 28px;
  font-weight: 700;
  color: var(--text);
  text-align: center;
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.cs-ds-font-preview--poppins {
  font-family: 'Poppins', sans-serif;
}

.cs-ds-font-role {
  font-size: var(--fs-label) !important;
  font-weight: 600;
  color: var(--text);
  padding: 12px 14px 4px;
  margin-bottom: 0 !important;
}

.cs-ds-font-desc {
  font-size: var(--fs-label) !important;
  font-weight: 400;
  color: var(--muted);
  line-height: 1.5;
  padding: 0 14px 14px;
  margin-bottom: 0 !important;
}

/* ── Mascot row ── */
.cs-mascot-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 40px;
  align-items: center;
}

.cs-mascot-row p {
  margin-bottom: 0 !important;
}

.cs-mascot-img {
  width: 400px;
  border-radius: 12px;
  display: block;
  flex-shrink: 0;
}

/* ── Problem Issue List ── */
.cs-problem-row {
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  gap: 48px;
  align-items: start;
}

.cs-problem-img-wrap {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.cs-problem-img-label {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0;
}

.cs-problem-img {
  width: 100%;
  border-radius: 12px;
  display: block;
}

@media (max-width: 700px) {
  .cs-problem-row {
    grid-template-columns: 1fr;
  }
}

.cs-problem-split {
  display: flex;
  flex-direction: column;
  max-width: 560px;
}

.cs-problem-category {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 4px;
}

.cs-problem-item {
  padding: 18px 0;
  border-bottom: 1px solid var(--border);
}

.cs-problem-item:last-child {
  border-bottom: none;
}

.cs-problem-item-title {
  font-family: var(--sans);
  font-size: var(--fs-h4);
  font-weight: 600;
  color: var(--text);
  margin-bottom: 6px;
}

.cs-problem-item-desc {
  font-family: var(--sans);
  font-size: var(--fs-sm);
  font-weight: 400;
  color: var(--muted);
  line-height: 1.6;
  margin-bottom: 0 !important;
}

/* ── Competitive Analysis Table ── */
.cs-comp-table {
  width: 100%;
  /* Was rgba(255,255,255,0.03). A 3% white wash is enough to read on a
     near-black page and nothing at all once the ground comes up — measured 0.007
     luminance against Fitty's lifted ground, where every other panel sits at
     0.084-0.125. It takes the same token as the rest of the cards now, so it
     tracks whatever the page's ground does instead of depending on it being
     almost black. */
  background: var(--cs-surface, #556677);
  border: 1px solid var(--border);
  border-radius: 16px;
  overflow: hidden;
  margin-bottom: 32px;
}

.cs-comp-row {
  display: grid;
  grid-template-columns: 1.4fr repeat(5, 1fr);
  border-bottom: 1px solid rgba(255,255,255,0.09);
}

.cs-comp-row:last-child {
  border-bottom: none;
}

/* Ink, not white. The rows sit on a mid-tone panel and the text on them is
   near-white, so lightening a row moves it TOWARD the text: muted measured
   4.14:1 on the zebra and 3.68:1 on the header against the 4.5:1 they need,
   while the plain rows passed at 4.57:1. Stepping down puts the zebra at
   4.96:1 and the header at 5.46:1, and the banding reads just as clearly. */
.cs-comp-row:nth-child(even) {
  background: rgba(0,0,0,0.06);
}

.cs-comp-header {
  background: rgba(0,0,0,0.13);
}

.cs-comp-row--highlight {
  background: rgba(var(--accent-rgb), 0.18);
}
.cs-comp-row--highlight .cs-comp-app { font-weight: 700; }
/* Column labels at full strength; muted, they sank into the panel. */
.cs-comp-header .cs-comp-cell { color: var(--text); font-weight: 500; }

.cs-comp-cell {
  padding: 18px 16px;
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--muted);
  border-right: 1px solid rgba(255,255,255,0.09);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  line-height: 1.4;
}

.cs-comp-cell:last-child {
  border-right: none;
}

.cs-comp-app {
  justify-content: flex-start;
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--text);
  text-align: left;
}

.cs-comp-check {
  justify-content: center;
}

/* ── Takeaways ── */
.cs-takeaway-card {
  background: var(--cs-surface, #556677);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 32px 36px;
  margin-bottom: 14px;
}

.cs-takeaway-card:last-child {
  margin-bottom: 0;
}

.cs-takeaway-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 16px;
}

/* Takeaway titles share the result-title look (sans --fs-h4, 600, the
   project accent lifted toward white), so the colour comes from the
   project rather than from a per-card icon. */
.cs-section-body .cs-takeaway-title {
  font-family: var(--sans);
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: normal;
  text-transform: none;
  color: color-mix(in srgb, rgb(var(--accent-rgb)) 65%, #fff);
  margin-bottom: 0 !important;
}

.cs-takeaway-body {
  font-size: var(--fs-body);
  font-weight: 400;
  color: rgba(255,255,255,0.7);
  line-height: 1.75;
  margin-bottom: 0 !important;
  font-family: var(--sans);
}

/* Conclusion split layout */
.cs-conclusion-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 40px;
  align-items: center;
}

.cs-conclusion-split p {
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.8;
  font-family: var(--sans);
}

.cs-conclusion-split div p + p {
  margin-top: 16px;
}

.cs-conclusion-split img {
  width: 100%;
  border-radius: 10px;
  display: block;
}


.cs-body-text {
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.85;
  color: rgba(255,255,255,0.8);
  margin-top: 16px;
  font-family: var(--sans);
}

/* Placeholder image */
.cs-img-placeholder {
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: 10px;
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 24px auto;
  text-align: center;
}

.cs-img-placeholder span {
  font-size: var(--fs-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
}

/* Next project */
.cs-next {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 56px 0 0;
  border-top: 1px solid var(--border);
  margin-top: 0;
}

.cs-next-label {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
}

.cs-next-link {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: var(--fs-h3);
  font-weight: 400;
  letter-spacing: -0.02em;
  transition: opacity 0.2s;
  font-family: var(--serif);
}
.cs-next-link:hover { opacity: 0.45; }

@media (max-width: 680px) {
  .cs-section { gap: 12px; }
  .cs-meta     { gap: 20px; }
}

/* ── Scroll-snap gallery ───────────────────────────────────────────────── */
.cs-gallery { position: relative; margin: 24px 0; }
.cs-gallery-track {
  display: flex;
  overflow-x: scroll;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -ms-overflow-style: none;
  border-radius: 12px;
}
.cs-gallery-track::-webkit-scrollbar { display: none; }
.cs-gallery-slide {
  flex: 0 0 100%;
  scroll-snap-align: start;
}
.cs-gallery-slide img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 12px;
}
.cs-gallery-dots {
  display: flex;
  gap: 7px;
  justify-content: center;
  margin-top: 16px;
}
.cs-gallery-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  border: none;
  background: rgba(236, 235, 231, 0.18);
  cursor: pointer;
  padding: 0;
  transition: background 0.25s, transform 0.25s;
}
.cs-gallery-dot.active {
  background: rgba(var(--accent-rgb), 0.85);
  transform: scale(1.5);
}

/* ── Outline-fill on problem headline chars ────────────────────────────── */
h2.cs-problem-headline .split-char-inner {
  -webkit-text-stroke: 1.5px currentColor;
  -webkit-text-fill-color: transparent;
  transition: transform 0.72s cubic-bezier(0.16,1,0.3,1),
              opacity 0.4s ease,
              filter 0.6s ease,
              -webkit-text-fill-color 0.55s ease 0.1s;
}
h2.cs-problem-headline .split-char-inner.split-triggered {
  -webkit-text-fill-color: currentColor;
}

/* ── Per-project color themes ──────────────────────────────────────────── */
/* TO REVERT: remove the [data-theme] blocks below + data-theme attrs on body */
[data-theme="fitty"]     { --accent-rgb: 86, 136, 185; }
[data-theme="flowstate"] { --accent-rgb: 200, 185, 160; }
[data-theme="acm"]       { --accent-rgb: 225, 120, 45; }
[data-theme="bruinlife"] { --accent-rgb: 255, 184, 28; }

/* ── Panels, in each project's own hue ──
   Every case study painted its boxes the same slate: #556677 for the cards,
   #43515e / #5f7385 / #6e859b / #829eb8 for the rest of the ladder. Measured,
   that ladder sits at hue 208-210 — which is Fitty's accent hue. It was built
   for the blue project and never re-derived, so ACM's orange page, BruinLife's
   gold one and Flowstate's sand one all carried cold blue-grey cards.

   Same ladder, each at its own hue. Two things are deliberate:

   The rungs are matched on LUMINANCE, not on HSL lightness. A warm hue carries
   more luminance than a blue at the same lightness — hue 37 at 40% lightness
   put text at 4.34:1 where the blue gave 4.95:1 — so holding lightness constant
   would have quietly made the three warm pages the low-contrast ones.

   Saturation runs a little higher on the warm hues. At the blue's 17% a warm
   tone reads as grey rather than as warm, which would have defeated the point.

   The surface rung is also fractionally darker than the slate it replaces:
   muted text on #556677 measured 4.26:1, under the 4.5:1 it needs, on every
   case study. Every rung here is equal or better than what it replaces. */
[data-theme="fitty"] {
  --cs-cover-dark:  #404e5b;
  --cs-surface:     #506171;   /* text 5.4:1, muted 4.6:1 */
  --cs-hmw:         #5b6e80;
  --cs-font-card:   #6c8197;
  --cs-font-prev:   #90a1b1;
}
/* ── Flowstate: periwinkle, off the near-black ──
   Sampled from images/flowstate-cover.jpg, the product is periwinkle and coral
   — #6270a8 at hue 226 and #e09a70 — on cream paper (#e0e0d2, 56% of the
   backdrop). The sand accent this page carries was taken from that paper rather
   than from the product, which is why its boxes came out as warm brown while
   everything the app actually shows is blue.

   Ground and panels both move to hue 226, so the page is the product's own
   colour rather than the colour of the paper it was photographed on. The rungs
   sit at the same luminances as the other three themes, except the
   how-might-we one — see below. The sand accent stays: warm on a cool ground
   reads as the cream it came from, and it measures 6.49:1 on this ground. */
[data-theme="flowstate"] {
  --bg:             #242838;   /* painted ~#2e3347: text 10.47:1, muted 8.51:1 */
  --cs-cover-dark:  #3d4560;
  --cs-surface:     #535e82;   /* text 5.35:1, muted 4.57:1 */
  /* Pulled below the shared rung. It holds 19px copy, and at the standard
     luminance that copy could not clear 4.5:1 in either polarity — 4.40:1 at
     full strength, 3.82:1 muted. This is 4.66:1 with --text on it. */
  --cs-hmw:         #5b678e;
  --cs-font-card:   #727ea5;
  --cs-font-prev:   #959ebb;
}
body[data-theme="flowstate"] .cs-hmw-text { color: var(--text); }
/* ── ACM: the orange, off the near-black ──
   Sampled from images/acm-cover.png: 88% of the backdrop is white and the
   artwork is a vivid orange, #fc8c38 at hue 26 and 97% saturation, running up
   through amber #fcc454. The hue was already right — the per-project ladder
   put these panels at hue 26 — but at 22% saturation they read as mud rather
   than as ACM. Saturation goes to 32%, which is enough for the warmth to be a
   colour and short of the point where a mid-tone panel starts shouting.

   The ground leaves the near-black for the same hue. It is the darkest of the
   four grounds in saturation terms because the colour it is quoting is the
   loudest — a page this warm at BruinLife's 55% would be brown rather than
   dark. The section labels sit at 4.25:1 on it. */
[data-theme="acm"] {
  --bg:             #39291d;   /* painted ~#463324: text 10.01:1, muted 8.16:1 */
  /* The blue-grey has to go. --border is #7991a9 on :root and every theme just
     inherited it, which put a cold rule around 46 elements on a page whose
     whole palette is orange. Same lightness in ACM's own hue, so it reads as a
     rule rather than as a colour: 4.40:1 on the ground against the blue's
     3.95:1, so it is also a slightly stronger edge than what it replaces.
     Fitty, Flowstate and BruinLife still inherit the blue-grey — it suits their
     cool grounds, but say the word and they can each take their own. */
  --border:         #b49074;
  --cs-cover-dark:  #5a412f;
  --cs-surface:     #79583f;   /* text 5.37:1, muted 4.56:1 */
  /* Already below the shared rung on this hue — 4.87:1 with --text, so unlike
     Flowstate and BruinLife this one did not need pulling down. */
  --cs-hmw:         #815e42;
  --cs-font-card:   #a27653;
  --cs-font-prev:   #bc977c;
}
body[data-theme="acm"] .cs-hmw-text { color: var(--text); }
/* ── BruinLife: UCLA navy, gold on top ──
   Sampled from images/bruinlife-cover.jpg: the backdrop is #002a54 at 64% —
   UCLA navy, hue 210 and fully saturated — with gold #e0b646 and white on it.
   The page had gold-brown boxes on near-black, which took the accent as the
   whole palette; the gold is the accent ON the navy, not the navy's stand-in.
   So the ground and the panels become the navy and the gold stays where it
   belongs, as the thing that sits on it. The label reads 7.46:1 there.

   Saturation is what separates this from Fitty, which is also hue 210. Fitty is
   a desaturated blue-grey at 17%; this is a brand navy at 55%, because the
   colour it is quoting is 100%. Same hue, and nobody will confuse them. */
[data-theme="bruinlife"] {
  --bg:             #12283f;   /* painted ~#173350: text 10.83:1, muted 8.75:1 */
  --cs-cover-dark:  #214970;
  --cs-surface:     #2c6197;   /* text 5.39:1, muted 4.57:1 */
  /* Below the shared rung, same reason as Flowstate: 19px copy sits on it and
     could not clear 4.5:1 at the standard luminance (4.45:1 at full strength).
     This is 4.72:1. */
  --cs-hmw:         #306aa5;
  --cs-font-card:   #3e82c7;
  --cs-font-prev:   #70a2d5;
}
body[data-theme="bruinlife"] .cs-hmw-text { color: var(--text); }

/* The two lightest rungs are a type-specimen ramp — they are SUPPOSED to be
   pale, so near-white labels on them were never going to work. Measured 3.07:1
   on the old slate and 3.17:1 on the new ladder: improved by the rehue, but
   still under the 4.5:1 an 11-12px label needs, and no panel colour that is
   still recognisably a light specimen card would get there. Ink does — but it
   has to be nearly black rather than a soft brown: the rung is mid-tone, so it
   is a poor ground for BOTH polarities, and a comfortable dark (#241a12) only
   reached 4.24:1. Worst case across the four themes at this value is 4.72:1.
   Role and description share it; a second, softer tone for the description
   landed at 3.27:1, which is the same mistake one step quieter. */
.cs-ds-font-card .cs-ds-font-role,
.cs-ds-font-card .cs-ds-font-desc,
.cs-ds-font-preview { color: #150f0b; }

/* Section titles: a phase label in the project's accent, then a headline
   that says what happened ("One Tap Beats a Chat Window") in off-white.
   The label keeps the structure recruiters scan for; the headline is the
   part worth reading. The headline used to be the accent at 72%, which was
   fine for one word and too faint for a sentence. */
[data-theme] .cs-section-label {
  color: var(--text);
  text-wrap: balance;
  line-height: 1.15;
}
.cs-section-label .cs-kicker {
  display: block;
  font-family: var(--sans);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  line-height: 1.4;
  color: rgb(var(--accent-rgb));
  margin-bottom: 10px;
}


/* Fitty comes off the near-black. Its panels are blue-grey and its orbs are
   blue, and #111010 under them read as black-with-blue-on-it rather than as a
   dark blue page. Same hue as the panels, just far down: the page, its boxes
   and its orbs are one colour at three depths.

   This is the last step it has. Going up costs the gap between the ground and
   the cards, and the cards cannot follow — muted text on --cs-surface is
   already at 4.55:1, so lifting them too would take that under. Measured, the
   lift goes 0.084 at #232b34, 0.075 here, 0.064 one step further, 0.051 after
   that. And the section label fails before the lift does: the accent lands at
   3.14:1 on this ground against the 3:1 a 36px heading needs, and 2.79:1 one
   step up. Hence the lighter accent below — it buys the label back to 3.68:1,
   which is what makes THIS step safe, but it does not extend the runway,
   because the next thing to go is the cards. */
[data-theme="fitty"] { --bg: #29333d; }

/* Two things the lifted ground broke, fixed here rather than left.

   The section labels are the accent at 0.72 alpha. That softening was tuned
   against near-black; on the ground above it measured 2.27-2.45:1 against the
   3:1 a 36px heading needs. At full strength the same blue is 3.48:1, so the
   alpha comes off — the label keeps its colour, it just stops being diluted
   into a page that is no longer nearly black.

   The how-might-we rung was the other one: its 19px copy sat at 4.05:1. Pulled
   down one step, which costs a little of its separation from the ordinary
   cards — 0.021 luminance rather than 0.035 — but that block is marked out by
   its accent border rather than by its fill, so the border carries it. */
/* Headlines are off-white now; the accent lives on the phase label, which
   takes it at full strength everywhere. */
body[data-theme="fitty"] .cs-section-label .cs-kicker { color: rgb(var(--accent-rgb)); }
/* The label accent only. The blob on /work and the progress bar still key off
   the project's real colour (86,136,185) — this is the one place the ground
   got close enough to it to matter.

   Sized against the BRIGHTEST the ground ever gets, not its average. The orbs
   drift, so a label's local ground is whatever orb happens to be behind it: the
   page reads rgb(44,56,68) away from one and rgb(51,66,81) under the middle of
   one. A first pass at 103,148,193 was set against the average and measured
   3.23:1 under an orb — over the line by 0.23, which is not a margin when the
   thing moving is the background itself. This is 3.63:1 there and 4.22:1 on the
   plain ground. */
body[data-theme="fitty"] { --accent-rgb: 100, 158, 214; }
[data-theme="fitty"] { --cs-hmw: #57697a; }
body[data-theme="fitty"] .cs-hmw-text { color: var(--text); }

/* The ✕ marks in the comparison table. They were #f87171 chosen against a
   near-black cell; the table is a solid panel now, so they sit on #506171 and
   measured 2.31:1. These are aria-hidden decorative glyphs — each cell carries
   role="img" with its own aria-label ("Yes" / "No"), so the state is announced
   regardless — which puts them under the 3:1 for a graphical indicator rather
   than the 4.5:1 for text. This is 3.78:1. Getting to 4.5:1 on a panel this
   light needs roughly #ffd0d0, which has stopped being red. The ✓ beside them
   is an SVG stroke at 3.66:1 and clears the same bar untouched.

   The colour moved out of the markup to get here. Each of these spans carried
   style="color:#f87171" inline, which beat the theme rule outright — the right
   red depends on the panel it lands on, and the panel is per-theme, so this is
   a decision the stylesheet has to be able to make. The token keeps the old
   value as its default, so the three unthemed case studies are untouched. */
.cs-comp-check span { color: var(--cs-no, #f87171); }
[data-theme="fitty"] { --cs-no: #ffb4b4; }
/* Flowstate's checks are filled icons coloured by currentColor. They were
   #5b72a8 on a #535e82 panel, close to invisible; green and pink read clearly
   against it, matching the Fitty table. */
[data-theme="flowstate"] { --cs-no: #ffb4b4; }
[data-theme="flowstate"] .cs-comp-check { color: #86efac; }


/* ── Case-study entrance: hide until GSAP takes over, to prevent a flash of
   fully-visible content right before the entrance animation snaps it invisible
   and re-animates it in. Removed by animateCaseStudyEntrance() in
   gsap-animations.js; main.js has a safety-net fallback if GSAP fails.
   .cs-cover is deliberately NOT included — it's the largest, most visible
   element on the page, and any timing lag in its own animation reads as a
   blank hole rather than a subtle effect, so it just renders immediately. ── */
body.entrance-pending .cs-back,
body.entrance-pending .cs-wrap h1,
body.entrance-pending .cs-hero-title,
body.entrance-pending .cs-tags .tag,
body.entrance-pending .cs-intro,
body.entrance-pending .work-header-title,
body.entrance-pending .work-header-sub {
  opacity: 0;
}

/* ══════════════════════════════════════════════════════════════════════
   CENTRED NAV — logo inside the pill
   Applied only to headers carrying .nav-centered, so the archived
   index-original.html keeps the original left-aligned layout.
   ══════════════════════════════════════════════════════════════════════ */
/* ── Centred nav with the logo inside the pill ──
   Taken from the intro prototype. Inside the pill the logo would otherwise
   fall under `.nav-right a { font-size: 13px; font-weight: 400 }`, which
   outranks .nav-name, so the mark is sized explicitly here. */
#nav.nav-centered { justify-content: center; }
#nav.nav-centered .nav-right { gap: 22px; }
#nav.nav-centered .nav-right .nav-name {
  display: inline-flex;
  align-items: center;
  line-height: 1;
  margin-right: 2px;
  color: var(--text);
}
#nav.nav-centered .nav-mark { height: 26px; width: auto; display: block; overflow: visible; }
#nav.nav-centered .nav-mark path { fill: currentColor; }
#nav.nav-centered .nav-sep {
  width: 1px;
  height: 15px;
  background: rgba(236, 235, 231, 0.18);
  flex-shrink: 0;
}
/* The pill is centred, so the burger is pulled out of the flow */
#nav.nav-centered .burger { position: absolute; right: clamp(20px, 3vw, 48px); }

/* Apple-style glass: much more transparent than the site's pill, with a
   heavier blur and a saturation boost. The saturation is the part that
   makes it read as Apple's material rather than as a grey scrim — colour
   from the blob behind it blooms through instead of being muted. */
#nav.nav-centered .nav-right {
  background: rgba(30, 28, 34, 0.20);
  backdrop-filter: saturate(180%) blur(26px);
  -webkit-backdrop-filter: saturate(180%) blur(26px);
  border: 1px solid rgba(236, 235, 231, 0.10);
  box-shadow: 0 1px 24px rgba(0, 0, 0, 0.18);
  transition: background 0.3s ease, backdrop-filter 0.3s ease, -webkit-backdrop-filter 0.3s ease;
}
/* Scrolled, the pill can sit over a white screenshot, where the 20% glass
   let the light links wash out (about 1.4:1). It stays glass, just a little
   heavier: the backdrop is dimmed to 70% before blurring and the tint goes
   to 0.34. Over pure white that lands the links near 3.4:1; over the dark
   ground the dimming barely registers, so it still looks like the same
   pill. */
#nav.nav-centered.scrolled .nav-right {
  background: rgba(30, 28, 34, 0.34);
  backdrop-filter: saturate(180%) blur(26px) brightness(0.7);
  -webkit-backdrop-filter: saturate(180%) blur(26px) brightness(0.7);
}
/* The scrolled state adds a second wash over the whole bar; at this
   transparency that reads as a grey band behind the glass. */
#nav.nav-centered.scrolled {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom-color: transparent;
}

/* ══════════════════════════════════════════════════════════════════════
   PHONE MOCKUP — drawn, not photographed
   Flowstate used to layer a phone-frame PNG over each screenshot. That put a
   second <img> on top of the content, so a click landed on the frame and the
   lightbox dutifully enlarged a picture of an empty phone. Building the frame
   in CSS leaves exactly one image per phone — the screenshot — so the only
   thing that can be clicked or zoomed is the design itself.

   Every dimension is a percentage of the phone's own width, because these sit
   in a grid that stays three columns all the way down to phone widths; fixed
   pixel chrome would be right at exactly one size. The two-value
   border-radius compensates for the box being ~2.17x taller than it is wide,
   which keeps the corners circular instead of stretched into ovals.
   ══════════════════════════════════════════════════════════════════════ */
.cs-phone {
  position: relative;
  background: #000;
  border-radius: 17.7% / 8.2%;
  padding: 3.85%;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08),
              0 32px 80px rgba(0, 0, 0, 0.5);
}
.cs-phone-screenbox {
  position: relative;
  background: #000;
  border-radius: 15.8% / 7.3%;
  overflow: hidden;
}
/* The Game Jam screens are drawn on cream rather than filling to the edges */
.cs-phone-screenbox--light { background: #f5f0e8; }

.cs-phone-shot { width: 100%; display: block; }

.cs-phone-notch {
  position: absolute;
  top: 1.9%;
  left: 50%;
  transform: translateX(-50%);
  width: 33%;
  height: 3.8%;
  background: #000;
  border-radius: 999px;
  z-index: 2;
  box-shadow: 0 0 0 2px #111;
}
.cs-phone-btn {
  position: absolute;
  width: 1.15%;
  background: #3a3a3a;
  z-index: 2;
}
.cs-phone-btn--mute  { left: -1.15%;  top: 14%;   height: 4.8%;  border-radius: 2px 0 0 2px; }
.cs-phone-btn--up    { left: -1.15%;  top: 20.3%; height: 7.75%; border-radius: 2px 0 0 2px; }
.cs-phone-btn--down  { left: -1.15%;  top: 29.5%; height: 7.75%; border-radius: 2px 0 0 2px; }
.cs-phone-btn--power { right: -1.15%; top: 22.9%; height: 10.3%; border-radius: 0 2px 2px 0; }

/* The hero blob's canvas. Lives inside .page-fade, whose transform makes it a
   containing block, so this is absolute + translated by scrollY each frame
   rather than fixed. z-index -1 keeps it behind the page's own content. */
.page-fade > .hero-orb {
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
  display: block;
  pointer-events: none;
  will-change: transform;
}

/* ══════════════════════════════════════════════════════════════════════
   WORK PAGE — 2x2
   .work-page is shared with the home page, so this is scoped to the modifier
   rather than applied to the class. The eyebrow spans both tracks so it stays
   a heading over the pair of columns rather than becoming a third cell.
   ══════════════════════════════════════════════════════════════════════ */
@media (min-width: 861px) {
  .work-page--grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    column-gap: clamp(24px, 3vw, 44px);
    row-gap: 8px;
    align-items: start;
  }
  .work-page--grid .work-eyebrow { grid-column: 1 / -1; }
  /* Half-width cards do not need the full-width card's tail of padding */
  .work-page--grid .project { padding-bottom: 28px; }
}

/* ============================================================
   CASE STUDY — CENTRED MEDIA WITH TEXT UNDER
   Screens are the evidence, so they take the middle and the words go
   underneath them, the same way the lo-fi wireframe rows already work.
   .cs-showcase   a centred row of phones, each captioned under itself
   .cs-figure-center  one image, centred
   .cs-blurbs     2-3 short titled blurbs in a row (under a figure)
============================================================ */
.cs-showcase {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  gap: 40px 32px;
  margin-top: 16px;
}
.cs-showcase-item {
  width: 260px;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.cs-section-body .cs-showcase-item figcaption .cs-mini-heading { margin: 0 0 6px; }
.cs-section-body .cs-showcase-item figcaption p:not(.cs-mini-heading),
.cs-section-body .cs-blurbs p:not(.cs-mini-heading) {
  font-size: var(--fs-sm);
  line-height: 1.6;
  margin: 0;
}
.cs-figure-center { margin: 16px auto 0; }
/* One-line result under a row of screens or an image. */
.cs-section-body .cs-caption-center {
  max-width: 60ch;
  margin: 20px auto 0;
  text-align: center;
  font-size: var(--fs-sm);
  line-height: 1.6;
}
.cs-figure-center img { width: 100%; border-radius: 12px; display: block; }
.cs-blurbs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 24px 32px;
  margin-top: 28px;
}
.cs-section-body .cs-blurbs .cs-mini-heading { margin: 0 0 6px; }

@media (max-width: 700px) {
  /* The row of phones swipes on a phone, like the other phone rows. */
  .cs-showcase {
    flex-wrap: nowrap;
    justify-content: flex-start;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    padding: 4px 5vw 28px;
    margin-inline: -5vw;
    scroll-padding: 0 5vw;
  }
  .cs-showcase::-webkit-scrollbar { display: none; }
  .cs-showcase-item { flex-shrink: 0; scroll-snap-align: center; }
  .cs-showcase-item > [style*="box-shadow"] {
    box-shadow: 0 0 0 1px rgba(255,255,255,0.08), 0 14px 24px rgba(0,0,0,0.4) !important;
  }
  .cs-showcase-deco { display: none; }
  .cs-showcase-item[style*="width: min(432px"] { width: min(432px, 85vw) !important; }
  .cs-showcase:has(> .cs-showcase-item:only-of-type) { justify-content: center; }
}

/* Boxed blurbs: the same titled points, each set off as a card, for a set of
   parallel moves ("Our Plan"). */
.cs-blurbs--cards > div {
  background: var(--cs-surface, #556677);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 20px 22px;
}

/* Feedback panel: critique that changed the work, set apart from the copy
   around it so it reads as a turning point, not another list. The accent
   outline ties it to the project; points sit at --fs-h4 so they
   outrank body text without competing with the section headline.
   Text stays --text: the Fitty blue on #506171 is about 2.2:1, fine for the
   outline, too faint for words. Inner selectors are doubled with .cs-feedback
   so they outrank .cs-section-body p. */
.cs-feedback {
  background: var(--cs-surface, #556677);
  /* One even outline in the accent, all the way round. A thicker left
     stripe, as a border or a clipped bar, always bent awkwardly into the
     rounded corners. */
  border: 1px solid rgba(var(--accent-rgb), 0.55);
  border-radius: 16px;
  padding: clamp(24px, 3.5vw, 40px);
  margin: 48px 0 8px;
}
.cs-feedback .cs-feedback-label {
  font-family: var(--sans);
  font-size: var(--fs-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--text);
  margin: 0 0 10px;
}
.cs-feedback .cs-feedback-lead {
  font-family: var(--sans);
  font-size: var(--fs-h3);
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--text);
  margin: 0 0 28px;
}
.cs-feedback-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: clamp(20px, 3vw, 40px);
}
.cs-feedback-list li {
  display: flex;
  gap: 16px;
  align-items: flex-start;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}
.cs-feedback-num {
  font-family: var(--sans);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
  line-height: 1.6;
  flex-shrink: 0;
}
.cs-feedback .cs-feedback-list p {
  font-family: var(--sans);
  font-size: var(--fs-h4);
  line-height: 1.5;
  color: var(--text);
  margin: 0;
}
.cs-feedback-list strong { font-weight: 600; }

/* ============================================================
   CASE STUDY — RESULT ROW
   A final screen with its caption beside it: the screen on the left, a
   title in the project's accent and two or three short bullets on the right,
   centred together as a pair. Rows of phones become one row per screen.
   .cs-result--wide lets the media take the spare width (a row of three
   phones, a wide image) instead of sizing to its content.
============================================================ */
.cs-result {
  display: grid;
  grid-template-columns: auto minmax(220px, 360px);
  justify-content: center;
  align-items: center;
  column-gap: clamp(32px, 5vw, 80px);
  row-gap: 24px;
  margin-top: 32px;
}
.cs-result + .cs-result { margin-top: 64px; }
.cs-result--wide { grid-template-columns: minmax(0, 1fr) minmax(220px, 320px); }
.cs-result-media { min-width: 0; }
.cs-section-body .cs-result-title {
  font-family: var(--sans);
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.3;
  /* The accent, lifted toward white so an 18px title clears contrast on the
     dark ground for every project's colour. */
  color: color-mix(in srgb, rgb(var(--accent-rgb)) 65%, #fff);
  margin: 0 0 12px;
}
.cs-result-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
}
.cs-result-list li {
  position: relative;
  padding-left: 18px;
  font-size: var(--fs-body);
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.86);
}
.cs-result-list li::before {
  content: '';
  position: absolute;
  left: 2px;
  top: 0.68em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
}
.cs-result-deco { width: 150px; margin-top: 24px; display: block; }
@media (max-width: 700px) {
  .cs-result, .cs-result--wide { grid-template-columns: 1fr; justify-items: center; }
  /* Media takes the full column so images sized in px can shrink to it. */
  .cs-result-media { width: 100%; display: flex; justify-content: center; }
  .cs-result-text { width: 100%; max-width: 360px; }
  .cs-result + .cs-result { margin-top: 48px; }
  .cs-result-deco { display: none; }
}

/* Result phones a size up. The Fitty frames are built in fixed px (260px
   frame, 240px screens, a 240px-step tutorial slider), so zoom scales the
   whole frame together rather than stretching the screen inside it. */
.cs-result-media > .cs-phone-frame { zoom: 1.2; }
/* A row of phones beside its caption fills the media column, instead of the
   68% cap it has on its own. */
.cs-result .cs-three-col { max-width: none; margin-top: 0; }
.cs-result-list strong { color: var(--text); font-weight: 600; }

/* Result group: the phones of one subsection side by side, each with its
   title and bullets underneath, instead of one row per screen. Columns are
   the width of a zoomed phone (260px x 1.2), so three fit across the frame
   on a laptop and they wrap to fewer on narrower screens. */
.cs-result-group {
  display: grid;
  grid-template-columns: repeat(auto-fit, 312px);
  justify-content: center;
  justify-items: center;
  align-items: start;
  gap: 56px clamp(32px, 6vw, 120px);
  margin-top: 32px;
}
.cs-result-group > .cs-result {
  grid-template-columns: 1fr;
  justify-items: center;
  align-items: start;
  row-gap: 24px;
  margin: 0;
}
.cs-result-group .cs-result-media { display: flex; justify-content: center; }
.cs-result-group .cs-result-text { width: 312px; max-width: 100%; }
@media (max-width: 700px) {
  .cs-result-group { grid-template-columns: minmax(0, 1fr); row-gap: 48px; }
}

/* ============================================================
   CASE STUDY — PHONE LAYOUT
   Much of the case-study markup lays itself out with inline styles sized for
   a desktop column: rows of 260px phone mockups beside their copy, three
   220px wireframes in a row, four-up stat cards. Under 700px those rows ran
   the page up to 970px wide and it scrolled sideways. These hooks only act
   below that width (hence !important, to beat the inline styles), and
   .cs-mstrip is display:contents above it, so the desktop layout is untouched.
============================================================ */
.cs-mstrip { display: contents; }

@media (max-width: 700px) {
  /* Side-by-side rows → stacked */
  .cs-mstack {
    flex-direction: column !important;
    align-items: stretch !important;
    gap: 24px !important;
  }
  .cs-mstack > img,
  .cs-mstack > div > img[alt="Fitty guy"] {
    width: min(60%, 240px) !important;
    align-self: center !important;
    margin: 0 auto !important;
  }
  .cs-mstack > * { align-self: stretch; }

  /* Rows of phone mockups: phones become a swipeable strip, copy below */
  .cs-mrow {
    flex-direction: column !important;
    align-items: stretch !important;
    gap: 24px !important;
  }
  .cs-mstrip {
    display: flex;
    gap: 16px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    /* Runs edge to edge (the page gutter is 5%), with room for the side
       buttons and shadows, which the scroller would otherwise clip. */
    padding: 4px 5vw 36px;
    margin: 0 -5vw;
    scroll-padding: 0 5vw;
  }
  .cs-mstrip::-webkit-scrollbar { display: none; }
  .cs-mstrip > * { scroll-snap-align: center; flex-shrink: 0; }
  /* The desktop shadow blurs 80px, far past what the scroller can show, so
     it was cut into a visible box. A tighter one fits inside the padding. */
  .cs-mstrip > [style*="box-shadow"] {
    box-shadow: 0 0 0 1px rgba(255,255,255,0.08), 0 14px 24px rgba(0,0,0,0.4) !important;
  }

  /* Wireframe rows: same swipe treatment */
  .cs-mswipe {
    display: flex !important;
    justify-content: flex-start !important;
    gap: 16px !important;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    padding-bottom: 8px;
  }
  .cs-mswipe::-webkit-scrollbar { display: none; }
  .cs-mswipe > * { flex: 0 0 200px; scroll-snap-align: start; }

  /* Grids that simply need fewer columns */
  .cs-m1col { grid-template-columns: 1fr !important; }
  .cs-m2col { grid-template-columns: 1fr 1fr !important; gap: 16px !important; }
  .cs-m2col img { width: 100% !important; }
  .cs-m2col p { white-space: normal !important; }
  .cs-mpair { flex-shrink: 1 !important; justify-content: center; }
  .cs-mpair img { width: calc(50% - 6px) !important; }

  .cs-results > img { width: 96px !important; margin-right: -24px !important; }
  .cs-results > div { font-size: 28px !important; padding: 18px 36px 18px 42px !important; }

  .fitty-market-row { grid-template-columns: 1fr 1fr; gap: 12px; }
  .fitty-market-card { padding: 20px 16px; }
  .fitty-market-num { font-size: var(--fs-h2); }

  .cs-highlight-cards { grid-template-columns: 1fr; }
  .cs-highlight-card { padding: 22px; }

  .cs-design-system-row { grid-template-columns: 1fr; }
  .cs-ds-panel { padding: 24px; }
  .cs-design-system-row > * { min-width: 0; }
  .cs-ds-fonts { grid-template-columns: 1fr; }
  .cs-ds-color-row { flex-wrap: wrap; }

  .cs-bar-chart { width: 100%; gap: 12px; }

  /* The comparison table keeps its columns and scrolls sideways inside its
     own rounded box, rather than squeezing six columns into 350px. */
  .cs-comp-table { overflow-x: auto; }
  .cs-comp-row { min-width: 600px; }
}

/* ============================================================
   TEXT OVER THE BLOB — PHONE AND TABLET
   On pages with the hero blob (home, work), below 1000px it drifts under the
   project cards and the menu button, and its pale passages sit right behind
   white text. Each card gets a near-opaque panel: at 0.9 over pure white it
   composites to about rgb(72, 83, 94), where the off-white copy still
   measures ~6.6:1, so contrast holds wherever the blob is. A panel, not a
   text-shadow (a halo reads as a smudge; it was taken off the footer for
   that). No backdrop blur either: the canvas redraws every frame, and
   re-blurring it under every card would cost smooth scrolling.
   The home hero copy gets the same panel in index.html.
============================================================ */
@media (max-width: 999px) {
  body:has(canvas.hero-orb) .project {
    background: rgba(52, 64, 76, 0.9);
    border-radius: 18px;
    padding: 12px 12px 22px;
    margin-bottom: 24px;
  }
  body:has(canvas.hero-orb) .project-desc { color: rgba(236, 235, 231, 0.92); }
  body:has(canvas.hero-orb) .work-header {
    width: auto;
    background: rgba(52, 64, 76, 0.9);
    border-radius: 18px;
    margin: calc(var(--nav-h) + 40px) clamp(20px, 5vw, 48px) 32px;
    padding: 22px 20px 24px;
  }
  body:has(canvas.hero-orb) .work-header-sub { color: rgba(236, 235, 231, 0.92); }
  body:has(canvas.hero-orb) .work-eyebrow {
    background: rgba(52, 64, 76, 0.9);
    border-top: 0;
    border-radius: 12px;
    padding: 12px 16px;
  }
  body:has(canvas.hero-orb) .burger {
    background: rgba(52, 64, 76, 0.9);
    border-radius: 12px;
    padding: 12px 11px;
  }
}

/* ============================================================
   CASE STUDY — FRAME AND MEASURE
   Case studies use the home page's frame (min(1360px, 90vw)), so the side
   margins match across the site. Everything shares the frame's left edge:
   text, headings, cards, tables and figures all run its full width. Rows of
   screens and single figures stay centred in the frame.
============================================================ */
/* Tall full-page screenshots (ACM wireframe and homepage): narrow, so a
   reader sees the whole page's shape without scrolling past 2,000px of it. */
.cs-inner .cs-section-body > .cs-sm-img { max-width: 440px; }

/* One rhythm for subsections: the case studies had 24, 32 and 40px inline. */
.cs-section-body > .cs-subsection-label:not(:first-child) { margin-top: 48px !important; }
@media (max-width: 700px) {
  .cs-section-body > .cs-subsection-label:not(:first-child) { margin-top: 36px !important; }
}

/* ============================================================
   SITE FRAME
   Home, Work, About, every case study and the footer sit in the same frame,
   min(1360px, 90vw), so the side margins match on every page.
============================================================ */
.work-header,
.work-page--grid,
.about-page,
.footer-inner {
  width: min(1360px, 90vw);
  margin-inline: auto;
  padding-left: 0;
  padding-right: 0;
}
.footer { padding-left: 0; padding-right: 0; }
