/* ==========================================================================
   case-study.css — Emirates NBD case study page. Built step by step, per
   direct request — this first pass covers the hero (title/subtitle/
   feature image) and the Client/My Role/etc. + intro-paragraph "context"
   row directly below it. Every measurement here was read pixel-for-pixel
   off assets/Case study.pdf (rendered at 1728px wide, the site's own
   design baseline — see style.css's fluid-scale comment) and the two
   assets/Case study grid *.png column overlays provided alongside it —
   each rule below says which measurement it's matching.
   ========================================================================== */

.case-hero {
  padding-inline: var(--margin);
  padding-top: 10.25rem; /* same standard hero top offset as .about-hero/.pg-hero/.doodles-hero */
}

.case-hero__inner {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: var(--gutter);
}

/* 40px, spans 6 of the 12 grid columns (grid-column: 1 / 7) — measured
   against assets/Case study grid a.png's column overlay, where the
   title's own text-wrap point falls comfortably inside a 6-column-wide
   box. Two lines is a deliberate design break (see the <br> in the
   markup), not organic wrapping — the phrase reads as title + a
   sub-clause, not one continuous sentence. */
.case-hero__title {
  grid-column: 1 / 7;
  font-size: 2.5rem; /* 40px, per direct spec */
  font-weight: 500; /* medium — matches every other page's heading weight (see about-hero__title); the source PDF's mockup font renders visually bolder at this size, but the site standardized all headings to medium in an earlier pass */
  line-height: 1.2; /* pixel-measured line pitch (48px) / font-size (40px) = 1.2 */
  letter-spacing: -0.01em;
}

/* Two explicit sibling lines (matching the source's deliberate title +
   sub-clause break) rather than a <br> inside one text node — a <br>
   gets carried through TextReveal.splitLines() as its own atomic
   "word" (see collectWordNodes in reveal.js), which the line-grouping
   pass then reads as an extra, empty third line, inserting a whole
   spare line-height's worth of gap between the two real lines. Same
   fix as the site's other multi-line headings (see .hero__based-line
   in home.css): separate block-level elements, split independently
   (see initHeroIntro in case-study.js), not one shared text node. */
.case-hero__title-line {
  display: block;
}

/* Fix for descenders (the "g" in "Making"/"redesigning") getting
   clipped flat by the line-mask's overflow:hidden — the mask's box
   sizes to exactly the 48px line-height (see .case-hero__title above),
   which isn't quite tall enough to fit Satoshi's full descender depth
   at this font-size, so the very bottom of the loop was getting cut
   off. Padding the mask's own box down gives the glyph room to render
   fully within the (still overflow:hidden) mask; the equal negative
   margin-bottom cancels that added height back out so the pixel-
   measured line-to-line pitch and the gap down to the subtitle both
   stay exactly as measured — nothing else shifts. */
.case-hero__title .line-mask {
  padding-bottom: 0.15em;
  margin-bottom: -0.15em;
}

/* 16px, #BABABA, spans 5 of the 12 grid columns (grid-column: 1 / 6) —
   same measurement method as the title above; its own wrap point lands
   just inside a 5-column-wide box. 30px gap above it (title's last line
   to subtitle's first line) is also a direct pixel read. */
.case-hero__subtitle {
  grid-column: 1 / 6;
  margin-top: 1.875rem; /* 30px, pixel-measured gap from title's last line to here */
  font-size: 1rem; /* 16px */
  font-weight: 500; /* medium — site-wide body-text standard, was missing here (fell back to body's 400 default) */
  line-height: 1.3; /* site-standard body line-height */
  color: #BABABA;
}

/* Full-width (all 12 columns, edge to edge on the standard --margin
   inset) — confirmed against assets/Case study grid a.png, where the
   image block fills the same left/right bounds as every grid column
   combined. 80px below the subtitle, per direct spec. Placeholder box
   for now (no images yet, per direct request) — aspect-ratio measured
   off the PDF's rendered image block (1644 x 851px at the 1728px
   baseline, i.e. ~1.933:1), same ratio as a 2784x1440 export.

   Curtain + zoom-settle reveal, same shared mechanic as every other
   image on the site (see Common.initImageReveal in common.js and
   .about-intro__media/__inner/__cover for the closest sibling
   example) — this element is the mask (position:relative,
   overflow:hidden), its two children carry data-image-inner/
   data-image-cover. There's no real photo yet, so the "inner" layer is
   just the placeholder fill for now; swapping in a real <img> later is
   a markup-only change, the reveal mechanic doesn't need to move. */
.case-hero__image {
  grid-column: 1 / 13;
  margin-top: 5rem; /* 80px, per direct spec */
  /* Matches hero.png's own real ratio (2796x2096) exactly, so the
     whole image shows with no cropping ("I want it to fit fully") —
     background-size:contain on .case-hero__image-inner below is the
     actual guarantee of that, this aspect-ratio just means contain
     doesn't ALSO have to letterbox it to do so. Width still fills all
     12 columns; height follows directly from the image's own shape. */
  aspect-ratio: 2796 / 2096;
  position: relative;
  overflow: hidden;
}

.case-hero__image-inner {
  position: absolute;
  inset: -1px; /* 1px overscan on every edge — see the comment on .case-block__image-inner below for why */
  width: calc(100% + 2px);
  height: calc(100% + 2px);
  background-color: var(--black); /* fallback while the real image loads / for any hero without one yet */
  background-size: contain; /* show the full image, uncropped, rather than cover's crop-to-fill */
  background-repeat: no-repeat;
  background-position: center;
  transform: scale(1.35);
  will-change: transform;
}

.case-hero__image-cover {
  position: absolute;
  inset: -1px; /* see .case-block__image-cover below */
  width: calc(100% + 2px);
  height: calc(100% + 2px);
  z-index: 2;
  background: #0B0A0B; /* page bg — dark-mode only site, same as html.dark-mode .about-intro__cover */
  will-change: transform;
}

/* ---------- context row ----------
   Two columns, centered in the page: 2 empty grid columns on each side,
   the label column taking the next 2 grid columns (grid-column: 3 / 5)
   and the body column taking the 6 after that (grid-column: 5 / 11) —
   2 + 2 + 6 + 2 = 12, matching assets/Case study grid b.png's overlay
   exactly (label column starts flush with grid line 3, body column's
   longest line wraps flush with grid line 10). 164px above the hero
   image — pixel-measured, and it lines up with the site's own standard
   10.25rem hero-top offset (used above on .case-hero), so it's
   expressed the same way here for consistency rather than as a
   one-off px value. */
