:root {
  --bg: #FAFAFA;
  --bg-alt: #F0F0F0;
  --card: #FFFFFF;
  --text: #1B1730;
  --text-muted: #6B6684;
  --primary: #4F7DF3;
  --secondary: #4F7DF3;
  --accent: #4F7DF3;
  --heading: #1B1730;
  --btn-color: #4F7DF3;
  --btn-text: #FFFFFF;
  --border: rgba(27,23,48,0.08);
  /* Stronger-than-built-in curves: ease-out for anything entering or
     responding to input, ease-in-out for on-screen movement. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  /* Headings site-wide are Fira Sans (bold, loaded via Google Fonts on
     every page) — this single token drives every h1/h2/h3, the marquee/
     logo strip, and every other place that already reads var(--font-display)
     directly, so they all picked up Fira Sans automatically instead of
     needing a per-selector edit. Body copy stays on the rounded system
     stack (ui-rounded = SF Pro Rounded on Apple platforms; DM Sans, also
     loaded, is the non-Apple fallback). */
  --font-display: 'Fira Sans', ui-rounded, "SF Pro Rounded", "SF Pro Display", -apple-system, BlinkMacSystemFont, 'DM Sans', system-ui, sans-serif;
  --font-body: ui-rounded, "SF Pro Rounded", "SF Pro Display", -apple-system, BlinkMacSystemFont, 'DM Sans', system-ui, sans-serif;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

.wrap { max-width: 1100px; margin: 0 auto; padding: 0 24px; }

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 800;
  margin: 0;
  text-wrap: balance;
  text-transform: uppercase;
  letter-spacing: -0.01em;
}

p, li { text-wrap: pretty; }

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

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .marquee-track { animation: none !important; }
  .chat-card::after { animation: none !important; }
  .hero-aurora::before { animation: none !important; }
}

/* Nav */
/* A plain full-width bar (not the floating rounded capsule the rest of
   the site used before) — logo left, links + CTA right, thin bottom
   border instead of a shadow. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}

.nav-capsule {
  max-width: 1100px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 16px 24px;
}

.logo {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text);
  white-space: nowrap;
}

.logo-avatar {
  flex-shrink: 0;
  font-size: 1.05rem; font-weight: 800; letter-spacing: 0.01em; line-height: 1;
  color: var(--heading);
}

.nav-links { display: flex; align-items: center; gap: 24px; margin-left: auto; }
.nav-links a { font-size: 0.88rem; font-weight: 600; color: var(--text-muted); transition: color 160ms var(--ease-out); }
.nav-links a[aria-current="page"] { color: var(--text); }
@media (hover: hover) and (pointer: fine) { .nav-links a:hover { color: var(--text); } }
.nav-links a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
.nav-right { display: flex; align-items: center; justify-content: flex-end; gap: 22px; }

.nav-cta {
  background: var(--primary);
  color: #fff;
  font-weight: 700;
  font-size: 0.85rem;
  padding: 10px 20px;
  border-radius: 16px;
  white-space: nowrap;
}

/* Logo / company marquee. Sits directly under the pinned hero, and its
   glass effect (backdrop-filter) samples whatever is behind it — right at
   the scroll position where the hero's sticky content is detaching, that
   was blurring straight over the tail end of the tagline with zero gap to
   separate them. A real margin here (rather than relying on the hero's own
   unpin math) keeps the two comfortably apart regardless of scroll timing. */
.marquee {
  overflow: hidden;
  margin-top: 2vh;
  border-top: 1px solid rgba(255,255,255,0.4);
  border-bottom: 1px solid rgba(255,255,255,0.4);
  background: rgba(255,255,255,0.25);
  backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  padding: 36px 0;
}

.marquee-track {
  display: flex;
  width: max-content;
  gap: 28px;
  animation: marquee-scroll 32s linear infinite;
}

.marquee-track span {
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: clamp(1.5rem, 3vw, 2.1rem);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--text-muted);
  white-space: nowrap;
}

@keyframes marquee-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Case studies (home page, right after the hero).
   Default rendering: the 3 case studies stacked as normal white cards in
   normal document flow, all visible at once — this is the real content,
   and what no-JS/prefers-reduced-motion visitors get.
   Enhanced rendering (script.js adds .is-pinned to the section, motion
   allowed only): the section becomes 3x the viewport tall and its inner
   .case-studies-sticky pins for that whole distance. Each card is now
   absolutely stacked on the others at identical position — matching
   georgegeo.vercel.app's actual "deck" pattern (verified by inspecting
   its DOM: each slide is one self-contained absolutely-positioned unit
   toggled by a `.is-current`-equivalent class, not a shared text panel
   with a separately animated image) — and only the .active one is
   visible, crossfading in. Because every card's text sits at the exact
   same layout position, the text reads as staying put across the swap;
   the image gets its own extra translateY so it visibly rises into place
   on top of that. The shared counter/heading above the stack never
   changes position — only its number updates. */
.case-studies { padding: 40px 0; }
/* Only on the pinned/scroll-jacked desktop path (mirrors the hero's own
   .is-pinned gate): a plain, un-hijacked scroll buffer between the hero's
   two stages finishing and this deck's own sticky engaging, so there's a
   real stretch of ordinary scrolling as the "third scroll" rather than
   one scroll-jack running straight into the next. 18vh (plus the
   marquee's own margin, stacked on top of it) read as too much empty
   space; 6vh is enough to still feel like a distinct scroll. */
.case-studies.is-pinned { margin-top: 6vh; }
.case-studies-head { display: flex; align-items: baseline; gap: 16px; margin-bottom: 24px; }
.case-studies-counter { font-weight: 700; color: var(--text-muted); font-size: 1.1rem; }
.case-studies-counter .cs-current { color: var(--heading); }
.case-studies-heading {
  font-family: 'Fira Sans', var(--font-display);
  font-weight: 800;
  font-size: clamp(1.6rem, 3vw, 2.1rem);
  color: var(--heading);
}
.case-studies-stack { display: flex; flex-direction: column; gap: 40px; }
.case-studies-card {
  /* Needed so .project-link::after (position:absolute; inset:0 — see its
     rule for why) resolves against this card instead of escaping to the
     nearest positioned ancestor up the tree (or the page itself, with no
     positioned ancestor at all) and covering far more than this one card
     — .project-link was written for .project-card, which already sets
     this, so every other place that reuses the class needs its own. */
  position: relative;
  display: flex;
  align-items: center;
  gap: 56px;
}
/* The media is its own block, separate from the text: no shared card
   background, so the picture and the copy sit directly on the page.
   position:relative so a locked card's .project-thumb-lock badge (reused
   as-is from the older .project-thumb grid — same badge, new card shape)
   can position itself against this box instead of escaping to the page. */
.case-studies-media {
  position: relative;
  flex: 1 1 64%;
  aspect-ratio: 16 / 9;
  border-radius: 20px;
  box-shadow: 0 20px 50px rgba(27,23,48,0.10);
  background-size: cover;
  background-position: center;
}
.case-studies-info { flex: 1 1 30%; max-width: 420px; }
.case-studies-kicker {
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--text-muted);
  margin-bottom: 10px;
}
.case-studies-tags {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--accent);
  margin-bottom: 6px;
}
.case-studies-title {
  font-family: 'Fira Sans', var(--font-display);
  font-weight: 700;
  font-size: clamp(1.3rem, 2.4vw, 1.7rem);
  color: var(--heading);
  margin-bottom: 24px;
}
/* Results grid — matches the Figma frame's "Results" stat block on each
   card. The real numbers aren't final yet, so each value/caption is a
   plain gray placeholder bar (like the image boxes) rather than invented
   figures — swap .ph-number/.ph-caption for real text once the stats are
   ready. */
.case-studies-results { margin-bottom: 24px; }
.case-studies-results-label {
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--text-muted);
  margin-bottom: 12px;
}
.case-studies-results-grid {
  display: grid;
  grid-template-columns: repeat(2, auto);
  gap: 16px 32px;
}
.ph { display: block; border-radius: 6px; background: var(--bg-alt); }
.ph-number { width: 64px; height: 26px; margin-bottom: 8px; }
.ph-caption { width: 88px; height: 12px; }

/* --- Enhanced (pinned) rendering: scroll-controlled 3D "drum" deck ---
   Cards rotate around the X axis as they move from front-and-center to
   peeking beyond the top/bottom edge, like slides rolling over a drum.
   The displayed position is a continuous spring value (script.js),
   independently interpolated every rAF frame — not a discrete
   active/is-before/is-after class swap — so transform/opacity/the veil
   are all written inline per frame instead of via CSS transitions (a
   transition here would fight the spring's own easing/overshoot). */
/* position:relative + overflow:hidden unconditionally (not just when
   pinned) — .case-studies-bg below is position:absolute with a negative
   inset, and without a sized, clipping ancestor here it was escaping all
   the way up to the nearest positioned ancestor (main) on mobile, where
   main's height is the WHOLE page's content height, not this section's —
   inset:-20% of "the whole page" added ~600px of dead empty space below
   the footer. */
.case-studies-sticky { position: relative; overflow: hidden; }
/* Extended wireframe grid (.hero-grid markup reused, see index.html) behind
   the case-studies deck. Faded relative to the hero's own copy so it reads
   as background texture, not a second foreground element; the hover-glow
   mechanic (--glow via color-mix, see .hero-grid rules above) still applies
   since it shares the .hero-grid class. Needs its own position/z-index here
   (the hero's version relies on .hero being its only positioned ancestor,
   which doesn't apply here) and .case-studies-panel needs an explicit
   position+z-index in BOTH layouts (not just .is-pinned) so it reliably
   paints above this absolutely-positioned sibling — a position:absolute
   element paints above a position:static one regardless of DOM order. */
.case-studies-grid { position: absolute; inset: 0; z-index: 0; opacity: 0.35; }
.case-studies-panel { position: relative; z-index: 1; }
/* One sticky viewport (for the first slide to sit still in) plus a SHORT
   fixed scroll distance per additional slide — not another full 100vh
   each, which took many scroll ticks to get through one transition. 330px
   (50% more than the previous 220px, per feedback that it still felt too
   fast) is more than one wheel/trackpad scroll gesture, so it now takes a
   couple of scroll ticks to move to the next slide instead of a near-
   instant snap. */
.case-studies.is-pinned { position: relative; height: calc(100vh + (var(--cs-count, 4) - 1) * 330px); padding: 0; }
.case-studies.is-pinned .case-studies-sticky {
  position: sticky; top: 0; height: 100vh;
  display: flex; align-items: center;
  perspective: 1200px;
}
/* Slow decorative drift (script.js writes its transform from continuous
   scroll progress, not the stepped slide index) — sits behind the panel. */
.case-studies-bg {
  position: absolute; inset: -20% -10%;
  background: radial-gradient(circle at 30% 30%, rgba(79,125,243,0.08), transparent 60%);
  pointer-events: none;
  z-index: 0;
  will-change: transform;
}
.case-studies.is-pinned .case-studies-panel { width: 100%; height: calc(100vh - 96px); display: flex; flex-direction: column; position: relative; z-index: 1; }
.case-studies.is-pinned .case-studies-head { flex-shrink: 0; }
.case-studies.is-pinned .case-studies-stack { position: relative; flex: 1; min-height: 0; perspective: 1200px; }
.case-studies.is-pinned .case-studies-card {
  position: absolute;
  inset: 0;
  /* center, not stretch — the media box keeps the video's own 16:9 shape
     (below) instead of being forced to fill the full pinned-panel height,
     so the row sizes itself and sits centered in the available space. */
  align-items: center;
  pointer-events: none;
  transform-style: preserve-3d;
}
.case-studies.is-pinned .case-studies-card.active { pointer-events: auto; }
.case-studies.is-pinned .case-studies-media {
  aspect-ratio: 16 / 9;
  position: relative;
  transform-origin: 50% 50%;
}
/* The veil: darkens a card as it rolls away from center. --veil is set
   inline (0 to 0.75) by script.js each frame, not via a class. */
