/* ── case-study.css ── long-form reading layout ── */

.cs-nav { display: flex; align-items: center; justify-content: space-between; max-width: 680px; margin: 0 auto; padding: 2rem 1.5rem 1.25rem; }
.cs-nav .nav-name { font-family: var(--font-display); font-weight: 700; font-size: 16px; color: var(--ink); letter-spacing: -0.01em; white-space: nowrap; } /* nowrap fixes mobile 3-line break */
.cs-nav-links { display: flex; gap: 1.25rem; }
.cs-nav-links a { font-family: var(--font-body); font-weight: 600; font-size: 13.5px; color: var(--ink-faint); text-decoration: none; }
.cs-nav-links a:hover { color: var(--teal); }

.cs-main { max-width: 680px; margin: 0 auto; padding: 0 1.5rem 6rem; }

.back-link { display: inline-flex; align-items: center; gap: 10px; font-weight: 600; font-size: 13px; color: var(--ink-faint); text-decoration: none; margin-bottom: 2.5rem; }
.back-link:hover { color: var(--teal); }

/* hero — full-bleed deep-teal block, breaking out of the reading column via
   .fullbleed (margin: 0 calc(50% - 50vw), defined in brand.css). Requires
   overflow-x:hidden on html/body (set in brand.css) or it overflows by the
   scrollbar width.
   Same max-width + 48px edge padding as .nav-inner AND .cs-layout below — all
   three must share this exact formula (not just the same pixel value at one
   viewport) so "Back to work" and the hero title both stay left-aligned with
   the "LU CHU WEN" logo at every viewport width, not just the one it was
   eyeballed at. */