.case-context {
  margin-top: 10.25rem; /* 164px, pixel-measured gap from the hero image's bottom edge to here */
  align-items: start; /* keeps the (currently shorter) label column from stretching to the body column's height */
}

.case-context__meta {
  grid-column: 3 / 5;
  display: flex;
  flex-direction: column;
}

/* 30px between each Client/My Role/Location and Year/View Live group —
   pixel-measured (the gap between one group's last value line and the
   next group's label is consistently ~30px, vs. ~10-14px between a
   label and its own value within one group — see .case-context__value
   below). */
.case-context__item + .case-context__item {
  margin-top: 1.875rem; /* 30px */
}

.case-context__label,
.case-context__value {
  display: block;
  font-size: 1rem; /* 16px, per direct spec — "1st column all text is 16px" */
  font-weight: 500; /* medium — site-wide body-text standard */
  line-height: 1.3;
}

/* Titles (labels) are grey, per direct spec — values are left at the
   default full-white text color. */
.case-context__label {
  color: #BABABA;
}

/* Small explicit gap on top of the value's own line-height leading —
   matches the ~10-14px pixel-measured gap between a label's baseline
   and its value directly below it (tighter than the 30px between
   groups). Also covers the two stacked "View Live" values (App
   store/Play store), which sit at the same tight spacing from each
   other in the source. */
.case-context__value {
  margin-top: 0.25rem; /* 4px */
}

/* Same "explicit sibling block elements, not a shared text node with a
   <br>" fix as .case-hero__title-line above (see that class's own
   comment for why a <br> breaks TextReveal.splitLines() — it gets
   read as its own atomic third "line", not a break inside one) —
   needed here for the Client value's forced "Dubai Electricity &" /
   "Water Authority" line break, per direct spec, rather than however
   this column's own width happens to auto-wrap it. Font
   size/weight/line-height all inherit down from the parent
   .case-context__value above, only the block-level break is new. See
   .case-context__value--multiline below and initContextReveals in
   case-study.js, which excludes the outer .case-context__value from
   its own generic word-split pass here and targets these two lines
   directly instead — same exclusion pattern as
   .case-context__value--link just above it in that file. */
.case-context__value-line {
  display: block;
}

/* App store / Play store links — same diagonal external-link arrow as
   .pg-overlay__link on the AI Playground overlay (see ai-playground.css),
   reproduced here rather than shared since the two live in separate
   stylesheets. */
.case-context__link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: inherit;
  transition: opacity 0.3s var(--ease-out);
}

.case-context__link:hover {
  opacity: 0.6;
}

.case-context__link-arrow {
  width: 0.625rem; /* 10px — matches .pg-overlay__link-arrow's 10x10 viewBox */
  height: 0.625rem;
}

.case-context__body {
  grid-column: 5 / 11;
  color: #BABABA; /* "2nd column is 16px grey" — uniform, unlike column 1's label/value split */
}

.case-context__body p {
  font-size: 1rem; /* 16px */
  font-weight: 500; /* medium — site-wide body-text standard */
  line-height: 1.3;
}

/* 24px between paragraphs — pixel-measured (consistently tighter than
   the label/value column's 30px group gap). */
.case-context__body p + p {
  margin-top: 1.5rem; /* 24px */
}

/* ---------- info banner ----------
   A callout box for disclaimers/asides — an NDA note here, but generic
   enough (plain class name, not case-study-prefixed) to reuse anywhere
   else on the site later without renaming anything. Per direct spec:
   16px black text, a 4px #2977FF line on the left, #809FE8 background,
   16px padding all round. */
.info-banner {
  /* Full-width when used directly inside a grid (e.g. the green variant
     sitting straight in .case-section's 10-column grid, per direct
     spec) — a no-op everywhere else (like the original blue banner,
     which sits inside .case-context__body, a plain block, not a grid).
     Without this, an un-placed grid child only claims one implicit
     1-of-10 column, which is what made the green banner collapse to a
     sliver with every word wrapping onto its own line. */
  grid-column: 1 / 11;
  margin-top: 2.5rem; /* 40px, per direct spec ("after 40px") */
  padding: 1rem; /* 16px all round, per direct spec */
  border-left: 4px solid #2977ff;
  background-color: #809fe8;
}

.info-banner p {
  font-size: 1rem; /* 16px */
  font-weight: 500; /* medium — site-wide body-text standard */
  line-height: 1.3;
  color: #0B0A0B;
}

/* Half-width variant — matches the width of the square image pair in
   .case-block__images (each spans 5 of 10 columns), for a banner that
   sits under a paragraph rather than spanning the full section.
   Explicit 1/6 (not just "span 5") — the preceding .case-block__body
   is itself only 1/6, so an unanchored span-5 item would auto-place
   into the same row's open 6/11 slot, landing beside the paragraph
   instead of on its own row below it. */
.info-banner--half {
  grid-column: 1 / 6;
}

/* ---------- body: sticky nav + section content ----------
   Only the Problem section is built so far, per direct request — the
   nav lists all 7 (matching the source PDF's own list) but only
   "Problem" points at a real section right now; the rest are inert
   until each one gets built in a later pass. */
.case-body {
  margin-top: 10rem; /* 160px, per direct spec — gap from the context row above */
  align-items: start; /* keeps the sticky nav column from stretching to the (much taller) content column's height */
}

.case-nav {
  grid-column: 1 / 3; /* 2 of the 12 grid columns, per direct spec */
  position: sticky;
  top: 8rem;
  display: flex;
  flex-direction: row; /* track + links side by side */
  align-items: stretch; /* track's 100% height matches the links column's own height */
  gap: 0.75rem;
}

/* ---------- expanding progress line ----------
   Modeled on aidesignfieldguide.com/articles/amelia-wattenberger's own
   side-nav: a thin track next to the links, a faint full-height
   background layer, and a short foreground "bar" that sits beside the
   active link and grows taller as you scroll through that link's
   section, then jumps down to the next link's row (height resets) once
   the next section takes over. Reverse-engineered live via DOM/style
   inspection (that site doesn't ship the mechanic as a documented
   component) — see initNavTrack in case-study.js for the scroll-driven
   height/position math. */
.case-nav__track {
  position: relative;
  width: 2px;
  flex-shrink: 0;
}

.case-nav__track-bg {
  position: absolute;
  inset: 0;
  background: rgba(255, 255, 255, 0.12);
}

.case-nav__track-bar {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 14px; /* one line's height — the bar's resting/minimum size at the top of a section */
  background: #ffffff; /* pure white — per direct request */
  will-change: transform, height;
}