.case-studies.is-pinned .case-studies-media::after {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  background: #0b0b12;
  opacity: var(--veil, 0);
  pointer-events: none;
}
.case-studies.is-pinned .case-studies-info { align-self: center; }

/* Hero */
main { position: relative; }
.hero { position: relative; }
/* Pinned only once script.js confirms motion + width allow it (mirrors
   .case-studies.is-pinned): the section grows tall enough to give the
   two-stage scroll-scrub a deliberate distance, and .hero-sticky pins for
   that whole range. Unpinned (mobile / reduced-motion / no-JS) it's just a
   normal in-flow section — see the --stage1/--stage2 defaults below. */
.hero.is-pinned { height: 220vh; }
.hero.is-pinned .hero-sticky { position: sticky; top: 0; height: 100vh; display: flex; align-items: center; }
.hero-sticky { position: relative; min-height: 100vh; display: flex; align-items: center; padding: 64px 0 40px; }

/* Decorative wireframe grid behind the hero content. Muted by default;
   the hit area is a transparent rect covering the whole cell, not just the
   thin lines themselves, since a stroke a couple of px wide is too small a
   target to hover reliably. Hover color is driven by a --glow custom
   property (0-1, set per cell by script.js) rather than a hard :hover
   swap — script.js gives the hovered cell and a few cells along its own
   row/column a falling-off --glow value, so the color reads as a soft
   blue that fades at both ends and spreads into the connecting lines,
   not a flat on/off swap confined to one cell. */
.hero-grid {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  z-index: 0;
  overflow: visible;
}
/* Fainter lines so the headline and subheading read cleanly over them (the
   case-studies copy sets its own opacity). */
.hero-grid:not(.case-studies-grid) { opacity: 0.4; }
.hero-grid .hit { fill: transparent; pointer-events: fill; }
.hero-grid .border,
.hero-grid .diag {
  stroke: color-mix(in srgb, var(--primary) calc(var(--glow, 0) * 100%), var(--border) calc((1 - var(--glow, 0)) * 100%));
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
  transition: stroke 0.25s var(--ease-out);
  pointer-events: none;
}
.hero-grid .pill {
  fill: none;
  stroke: var(--border);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

/* pointer-events:none so this (mostly empty/transparent) box doesn't sit
   on top of and block hover on .hero-grid underneath it — it's a div, so
   it's opaque to hit-testing everywhere inside its bounds regardless of
   what's actually visible there. Re-enabled on the one child that needs
   real hover/click/focus (.hero-circle, below). */
.hero .wrap { position: relative; z-index: 1; width: 100%; text-align: center; display: flex; flex-direction: column; align-items: center; pointer-events: none; }

/* Card-stack + badge reveal, driven by --stage1 (0 at rest, 1 once the
   cards have fully fanned out) and --stage2 (0 until stage 1 finishes, 1
   once the tagline has fully darkened) — see script.js for how these are
   written from scroll position. Both default to 1 on .hero itself, so any
   path that never runs the scroll JS (mobile, reduced-motion, no-JS) just
   renders the fully-revealed end state instead of getting stuck at rest. */
.hero { --stage1: 1; --stage2: 1; }

/* Original icon row, brought back in place of the card stack (hidden
   below). Five icons overlap in a condensed cluster and spread into an
   evenly spaced row as --stage1 goes 0 -> 1 — the same variable the card
   fan used to read, so it expands on "one scroll" exactly like before,
   with no separate scroll listener needed. --cx/--lx (condensed/spread
   x-offset) are set inline per icon in the HTML; a shared --hover-scale
   rides along so hover works at any point in the expansion. */
.hero-circles {
  position: relative;
  width: 320px;
  height: 64px;
  margin: 0 auto 32px;
}
.hero-circle {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 56px;
  height: 56px;
  margin: -28px;
  border-radius: 50%;
  background: #D9D9D9 center / cover no-repeat;
  pointer-events: auto;
  transform:
    translate(
      calc(var(--cx) + (var(--lx) - var(--cx)) * var(--stage1, 0)),
      0px
    )
    scale(var(--hover-scale, 1));
  transition: background 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease-out;
  cursor: pointer;
}
.hero-circle:hover { --hover-scale: 1.12; }
/* Icons run light blue to purple left to right; later DOM siblings stack on
   top, so each icon overlaps the one before it. The thin page-colored ring
   keeps the overlaps readable. */
.hero-circle { box-shadow: 0 0 0 2px var(--bg); }
.hero-circle:nth-child(1) { background-image: url('assets/icons/product-design.png'); }
.hero-circle:nth-child(2) { background-image: url('assets/icons/growth.png'); }
.hero-circle:nth-child(3) { background-image: url('assets/icons/research.png'); }
.hero-circle:nth-child(4) { background-image: url('assets/icons/prototype.png'); }
.hero-circle:nth-child(5) { background-image: url('assets/icons/crm.png'); }
/* Neighboring circles overlap in the cluster formation, so without this
   the tooltip of a circle whose sibling happens to sit later in the DOM
   (e.g. CRM & Lifecycle, next to Prototyping) can render underneath that
   neighbor. Raising the hovered/focused circle's own stacking level — not
   just its ::after/::before — lifts the whole thing above every sibling
   regardless of DOM order. */
.hero-circle:hover, .hero-circle:focus-visible {
  z-index: 10;
}
/* Tooltip: a plain CSS bubble (content pulled from data-label) rather
   than the native title="" attribute, so it matches the site instead of
   the browser's own unstyled tooltip. :focus-visible mirrors :hover so
   keyboard users (tabindex="0" on each circle) get the same label —
   aria-label carries the same text for screen readers, since generated
   ::after content isn't reliably exposed to them. */
.hero-circle::after {
  content: attr(data-label);
  position: absolute;
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  background: rgba(255,255,255,0.72);
  -webkit-backdrop-filter: blur(14px) saturate(180%);
  backdrop-filter: blur(14px) saturate(180%);
  border: 1px solid rgba(255,255,255,0.7);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.9), 0 6px 20px rgba(27,23,48,0.12);
  color: var(--heading);
  font-size: 0.75rem;
  font-weight: 600;
  padding: 6px 12px;
  border-radius: 10px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 125ms var(--ease-out), transform 125ms var(--ease-out);
  z-index: 5;
}
.hero-circle::before {
  content: '';
  position: absolute;
  bottom: calc(100% + 5px);
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: rgba(255,255,255,0.85);
  filter: drop-shadow(0 2px 2px rgba(27,23,48,0.08));
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
  z-index: 5;
}
.hero-circle:hover::after, .hero-circle:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(-2px);
}
.hero-circle:hover::before, .hero-circle:focus-visible::before {
  opacity: 1;
}

/* Card stack hidden in favor of the icon row above — see the HTML comment
   next to it for why the markup is left in place. */
.hero-stack { display: none; }

/* 8px read as basically nothing once the cards are fully fanned — their
   real (rotated) bottom edge sits much closer to the tagline than the
   box's own declared 600px height suggests, leaving only ~5px of actual
   clearance. 44px gives it real breathing room instead. Negative top
   margin pulls the whole stack up within the vertically-centered hero. */
.hero-stack { position: relative; width: min(800px, 94vw); height: 600px; margin: -70px auto 44px; }

/* Badges are hidden for now — the line pattern above stands in as the
   background texture instead. Positions/motion are left defined below so
   turning them back on is a one-line `display` flip, nothing to redo. */
.hero-badge {
  display: none;
  position: absolute; top: 50%; left: 50%;
  width: 72px; height: 72px; margin: -36px 0 0 -36px;
  border-radius: 50%;
  background-size: cover; background-position: center;
  box-shadow: 0 6px 16px rgba(27,23,48,0.18);
  z-index: 50;
  opacity: var(--stage1);
  transform: translate(calc(var(--badge-x) * var(--stage1)), calc(var(--badge-y) * var(--stage1)));
}
.hero-badge-1 { --badge-x: -330px; --badge-y: -230px; }
.hero-badge-2 { --badge-x: 10px;   --badge-y: -320px; }
.hero-badge-3 { --badge-x: 360px;  --badge-y: -110px; }
.hero-badge-4 { --badge-x: 280px;  --badge-y: 280px; }
.hero-badge-5 { --badge-x: -300px; --badge-y: 260px; }

/* Card fan: kept centered at BOTH ends of the animation rather than
   following the literal Figma canvas coordinates — each set of offsets
   below sums to (0,0) across the 4 cards, so the cluster's own visual
   center never drifts from the stack's center, at rest, at full fan, or
   anywhere in between (checked directly, not eyeballed). Figma's
   rotation values are still used exactly as given; only position was
   re-derived for this. Rest is a flat, unrotated, evenly-spaced cascade
   (matching the reference); the fanned target spreads into a symmetric
   X, two cards up, two down. */
.hero-card {
  position: absolute; top: 50%; left: 50%;
  width: 300px; height: 300px;
  margin: -150px 0 0 -150px;
  border-radius: 24px;
  background-size: cover; background-position: center;
  box-shadow: 0px 6px 23.7px 0px rgba(0,0,0,0.17);
  /* Centering is done once, via the negative margin above — no
     translate(-50%,-50%) here too, since stacking both would double the
     offset (by the card's own half-size). */
  transform:
    translate(
      calc(var(--rest-x, 0px) + (var(--target-x, 0px) - var(--rest-x, 0px)) * var(--stage1)),
      calc(var(--rest-y, 0px) * (1 - var(--stage1)) + var(--target-y, 0px) * var(--stage1))
    )
    rotate(calc(var(--card-rot, 0deg) * var(--stage1)));
}
.hero-card-1 { z-index: 40; --rest-x: -135px; --target-x: -220px; --target-y: 25px;   --card-rot: 0deg; }
.hero-card-2 { z-index: 30; --rest-x: -45px;  --target-x: -75px;  --target-y: -135px; --card-rot: 14.27deg; }
.hero-card-3 { z-index: 20; --rest-x: 45px;   --target-x: 75px;   --target-y: 135px;  --card-rot: -7.18deg; }
.hero-card-4 { z-index: 10; --rest-x: 135px;  --target-x: 220px;  --target-y: -25px;  --card-rot: 6.24deg; }

/* The tagline now carries the H1 treatment that "Product Designer" used to
   (same family/weight — but NOT uppercase; a full sentence reads better in
   normal case than the base h1/h2/h3 rule's all-caps, which was fine for
   short headings/labels but not a whole readable sentence), just sized for
   a full sentence instead of two words. Compound selector so this beats
   .tagline's own font-size/color regardless of source order — both .tagline
   and .hero-tagline are single-class selectors otherwise. */
.tagline.hero-tagline {
  font-family: 'Fira Sans', var(--font-display);
  font-weight: 800;
  font-size: clamp(2.4rem, 7vw, 5.2rem);
  line-height: 1.08;
  letter-spacing: -0.02em;
  text-transform: none;
  /* .tagline's own max-width:600px is meant for a body-copy paragraph —
     too narrow for an H1-sized headline, which is why each line was
     wrapping a second time inside it. */
  max-width: none;
  margin: 0;
}

/* Each line comes into focus on its own scroll stage — line 1 with the
   cards fanning out (--stage1), line 2 once that's settled (--stage2) —
   rather than the whole tagline fading in at once. display:block forces
   each span onto its own row regardless of viewport width, so "one line
   at a time" is a fixed split, not just wherever the text happens to wrap. */
.hero-tagline-line { display: block; }
.hero-tagline-line-1 {
  color: color-mix(in srgb, var(--text) calc(var(--stage1) * 100%), var(--text-muted) calc((1 - var(--stage1)) * 100%));
  opacity: calc(0.15 + var(--stage1) * 0.85);
}
.hero-tagline-line-2 {
  color: color-mix(in srgb, var(--text) calc(var(--stage2) * 100%), var(--text-muted) calc((1 - var(--stage2)) * 100%));
  opacity: calc(0.15 + var(--stage2) * 0.85);
}