.cs-hero { background: var(--teal-deep); color: var(--white); }
.cs-hero-in { max-width: 1280px; margin: 0 auto; padding: 3.25rem 48px 3rem calc(48px + 200px + 3rem); }
.cs-hero .eyebrow { color: #9CCFC5; }
.cs-hero-title { font-family: var(--font-display); font-weight: 800; font-size: clamp(2.1rem, 5.6vw, 3.1rem); line-height: 1.08; letter-spacing: -0.03em; color: var(--white); margin-bottom: 1.1rem; }
.cs-hero-title mark { background: var(--teal); color: var(--white); padding: 0 0.12em; border-radius: 6px; box-decoration-break: clone; -webkit-box-decoration-break: clone; }
.cs-subtitle { font-size: 1.15rem; color: #CFE6E1; max-width: 540px; line-height: 1.6; margin: 0; }

/* two-column reading layout — left section nav + progress rail, right content.
   Used on all three case-study pages via .cs-layout. Same max-width (1280px)
   and 48px edge padding as .nav-inner, so the sidebar's "Back to work" aligns
   with the nav logo at every viewport width. The 680px reading-column cap
   moves to .cs-main itself (below) instead of this outer grid, so the text
   column stays narrow for readability even though the grid is now wider.
   Bottom padding removed entirely — it was 6rem (96px) to lead into
   the "More work" carousel that used to always follow, then 2.5rem
   once that was hidden, matching .cs-section's own padding. Down to 0
   now: the last .cs-section already carries its own 2.5rem bottom
   padding (part of its background, same as every section boundary
   on the page), so this was a second, redundant gap stacked after it
   rather than the only thing providing one. */
.cs-layout { max-width: 1280px; margin: 0 auto; padding: 0 48px 0; display: grid; grid-template-columns: 200px 1fr; gap: 3rem; align-items: start; }
.cs-layout .cs-main { max-width: 680px; margin: 0; padding: 0; }

/* margin-top matches the 2rem top margin on .img-full/.meta-grid, the first thing
   in .cs-main, so "Back to work" lines up with the main image instead of floating above it. */
.cs-sidebar { position: sticky; top: 5.5rem; margin-top: 2rem; display: flex; flex-direction: column; gap: 1.75rem; z-index: 1; }
.cs-sidebar .back-link { margin-bottom: 0; }
.cs-nav-rail { position: relative; display: grid; grid-template-columns: 2px 1fr; gap: 18px; }
/* track/fill are positioning references only now — the clover dot is the sole
   visible indicator, no line behind it. */
.cs-rail-track { position: relative; }
.cs-rail-fill { position: absolute; top: 0; left: 0; width: 100%; height: 0; }
/* leading node — the "you are here" mark rides the fill's tip, pinned in JS to the
   active anchor's own vertical center (not a % of scroll) so it always sits level
   with its label, even though sections differ wildly in length. The clover is the
   site's own mark (nav dots / card watermark), just at "you are here" scale — 14px
   matches the eyebrow icon, the smallest size on the site where the four petals
   still read as a clover instead of flattening into a circle. */
.cs-rail-dot { position: absolute; left: 0; top: 0; width: 14px; height: 14px; transform: translateY(-50%); transition: top 0.12s linear; }
/* rotation/scale live on the svg, never on the wrapper above — the wrapper's own
   transform is reserved for translate(-50%,-50%) positioning, so the mark spins
   truly in place instead of fighting the position transition for the same
   property. JS triggers this once per section change (not every scroll tick),
   spin + pulse combined into one arrival flourish. */
.cs-rail-dot svg { width: 100%; height: 100%; display: block; transform-origin: center; }
.cs-rail-dot.is-spinning svg { animation: cs-rail-spin 0.55s cubic-bezier(0.34, 1.56, 0.64, 1); }
@keyframes cs-rail-spin {
  0% { transform: rotate(0deg) scale(1); }
  45% { transform: rotate(190deg) scale(1.45); }
  100% { transform: rotate(360deg) scale(1); }
}
.cs-rail-dot circle { fill: var(--teal); }
.cs-anchors { display: flex; flex-direction: column; gap: 15px; }
/* --ink-faint measures ~5:1 on plain paper (already the site's stated floor —
   see brand.css), which dips just under the 4.5:1 minimum on the section
   washes it can now sit over (~4.4:1) since the sidebar isn't boxed off from
   them anymore. A touch darker here holds comfortably (5.5:1+) against paper
   and both washes alike — scoped to this nav specifically, not the shared
   token, since nothing else needs it. */
.cs-anchors a { font-family: var(--font-body); font-weight: 600; font-size: 12px; color: #5F5D55; text-decoration: none; transition: color 0.15s; }
.cs-anchors a:hover { color: var(--teal); }
.cs-anchors a.active { color: var(--teal); font-weight: 700; }

/* mobile reading-progress bar — fixed top hairline, shown only where the
   sidebar rail is hidden (<900px). scaleX driven from JS (0→1). */
.cs-progress { position: fixed; top: 0; left: 0; right: 0; height: 3px; z-index: 60; background: var(--teal-soft); display: none; }
.cs-progress > span { display: block; height: 100%; background: var(--teal); transform: scaleX(0); transform-origin: left; transition: transform 0.1s linear; }

/* keyboard focus — the pages had no visible focus style at all before this */
a:focus-visible, button:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--teal); outline-offset: 3px; border-radius: 4px;
}

@media (max-width: 900px) {
  .cs-hero-in { padding-left: 1.25rem; padding-right: 1.25rem; }
  .cs-layout { grid-template-columns: 1fr; padding: 0 1.25rem 0; }
  .cs-sidebar { position: static; }
  .cs-nav-rail { display: none; }
  .cs-sidebar .back-link { margin-bottom: 1.5rem; }
  .cs-progress { display: block; } /* the rail is gone here — give mobile a progress cue */
}

/* motion off — honour the OS setting. Scroll tweens collapse to nothing.
   The rail still tracks position (that's orientation, not decoration) but
   without an eased transition. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .cs-rail-fill, .cs-rail-dot, .cs-progress > span { transition: none !important; }
  .cs-rail-dot.is-spinning svg { animation: none !important; }
}

.meta-grid { display: flex; flex-wrap: wrap; gap: 2rem; padding: 1.5rem 0; border-bottom: 1px solid var(--rule); margin: 2rem 0 2.75rem; }
.meta-label { font-size: 12px; color: var(--ink-faint); font-weight: 700; margin-bottom: 3px; text-transform: uppercase; letter-spacing: 0.04em; }
.meta-value { font-size: 15px; color: var(--ink); font-weight: 700; line-height: 1.35; }

/* section colour — a real, structural background on each section itself, the
   same way .cs-hero just has one (see above). No scroll listener, no "current
   section" trigger, nothing computed: the colour is simply part of the
   section's own layout, so it's there the instant the browser paints it,
   scrolls at normal document speed (not a separate tracked overlay), and
   holds exactly for as long as the section's own box does.
   Full-bleed via the box-shadow trick, not the .fullbleed margin trick above —
   .fullbleed only works because .cs-hero sits directly under <body>, centered
   on the true viewport. .cs-section is nested inside the sidebar+content grid,
   offset from viewport-center by the sidebar's width, so that margin math
   would land the edges in the wrong place. A huge horizontal box-shadow drawn
   from the section's own box needs no knowledge of that offset — it just
   extends past both true viewport edges regardless of nesting, clipped clean
   by the overflow-x:hidden already on html (see brand.css).
   Rhythm: opening/closing sections stay plain paper, everything else
   alternates into one quiet stone neutral — a single rule (nth-of-type),
   no per-section ID special-casing ("problem" used to get its own warm tone
   and a different eyebrow treatment; now it's styled exactly like any other
   section, same as the rest of the site treats it in the sidebar already).
   The neutral is deliberately NOT teal-hued (a first pass tried a pale teal
   wash and it read muddy against the teal eyebrow/accent sitting on it —
   same problem either way, teal-on-teal has nowhere to pop). Biased warm,
   toward the paper ground it sits next to, not toward the accent. */
:root {
  --wash-neutral: #F5F3EE;
}
/* Eyebrow stays the plain --teal on every section, tinted or not — one
   colour, so the icon+text always read as the exact same mark regardless
   of which background they're sitting on. (Tried a deeper/brighter variant
   on tinted sections for WCAG contrast — technically correct, ~4.2:1 vs the
   4.5:1 minimum, but it made the label visibly two different colours
   depending on section, which reads as more of a bug than the marginal
   contrast miss it was fixing.) */
.cs-section {
  /* padding, not margin — margin sits outside the section's own coloured box,
     so it left a bare strip of plain paper between every section regardless
     of their actual colours, breaking the "one continuous surface" this is
     supposed to be. Padding keeps the colour edge-to-edge between sections
     and gives the content breathing room from that edge, both at once. */
  padding: 2.5rem 0;
  scroll-margin-top: 2rem;
  --section-bg: var(--paper);
  background: var(--section-bg);
  /* a plain box-shadow is defined to exclude the element's own border box —
     it only ever paints in the region outside it, never underneath. background
     covers the section's own area; the shadow below covers the overflow into
     the margins either side. Spread (not offset) is what actually fills the
     gap out to the viewport edge — a plain offset just moves a section-sized
     copy of the shadow away, leaving the space in between empty. clip-path
     pulls the vertical overflow that same huge spread creates back down to
     the section's own height. */
  box-shadow: 0 0 0 100vmax var(--section-bg);
  clip-path: inset(0 -100vmax);
}
.cs-section:nth-of-type(even) { --section-bg: var(--wash-neutral); }
.cs-h2 { font-family: var(--font-display); font-weight: 700; font-size: 1.55rem; line-height: 1.25; letter-spacing: -0.015em; color: var(--ink); margin-bottom: 0.85rem; }
.cs-p { margin-bottom: 1.25rem; }
.cs-p strong { color: var(--ink); font-weight: 700; }

.pull-quote { font-family: var(--font-display); font-weight: 600; font-size: 1.3rem; line-height: 1.4; letter-spacing: -0.01em; color: var(--ink); border-left: 4px solid var(--teal); padding-left: 1.35rem; margin: 2.25rem 0; }

/* device / image frames — no "phone in the void".
   align-items:start on both the grid and the flex frame stops a taller
   sibling in the same row from stretching a shorter/wider image to match its
   height — Grid and Flexbox both default to align-items:stretch, which will
   silently distort an image's aspect ratio to fill a taller box it was never
   sized for (confirmed: a landscape image paired with a portrait one in the
   same .device-row.two rendered at the portrait image's aspect ratio). */
.device-row { display: grid; gap: 1rem; margin: 2rem 0; align-items: start; }
.device-row.two { grid-template-columns: 1fr 1fr; }
.device-row.three { grid-template-columns: repeat(3, 1fr); }
.device-frame { background: var(--paper); border: 1px solid var(--rule); border-radius: 12px; overflow: hidden; display: flex; align-items: center; justify-content: center; }
.device-frame img, .device-frame video { width: 100%; height: auto; display: block; }

/* device-row.matched — for pairing images of very different aspect ratios
   (a tall phone screenshot next to a landscape photo), where .two's equal
   width columns leave the shorter image floating in a mostly-empty frame.
   Sized by a shared height instead, like a justified photo row — neither
   image is cropped or stretched, they're just different widths. */
.device-row.matched { display: flex; justify-content: center; gap: 1rem; height: 320px; }
.device-row.matched .device-frame { flex: 0 0 auto; height: 100%; }
.device-row.matched .device-frame img { width: auto; height: 100%; }
.img-full { width: 100%; border-radius: 12px; border: 1px solid var(--rule); display: block; margin: 2rem 0; }
.img-caption { font-size: 13px; color: var(--ink-faint); text-align: center; margin-top: 0.5rem; }

/* flow-row — one shipped screen per row: a wide device frame with a short
   headline + description beside it, instead of a caption underneath.
   The frame keeps most of the width (58%) so the screen recording stays
   legible — a narrower device column (e.g. a true 50/50 split) shrank
   these specific near-square recordings back down to the illegible size
   this replaced. */
.flow-row { display: flex; align-items: center; gap: 2.5rem; margin: 3rem 0; }
.flow-row .device-frame, .flow-row .img-placeholder { flex: 1.4 1 0; min-width: 0; margin: 0; }
.flow-text { flex: 1 1 0; }
.flow-title { font-family: var(--font-display); font-weight: 700; font-size: 1.15rem; color: var(--ink); margin-bottom: 0.5rem; letter-spacing: -0.01em; }
.flow-desc { font-size: 15px; color: var(--ink-faint); line-height: 1.6; }
@media (max-width: 640px) {
  .flow-row { flex-direction: column; align-items: stretch; gap: 1rem; }
  /* flex-basis:0 above is a row-axis trick (splits width by flex-grow
     ratio, ignoring intrinsic size) — in a column flow that basis
     applies to height instead, collapsing .device-frame to near-0 and
     leaving its video/image overflowing outside it. flex:none restores
     auto sizing so each child's real content height (the video's own
     aspect ratio, the text block's line height) is what mobile lays
     out against. */
  .flow-row .device-frame, .flow-row .img-placeholder, .flow-text { flex: none; }
}

.img-placeholder { background: var(--teal-soft); border-radius: 12px; min-height: 200px; display: flex; align-items: center; justify-content: center; text-align: center; padding: 1.5rem; margin: 1.5rem 0; }
.img-placeholder span { font-size: 13px; color: #0B6E60; font-weight: 700; } /* darker than --teal specifically for this light background — --teal alone measured 4.31:1 here, below the 4.5 AA threshold; #0B6E60 measures 5.21:1 */

/* "more work" carousel — every case study used to dead-end straight
   into a plain text footer once a reader finished it, with no way to
   keep going except a small "All work" link. Small versions of the
   real homepage cards (same rounded-card language, no attempt at
   each project's own bespoke homepage illustration — those are
   homepage-only set pieces, not something to fork 3 smaller copies
   of) give the other 2 case studies an actual entry point right
   where a finished reader's attention already is. overflow-x:auto +
   scroll-snap instead of a JS carousel library — genuinely just 2
   cards per page, so this is future-proofed for more rather than
   solving a problem that exists today. */
/* No top border — every other section boundary on these pages is just
   a background-colour change (.cs-section's zebra striping), so a
   stray rule here read as a leftover rather than a deliberate divider.
   max-width still matches the shared 680px .cs-main default; pages
   that override their own reading-column width (claim-submission.html)
   re-override this too, in their own stylesheet, to stay aligned with
   it — see that page's own comment for why this can't just be one
   shared value. margin-top dropped to 0 — .cs-layout's own 96px
   bottom padding (meant as breathing room before the page ends) was
   stacking with this wrap's own top spacing, since the carousel now
   always follows that layout on all 3 pages: 96px + 48px margin + 40px
   padding measured out to ~184px of dead space before "More work",
   over twice the ~80px gap between any two regular sections. The
   remaining 40px padding-top is this wrap's own breathing room. */
.cs-carousel-wrap { max-width: 680px; margin: 0 auto; padding: 2.5rem 1.5rem 0; }
.cs-carousel-label { font-family: var(--font-body); font-weight: 700; font-size: 12px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 1.1rem; }
.cs-carousel { display: flex; gap: 1.5rem; overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: 0.6rem; -webkit-overflow-scrolling: touch; }
/* Matches the homepage's own .case-card language (radius, shadow at
   rest/hover, the bottom scrim carrying title text over the media)
   instead of the smaller/plainer card this used to be — same visual
   vocabulary as the cards these links actually lead to. Now also
   reuses the actual per-project thumbnail each homepage card shows
   (the claims gif / the real fwa-alert.png and generate-referral-
   button.png raster assets, not the giant hand-vectorized SVGs those
   cards use at full size — same picture, far less markup to duplicate
   across 3 pages) instead of the generic clover icon this started
   with. Wider (300px vs 190px) and taller to give that treatment room
   to read the same way it does on the homepage.
   An earlier pass here also added a double-ruled inner frame and put
   a small category tag above the title — .case-card-frame exists in
   index.html's CSS but no card there actually uses it (dead rule,
   likely left over from a removed variant), and the real card order
   is title first, then a meta row (a small teal dot + category · year)
   below it, not a tag above. Corrected to match what's actually
   rendered on the homepage, not just what its stylesheet defines. */
.cs-carousel-card { scroll-snap-align: start; flex: 0 0 300px; display: block; position: relative; height: 240px; border-radius: 20px; overflow: hidden; text-decoration: none; color: inherit; background: var(--white); box-shadow: 0 4px 16px rgba(26, 36, 38, 0.09); transition: box-shadow 0.4s ease, transform 0.4s cubic-bezier(0.2, 0, 0.2, 1); }
.cs-carousel-card:hover { box-shadow: 0 26px 60px rgba(26, 36, 38, 0.16); transform: translateY(-8px); }
/* padding-bottom, not centred on the full card height — the scrim+body
   below cover the bottom ~55%, so centring against the full box read
   as the thumbnail sitting low, crowded against the title instead of
   centred in the actual open space above it. */
.cs-carousel-media { position: absolute; inset: 0; background: #F0F9FF; display: flex; align-items: center; justify-content: center; padding-bottom: 74px; }
.cs-carousel-media svg { width: 30px; height: 30px; color: var(--teal); transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1); }
.cs-carousel-card:hover .cs-carousel-media svg { transform: scale(1.08); }
/* The two small UI-graphic thumbnails (fraud alert pair, referral
   button) at close to their own natural size, centred — not full-
   bleed, same "show the real thing at its own size" choice the
   homepage cards make for these two. White background, matching
   homepage's --fraud/--referral variants exactly (only the claim
   thumbnail sits on the blue #F0F9FF — that one's full-bleed cover,
   these two are centred objects on plain white behind them). */
.cs-carousel-media--white { background: var(--white); }
.cs-carousel-media img { max-width: 66%; max-height: 56px; width: auto; height: auto; object-fit: contain; transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1); }
.cs-carousel-card:hover .cs-carousel-media img { transform: scale(1.06); }
/* Fraud card specifically shows both pills stacked, same as homepage —
   fwa-alert.png alone was only half the real graphic. */
.cs-carousel-media--fraud { flex-direction: column; gap: 8px; }
.cs-carousel-media--fraud img { max-width: 78%; }
/* The claim-submission thumbnail is a tall (479x963) phone screenshot,
   not a small fixed-size UI graphic, but it stays in the same centred-
   contain family as the fraud/referral pair above rather than the
   homepage's full-bleed cover treatment — that treatment is tuned for
   homepage's own tall card (a translateY/scale transform, see
   index.html's own comment on it) built for a box shaped much closer
   to the image's own aspect ratio. This card's box is short and
   landscape; cover on a portrait image that different in shape has no
   crop window that shows the phone's own silhouette (the rounded top
   corners) at all, only some cropped-in slice of its middle — visible
   as what read like stray bezel lines rather than a recognizable
   phone. contain guarantees the whole phone is always in frame,
   letterboxed on the sides against the same blue background, same as
   how the fraud/referral graphics already sit in their own cards.
   A single still frame (claims-carousel-still.png), not the source
   gif — the gif's own internal animation was tuned for that homepage
   crop too, so a frame mid-animation here could show a moment with
   even less of the phone in a useful position. */
.cs-carousel-media--content img { max-height: 166px; }
.cs-carousel-scrim { position: absolute; inset: auto 0 0 0; height: 55%; background: linear-gradient(180deg, rgba(252,251,248,0) 0%, rgba(252,251,248,0.94) 55%, rgba(252,251,248,0.99) 100%); pointer-events: none; }
.cs-carousel-body { position: absolute; inset: auto 0 0 0; padding: 1rem 1.2rem 1.2rem; }
.cs-carousel-title { font-family: var(--font-display); font-weight: 700; font-size: 1.05rem; color: var(--ink); letter-spacing: -0.01em; line-height: 1.25; margin-bottom: 0.4rem; transition: color 0.25s ease; }
.cs-carousel-card:hover .cs-carousel-title { color: var(--teal-deep); }
/* Same meta row as .case-card-meta/.case-card-dot on the homepage —
   small teal clover dot, category · year, sitting below the title
   (not a tag above it). */
.cs-carousel-meta { display: flex; align-items: center; gap: 7px; font-family: var(--font-body); font-size: 11px; font-weight: 600; letter-spacing: 0.07em; text-transform: uppercase; color: var(--ink-faint); }
.cs-carousel-dot { flex-shrink: 0; width: 11px; height: 11px; color: var(--teal); }

/* Hidden for now at every width, not removed — was mobile-only at
   first (the 300px scroll-snap cards and hover-driven interactions
   haven't been tuned for touch/narrow viewports yet), but that left it
   showing on desktop, which isn't what "hidden for now" meant. Shared
   here so it's off on all 3 case-study pages consistently. */
.cs-carousel-wrap { display: none; }

@media (max-width: 640px) {
  .meta-grid { gap: 1rem; }
  .device-row.two, .device-row.three { grid-template-columns: 1fr; }
  .device-row.matched { flex-direction: column; height: auto; }
  .device-row.matched .device-frame { height: auto; width: 100%; }
  .device-row.matched .device-frame img { width: 100%; height: auto; }
  .cs-hero-in { padding: 2.5rem 1.25rem 2.25rem; }
}