.case-nav__links {
  display: flex;
  flex-direction: column;
  gap: 1rem; /* pixel-measured item pitch (~35px) minus one line's own line-height */
}

.case-nav a {
  font-size: 0.875rem; /* 14px — estimated from the source's rendered nav-item width against average Satoshi character width; not an exact pixel spec like the content type sizes above */
  font-weight: 500; /* medium — site-wide UI-text standard */
  line-height: 1.3;
  color: #BABABA;
  transition: color 0.35s var(--ease-in-out);
}

.case-nav a:hover {
  color: #ffffff; /* pure white — per direct request */
}

.case-nav a.is-active {
  color: #ffffff; /* pure white — per direct request */
}

/* The other 10 grid columns, per direct spec — its own nested 10-column
   grid (not the shared 12-column .grid-12) so every child below can be
   placed with the same "5 of 10" / "5 of 10" column math the PDF
   measures to, without having to convert back and forth against the
   outer 12-column page grid. */
.case-content {
  grid-column: 3 / 13;
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  column-gap: var(--gutter);
}

.case-section {
  grid-column: 1 / 11;
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  column-gap: var(--gutter);
  scroll-margin-top: 8rem; /* keeps a clicked .case-nav link's target from landing flush under the fixed header — same offset as .case-nav's own sticky top */
}

/* 160px between one section (Problem, Approach, ...) and the next —
   per direct spec ("160px after this the next section starts"), same
   value as .case-body's own top offset above. Kept as a fallback for
   any two .case-section elements that ever end up directly adjacent
   (no .case-separator between them) — in the current markup every
   section boundary after the first has a .case-separator in between
   instead (see below), so this rule doesn't actually match anywhere
   right now. */
.case-section + .case-section {
  margin-top: 10rem;
}

/* Divider line between sections (every section boundary except before
   the very first section, Problem) — per direct spec: 80px space above
   and below, replacing the plain 160px gap above with the same total
   spacing (80 + 80 = 160px) but with a visible rule in the middle. */
.case-separator {
  grid-column: 1 / 11;
  height: 1px;
  margin: 7.5rem 0; /* 120px top and bottom, per direct spec — was 80px */
  background: #161616; /* separator — same dark-mode separator color as .about-list__row's border-top (see about.css) */
}

/* Spans 5 of the content area's 10 columns — per direct spec. Shares
   its row with .case-block__eyebrow (see below) via plain grid
   auto-placement: each explicitly claims half the row (1/6 and 6/11),
   so they land side by side without needing a wrapper element.

   40px — this is specifically a section's own opening/"main" title
   (e.g. "A legacy app that had run its course", "Using the Dubai app
   as the foundation"), per direct spec: "the main title is the title
   from where each section starts". Sub-titles further down the same
   section (e.g. "The obvious solution") use the --after-images
   modifier below instead, which keeps the original 24px. */
.case-block__title {
  grid-column: 1 / 7; /* 6 columns — one wider than the original 5 (1/6), per direct spec */
  font-size: 2rem; /* 32px, per direct spec — was 40px */
  font-weight: 500; /* medium — matches every other heading on this page/site */
  line-height: 1.3;
  color: #ffffff;
}

/* 80px above a title that follows the image pair — per direct spec
   ("80px after this is again our title and paragraph combo"). The
   first title in the section doesn't need this; its own spacing comes
   from .case-body's margin-top instead. font-size explicitly restored
   to 24px here since the base .case-block__title above is now 40px
   (main-title only) — these sub-titles stay as they were. */
.case-block__title--after-images {
  grid-column: 1 / 6; /* 5 columns — explicitly restored since the base .case-block__title above just went to 1/7 (main-title only, one column wider) */
  margin-top: 7.5rem; /* 120px, per direct spec — was 5rem/80px */
  font-size: 1.3125rem; /* 21px — was 1rem/16px */
}

/* 12px, all caps, #1e1e1e — per direct spec. Right-aligned within its
   own half of the row, sitting flush with grid line 10 same as the
   image pair's second image below it.

   grid-column starts at 7, not 6 — the main title above widened from
   1/6 to 1/7, so 6/11 would overlap it by one column. With an explicit
   (non-auto) column range on both items, CSS Grid's auto-placement
   doesn't overlap them in the same row when they collide — it silently
   pushes the eyebrow down to a second row instead, which is why it
   stopped lining up with the title's top. 7/11 clears the title's new
   width and puts both back on the same row. */
.case-block__eyebrow {
  grid-column: 7 / 11;
  align-self: start;
  text-align: right;
  font-size: 2.5rem; /* 40px — matches homepage .work__counter, per direct spec */
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: #1e1e1e;
}

/* 16px, grey, spans the same 5 columns as the title above it — per
   direct spec. grid-column:1/6 (rather than a plain span) forces this
   onto its own new grid row even when a title+eyebrow pair filled the
   previous row's columns 1-10 between them. 30px gap above — same
   title-to-body rhythm already established on the context row. */
.case-block__body {
  grid-column: 1 / 6;
  margin-top: 1.5rem; /* 24px, per direct spec — was 30px */
  font-size: 1rem; /* 16px */
  font-weight: 500; /* medium — site-wide body-text standard */
  line-height: 1.3;
  color: #BABABA;
}

/* A block can carry more than one body paragraph (e.g. Approach's
   "Using the Dubai app as the foundation" block) — a tighter 24px gap
   between consecutive paragraphs, same title-to-body vs
   paragraph-to-paragraph rhythm split already used on the context row
   above. */
.case-block__body + .case-block__body {
  margin-top: 1.5rem; /* 24px */
}

/* Keeps a hyphenated compound (e.g. "built-in") from being split across
   a line-wrap — the hyphen is a normal CSS break opportunity, and in a
   narrow 5-column .case-block__body it landed mid-word, leaving an
   orphaned single-word line ("built-" / "in" alone / "recovery..."
   on the next). Needs a CLASS rather than an inline style: this text
   goes through TextReveal.splitLines() (see initBlockReveals in
   case-study.js), which rebuilds each wrapped element fresh from only
   its tag + className (see buildWordNode/collectWordNodes in
   reveal.js) — any inline style= on a nested span gets silently
   dropped on that rebuild, so only a stylesheet class survives. */
.no-break {
  white-space: nowrap;
}

/* The paragraph directly below a sub-title (--after-images) gets its
   own tighter gap — per direct spec. Two classes in the selector beats
   .case-block__body's own 24px margin-top regardless of source order
   (same specificity trick as .case-compare__card.case-compare__card--
   before elsewhere in this file). */
.case-block__title--after-images + .case-block__body {
  margin-top: 1rem; /* 16px, per direct spec */
}