/* Supporting line under the headline. Fades up with line 2 (--stage2) so
   the headline and its subheading finish revealing together. */
.hero-subline {
  margin: 14px 0 0;
  max-width: 680px;
  font-size: clamp(1rem, 1.8vw, 1.25rem);
  line-height: 1.5;
  color: var(--text-muted);
  opacity: calc(0.15 + var(--stage2) * 0.85);
}

/* Industry labels under the subheading; reveal with the subheading (--stage2). */
.hero-tags {
  position: absolute; left: 0; right: 0; bottom: 28px; z-index: 1;
  text-align: center;
  margin: 0;
  font-size: clamp(0.9rem, 1.6vw, 1.05rem);
  font-weight: 600;
  color: var(--text);
  opacity: calc(0.15 + var(--stage2) * 0.85);
}

/* Soft, blurred color-mesh backdrop, drifting right to left in a seamless
   loop. Fixed to the viewport (not just sized to the hero) so it sits behind
   the entire page as you scroll, not only the first section or two — a
   negative z-index keeps it behind every normal-flow section regardless of
   DOM stacking order, so opaque section backgrounds simply paint over it;
   sections without their own opaque background (or with a translucent one,
   like .section-alt and the marquee's glass effect) let it show through.
   Tiled via background-repeat rather than a duplicated element (unlike
   .marquee-track) since it's one continuous gradient, not discrete content. */
.hero-aurora {
  position: fixed; inset: 0;
  z-index: -1; overflow: hidden; pointer-events: none;
}
.hero-aurora::before {
  content: '';
  position: absolute; inset: -10% -10%;
  background-image:
    radial-gradient(circle at 8% 55%, rgba(35,150,150,0.9) 0%, transparent 26%),
    radial-gradient(circle at 30% 15%, rgba(10,12,18,0.85) 0%, transparent 22%),
    radial-gradient(circle at 55% 75%, rgba(230,80,35,0.9) 0%, transparent 28%),
    radial-gradient(circle at 78% 25%, rgba(242,160,65,0.9) 0%, transparent 30%),
    radial-gradient(circle at 100% 65%, rgba(248,222,182,0.95) 0%, transparent 32%);
  background-repeat: repeat-x;
  background-size: 1800px 100%;
  filter: blur(45px);
  opacity: 0.85;
  animation: aurora-drift 36s linear infinite;
}

@keyframes aurora-drift {
  from { background-position: 0 0; }
  to { background-position: -1800px 0; }
}

.profile-photo {
  width: 104px;
  height: 104px;
  border-radius: 50%;
  background: var(--primary);
  border: 3px solid var(--card);
  box-shadow: 0 8px 24px rgba(27,23,48,0.12);
  margin-bottom: 24px;
  object-fit: cover;
}

.eyebrow-bar {
  width: 52px;
  height: 4px;
  border-radius: 2px;
  background: var(--accent);
  margin-bottom: 20px;
}

.kicker {
  font-weight: 700;
  color: var(--text-muted);
  font-size: 0.95rem;
  margin: 0 0 6px;
}

/* The rotating role word (Designer / Thinker / Marketeer / Strategist /
   All-Rounder). JS in script.js swaps .swap on and off around each text
   change; the transition gives it a quick fade + slight rise. */
.role-word {
  display: inline-block;
  text-align: left;
  vertical-align: top;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.role-word.swap {
  opacity: 0;
  transform: translateY(6px);
}
@media (prefers-reduced-motion: reduce) {
  .role-word { transition: none; }
}

.tagline {
  font-size: clamp(1.05rem, 2vw, 1.3rem);
  color: var(--text-muted);
  max-width: 600px;
  margin: 0 0 32px;
}

.hero-actions { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 48px; }

/* Buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 10px 20px; border-radius: 16px; font-weight: 700; font-size: 0.85rem;
  transition: transform 160ms var(--ease-out); border: 2px solid transparent;
}
@media (hover: hover) and (pointer: fine) {
  .btn:hover { transform: translateY(-2px); }
}
.btn:active { transform: scale(0.97); }
.btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; }
.btn-primary {
  background: var(--btn-color);
  color: var(--btn-text);
}
.btn-outline {
  background: var(--card);
  color: var(--text);
  border-color: var(--btn-color);
}
.btn-outline:hover { border-color: var(--accent); }
.btn.big { font-size: 0.85rem; padding: 10px 20px; }

/* Chat card */
/* A compact "thinking" indicator above the chat header — a sphere made of
   many small dots spread evenly over its surface and rotated, so it reads
   as a solid rotating 3D orb of particles rather than a flat ring. The
   dot layout and rotation are computed in script.js (Fibonacci-sphere
   point distribution, drawn to a <canvas>) since that geometry isn't
   practical in CSS alone; this just sizes the container the canvas fills.
   Sits in normal flow (not absolutely overlapping the card edge) so it
   isn't clipped by .chat-card's own overflow:hidden. */
.thinking-orb { width: 64px; height: 64px; margin: 0 auto 18px; }
.thinking-orb-canvas { display: block; width: 100%; height: 100%; }

.chat-card {
  width: 100%;
  max-width: 720px;
  background: rgba(255,255,255,0.55);
  backdrop-filter: blur(22px) saturate(140%);
  -webkit-backdrop-filter: blur(22px) saturate(140%);
  border: 1px solid rgba(255,255,255,0.6);
  border-top-color: rgba(255,255,255,0.85);
  border-radius: 26px;
  padding: 32px;
  box-shadow: 0 20px 50px rgba(27,23,48,0.14), inset 0 1px 0 rgba(255,255,255,0.5);
  position: relative;
  overflow: hidden;
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .chat-card { background: var(--card); }
}

/* Border beam: a single-hue (mono) light chasing around the card's edge.
   The ring is a conic-gradient hollowed out to just its border thickness
   via mask-composite:exclude, so only a thin rotating band is visible —
   the card's own fill and content are untouched. @property lets the
   custom angle animate smoothly through the gradient instead of jumping;
   browsers without @property support just show a static ring (no spin),
   a harmless fallback rather than a broken one. */
@property --border-beam-angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}
.chat-card::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 2px;
  background: conic-gradient(from var(--border-beam-angle), transparent 0%, transparent 74%, rgba(79,125,243,0.25) 84%, #4F7DF3 92%, rgba(79,125,243,0.25) 97%, transparent 100%);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  animation: border-beam-spin 4.5s linear infinite;
  pointer-events: none;
  z-index: 2;
}
@keyframes border-beam-spin {
  to { --border-beam-angle: 360deg; }
}

.chat-content { position: relative; z-index: 1; padding-top: 6px; text-align: left; }
.chat-card-header { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 16px; }
.chat-dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--heading);
  box-shadow: 0 0 12px rgba(79,125,243,0.6);
  flex-shrink: 0;
}
.chat-card-header h2 { font-size: 1.05rem; text-transform: none; }
.chat-log {
  display: flex; flex-direction: column; gap: 12px;
  margin-bottom: 18px;
}
.chat-msg p { margin: 0; padding: 12px 16px; border-radius: 14px; font-size: 0.92rem; line-height: 1.55; max-width: 92%; }
.chat-msg-bot p { background: var(--bg); border: 1px solid var(--border); color: var(--text); border-bottom-left-radius: 4px; }
.chat-msg-user { display: flex; justify-content: flex-end; }
.chat-msg-user p {
  background: var(--btn-color);
  color: var(--btn-text); font-weight: 600; border-bottom-right-radius: 4px;
}
.chat-form { display: flex; gap: 10px; margin-bottom: 14px; }
.chat-form input {
  flex: 1; min-width: 0; background: var(--bg); border: 1px solid var(--border);
  border-radius: 999px; padding: 12px 18px; color: var(--text); font-family: var(--font-body); font-size: 0.92rem;
}
.chat-form input::placeholder { color: var(--text-muted); }
.chat-form input:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.chat-send { padding: 12px 22px; font-size: 0.9rem; white-space: nowrap; }
.chat-suggestions { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  background: var(--bg); border: 1px solid var(--border); color: var(--text-muted);
  font-size: 0.78rem; font-weight: 600; padding: 8px 14px; border-radius: 16px;
  cursor: pointer; transition: border-color 0.15s ease, color 0.15s ease; font-family: var(--font-body);
}
.chip:hover, .chip:focus-visible { border-color: var(--accent); color: var(--text); }

/* Sections */
.section { padding: 40px 0; }
/* Translucent (not solid) so the fixed aurora backdrop tints through rather
   than being fully blocked. */
.section-alt { background: rgba(240,240,240,0.6); }
.section-title { font-size: clamp(1.7rem, 3.2vw, 2.3rem); margin-bottom: 16px; }
.section-lede { color: var(--text-muted); max-width: 560px; margin: 0 0 40px; font-size: 1.02rem; }

/* Stats */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 32px;
}
.stat-item { border-top: 3px solid var(--accent); padding-top: 16px; }
.stat-item:nth-child(2) { border-top-color: var(--secondary); }
.stat-item:nth-child(3) { border-top-color: var(--primary); }
.stat-number {
  font-family: var(--font-display);
  font-size: clamp(2.4rem, 5vw, 3.4rem);
  line-height: 1;
  display: block;
  margin-bottom: 8px;
}
.stat-label { color: var(--text-muted); font-size: 0.92rem; font-weight: 500; }

/* Scroll-reveal: stats stay hidden until the grid scrolls into view, then
   cascade in one at a time. Gated behind .js-ready so content never gets
   stuck hidden if JS fails to run (progressive enhancement). */
.js-ready .stat-item {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}
.js-ready .stat-grid.in-view .stat-item { opacity: 1; transform: translateY(0); }
.js-ready .stat-grid.in-view .stat-item:nth-child(1) { transition-delay: 0s; }
.js-ready .stat-grid.in-view .stat-item:nth-child(2) { transition-delay: 0.18s; }
.js-ready .stat-grid.in-view .stat-item:nth-child(3) { transition-delay: 0.36s; }

@media (prefers-reduced-motion: reduce) {
  .js-ready .stat-item { opacity: 1; transform: none; transition: none; }
}

/* Work preview / case cards */
.project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 28px;
}
.project-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 20px;
  overflow: hidden;
  position: relative;
  transition: transform 200ms var(--ease-out), border-color 200ms ease;
}
@media (hover: hover) and (pointer: fine) {
  .project-card:hover { transform: translateY(-6px); border-color: var(--accent); }
}
/* Only cards with a real case-study link (not the plain summary cards on
   resume.html) get the pointer cursor and the focus-visible border cue. */
.project-card:has(.project-link) { cursor: pointer; }
.project-card:has(.project-link:focus-visible) { border-color: var(--accent); }
.project-thumb { height: 170px; position: relative; }
.project-thumb-lock {
  position: absolute; top: 14px; right: 14px; z-index: 2;
  display: flex; align-items: center; gap: 6px;
  background: rgba(255,255,255,0.92); color: var(--text);
  padding: 6px 12px; border-radius: 999px; font-size: 0.75rem; font-weight: 700;
}
.project-thumb-lock svg { width: 13px; height: 13px; }
.thumb-1 { background: var(--primary); }
.thumb-2 { background: var(--secondary); }
.thumb-3 { background: var(--accent); }
.thumb-4 { background: var(--primary); }
.thumb-5 { background: var(--secondary); }

/* A real case-study photo, reused as both the home-page card thumbnail and the
   case-study page's full-bleed banner. url() here resolves relative to this
   stylesheet's own location (project root), not the including page — so this one
   rule works correctly from both index.html and work/*.html without adjustment. */
.thumb-purecs-hero {
  background-image: url('assets/purecs/hero.png');
  background-size: cover;
  background-position: center;
}
.thumb-jugnu-hero {
  background-image: url('assets/jugnu/hero.png');
  background-size: cover;
  background-position: center;
}
.thumb-mualim-hero {
  background-image: url('assets/mualim/hero.png');
  background-size: cover;
  background-position: center;
}
/* Compound selectors (specificity 0,2,0) rather than relying on source
   order, since .case-gallery-shot's own dashed "needs a real photo" border
   sits later in this file and would otherwise win regardless of what a
   single-class .thumb-*-hero rule says — that dashed border is a placeholder
   signal, and shouldn't wrap a real photo once one is showing. */
.case-gallery-shot.thumb-purecs-hero,
.case-gallery-shot.thumb-jugnu-hero,
.case-gallery-shot.thumb-mualim-hero {
  border: none;
}
.thumb-kahunas-hero {
  background-image: url('assets/kahunas/hero.png');
  background-size: cover;
  background-position: center;
}
.thumb-fitt-hero {
  background-image: url('assets/fitt-meals/hero.png');
  background-size: cover;
  background-position: center;
}
/* No real image yet (see .case-gallery-shot) — a plain placeholder, not a
   real photo like the other .thumb-*-hero classes, until the FITT Meals
   subscription-journey assets are shared. */
.thumb-fitt-subscription { background: var(--bg-alt); border: 1px dashed var(--border); }
/* Card thumbnails (short, cropped strip) align every hero shot to the top so
   the mockup isn't cut off mid-frame. The full-bleed case-study banner keeps
   a centered crop by default — Kahunas's banner is top-aligned too since its
   tablet mockup sits high in the frame with a hand at the very bottom. */
.project-thumb.thumb-purecs-hero,
.project-thumb.thumb-jugnu-hero,
.project-thumb.thumb-kahunas-hero,
.case-hero-image.thumb-kahunas-hero {
  background-position: top;
}
/* FITT Meals card: shifted left (rather than centered) so the laptop reads
   fully instead of being cropped evenly off both edges. */
/* This image fills the card's width exactly under plain `cover` (it only
   crops top/bottom), leaving no horizontal room to pan — zoom in a bit so
   background-position-x actually has slack to shift left. */
.project-thumb.thumb-fitt-hero {
  background-size: 115% auto;
  background-position: 35% top;
}
/* Mualim's hero has a band of empty gradient above the laptop mockup, so a
   plain top crop shows mostly blank space on the short card thumbnail —
   nudge the crop window down past it instead of all the way to the top. */
.project-thumb.thumb-mualim-hero {
  background-position: center 22%;
}
.project-body { padding: 22px; }
.project-body h3 { font-size: 1.15rem; margin-bottom: 8px; text-transform: none; }
.project-body p { color: var(--text-muted); font-size: 0.92rem; margin: 0 0 16px; }
.project-link { font-weight: 700; color: var(--text); font-size: 0.88rem; }
.project-link .arrow { color: var(--accent); margin-left: 4px; transition: margin-left 0.15s ease; }
/* Stretches the case-study link over the whole card so the entire card is
   clickable, while keeping exactly one real <a> per card (no nested links).
   .project-link itself stays position:static so this ::after's absolute
   positioning resolves against .project-card (position:relative) instead. */
.project-link::after { content: ''; position: absolute; inset: 0; z-index: 1; }
.project-card:hover .project-link .arrow { margin-left: 8px; }
/* Card with two actions: the stretched .project-link covers the card, and this
   secondary link sits above that overlay so it stays separately clickable. */
.case-studies-actions { display: flex; flex-wrap: wrap; gap: 8px 24px; align-items: center; }
.project-link-plain { position: relative; z-index: 2; font-weight: 700; color: var(--text); font-size: 0.88rem; }
.project-link-plain .arrow { color: var(--accent); margin-left: 4px; transition: margin-left 0.15s ease; }
@media (hover: hover) and (pointer: fine) { .project-link-plain:hover .arrow { margin-left: 8px; } }
.project-link-plain:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

/* Scroll-driven stacking cards (home page "The work." section). Each card is
   sticky with a slightly deeper `top` than the one before it, so as the page
   scrolls, cards pile up under the floating nav one at a time — the next
   card's sticky point is a little lower, so it comes to rest just below the
   previous card's top edge, leaving a "peeking" sliver visible and reading
   as an overlapping deck. This is the whole effect: no scroll-jacking, no
   JS required for the core mechanic (script.js adds a small depth-scale
   polish on top — see the work-stack block there). Generous margin-bottom
   per card (not card height) is what gives each reveal room to scroll
   through, so the cards themselves stay the same size as the .my-work.html
   grid version. */
.work-stack { position: relative; padding: 8px 0 40px; }
.work-stack .work-stack-card {
  position: sticky;
  width: 100%;
  margin: 0 auto;
  margin-bottom: 55vh;
  box-shadow: 0 24px 50px rgba(27,23,48,0.16);
  will-change: transform;
  transition: transform 0.2s ease-out, filter 0.2s ease-out;
}
.work-stack .work-stack-card:nth-child(1) { top: 110px; }
.work-stack .work-stack-card:nth-child(2) { top: 138px; }
.work-stack .work-stack-card:nth-child(3) { top: 166px; }
.work-stack .work-stack-card:nth-child(4) { top: 194px; }
.work-stack .work-stack-card:nth-child(5) { top: 222px; }
/* Desktop ("web view"): each card fills the whole viewport below the
   floating nav, so the stack reads as full screens sliding over one
   another rather than small cards. min-height is sized off the shallowest
   card's top offset (110px, minus a small buffer) so every card's bottom
   edge reaches or passes the viewport's bottom edge — with no gap of
   background showing beneath it — and cards with a deeper top offset just
   overflow further below the fold, which is invisible and harmless. The
   thumbnail grows to fill the leftover space; the body stays its natural
   height at the bottom. Scoped to wider screens only — the compact card
   size below 760px is unchanged. */
@media (min-width: 761px) {
  .work-stack .work-stack-card {
    min-height: calc(100vh - 100px);
    display: flex;
    flex-direction: column;
  }
  .work-stack .work-stack-card .project-thumb {
    flex: 1 1 auto;
    height: auto;
    min-height: 320px;
  }
  .work-stack .work-stack-card .project-body {
    flex: 0 0 auto;
  }
}

/* Tags */
.tags { display: flex; gap: 8px; margin-bottom: 4px; flex-wrap: wrap; }
.tags span {
  font-size: 0.75rem; font-weight: 700; color: var(--accent);
  background: rgba(127,178,240,0.15); padding: 4px 10px; border-radius: 999px;
}

/* How I Work — a vertical run of "bubbles" that pop in one at a time as each
   scrolls into view (see the per-step IntersectionObserver in script.js). */
.how-steps {
  position: relative; display: flex; flex-direction: column; align-items: center;
  gap: 88px; margin-top: 8px;
}
.how-steps::before {
  content: ''; position: absolute; left: 50%; top: 8px; bottom: 8px; width: 2px;
  background: var(--border); transform: translateX(-50%); z-index: 0;
}
.how-step {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  max-width: 460px; position: relative; z-index: 1;
}
.how-bubble {
  display: flex; align-items: center; justify-content: center;
  width: 116px; height: 116px; border-radius: 50%; flex-shrink: 0;
  background: var(--card); border: 2px solid var(--accent);
  box-shadow: 0 16px 32px rgba(20, 20, 40, 0.08);
  margin-bottom: 22px;
}
.how-bubble-number {
  font-family: var(--font-display); font-weight: 700; color: var(--accent);
  font-size: 2rem; line-height: 1;
}
.how-text h3 { font-size: 1.3rem; text-transform: none; margin-bottom: 10px; }
.how-text p { color: var(--text-muted); font-size: 0.95rem; }

/* Scroll-reveal: each bubble stays hidden until its step scrolls into view
   (see the per-step IntersectionObserver in script.js — the same one that
   was already triggering these one at a time), then "shoots" into place
   like a basketball arcing into a hoop: held low, arcs up and over with an
   overshoot, dips past its landing spot, rebounds slightly, and settles.
   The step's title/description fade in just after it lands. Gated behind
   .js-ready so everything renders visible immediately if JS fails
   (progressive enhancement), matching the stat-grid reveal pattern above. */
.js-ready .how-bubble { opacity: 0; }
.js-ready .how-step.in-view .how-bubble {
  animation: how-bubble-shoot 0.9s cubic-bezier(0.3, 1, 0.6, 1) forwards;
}

.js-ready .how-text {
  opacity: 0; transform: translateY(18px);
  transition: opacity 0.45s ease 0.4s, transform 0.45s ease 0.4s;
}
.js-ready .how-step.in-view .how-text { opacity: 1; transform: translateY(0); }

@keyframes how-bubble-shoot {
  0%   { opacity: 0; transform: translate(-64px, 86px) scale(0.42) rotate(-28deg); }
  40%  { opacity: 1; }
  58%  { transform: translate(18px, -36px) scale(1.14) rotate(12deg); }
  76%  { transform: translate(-6px, 10px) scale(0.93) rotate(-5deg); }
  90%  { transform: translate(2px, -4px) scale(1.03) rotate(2deg); }
  100% { opacity: 1; transform: translate(0, 0) scale(1) rotate(0deg); }
}

@media (prefers-reduced-motion: reduce) {
  .js-ready .how-bubble,
  .js-ready .how-text {
    opacity: 1; transform: none; animation: none; transition: none;
  }
}

/* What Makes Different */
.diff-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 40px; text-align: center;
}
.diff-icon {
  width: 56px; height: 56px; margin: 0 auto 20px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 16px; background: var(--card); border: 1px solid var(--border);
  color: var(--accent);
}
.diff-icon svg { width: 26px; height: 26px; }
.diff-item h3 { font-size: 1.1rem; text-transform: none; margin-bottom: 10px; }
.diff-item p { color: var(--text-muted); font-size: 0.92rem; }

/* Testimonials — a single-card carousel (one testimonial visible at a
   time, prev/next arrows centered below) rather than a grid of all of
   them at once. Matches the Figma frame: quote text on the left, the
   person's photo/name/title as their own right-hand column, top-aligned
   with the quote — not the centered single-column stack used before.
   Like the case-studies section, every slide's real content stays in the
   HTML and non-active slides are just hidden (display:none, toggled by
   script.js) — a no-JS visitor still gets slide 1 (marked active in the
   markup), and nothing here depends on JS to be readable, just to cycle. */
.testimonial-carousel { position: relative; }
.testimonial-slide { display: none; }
.testimonial-slide.active { display: block; }
.testimonial-row { display: flex; gap: 64px; align-items: flex-start; }
.testimonial-text { flex: 1 1 65%; color: var(--text); font-size: 1.1rem; line-height: 1.6; }
.testimonial-person { flex: 0 0 auto; width: 200px; }
/* Placeholder avatar (flat color, like .profile-photo/.about-photo elsewhere on
   the site) until real photos are dropped into assets/testimonials/ — swap each
   for <img class="testimonial-photo" src="assets/testimonials/name.jpg" alt="Name">
   and this rule's background no longer matters. */
.testimonial-photo {
  display: block;
  width: 56px; height: 56px; border-radius: 50%;
  background: var(--primary); object-fit: cover;
  margin-bottom: 12px;
}
/* .testimonial-name/-title are <p> tags, which carry a browser default
   top/bottom margin — reset it so the name and title sit tight together
   as one block instead of spaced like separate paragraphs. */
.testimonial-name { font-weight: 700; font-size: 0.95rem; margin: 0 0 2px; }
.testimonial-title { color: var(--text-muted); font-size: 0.85rem; margin: 0; }
.testimonial-nav { display: flex; justify-content: center; gap: 12px; margin-top: 40px; }
.testimonial-nav button {
  width: 40px; height: 40px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--card);
  color: var(--text); font-size: 1.1rem;
  display: flex; align-items: center; justify-content: center;
  transition: border-color 0.15s ease, transform 160ms var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .testimonial-nav button:hover { border-color: var(--accent); transform: translateY(-2px); }
}
.testimonial-nav button:active { transform: scale(0.95); }

/* Going above and beyond — no buttons, no manual drag: the track's
   horizontal position is driven entirely by the section's own scroll
   progress through the viewport (see script.js), so it just drifts into
   view as the visitor scrolls past. overflow:hidden on the outer element
   means the only way to move the track is that scroll-linked transform. */