/* Two images, 5 of the 10 content columns each, side by side — per
   direct spec. 40px above, per direct spec ("2 images that start 40px
   after this"). */
.case-block__images {
  grid-column: 1 / 11;
  margin-top: 2.5rem; /* 40px */
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  column-gap: var(--gutter);
  row-gap: 1rem; /* 16px — same image-to-caption gap as .case-block__caption below, and doubles as the gap between one image row and the next in a multi-row gallery (Final Designs) when a caption row sits between them via plain grid auto-flow */
}

/* Curtain + zoom-settle reveal, same shared mechanic as the hero image
   above (see Common.initImageReveal in common.js) — these two are
   scroll-triggered rather than played on load, since they sit well
   below the fold (see initBlockImageReveals in case-study.js). */
.case-block__image {
  grid-column: span 5;
  aspect-ratio: 672 / 468; /* pixel-measured off the PDF's rendered image block */
  position: relative;
  overflow: hidden;
}

/* Square variant — for images too tall/narrow to sit naturally in the
   normal 672:468 frame (a scrolling screenshot of reviews, a long
   annotated audit doc, etc.). Paired with [data-pan-image] below,
   which pans the image vertically inside this fixed square as the
   page scrolls, rather than cropping/squashing it to fit. */
.case-block__image--square {
  aspect-ratio: 1 / 1;
}

/* Full-width variant — for a block with only one image instead of the
   usual side-by-side pair. Spans all 10 content columns; layout only —
   each instance sets its own aspect-ratio inline (via style=) to match
   its own real image dimensions, since different full-width images
   across the page have different native ratios. */
.case-block__image--full {
  grid-column: 1 / 11;
}

.case-block__image-inner {
  position: absolute;
  /* 1px overscan on every edge (inset:-1px + the matching calc()
     width/height below), instead of a flush inset:0 — a hairline of
     the inner layer's own image was visible past the cover's edge
     during the reveal (most visible against a light-background image
     like the review screenshots, easy to miss on a dark placeholder),
     from ordinary sub-pixel rounding between this absolutely
     positioned box and its aspect-ratio'd parent. Both this and
     .case-block__image-cover below carry the same overscan so they
     stay pixel-for-pixel matched to each other, not just individually
     "close enough" to the parent's edge. */
  inset: -1px;
  width: calc(100% + 2px);
  height: calc(100% + 2px);
  background-color: var(--black); /* fallback while the real image loads / for any block without one yet */
  background-size: cover; /* default for a static (non-panning) real image — [data-pan-image] below overrides this */
  background-position: center;
  background-repeat: no-repeat;
  transform: scale(1.35);
  will-change: transform;
}

/* [data-pan-image]: the actual image is set as a background-image via
   inline style (see the HTML) rather than an <img> tag, so it drops
   straight into the exact same mask/inner/cover reveal machinery every
   other image on the site already uses (see Common.initImageReveal in
   common.js) with zero changes to that shared code. background-size:
   100% auto scales the image to the container's width and lets its
   (much taller) natural height overflow — see initScrollPanImages in
   case-study.js, which scrubs background-position-y from 0% to 100%
   against scroll so that overflow pans into view instead of just
   spilling out clipped and static. */
.case-block__image-inner[data-pan-image] {
  background-size: 100% auto;
  background-position: 0% 0%;
  background-repeat: no-repeat;
}

/* ---------- auto-scrolling tall diagram frame ----------
   For Rammas's IA tree / conversation-flow diagrams — both far taller
   than any fixed frame could show statically, same underlying problem
   [data-pan-image] above solves, but a deliberately different
   mechanic per direct spec: this one runs on its own continuously
   looping timer, independent of the page's own scroll position,
   rather than being scrubbed against it. Square frame (tall, 1:1),
   same as every other "long content" image on this page — no new
   frame ratio introduced, just a different fill inside it.

   Unlike every other image on the page, this variant's actual content
   is a real <img> (see the HTML), not a background-image — a
   translateY loop with an exact, computed distance is far simpler to
   reason about /implement on a real element than on background-
   position percentages, and the visible #EBF1FF fill on 3 sides here
   means there's no "crop to fill" background-size concern to begin
   with. Still nests inside the shared [data-image-inner]/
   [data-image-cover] curtain-reveal mask (see Common.initImageReveal
   in common.js) with zero changes needed there — that mechanic only
   ever touches those two elements directly, never what's inside
   [data-image-inner], so this nests inside it same as a plain
   background-image would.

   See initAutoscrollImages in case-study.js for the actual loop
   math — computed per-image from its own real rendered height once
   loaded, not hardcoded, so the two very differently-tall source
   images both read as scrolling at the same visual speed rather than
   one looking rushed. */
.case-block__image--autoscroll .case-block__image-inner {
  background-color: #EBF1FF;
  background-image: none; /* this variant never sets one inline, but belt-and-suspenders against the shared black fallback showing through at the edges */
}

/* Left/right are a permanent 24px gap the image never covers, per
   direct spec. Top/bottom deliberately carry NO inset here — per a
   later direct request, the 24px top/bottom gaps are each only true
   at one END of the loop (24px at the very top, at the very start;
   24px at the very bottom, at the very end), not a fixed padded box
   the image sits inside the whole time. Clipping the actual TOP/
   BOTTOM edges at the frame's own true bounds (not a pre-inset
   sub-box) is what lets the image visibly travel PAST/behind the top
   24px band once it starts moving ("move through the edge on the
   top") instead of that band staying a permanent untouchable border —
   see initAutoscrollImages in case-study.js for the actual start/end
   translateY math this pairs with. overflow:hidden is what actually
   clips the image's overflow-behind-the-frame content, same role
   .case-block__image's own overflow:hidden plays for the
   background-image variants. */
.case-block__autoscroll-viewport {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 1.5rem;
  right: 1.5rem;
  overflow: hidden;
}

.case-block__autoscroll-viewport img {
  display: block;
  width: 100%;
  height: auto;
  will-change: transform;
}

/* ---------- video fill (Final Designs walkthrough) ----------
   Same idea as .case-block__autoscroll-viewport img above and
   .work__image-item__inner video in home.css: nests a real element
   inside the shared [data-image-inner] curtain-reveal mask instead of
   the usual background-image, filling it edge-to-edge via object-fit
   rather than background-size:cover. Common.initImageReveal in
   common.js only ever animates data-image-inner/data-image-cover
   directly, so this drops in with zero JS changes. */
.case-block__image-inner video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.case-block__image-cover {
  position: absolute;
  inset: -1px; /* see the overscan comment on .case-block__image-inner above */
  width: calc(100% + 2px);
  height: calc(100% + 2px);
  z-index: 2;
  background: #0B0A0B;
  will-change: transform;
}

/* Optional per-image caption — sits directly under its image, same
   5-column span, and simply falls onto the row below in
   .case-block__images' own grid via plain auto-placement (no wrapper
   figure needed, same trick as .case-block__title/__eyebrow sharing a
   row above). Per direct spec: 12px, #BABABA, 16px below the image. */
.case-block__caption {
  grid-column: span 5;
  font-size: 0.75rem; /* 12px */
  font-weight: 500; /* medium — site-wide standard */
  line-height: 1.3;
  color: #BABABA;
  align-self: start;
}

/* Full-width variant, paired with .case-block__image--full above. */
.case-block__caption--full {
  grid-column: 1 / 11;
}

/* ---------- Final Designs gallery (1 image / 2 side by side / compare
   slider) ----------
   A one-off layout for the Final Designs section, per direct spec: no
   captions, 24px between each row (image 12 alone, then 13+14 side by
   side, then the 15a/15b compare slider). Not built on .case-block__
   images (that component's row-gap/caption-row grid doesn't apply
   here) — instead a plain block container so each row's spacing is
   just a margin-top. The individual images still reuse .case-block__
   image so they get the same curtain reveal as every other real image
   on the page (see Common.initImageReveals(".case-block__image") in
   case-study.js) — .case-final-gallery only replaces the *layout*
   around them. */
.case-final-gallery {
  grid-column: 1 / 11;
  margin-top: 2.5rem; /* 40px — same top-of-block spacing as .case-block__images elsewhere */
}

.case-final-gallery__row {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  column-gap: var(--gutter); /* 24px — same as every other side-by-side image pair on the page */
  margin-top: 1.5rem; /* 24px, per direct spec */
}

/* Standalone full-width images inside .case-final-gallery (a single
   image row, not a .case-final-gallery__row pair) need the same 24px
   top spacing the row variant gets above — not covered by that rule
   since these sit as plain .case-block__image children instead.
   :not(:first-child) so the very first item in the gallery (right
   under the section's own body copy) doesn't get a redundant gap on
   top of .case-final-gallery's own margin-top. */
.case-final-gallery > .case-block__image:not(:first-child) {
  margin-top: 1.5rem; /* 24px, per direct spec — same rhythm as the row's own margin-top */
}

/* Gif frame variant — a dark (#0E0E11) letterbox background instead of
   the usual edge-to-edge cover-crop, with the gif shown at its own
   native size/ratio and centered in the middle of the frame, per
   direct spec ("dark bg... place the gif in the center"). Overrides
   .case-block__image-inner's default background-size:cover (which
   would otherwise crop the gif to fill the frame edge-to-edge), and
   swaps the inner's fallback background-color for the spec's own dark
   tone instead of the site's near-black default — that color is what
   actually shows in the letterboxed space around the gif.
   background-size: auto 60% — the gif (13.gif) is a square 600x600, so
   a plain `contain` against this box's own aspect-ratio was
   height-bound: it rendered the gif at exactly 100% of the box's
   height (720px worth), width auto-following the same 1:1 ratio.
   60% is a direct 40%-smaller-per-direct-request scale-down from that
   same height-bound baseline — since the gif is square, one axis at
   60% with the other auto still comes out proportionally 60%x60%,
   not distorted. background-position:center (inherited from the base
   .case-block__image-inner rule) keeps it centered either way. */
.case-block__image--gif-frame .case-block__image-inner {
  background-color: #0E0E11;
  background-size: auto 60%;
}

/* .case-block__image's own grid-column:span 5 (meant for the 10-column
   .case-block__images grid elsewhere on the page) doesn't fit this
   row's 2-column grid — a span of 5 inside only 2 explicit tracks
   forces the grid to generate implicit tracks to satisfy it, which
   pushed the second image onto its own new row instead of sitting
   beside the first (confirmed live: both images shared the same x but
   different y). Override back down to a plain 1-of-2 span here. */
.case-final-gallery__row > .case-block__image {
  grid-column: span 1;
}

/* The compare slider sits in the same vertical rhythm as the row above
   it — 24px, per direct spec. */
.case-compare-slider {
  grid-column: 1 / 11;
  margin-top: 1.5rem; /* 24px, per direct spec */
  position: relative;
  overflow: hidden;
  cursor: grab; /* open hand at rest, per direct spec — swaps to grabbing (closed hand) while actively dragging, see .case-compare-slider.is-dragging below and initCompareSliders in case-study.js */
  touch-action: none; /* dragging is a horizontal gesture — stop touch scroll from fighting it */
  user-select: none;
}

.case-compare-slider.is-dragging {
  cursor: grabbing;
}

.case-compare-slider__image {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  pointer-events: none; /* the container itself owns the drag, see initCompareSliders in case-study.js */
}

/* .case-compare-slider__image--before (15a) is the plain base layer —
   always shown in full underneath. --after (15b) sits on top, clipped
   via clip-path (updated live by JS as the handle moves) so it's only
   visible from the left edge up to the handle position — dragging the
   handle right reveals more of it, per direct spec ("drag right to see
   15b, drag left to see 15a"). 50% is just the pre-JS/no-JS fallback;
   initCompareSliders() sets the real starting clip on load. */
.case-compare-slider__image--after {
  clip-path: inset(0 50% 0 0);
}

.case-compare-slider__handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 2px;
  /* Same dark tone as .case-compare-slider__handle-grip below, but no
     backdrop-filter here — the grip is a CHILD of this element, so its
     own blur was sampling this line's already-translucent background
     underneath it, compounding into a visible smear/ring showing
     through the middle of the circle ("I can see the separator behind
     the circle"). Flat semi-transparent color avoids the double-blur;
     the glass/frosted effect stays on the grip only. */
  background: rgba(0, 0, 0, 0.6);
  transform: translateX(-50%);
  pointer-events: none; /* purely visual — see .case-compare-slider's own cursor/drag handling above */
}

.case-compare-slider__handle-grip {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 3rem; /* 48px */
  height: 3rem;
  border-radius: 50%;
  /* Dark glassy circle, per direct spec — a translucent black fill
     plus a backdrop blur, so it reads as frosted glass over whichever
     image is behind it rather than a flat dark circle (unrelated to
     the cursor-follow labels elsewhere on the site, which moved to a
     solid random-color chip — see .pg-cursor-label in ai-playground.css).
     Opacity bumped up (0.35 -> 0.6) per direct follow-up request for a
     darker fill. */
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(2.5rem); /* 40px */
  -webkit-backdrop-filter: blur(2.5rem);
  color: #ffffff;
  font-size: 0.625rem; /* 10px, per direct spec — was 12px */
  font-weight: 500; /* medium — site-wide standard */
  letter-spacing: 0.03em;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
}