.beyond-carousel { overflow: hidden; }
.beyond-track { display: flex; gap: 28px; width: max-content; }
.beyond-card { display: block; width: clamp(220px, 30vw, 280px); flex-shrink: 0; }
.beyond-media {
  padding: 0; border: 0; cursor: pointer; text-align: left; display: block; position: relative;
  aspect-ratio: 4 / 5; border-radius: 18px; overflow: hidden;
  background: var(--bg-alt); border: 1px solid var(--border);
  margin-bottom: 16px;
}
.beyond-card h3 { font-family: 'Fira Sans', var(--font-display); font-size: 1rem; text-transform: none; margin-bottom: 4px; }
.beyond-card p { color: var(--text-muted); font-size: 0.9rem; }
/* A small play affordance on the thumbnail hints the card expands. */
.beyond-play {
  position: absolute; inset: 0; margin: auto; width: 44px; height: 44px;
  border-radius: 50%; background: rgba(13,13,15,0.55);
  opacity: 0; transition: opacity 160ms var(--ease-out), transform 160ms var(--ease-out);
  transform: scale(0.9);
}
.beyond-play::after {
  content: ''; position: absolute; top: 50%; left: 54%; transform: translate(-50%, -50%);
  border-style: solid; border-width: 8px 0 8px 14px; border-color: transparent transparent transparent #fff;
}
@media (hover: hover) and (pointer: fine) {
  .beyond-media:hover .beyond-play { opacity: 1; transform: scale(1); }
}
.beyond-media:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.beyond-media:active .beyond-play { transform: scale(0.94); }

/* Lightbox: click a "Going above and beyond" video to see it larger. The
   outer layer scrolls vertically (not the card) so a tall video, or a short
   viewport, never clips — you scroll the overlay itself to see all of it. */
.beyond-lightbox { position: fixed; inset: 0; z-index: 300; }
.beyond-lightbox[hidden] { display: none; }
.beyond-lightbox-backdrop { position: fixed; inset: 0; background: rgba(13,13,15,0.82); }
.beyond-lightbox-scroll {
  position: relative; height: 100%; overflow-y: auto; overscroll-behavior: contain;
  display: flex; align-items: center; justify-content: center; padding: 56px 24px;
}
.beyond-lightbox-card { position: relative; width: min(560px, 100%); }
.beyond-lightbox-stage { line-height: 0; }
.beyond-lightbox-video {
  display: block; width: 100%; max-height: calc(100vh - 112px);
  border-radius: 16px; background: #000;
}
.beyond-lightbox-close {
  position: absolute; top: -44px; right: 0; width: 36px; height: 36px;
  border-radius: 50%; border: none; background: rgba(255,255,255,0.12); color: #fff;
  font-size: 1.3rem; line-height: 1; cursor: pointer; font-family: var(--font-body);
  display: flex; align-items: center; justify-content: center;
  transition: background-color 160ms var(--ease-out);
}
@media (hover: hover) and (pointer: fine) { .beyond-lightbox-close:hover { background: rgba(255,255,255,0.22); } }
.beyond-lightbox-close:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* About preview (home page teaser — the full story lives on about.html).
   No heading here (removed per feedback — the section reads fine as a
   direct photo+bio without one): a photo+bio column on the left with a
   large supporting image placeholder on the right. */
.about-preview-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 64px; align-items: start; }
/* position:relative for the same reason as .case-studies-card above — so
   the "Read the full story" .project-link::after overlay scopes to this
   column instead of escaping past it. */
.about-preview-text { position: relative; }
.about-preview-photo {
  width: 120px; height: 120px; border-radius: 50%;
  object-fit: cover; border: 3px solid var(--card);
  box-shadow: 0 8px 24px rgba(27,23,48,0.12);
  margin-bottom: 24px;
}
/* Sized and set in the display face (same 'Fira Sans' bold treatment as the
   hero tagline) rather than the usual muted body-copy styling, since this
   bio now reads as a statement, not filler text. Falls back to a solid
   --heading color when JS doesn't run; .about-reveal-word (below) overrides
   that per word once script.js wraps them for the scroll highlight. */
.about-preview-text p {
  color: var(--heading);
  font-family: 'Fira Sans', var(--font-display);
  font-weight: 700;
  font-size: clamp(1.15rem, 1.9vw, 1.5rem);
  line-height: 1.4;
  margin-bottom: 20px;
  max-width: 520px;
}
/* Per-word highlight for the about-preview bio, driven by script.js: muted
   at --reveal:0, full color at --reveal:1. JS sets this continuously as the
   paragraph scrolls through the reading band (same color-mix technique as
   .hero-tagline-line), so words light up in scroll order and dim back out
   on scroll-up instead of firing once. The --reveal:1 default means a word
   renders fully colored until JS actually starts driving it (or if it never
   runs at all under prefers-reduced-motion), so nothing flashes muted. */
.about-reveal-word {
  color: color-mix(in srgb, var(--heading) calc(var(--reveal, 1) * 100%), var(--text-muted) calc((1 - var(--reveal, 1)) * 100%));
}
.about-preview-media {
  aspect-ratio: 4 / 5;
  background: url('assets/about/awards.png') center / contain no-repeat;
}

/* Let's connect — a dark panel for the closing CTA, echoing the case
   studies section's departure from the rest of the page but in a near-
   black rather than light-editorial direction, as a deliberate bookend. */
.connect-panel {
  background: #12161F;
  border-radius: 28px;
  padding: 64px 48px;
  text-align: center;
  color: #fff;
}
.connect-panel .section-title { color: #fff; }
.connect-panel .section-lede { color: rgba(255,255,255,0.65); }
.connect-links { display: flex; justify-content: center; gap: 16px; flex-wrap: wrap; margin-top: 8px; }

/* The rest of the site's section titles intentionally keep the bold
   condensed all-caps display font — only this redesigned homepage's new
   sections switch to the rounded body font at normal case, matching the
   Figma reference instead of the site's older established look. Scoped
   by section/component rather than changing .section-title globally, so
   about.html/resume.html/my-work.html and the case-study pages are
   unaffected. */
.connect-panel .section-title {
  font-family: var(--font-body);
  text-transform: none;
  font-weight: 700;
}

/* "Going above and beyond." and "What people say about me" match the
   hero's own headline treatment (Fira Sans 800, all-caps — inherited from
   the base h1/h2/h3 rule since it's not overridden to none here like
   .connect-panel above). */
#beyond .section-title,
#testimonials .section-title {
  font-family: 'Fira Sans', var(--font-display);
  font-weight: 800;
}

/* Closing CTA glow */
.section-glow { position: relative; overflow: hidden; }
.section-glow::before {
  content: '';
  position: absolute; left: 50%; bottom: -220px; transform: translateX(-50%);
  width: 900px; height: 420px; max-width: 160%;
  background: radial-gradient(ellipse at center, rgba(127,178,240,0.3), transparent 72%);
  filter: blur(10px);
  pointer-events: none;
  z-index: 0;
}
.section-glow .wrap { position: relative; z-index: 1; }

/* About */
.about-grid { display: grid; grid-template-columns: 220px 1fr; gap: 48px; align-items: start; }
.about-photo {
  display: block; object-fit: cover;
  width: 220px; height: 220px; border-radius: 24px;
  background: var(--primary);
}
.about-text p { color: var(--text-muted); max-width: 640px; }
.skills { display: flex; flex-wrap: wrap; gap: 10px; list-style: none; padding: 0; margin: 24px 0 0; }
.skills li {
  background: var(--card); border: 1px solid var(--border); padding: 8px 16px;
  border-radius: 999px; font-size: 0.85rem; font-weight: 600; color: var(--text);
}

/* Resume page */
.resume-inner {
  text-align: center; display: flex; flex-direction: column; align-items: center; gap: 18px;
}
.resume-note { color: var(--text-muted); font-size: 0.85rem; }
.resume-note code { background: var(--card); border: 1px solid var(--border); padding: 2px 8px; border-radius: 6px; }

/* Case study pages */
.case-hero-image { height: 420px; }
.case-hero { padding: 56px 0 40px; }
.breadcrumb { font-size: 0.85rem; font-weight: 700; color: var(--text-muted); margin-bottom: 20px; display: inline-flex; align-items: center; gap: 6px; }
.breadcrumb:hover { color: var(--accent); }
.case-meta { display: flex; flex-wrap: wrap; gap: 10px; margin: 22px 0 0; }
.case-meta span {
  font-size: 0.78rem; font-weight: 700; color: var(--text-muted);
  border: 1px solid var(--border); padding: 6px 14px; border-radius: 999px;
}
.case-hero h1 { font-size: clamp(2.1rem, 5vw, 3.4rem); text-transform: none; margin-bottom: 14px; }
.case-summary { color: var(--text-muted); font-size: 1.05rem; max-width: 680px; }

.case-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 24px; padding: 40px 0; }
.case-stats .stat-number { font-size: clamp(1.8rem, 3.5vw, 2.4rem); }

.case-body { padding-top: 8px; padding-bottom: 64px; }
.case-body h2 { font-size: 1.4rem; text-transform: none; margin: 40px 0 14px; }
.case-body h2:first-child { margin-top: 0; }
/* Sub-heading for items grouped under a case-body h2 (e.g. the three demos
   under "AI learning for kids"). One size down from h2, same weight. */
.case-body h3 { font-size: 1.1rem; text-transform: none; margin: 28px 0 12px; }
.case-body p { color: var(--text-muted); margin: 0 0 18px; font-size: 1rem; }
.case-body ul { color: var(--text-muted); padding-left: 20px; margin: 0 0 18px; }
.case-body li { margin-bottom: 8px; }
.case-prose { max-width: 720px; }

.case-callout {
  background: var(--card); border-left: 3px solid var(--accent);
  border-radius: 4px 16px 16px 4px; padding: 20px 24px; margin: 28px 0;
  font-size: 1rem; color: var(--text);
  max-width: 720px;
}

/* Case study visual placeholders — before/after comparisons and screen
   galleries. Neutral (not accent-colored) so they read clearly as "swap in
   a real screenshot here" rather than as intentional decoration. */
.case-compare {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 24px; margin: 24px 0 40px;
}
.case-compare-label {
  font-size: 0.75rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 10px;
}
.case-compare-shot {
  height: 300px; border-radius: 16px;
  background: var(--bg-alt); border: 1px dashed var(--border);
}
.case-compare-img {
  display: block; width: 100%; height: auto;
  border-radius: 16px; border: 1px solid var(--border);
}

/* In-case-study video demo (e.g. the Mualim parent dashboard walkthrough).
   Same card treatment as .case-compare-img so it reads as part of the same
   visual family as the still screenshots. */
.case-video {
  display: block; width: 100%; height: auto;
  border-radius: 16px; border: 1px solid var(--border);
  background: var(--bg-alt);
  margin: 20px 0 40px;
}
/* Two (or more) videos shown side by side — e.g. the pair of AI-learning
   demo clips. Collapses to one column on narrow screens. */
.case-video-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 20px; margin: 20px 0 40px;
}
.case-video-grid .case-video { margin: 0; }

/* Wide reference images (e.g. a full design-system sheet) — scale to fit on
   desktop, but hold a legible minimum size on mobile so detail doesn't
   shrink into mush; the container scrolls horizontally instead. */
.case-design-system {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 20px 0 40px;
}
.case-design-system img {
  display: block; width: 100%; height: auto;
  border-radius: 16px; border: 1px solid var(--border);
}

.case-gallery {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px; margin: 20px 0 40px;
}
/* background-color (not the `background` shorthand) — a shorthand here
   would reset background-image to none on any element that also carries a
   .thumb-*-hero class (e.g. .case-gallery-shot.thumb-mualim-hero), silently
   hiding that real photo behind this placeholder's fallback fill regardless
   of which rule comes first in the file. */
.case-gallery-shot {
  height: 200px; border-radius: 14px;
  background-color: var(--bg-alt); border: 1px dashed var(--border);
}

/* Real-image grid (e.g. early exploration screens) — unlike .case-gallery's
   dashed placeholder boxes, these are actual <img> tags. */
.case-exploration-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px; margin: 20px 0 40px;
}
.case-exploration-grid img {
  display: block; width: 100%; height: auto;
  border-radius: 14px; border: 1px solid var(--border);
}

.case-flow-tags { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 24px; }
.case-flow-tags button {
  font-family: var(--font-body); font-size: 0.78rem; font-weight: 700; color: var(--text-muted);
  background: var(--card); border: 1px solid var(--border); padding: 8px 16px; border-radius: 16px;
  cursor: pointer; transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.case-flow-tags button:hover { border-color: var(--accent); color: var(--text); }
.case-flow-tags button[aria-selected="true"] {
  color: var(--text); border-color: var(--accent); background: var(--bg-alt);
}

.case-flow-panel[hidden] { display: none; }
.case-flow-columns {
  display: grid; grid-template-columns: 1fr 1fr; gap: 32px; margin-top: 4px;
}
.case-flow-col-label {
  font-size: 0.75rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 10px;
}
.case-flow-shots {
  display: flex; flex-wrap: nowrap; gap: 14px;
  overflow-x: auto; padding-bottom: 4px;
}
.case-flow-shots img {
  height: 260px; width: auto; flex-shrink: 0; display: block;
  border-radius: 14px; border: 1px solid var(--border);
}

.case-nav {
  display: flex; justify-content: space-between; align-items: center;
  padding-top: 32px; padding-bottom: 64px;
  flex-wrap: wrap; gap: 16px;
}

/* Sidebar-nav case study layout (FITT Meals subscription-journey page): a
   left-column jump nav that stays in view (position: sticky) while the
   sections scroll past on the right — plain anchor links, no scroll-spy,
   matching how .case-nav elsewhere on the site keeps navigation simple. */
.case-meta-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 24px; margin: 28px 0 0; padding-top: 8px;
}
.case-meta-grid dt {
  font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-muted); margin-bottom: 8px;
}
.case-meta-grid dd { font-size: 0.92rem; color: var(--text); margin: 0; line-height: 1.5; }

.case-layout { display: grid; grid-template-columns: 200px 1fr; gap: 56px; align-items: start; }
.case-sidebar { position: sticky; top: 96px; }
.case-sidebar-nav { display: flex; flex-direction: column; gap: 4px; }
.case-sidebar-nav a {
  font-size: 0.88rem; font-weight: 700; color: var(--text-muted);
  padding: 8px 0 8px 14px; border-left: 2px solid transparent;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.case-sidebar-nav a:hover { color: var(--text); }
.case-sidebar-nav a.active { color: var(--heading); border-left-color: var(--accent); }
.case-layout .case-body { padding-bottom: 0; }
/* Grid items default to min-width:auto, and percentage-width <img> children
   (the plain <img style="width:100%"> tags used for case-study screenshots)
   report their *intrinsic* natural width for that auto-minimum instead of the
   percentage — so without this, the single "1fr" column on mobile refuses to
   shrink below the widest embedded image's real pixel width, overflowing the
   whole page horizontally. */
.case-body { min-width: 0; }
.case-toggle-panels, .case-toggle-panel { min-width: 0; }
.case-layout section + section { margin-top: 48px; }

/* Problem / Context / Result — a numbered vertical list, each item a
   circular index badge plus a heading and paragraph. */
.case-steps { list-style: none; margin: 24px 0 32px; padding: 0; display: flex; flex-direction: column; gap: 20px; }
.case-steps li { display: flex; gap: 18px; align-items: flex-start; }
.case-step-num {
  flex-shrink: 0; width: 32px; height: 32px; border-radius: 50%;
  background: var(--bg-alt); color: var(--heading); font-weight: 700; font-size: 0.85rem;
  display: flex; align-items: center; justify-content: center;
}
.case-steps h4 { font-family: 'Fira Sans', var(--font-display); font-size: 1rem; text-transform: none; margin: 0 0 6px; color: var(--heading); }
.case-steps p { margin: 0; }

/* Defining personas — a row of neutral cards, one per persona: a name plus
   placeholder blurb bars (real per-persona copy still needed). */
.case-personas-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 16px; margin: 20px 0 36px;
}
/* Card background is var(--card) (not --bg-alt, which the .ph placeholder
   bars themselves are colored with) so the bars stay visible against it. */
.case-persona-card { background: var(--card); border: 1px solid var(--border); border-radius: 14px; padding: 20px; display: flex; flex-direction: column; gap: 10px; }
.case-persona-card h4 { font-family: 'Fira Sans', var(--font-display); font-size: 0.92rem; text-transform: none; margin: 0; color: var(--heading); }

/* Research board banner — the whole Research section collapses to this one
   link out to the actual board rather than reproducing its content inline.
   href is a placeholder until the real Figjam/board link is given. */
.case-research-banner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; height: 140px; padding: 0 28px; margin: 20px 0 8px;
  border-radius: 16px; text-decoration: none;
}
.case-research-banner-label { font-family: var(--font-display); font-weight: 700; font-size: 1.1rem; color: var(--heading); }
.case-research-banner-arrow { font-size: 1.3rem; color: var(--heading); transition: transform 160ms var(--ease-out); }
.case-research-banner { transition: transform 160ms var(--ease-out); }
.case-research-banner:active { transform: scale(0.98); }
@media (hover: hover) and (pointer: fine) {
  .case-research-banner:hover .case-research-banner-arrow { transform: translateX(4px); }
}

/* Survey demographic bar chart — real breakdown still needed, so the bar
   names are .ph placeholder bars too, not just the fill amounts. Follows
   the site's scroll-driven pattern: the fill's real (final) width is set
   inline via --pct and is what no-JS/reduced-motion visitors see at rest;
   script.js only ever animates FROM 0 up TO that value once the chart
   scrolls into view, it never invents the value itself. */
.case-chart { margin: 20px 0 8px; }
.case-chart-label { font-size: 0.85rem; font-weight: 700; color: var(--text); margin-bottom: 16px; }
.case-chart-bars { display: flex; flex-direction: column; gap: 14px; }
.case-chart-bar-row { display: flex; align-items: center; gap: 14px; }
.case-chart-bar-name { flex: 0 0 136px; font-size: 0.85rem; color: var(--text); }
.case-chart-bar-track { flex: 1; height: 14px; border-radius: 999px; background: var(--bg-alt); overflow: hidden; }
.case-chart-bar-fill {
  display: block; height: 100%; border-radius: inherit;
  background: var(--accent);
  width: calc(var(--pct, 0) * 100%);
  transition: width 900ms var(--ease-out);
}
.case-chart-note { font-size: 0.78rem; color: var(--text-muted); margin: 10px 0 0; }
/* Before script.js runs (or under reduced motion), the bar starts already
   at its resting width — animation is added, not required, to see the data. */
.case-chart-bar-fill.animate-in { width: 0; }

/* Core features — three self-contained blocks, each a short description, a
   video placeholder, and an expandable "how it works" detail. <details> is
   used for the expand/collapse so it works with zero JS and is keyboard-
   and screen-reader-accessible by default. */
.case-feature-block { margin: 14px 0 22px; }
.case-feature-block:last-child { border-bottom: none; padding-bottom: 0; }
.case-feature-block h4 { font-family: 'Fira Sans', var(--font-display); font-size: 1.05rem; text-transform: none; margin: 0 0 8px; color: var(--heading); }
.case-feature-video {
  display: flex; align-items: center; justify-content: center;
  height: 220px; margin: 16px 0;
  color: var(--text-muted); font-size: 0.78rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em;
}
.case-feature-expand {
  border: 1px solid var(--border); border-radius: 12px; padding: 14px 18px;
}
.case-feature-expand summary {
  cursor: pointer; font-weight: 700; color: var(--heading); font-size: 0.92rem;
  list-style: none;
}
.case-feature-expand summary::-webkit-details-marker { display: none; }
.case-feature-expand summary::after { content: '+'; float: right; color: var(--text-muted); }
.case-feature-expand[open] summary::after { content: '−'; }
.case-feature-expand p { margin: 12px 0 0; }

/* Research-heavy case-study components: sticky-note findings grid, empathy
   map quadrant, customer journey table, step-flow diagram, and alternating
   problem/solution panels. */
.case-note-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px; margin: 24px 0 40px;
}
.case-note {
  background: #FDE9B6; color: #4A3B12;
  border-radius: 10px; padding: 18px 20px;
  font-size: 0.92rem; font-weight: 700; line-height: 1.4;
}

.journey-flow {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin: 24px 0 40px;
}
.journey-flow .step {
  background: var(--card); border: 1px solid var(--border); border-radius: 12px;
  padding: 12px 18px; font-size: 0.85rem; font-weight: 700; color: var(--text);
  white-space: nowrap;
}
.journey-flow .arrow { color: var(--text-muted); font-size: 1.1rem; }

.empathy-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: var(--border); border: 1px solid var(--border);
  border-radius: 16px; overflow: hidden; margin: 24px 0 40px;
}
.empathy-quad { background: var(--card); padding: 24px 26px; }
.empathy-quad h4 {
  font-family: var(--font-display); font-size: 0.8rem; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--heading); margin: 0 0 12px; font-weight: 400;
}
.empathy-quad ul { margin: 0; padding-left: 18px; color: var(--text-muted); font-size: 0.9rem; }
.empathy-quad li { margin-bottom: 6px; }

.journey-table-wrap { overflow-x: auto; margin: 24px 0 40px; }
.journey-table { width: 100%; border-collapse: collapse; min-width: 680px; }
.journey-table th, .journey-table td {
  border: 1px solid var(--border); padding: 14px 16px; text-align: left;
  font-size: 0.86rem; vertical-align: top; color: var(--text-muted);
}
.journey-table th { background: var(--bg-alt); font-weight: 700; color: var(--text); }
.journey-table td:first-child, .journey-table th:first-child {
  font-weight: 700; background: var(--bg-alt); white-space: nowrap; color: var(--text);
}

.ps-pair {
  display: grid; grid-template-columns: 1fr 1fr;
  border-radius: 16px; overflow: hidden; margin: 0 0 20px;
}
.ps-problem, .ps-solution { padding: 28px 26px; }
.ps-problem { background: var(--bg-alt); }
/* Hardcoded (not var(--accent)) so this stays purple regardless of the
   sitewide accent color — this case study's problem/solution panels are
   meant to keep their original purple even where --accent changes. */