/* ---------- results stats grid ----------
   4-up metrics row for the Results section, replacing what would
   normally be a pair of images — per direct spec, recreated from a
   provided screenshot (layout reference only). Titles 32px/#1CE2A0,
   the "4.7★" stat alone bumped to 48px, per direct spec. Weight isn't
   specified directly, but the screenshot clearly reads noticeably
   bolder than the site's usual 500-medium body/heading weight, so
   this uses 700 to match that — easy to dial back to 500 if that
   reads too heavy in context.

   .case-stat is display:contents — its title/subtext children become
   direct grid items of .case-stats itself instead of being boxed
   inside their own flow. .case-stat__title/__subtext are each pinned
   to an explicit grid-row (1 and 2) — WITHOUT that, flattening via
   display:contents made plain row-major auto-placement fill row 1
   with the first 4 items it hit in DOM order (title1, subtext1,
   title2, subtext2), scrambling title/subtext into the same row
   instead of grouping all 4 titles together. Pinning the row and
   leaving the column on auto fixes it: the placement algorithm still
   processes items in DOM order, but now finds each item's first free
   cell WITHIN its pinned row, so titles 1-4 land in row 1 / cols 1-4
   and subtexts 1-4 land in row 2 / cols 1-4, matching their original
   .case-stat grouping.

   That's also what makes the bottom/top alignment below work:
   align-self operates against each row's own auto height, which is
   set by its tallest occupant (the 48px "4.7★" title, or a 2-line
   32px title, whichever is taller) — so every title's own box
   bottom-aligns to row 1 regardless of its font-size or line count,
   and every subtext top-aligns to row 2 the same way, keeping all
   four subtexts starting on the same line no matter how tall row 1
   got. */
.case-stats {
  grid-column: 1 / 11;
  margin-top: 5rem; /* 80px, per direct spec — was 40px */
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: auto auto;
  column-gap: var(--gutter);
}

.case-stat {
  display: contents;
}

.case-stat__title {
  grid-row: 1;
  align-self: end; /* bottom-align within row 1, per direct spec ("4.7star is bottom aligned with the other stats") */
  font-size: 2rem; /* 32px, per direct spec — was 24px */
  font-weight: 700; /* bold — see top-of-block comment */
  line-height: 1.3;
  color: #1ce2a0; /* per direct spec — was white */
}

.case-stat__title--big {
  font-size: 3rem; /* 48px, per direct spec — the "4.7★" stat only */
}

/* The star alone, smaller than the "4.7" it follows — per direct
   request, split into its own span so its font-size can be set
   independently of .case-stat__title--big's 48px. */
.case-stat__star {
  font-size: 2rem; /* 32px, per direct spec */
}

.case-stat__subtext {
  grid-row: 2;
  align-self: start; /* top-align within row 2, so all four line up regardless of row 1's height */
  margin-top: 1.5rem; /* 24px — same title-to-body rhythm used elsewhere on this page */
  font-size: 1rem; /* 16px, per direct spec */
  font-weight: 500; /* medium — site-wide body-text standard */
  line-height: 1.3;
  color: #BABABA; /* per direct spec */
}

/* ---------- before/after comparison cards ----------
   Sits directly in the Approach section's own content flow (right
   after its second paragraph) — same 40px-above spacing, same
   5-of-10-column split a block would normally give an image pair, but
   as two data cards instead of screenshots. Both cards share the
   #80E8C5 background; the "before" card is dropped to 60% opacity
   (blending down to a muted, darker green against the page's black)
   so it visually reads as the "old" state next to the after card's
   full-strength green. */
.case-compare {
  grid-column: 1 / 11;
  margin-top: 2.5rem; /* 40px, matches .case-block__images */
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  column-gap: var(--gutter);
}

.case-compare__card {
  grid-column: span 5;
  background-color: #e1fff5;
  padding: 1.5rem; /* 24px all round, per direct spec — left/right was 16px */
  color: #04140c; /* near-black — readable against the green */
  font-weight: 500; /* per direct spec — covers both the h4 title and the list items below */
}

.case-compare__card--before {
  opacity: 0.6; /* per direct spec */
}

/* Rammas's Approach section reuses this same green card component for
   a single standalone "Key achievements" highlight — no "before" card
   this time, per direct request. Briefly made full-width (grid-column
   1/11) so the lone card wouldn't sit next to 5 empty columns, then
   reverted back to the shared default span-5 above per direct
   request ("limit the green bg to half") — .case-compare--single is
   kept as a marker class on the wrapper (harmless, matches the
   before/after pair's own wrapper) even though it no longer carries
   its own override here. */

.case-compare__card h4 {
  font-size: 1rem; /* 16px, per direct spec */
  font-weight: 800; /* extrabold, per direct spec — was 500 */
  line-height: 1.3;
}

.case-compare__card ul {
  list-style: none;
  margin-top: 1.25rem; /* 20px, pixel-estimated title-to-list gap */
  display: flex;
  flex-direction: column;
  gap: 0.75rem; /* 12px between items, pixel-estimated */
}

.case-compare__card li {
  font-size: 1rem; /* 16px, per direct spec */
  line-height: 1.3;
  display: flex;
  gap: 0.5rem;
}

/* The "→" prefix is a pseudo-element (not typed into the copy) so the
   list text itself stays plain, reusable content. */
.case-compare__card li::before {
  content: "→";
  flex-shrink: 0;
}

/* ---------- next project ----------
   Closing full-bleed teaser (built from a provided screenshot, layout
   reference only, no pixel-measured PDF for this one). Still uses the
   page's own 12-column .grid-12 for its outer padding/margins (same as
   .case-hero/.case-context above it) — full-width regardless, since
   .case-next itself carries no horizontal padding of its own, only
   .case-next__inner does. Lives inside .page as regular content, per
   direct request ("part of the content, not the footer") — 160px
   above, matching the same section-to-section rhythm used everywhere
   else on this page (.case-body/.case-section's own 10rem top gap).
   No gap below, per direct request ("no black spacing after this
   banner") — see the .page override right below this block, which
   zeroes out .page's usual 160px padding-bottom just on this page so
   .case-next's own green background runs straight into the footer
   reveal with nothing in between. */