.ps-solution { background: #A78BFA; color: #fff; }
.ps-label { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.7; margin-bottom: 8px; }
.ps-title { font-family: var(--font-display); font-size: 1.2rem; margin: 0 0 10px; }
.ps-problem p { color: var(--text-muted); margin: 0 0 16px; font-size: 0.92rem; }
.ps-solution p { color: rgba(255,255,255,0.92); margin: 0 0 16px; font-size: 0.92rem; }

/* Real app screenshots inside a problem/solution panel — the full slide as
   uploaded (background, heading, and phone included), filling the column
   rather than cropped down to an isolated phone cutout. */
.ps-shot {
  display: block; width: 100%; height: auto;
  border-radius: 10px; margin: 0 0 16px;
}
.ps-solution .ps-shot { border: 1px solid rgba(255,255,255,0.25); }
.ps-problem .ps-shot { border: 1px solid var(--border); }

/* Wider before/after screenshot pairs (two phones side by side) used in the
   UI-revamp section. */
.case-shot-pair {
  display: block; width: 100%; max-width: 560px; height: auto;
  border-radius: 14px; border: 1px solid var(--border);
  margin: 4px 0 32px;
}

.case-format-tags { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 40px; }
.case-format-tags span {
  font-size: 0.78rem; font-weight: 700; color: var(--text);
  background: var(--bg-alt); border: 1px solid var(--border);
  padding: 8px 16px; border-radius: 999px;
}

/* Hardcoded (not var(--accent)) for the same reason as .ps-solution above —
   this case study's closing banner keeps its original purple. */
.case-banner {
  background: #A78BFA; color: #fff; padding: 72px 0; margin: 8px 0 0;
}
.case-banner .eyebrow {
  font-size: 0.8rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; opacity: 0.85;
}
.case-banner h2 {
  font-size: clamp(2.2rem, 7vw, 4rem); color: #fff; margin: 8px 0 0;
  text-transform: uppercase; line-height: 1.05;
}

/* Footer */
.site-footer { padding: 56px 0 0; background: #0d0d0f; color: #fff; overflow: hidden; }
.footer-top { display: flex; justify-content: space-between; align-items: flex-end; flex-wrap: wrap; gap: 32px; }
.footer-cta { display: flex; flex-direction: column; align-items: flex-start; gap: 24px; }
.footer-heading {
  font-family: 'Fira Sans', var(--font-display); font-weight: 800; color: #fff;
  font-size: clamp(2.4rem, 7vw, 5rem); line-height: 1.02; letter-spacing: -0.03em;
  margin: 0; max-width: 12ch;
}
/* Same size as the nav bar's Get in touch button. */
.footer-cta .btn { border: 0; }
.footer-links { display: flex; flex-direction: column; align-items: flex-end; gap: 10px; font-size: 0.9rem; }
.footer-links a { color: #fff; text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 1px; transition: opacity 160ms var(--ease-out); }
.footer-links a:focus-visible { outline: 2px solid #fff; outline-offset: 4px; border-radius: 2px; }
@media (hover: hover) and (pointer: fine) {
  .footer-links a:hover { opacity: 0.7; }
}

/* Locked case study modal. #lock-modal appears twice: on index.html it opens
   only when a .project-card.locked link is clicked; on the Jugnu case-study
   page it opens automatically on load (script.js checks its data-auto-open
   attribute) and sends every dismissal back to the homepage instead of
   revealing the page underneath. */
.lock-modal {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
.lock-modal[hidden] { display: none; }
.lock-modal-backdrop { position: absolute; inset: 0; background: rgba(27,23,48,0.55); }
.lock-modal-card {
  position: relative; z-index: 1; max-width: 380px; width: 100%;
  background: var(--card); border-radius: 24px; border: 1px solid var(--border);
  box-shadow: 0 30px 60px rgba(20,20,40,0.25);
  padding: 36px 32px; text-align: center;
}
.lock-modal-close {
  position: absolute; top: 16px; right: 16px; width: 32px; height: 32px;
  border-radius: 50%; border: none; background: var(--bg-alt); color: var(--text-muted);
  font-size: 1.2rem; line-height: 1; cursor: pointer; font-family: var(--font-body);
  display: flex; align-items: center; justify-content: center;
}
.lock-modal-close:hover { color: var(--text); }
.lock-modal-icon {
  width: 56px; height: 56px; margin: 0 auto 20px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 16px; background: var(--bg-alt); color: var(--accent);
}
.lock-modal-icon svg { width: 26px; height: 26px; }
.lock-modal-card h3 { font-size: 1.3rem; text-transform: none; margin-bottom: 10px; }
.lock-modal-card p { color: var(--text-muted); font-size: 0.95rem; margin-bottom: 24px; }

/* Responsive */
@media (max-width: 760px) {
  .nav-capsule { padding: 12px 16px; gap: 8px; }
  .logo-name { display: none; }
  .nav-links { gap: 14px; }
  .nav-links a { font-size: 0.8rem; }
  .nav-cta { padding: 8px 14px; font-size: 0.8rem; }
  .logo { font-size: 0.8rem; gap: 8px; }
  .logo-avatar { font-size: 0.9rem; }
  .logo { font-size: 0.72rem; }
  /* The pin never actually applies below 760px (see the hero-scroll script's
     pinnedAllowed() check), so --stage1/--stage2 stay at their CSS default
     of 1 and the stack/heading/tagline just render fully revealed — only
     sizing needs a mobile override here, no animation values. The fanned
     layout's card/badge offsets are all real px sized for the ~760px
     desktop stack, so a uniform scale-down (rather than re-deriving every
     offset for a narrow screen) is what keeps the whole composition —
     cards and badges alike, since they share the same coordinate system —
     proportionally intact instead of clipping at the screen edge. */
  /* -60px used to pull "Product Designer" up close enough that the fanned
     cards' real (scaled) bottom edge — taller than the box's own declared
     height, since the fan's unscaled footprint is much bigger than 340px —
     overlapped the heading by about 9px. -20px leaves a clean gap instead. */
  /* margin-top here overrides the desktop rule's -70px, which is tuned for
     the desktop card fan's own size — kept smaller here since the cards
     are already scaled down to fit a phone width, not because the layout
     itself (vertical centering) differs from desktop. */
  .hero-stack { width: 100%; height: 340px; margin-top: -24px; transform: scale(0.42); transform-origin: 50% 50%; margin-bottom: -20px; }
  .hero-circles { width: 160px; height: 48px;
    /* Closed by default on mobile — there's no scroll-driven reveal here
       (the hero never pins below 760px), so without this override the
       site-wide --stage1:1 default would render them permanently spread
       out. Scoping the override to .hero-circles itself (rather than the
       whole .hero) means only the icons are affected — the tagline lines,
       which read the same variable name from .hero's own value, are
       untouched. .is-open (added by script.js on tap) reveals them. */
    --stage1: 0;
  }
  .hero-circles.is-open { --stage1: 1; }
  .hero-circle { width: 40px; height: 40px; margin: -20px; }
  /* Fitting BOTH lines on one row each, at a true H1 size, isn't possible
     on a phone-width screen no matter how small the font goes short of
     genuinely illegible (~12-14px) — checked directly, not assumed. This
     size keeps it reading as a bold headline; each line may still wrap
     once on the narrowest phones, which reads better than either shrinking
     it into single-line-but-tiny or keeping it desktop-huge and overflowing. */
  .tagline.hero-tagline { font-size: 2.1rem; }
  /* The pinned/scroll-jacked deck never engages below 760px (see the
     case-studies script) — mobile always gets this plain stacked-column
     rendering, one full card after another, no is-pinned overrides needed. */
  .case-studies-card { flex-direction: column; align-items: flex-start; gap: 24px; }
  .case-studies-media { width: 100%; flex: none; }
  .about-preview-grid { grid-template-columns: 1fr; gap: 32px; }
  .testimonial-row { flex-direction: column; gap: 24px; }
  .testimonial-person { width: auto; }
  .connect-panel { padding: 40px 24px; }
  /* Same vertical-centering layout as desktop (the base .hero-sticky rule
     already gives min-height:100vh + align-items:center — this just tightens
     the padding a bit for a narrower screen, nothing about the alignment
     itself changes here). */
  .hero-sticky { padding: 32px 0 16px; }
  /* Tighter than the desktop 36px — on a narrow screen the marquee reads as
     a divider between the hero and Case Studies, not a section of its own,
     so it doesn't need much breathing room on either side. */
  /* Mobile never pins the hero, so there's no sticky-detach frame for the
     marquee's blur to catch — the desktop-only 6vh buffer above would just
     be dead space here, working against the tighter gap already set above. */
  .marquee { margin-top: 0; padding: 16px 0; }
  /* Force the rotating word onto its own line, always — "I'm Hafsah, the"
     stays together on line 1 no matter which word is showing. Without this,
     shorter words wrap onto line 1 with "the" and longer ones don't, so the
     heading's line break moves around as the word rotates. display:block
     takes it out of the inline flow entirely so it always starts a new line;
     margin:auto centers its fixed-width box (set by script.js) under the
     centered line above, and text-align:center keeps the word itself
     centered inside that box instead of flush against its left edge. */
  .role-word {
    display: block;
    margin: 4px auto 0;
    text-align: center;
  }
  .profile-photo { width: 84px; height: 84px; margin-bottom: 18px; }
  .chat-card { padding: 24px; }
  .thinking-orb { width: 48px; height: 48px; margin-bottom: 14px; }
  .case-hero-image { height: 220px; }
  .case-compare-shot { height: 220px; }
  .case-design-system img { width: auto; height: 480px; max-width: none; }
  .case-gallery-shot { height: 150px; }
  .case-flow-columns { grid-template-columns: 1fr; gap: 24px; }
  .case-flow-shots img { height: 220px; }
  .empathy-grid { grid-template-columns: 1fr; }
  .ps-pair { grid-template-columns: 1fr; }
  .case-layout { grid-template-columns: 1fr; gap: 0; }
  /* The jump nav only earns its space once there's a viewport tall enough
     to keep it in view alongside the content it points to — on mobile it's
     just one more thing to scroll past before reaching the real content. */
  .case-sidebar { display: none; }
  .journey-flow .step { font-size: 0.78rem; padding: 10px 14px; }
  .about-grid { grid-template-columns: 1fr; }
  .about-photo { width: 140px; height: 140px; }
  .chat-form { flex-wrap: wrap; }
  .chat-send { width: 100%; }
  .footer-inner { flex-direction: column; align-items: flex-start; }
  .how-steps { gap: 64px; }
  .how-steps::before { display: none; }
  .how-bubble { width: 96px; height: 96px; }
  .how-bubble-number { font-size: 1.6rem; }
  .section-glow::before { width: 600px; height: 300px; bottom: -140px; }
  .work-stack .work-stack-card { margin-bottom: 40vh; }
  .work-stack .work-stack-card:nth-child(1) { top: 88px; }
  .work-stack .work-stack-card:nth-child(2) { top: 106px; }
  .work-stack .work-stack-card:nth-child(3) { top: 124px; }
  .work-stack .work-stack-card:nth-child(4) { top: 142px; }
  .work-stack .work-stack-card:nth-child(5) { top: 160px; }
}


/* Press feedback: every pressable confirms it heard the tap. */
.chip, .case-flow-tags button {
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease, transform 160ms var(--ease-out);
}
.chip:active, .case-flow-tags button:active, .case-feature-expand summary:active { transform: scale(0.97); }
.case-feature-expand summary { transition: transform 160ms var(--ease-out); }

/* New motion honors reduced motion too: keep the state changes, drop the
   movement. */
@media (prefers-reduced-motion: reduce) {
  .case-studies.is-pinned .case-studies-media { transition: none; }
  .case-chart-bar-fill { transition: none; }
  .btn:active, .chip:active { transform: none; }
}

/* Type polish: comfortable reading measure for case-study prose, and
   tighter tracking/leading on large display headings so multi-line titles
   read as one block instead of loosely spaced lines. */
.case-body p, .case-body li { max-width: 65ch; }
.case-hero h1 { line-height: 1.1; letter-spacing: -0.02em; }
.case-body h2, .case-studies-heading { letter-spacing: -0.015em; line-height: 1.2; }
.case-callout, .case-steps p { max-width: 65ch; }

/* Shape scale (one rule, used everywhere): buttons = 16px radius,
   containers = 12-16px, large feature cards = 24px. */



/* Real video in place of the FITT Meals placeholder: fills its frame,
   cropped to cover. The case-page frame keeps the source's 16:9. */
.case-video-fill { display: block; width: 100%; height: 100%; object-fit: cover; }
.case-video { aspect-ratio: 16 / 9; border-radius: 14px; overflow: hidden; background: var(--bg-alt); margin-bottom: 48px; }
.case-studies-media:has(> .case-video-fill) { overflow: hidden; }

/* Initial concept / Final outcome toggle above a section's visual.
   Segmented pill (interactive = pill, like the rest of the site); the
   thumb slides on transform, the panels crossfade on opacity. */
.case-toggle { margin: 28px 0 8px; }
.case-toggle-bar {
  position: relative; display: inline-grid; grid-template-columns: 1fr 1fr;
  padding: 4px; border-radius: 16px; background: var(--bg-alt);
  border: 1px solid var(--border); margin-bottom: 16px;
}
.case-toggle-bar button {
  position: relative; z-index: 1; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 0.85rem; font-weight: 600; color: var(--text-muted);
  padding: 8px 18px; border-radius: 12px; white-space: nowrap;
  transition: color 160ms var(--ease-out), transform 160ms var(--ease-out);
}
.case-toggle-bar button[aria-selected="true"] { color: var(--heading); }
.case-toggle-bar button:active { transform: scale(0.97); }
.case-toggle-bar button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.case-toggle-thumb {
  position: absolute; top: 4px; bottom: 4px; left: 4px; width: calc(50% - 4px);
  border-radius: 12px; background: var(--card);
  box-shadow: 0 1px 3px rgba(27,23,48,0.12);
  transition: transform 220ms var(--ease-out);
}
.case-toggle-bar:has(button:last-of-type[aria-selected="true"]) .case-toggle-thumb { transform: translateX(100%); }
@media (hover: hover) and (pointer: fine) {
  .case-toggle-bar button[aria-selected="false"]:hover { color: var(--heading); }
}
.case-toggle-panels { display: grid; }
.case-toggle-panel {
  grid-area: 1 / 1; opacity: 0; visibility: hidden;
  transition: opacity 200ms var(--ease-out), visibility 0s linear 200ms;
}
.case-toggle-panel.is-active { opacity: 1; visibility: visible; transition-delay: 0s; }
/* A full-page screenshot is much taller than it is wide — showing it at
   full width would blow the panel's height out and make the toggle jump
   around when switching tabs. Capping the height and scrolling vertically
   inside it keeps the toggle's own height stable while still letting the
   whole screenshot be seen. */
.case-toggle-scroll { max-height: 480px; overflow-y: auto; border-radius: 12px; }
.case-toggle-scroll img { display: block; width: 100%; border-radius: 0; }
@media (max-width: 760px) { .case-toggle-bar { display: grid; } }
@media (prefers-reduced-motion: reduce) { .case-toggle-thumb { transition: none; } }

/* "The full story" callout: collapsed by default, opens on click. */
.case-story summary {
  cursor: pointer; list-style: none; font-weight: 700; color: var(--heading);
  transition: transform 160ms var(--ease-out);
}
.case-story summary::-webkit-details-marker { display: none; }
.case-story summary::after { content: '+'; float: right; color: var(--text-muted); font-weight: 400; }
.case-story[open] summary::after { content: '\2212'; }
.case-story summary:active { transform: scale(0.99); }
.case-story summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 4px; }
.case-story p { margin: 12px 0 0; }
.case-story[open] p { animation: case-story-in 220ms var(--ease-out); }
@keyframes case-story-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .case-story[open] p { animation: none; } }

/* Overview step icons replace the numbered circles. */
.case-step-icon { flex-shrink: 0; width: 44px; height: 44px; object-fit: contain; }

/* FITT Meals card results (real numbers instead of placeholder bars). */
.case-studies-result { display: flex; flex-direction: column; gap: 4px; }
.case-studies-stat { font-size: 1.7rem; font-weight: 700; line-height: 1.1; color: var(--heading); letter-spacing: -0.01em; }
.case-studies-stat-label { font-size: 0.85rem; color: var(--text-muted); }

/* Screenshot carousel: native scroll-snap track with prev/next buttons. */
.case-carousel-track {
  display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
  scrollbar-width: none; border-radius: 14px; border: 1px solid var(--border);
}
.case-carousel-track::-webkit-scrollbar { display: none; }
.case-carousel-track:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.case-carousel-slide { flex: 0 0 100%; scroll-snap-align: start; margin: 0; }
.case-carousel-slide img { display: block; width: 100%; height: auto; }
.case-carousel-controls { display: flex; align-items: center; justify-content: center; gap: 16px; margin-top: 14px; }
.case-carousel-count { font-size: 0.85rem; font-weight: 600; color: var(--text-muted); min-width: 3.5em; text-align: center; }
.case-carousel-btn {
  width: 40px; height: 40px; border-radius: 16px; border: 1px solid var(--border);
  background: var(--card); color: var(--heading); font: inherit; font-size: 1.4rem; line-height: 1;
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  transition: transform 160ms var(--ease-out), opacity 160ms var(--ease-out), background-color 160ms var(--ease-out);
}
.case-carousel-btn:active:not(:disabled) { transform: scale(0.95); }
.case-carousel-btn:disabled { opacity: 0.35; cursor: default; }
.case-carousel-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) { .case-carousel-btn:hover:not(:disabled) { background: var(--bg-alt); } }

/* Full-width image banners that link out to the research board / survey
   results (the banner artwork carries its own title and arrow). */
.case-link-banner {
  display: block; margin: 20px 0 8px; border-radius: 16px; overflow: hidden;
  border: 1px solid var(--border); background: #fff;
  transition: transform 160ms var(--ease-out), box-shadow 160ms var(--ease-out);
}
.case-link-banner img { display: block; width: 100%; height: auto; }
.case-link-banner:active { transform: scale(0.99); }
.case-link-banner:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
@media (hover: hover) and (pointer: fine) { .case-link-banner:hover { box-shadow: 0 10px 30px rgba(27,23,48,0.10); } }
.case-link-banner--static { transition: none; }
.case-link-banner--static:active { transform: none; }
@media (hover: hover) and (pointer: fine) { .case-link-banner--static:hover { box-shadow: none; } }

/* Survey chart: two groups (age, subscriber status) with a value on each
   row, then two headline stats underneath. Numbers count up on scroll-in. */
.case-chart-groups { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 28px 40px; }
.case-body .case-chart-group-title { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin: 0 0 12px; }
.case-chart-bar-value { flex: 0 0 3ch; text-align: right; font-size: 0.85rem; font-weight: 700; color: var(--heading); font-variant-numeric: tabular-nums; }
.case-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 24px 40px; margin: 28px 0 24px; }
.case-body .case-stat-num { font-size: clamp(2.4rem, 5vw, 3.2rem); font-weight: 700; line-height: 1; color: var(--heading); letter-spacing: -0.02em; margin: 0 0 8px; font-variant-numeric: tabular-nums; }
.case-body .case-stat-text { margin: 0; color: var(--text-muted); font-size: 0.95rem; max-width: 26ch; }

/* Persona cards */
.case-personas-grid { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); align-items: stretch; }
.case-persona-card { padding: 22px; gap: 14px; }
.case-persona-head { display: flex; align-items: center; gap: 12px; }
.case-persona-initial {
  flex-shrink: 0; width: 40px; height: 40px; border-radius: 50%; background: var(--bg-alt);
  color: var(--heading); font-weight: 700; display: flex; align-items: center; justify-content: center;
}
.case-persona-head h4 { font-size: 1.05rem; }
.case-body .case-persona-meta { margin: 2px 0 0; font-size: 0.78rem; color: var(--text-muted); }
.case-body .case-persona-title { margin: 0; font-weight: 700; color: var(--accent); font-size: 0.95rem; }
.case-persona-facts { margin: 0; display: flex; flex-direction: column; gap: 12px; }
.case-persona-facts dt { font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin-bottom: 3px; }
.case-persona-facts dd { margin: 0; font-size: 0.88rem; line-height: 1.5; }
.case-persona-quote { margin: auto 0 0; padding-top: 14px; border-top: 1px solid var(--border); font-style: italic; font-size: 0.85rem; line-height: 1.5; }
@media (max-width: 760px) { .case-chart-bar-name { flex-basis: 136px; } }