.case-next {
  display: block;
  margin-top: 10rem; /* 160px */
  background: #6492FF; /* the Unity Puzzle Platformer card's blue (see .pg-card__solid-bg on that card in ai-playground.html) — per direct request, was the logo's green (#1ce2a0) */
  padding-block: 2.5rem; /* 40px top and bottom, per direct spec — was 5rem/80px */
}

/* case-study.css is only loaded on this one page, so this is safe to
   override globally here — .case-next (the last thing in .page) is
   full-bleed with its own solid bg color, so the site-wide 160px
   padding-bottom (see .page in style.css, which exists to create a
   gap of plain page background before the footer reveals) would just
   show up as an unwanted band of black between the green banner and
   the footer. Zeroed here instead of on .case-next's own margin so it
   only affects the true end of .page, not spacing between .case-next
   and whatever section happens to precede it. */
.page {
  padding-bottom: 0;
}

.case-next__inner {
  align-items: center; /* vertically centers the (shorter) image against the two-line title block */
}

.case-next__text {
  grid-column: 1 / 9; /* starts at column 1, per direct request — was 3/9, inset to match .case-content's own left edge */
}

.case-next__label {
  display: block;
  font-size: 1rem; /* 16px, per direct spec */
  font-weight: 500; /* medium — site-wide UI-text standard */
  line-height: 1.3;
  color: rgba(0, 0, 0, 0.5); /* 50% black, per direct spec — was #BABABA, the muted grey didn't have enough contrast against the new green bg */
}

.case-next__title {
  margin-top: 0.75rem; /* 12px — label-to-title gap, estimated */
  font-size: 2rem; /* 32px — matches .case-block__title (e.g. "Reflections & takeaways"), per direct request; was 2.5rem/40px */
  font-weight: 500; /* medium — matches every other heading on this page */
  line-height: 1.3; /* matches .case-block__title's own line-height */
  letter-spacing: -0.01em;
  color: #0B0A0B; /* per direct spec — was #ffffff, flipped for contrast against the new green bg */
}

.case-next__image {
  grid-column: 10 / 13; /* reserves the last 3 columns as its placement track — right edge still lands flush with .case-content's own right edge (column 13) */
  justify-self: end; /* the box itself is sized in rem below (not 100% of the track) — this pins it flush with the track's own right edge, matching the page's own right margin */
  width: 9.5rem; /* 152px — sized to roughly match the two-line title block's own height, per the screenshot proportions, rather than the full grid-track width */
  aspect-ratio: 1 / 1;
  position: relative;
  overflow: hidden;
}

.case-next__image-inner {
  position: absolute;
  inset: -1px; /* 1px overscan — see the matching comment on .case-block__image-inner above */
  width: calc(100% + 2px);
  height: calc(100% + 2px);
  background-color: var(--black);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  transition: transform 0.6s var(--ease-out); /* subtle zoom-in on hover, see .case-next:hover below */
}

.case-next:hover .case-next__image-inner {
  transform: scale(1.05);
}

/* ---------- next-project cursor-follow label ----------
   Same solid-color chip recipe as .pg-cursor-label (ai-playground.css)
   and .footer-cursor-label (style.css) — duplicated rather than shared
   since each lives in its own page-specific stylesheet; keep the three
   in sync if this recipe ever changes. See initNextProjectLabel in
   case-study.js for the JS half (desktop-only, matchMedia-gated same
   as those two; color randomized there too via Common.pickCursorLabelColor,
   on every mouseenter). */
.case-next-cursor-label {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 950;
  pointer-events: none;
  display: flex;
  flex-direction: row-reverse; /* DOM is text-then-icon; this flips it to icon-first visually — same as .pg-cursor-label */
  align-items: center;
  gap: 0.25rem; /* 4px, per direct spec */
  color: #0B0A0B; /* black — per direct request, reads against every chip color */
  font-family: "Satoshi", sans-serif;
  font-size: 0.75rem; /* 12px, per direct spec */
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0;
  line-height: 1;
  white-space: nowrap;
  padding: 0.5rem 0.375rem 0.5rem 0.25rem; /* 8px top/bottom, 6px right, 4px left — per direct spec */
  border-radius: 0; /* sharp corners, per direct spec */
  background: #ff6831; /* JS overwrites this per zone entry — see pickCursorLabelColor in common.js; this is just the pre-JS fallback */
  opacity: 0;
  will-change: transform, opacity;
}

/* "Next project" icon — same icon_nextproject.svg used everywhere else
   on the site this exact zone/state shows up (AI Playground overlay's
   own Next Project hover, the homepage's "Read case study" label).
   Always shown here since this label only ever reads "Next project" —
   no other zone/state to gate it for. */
.case-next-cursor-label__arrow {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.case-next-cursor-label__arrow img {
  width: 0.75rem; /* 12px, matches text size */
  height: 0.75rem;
  display: block;
}

/* ==========================================================================
   mobile breakpoint (<=768px) — see style.css's "mobile breakpoint"
   comment for the shared root-font-freeze this all builds on, and
   Common.initMobileMenu in common.js for the hamburger/overlay nav
   wired into this page's header (see case-study-emirates-nbd.html).

   Everything below follows the same conventions already established on
   every other mobile page this session: 24px page padding-inline, 120px
   hero padding-top, full-width (grid-column 1/13) stacking for anything
   that was side-by-side on desktop, page titles left at their existing
   40px (this page's .case-hero__title was already 2.5rem/40px on
   desktop — no override needed), and content/section headings scaled
   down to 24px (matching About's job/expertise titles and the AI
   Playground overlay's own title). Per direct request: everything
   stacks single-column, and the sticky .case-nav side nav is removed
   entirely (no in-page nav on mobile) rather than adapted.
   ========================================================================== */
@media (max-width: 768px) {
  /* ---------- hero ---------- */
  .case-hero {
    padding-inline: 1.5rem; /* 24px, matches every other page's mobile hero */
    padding-top: 7.5rem; /* 120px, matches every other page's mobile hero */
  }

  .case-hero__title {
    grid-column: 1 / 13; /* was 1/7 — full width now that there's no room reserved for anything beside it */
  }

  .case-hero__subtitle {
    grid-column: 1 / 13; /* was 1/6 */
    max-width: none;
  }

  .case-hero__image {
    /* 200px from the hero subtitle — same "first image after the hero"
       gap already established on every other page with a hero image
       (About's bento image, the AI Playground/Doodles grids), was
       5rem/80px on desktop. */
    margin-top: 12.5rem;
  }

  /* ---------- context row ---------- */
  .case-context {
    margin-top: 5rem; /* 80px — was 10.25rem/164px, the desktop-only 2-empty-column centering gap no longer applies once everything's full width */
  }

  .case-context__meta {
    grid-column: 1 / 13; /* was 3/5 */
  }

  .case-context__body {
    grid-column: 1 / 13; /* was 5/11 */
    margin-top: 2.5rem; /* 40px — meta and body now stack instead of sitting side by side, so this is a new gap that didn't exist on desktop */
  }

  /* ---------- body: nav removed, content goes full width ---------- */
  .case-body {
    margin-top: 5rem; /* 80px — was 10rem/160px */
  }

  /* Sticky side nav removed entirely on mobile, per direct request —
     no in-page section nav on small screens, not an adapted/collapsed
     version of it. */
  .case-nav {
    display: none;
  }

  .case-content {
    grid-column: 1 / 13; /* was 3/13 — claims the columns .case-nav no longer needs */
  }

  /* ---------- section content ---------- */
  .case-block__title {
    grid-column: 1 / 11; /* was 1/7 — full width of the nested 10-column .case-content grid */
    font-size: 1.5rem; /* 24px — matches About's job/expertise titles and the AI Playground overlay title's own mobile scale; was 2rem/32px */
  }

  /* Both explicitly claim all 10 columns (can't share a row like the
     desktop version does), so auto-placement puts each on its own full-
     width row — `order: -1` moves the eyebrow's row before the title's
     row (DOM order is still title-then-eyebrow; only the visual/grid
     placement order changes), and text-align switches from the
     desktop's right-aligned-opposite-the-title to left, per direct
     request: "put them above the title on the left". */
  .case-block__eyebrow {
    grid-column: 1 / 11; /* was 7/11 */
    order: -1;
    margin-bottom: 0.5rem; /* 8px — gap to the title below it */
    text-align: left; /* was right */
    font-size: 2rem; /* 32px — was 2.5rem/40px */
  }

  .case-block__title--after-images {
    grid-column: 1 / 11; /* was 1/6 */
  }

  .case-block__body {
    grid-column: 1 / 11; /* was 1/6 */
  }

  /* Image pairs collapse to a single stacked column — same pattern as
     every other page's mobile image grid (About/Doodles/AI Playground). */
  .case-block__image {
    grid-column: span 10; /* was span 5 */
  }

  .case-block__caption {
    grid-column: span 10; /* was span 5 */
  }

  /* On desktop, a 2-image row's matching 2 captions (always immediately
     after both images in the DOM — image1, image2, caption1, caption2)
     land directly under their own image for free: same 5-column
     position, next row down, via plain auto-placement. Once every item
     spans the full 10 columns for mobile stacking, that same DOM-order
     auto-placement instead groups both images together (rows 1-2) and
     both captions together (rows 3-4), since each full-width item just
     takes the next available row regardless of which image it belongs
     to. `order` re-sequences the auto-placement pass itself (not just
     paint order, since these are still auto-placed grid items) to
     interleave them back into image1, caption1, image2, caption2 — this
     also happens to be a no-op for a single image+caption pair (nothing
     to reorder relative to). */
  .case-block__images > .case-block__image:nth-of-type(1) {
    order: 1;
  }
  .case-block__images > .case-block__caption:nth-of-type(1) {
    order: 2;
  }
  .case-block__images > .case-block__image:nth-of-type(2) {
    order: 3;
  }
  .case-block__images > .case-block__caption:nth-of-type(2) {
    order: 4;
  }

  /* 24px between the two images of a pair that sat side by side on
     desktop, per direct request — .case-block__images' own row-gap
     (1rem/16px, set on the desktop rule above) is shared by every row
     in this grid, including each image's own gap down to its caption
     (which per direct spec on that rule is specifically 16px, not to
     be touched). Adding an extra 8px of margin-top ONLY to the second
     image of a pair — on top of the existing 16px row-gap it still
     sits below — reaches 24px between caption1 and image2 without
     changing the 16px image-to-its-own-caption gaps on either side of
     it. A no-op for any section with just one image (no second
     .case-block__image to match). */
  .case-block__images > .case-block__image:nth-of-type(2) {
    margin-top: 0.5rem;
  }

  .case-separator {
    margin: 4rem 0; /* 64px top and bottom — was 7.5rem/120px, a lighter rhythm for the tighter mobile column */
  }

  /* ---------- before/after comparison cards ---------- */
  .case-compare {
    row-gap: 1rem; /* 16px — new gap needed now that the two cards stack instead of sitting side by side */
  }

  .case-compare__card {
    grid-column: span 10; /* was span 5 */
  }

  /* ---------- Final Designs gallery ---------- */
  .case-final-gallery__row {
    grid-template-columns: 1fr; /* was repeat(2, 1fr) */
    row-gap: 1.5rem; /* 24px — this pair sat side by side on desktop, per direct request; was 1rem/16px */
  }

  /* ---------- Results stats ---------- */
  .case-stats {
    grid-template-columns: 1fr; /* was repeat(4, 1fr) */
  }

  /* The display:contents row1/row2 trick (see the desktop rule's own
     comment) only works across multiple columns — with a single
     column, two "row 1" titles would collide since there's nowhere
     else for the second one to go. Switching back to a normal block
     box per stat sidesteps that: each .case-stat becomes its own
     self-contained row (title directly above its own subtext) instead
     of every title/subtext being flattened into two shared rows. */
  .case-stat {
    display: block;
  }

  .case-stat + .case-stat {
    margin-top: 2.5rem; /* 40px between each stacked stat */
  }

  .case-stat__title {
    font-size: 1.5rem; /* 24px — was 2rem/32px */
  }

  .case-stat__title--big {
    font-size: 2rem; /* 32px — was 3rem/48px */
  }

  .case-stat__star {
    font-size: 1.25rem; /* 20px — was 2rem/32px, keeps the same proportion to the shrunk "4.7" */
  }

  /* ---------- next project ---------- */
  .case-next {
    margin-top: 5rem; /* 80px — restored now that this is back inside .page as regular content */
    padding-block: 2.5rem; /* 40px, per direct spec, matches desktop — was 5rem/80px */
  }

  .case-next__text {
    grid-column: 1 / 13; /* was 3/9 — the "inline with the content above" inset was a desktop-only alignment; full width once everything else is full width too */
  }

  .case-next__title {
    font-size: 1.5rem; /* 24px — matches .case-block__title's own mobile size, same as the desktop request that kept these two in sync; was 2rem/32px */
  }

  .case-next__image {
    grid-column: 1 / 13; /* was 10/13 */
    justify-self: start; /* was end — sits under the text at the shared left margin instead of pinned to the (now irrelevant) right edge */
    margin-top: 2rem; /* 32px — text and image now stack instead of sitting side by side */
  }
}