/* Wide chart group (top pain points): full width, long labels get a roomier
   name column; on phones the label sits on its own line above its bar. */
.case-chart-group--wide { grid-column: 1 / -1; }
.case-chart-group--wide .case-chart-bar-name { flex-basis: 300px; }
@media (max-width: 760px) {
  .case-chart-group--wide .case-chart-bar-row { flex-wrap: wrap; row-gap: 6px; }
  .case-chart-group--wide .case-chart-bar-name { flex: 1 0 100%; }
  .case-chart-group--wide .case-chart-bar-track { flex: 1; }
}
.beyond-media video { display: block; width: 100%; height: 100%; object-fit: cover; }

/* "More case studies" at the foot of each case study page: the other two
   cards from the home page, side by side (stacked media over text so two
   fit in one row). */
.next-cases { padding-top: 64px; padding-bottom: 32px; }
.next-cases-title { font-size: clamp(1.4rem, 3vw, 1.9rem); margin: 0 0 28px; }
.next-cases-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 40px 32px; }
.next-case { position: relative; display: flex; flex-direction: column; gap: 20px; }
.next-case .case-studies-media { flex: none; width: 100%; aspect-ratio: 16 / 9; }
.next-case .case-studies-info { flex: none; max-width: none; }
@media (max-width: 760px) { .next-cases-grid { grid-template-columns: 1fr; } }
#about-preview { background: #F3F3F3; }

/* Plain Dubai skyline silhouette along the foot of the footer. Drawn as a
   mask (assets/skyline.svg) so its color is just the background below. The
   mask is scaled up and top-anchored, so any cropping comes off the empty
   ground/base of the artwork at the bottom rather than off the rooflines
   at the top — both axes scaled by the same factor (1726/1176), so the
   buildings aren't stretched, just cropped. */
.footer-skyline {
  margin-top: 40px; aspect-ratio: 6001 / 1176; width: 100%;
  background: #2b2b30;
  -webkit-mask: url('assets/skyline.svg') center top / 100% 146.8% no-repeat;
  mask: url('assets/skyline.svg') center top / 100% 146.8% no-repeat;
}
@media (max-width: 640px) { .footer-links { align-items: flex-start; } }



.case-text-link { display: inline-block; margin: 4px 0 8px; font-weight: 700; color: var(--accent); text-decoration: none; border-bottom: 2px solid transparent; transition: border-color 160ms var(--ease-out); }
.case-text-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
@media (hover: hover) and (pointer: fine) { .case-text-link:hover { border-bottom-color: var(--accent); } }

/* Legacy screens: full-width images stacked vertically instead of a carousel */
.case-legacy-stack { display: flex; flex-direction: column; gap: 20px; margin: 16px 0 28px; }
.case-legacy-stack img { display: block; width: 100%; height: auto; border-radius: 14px; border: 1px solid var(--border); background: #fff; }

.case-feature-img { display: block; width: 100%; height: auto; margin: 16px 0 8px; border-radius: 14px; border: 1px solid var(--border); background: #fff; }

/* Banner with live text, reusing the illustration from the image banners */
.case-link-banner--live { display: flex; align-items: center; container-type: inline-size; aspect-ratio: 927 / 208; color: var(--heading); text-decoration: none; }
.case-link-banner-art { flex: 0 0 37.2%; align-self: stretch; overflow: hidden; }
.case-link-banner-art img { width: 268.8%; max-width: none; height: 100%; object-fit: cover; object-position: left center; }
.case-link-banner-copy { flex: 1; display: flex; flex-direction: column; gap: 1.2cqw; padding: 0 2cqw 0 4cqw; }
.case-link-banner-copy strong { font-size: max(15px, 3cqw); font-weight: 700; line-height: 1.15; letter-spacing: -0.01em; }
.case-link-banner-copy span { font-size: max(11px, 1.8cqw); color: var(--text); }
.case-link-banner-arrow { flex: 0 0 auto; margin-right: 5cqw; width: max(18px, 3.9cqw); height: auto; }

/* "Exploration" group above the drifting carousel: one card that opens a Figma prototype. */
.beyond-group { margin: 32px 0 40px; }
.beyond-group-title { font-family: 'Fira Sans', var(--font-display); font-size: 1.15rem; font-weight: 800; text-transform: none; margin: 0 0 16px; }
.beyond-card--link { text-decoration: none; color: inherit; width: min(100%, 520px); }
.beyond-card--link .beyond-media { aspect-ratio: 16 / 9; }
.beyond-card--link .beyond-media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.beyond-card--link .beyond-media { transition: transform 160ms var(--ease-out), box-shadow 160ms var(--ease-out); }
.beyond-card--link:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 18px; }
.beyond-card--link:active .beyond-media { transform: scale(0.98); }
@media (hover: hover) and (pointer: fine) { .beyond-card--link:hover .beyond-media { box-shadow: 0 10px 30px rgba(27,23,48,0.12); } }
