/* ===================================================================
   Lauren Ward, Portfolio
   Swiss poster system: cream + ink ground, two coral accents (a darker
   one for text/links/buttons, a lighter one for section backgrounds and
   highlighted words on dark grounds), Gabarito / DM Sans / DM Mono,
   16px rounded card imagery, 32px rounded case-study hero media.
   =================================================================== */

:root {
  --cream: #fbfaf9;
  --ink: #1b1816;
  --ink-soft: #635f5c;
  --ink-faint: #8c8880;
  --accent: #d14620;
  --accent-block: #f15327;
  --pink: #f87ab8;
  --line: #e4e1dc;
  --placeholder-bg: #f4f4f4;

  --font-display: "Gabarito", sans-serif;
  --font-body: "DM Sans", sans-serif;
  --font-mono: "DM Mono", monospace;

  /* Figma's reference frame is 1440px wide with a flat 120px side margin
     (1200px content). Setting the container's own max-width to the full
     1440px and letting only the padding do the inset means that at the
     1440px breakpoint the auto-margin contributes nothing and the 120px
     padding alone reproduces Figma's exact 1200px content width - while
     still capping total width and centering gracefully on wider screens. */
  --container: 1440px;
  /* Fluid gutter: 20px at a 375px viewport, scaling continuously up to
     Figma's 120px at 1440px+, with no breakpoint jumps in between. A
     stepped version of this (20/32/64/120px at fixed breakpoints) used
     to jump straight from 64px to 120px exactly at 1440px - but almost
     no real browser window is exactly 1440px wide (a maximised window on
     a 1440pt display sits a few px under that once you count the
     scrollbar), so most people landed just below the jump and got the
     smaller 64px margin instead of the intended 120px. Slope worked out
     from (120-20)/(1440-375). */
  --gutter: clamp(20px, calc(20px + (100vw - 375px) * 0.0939), 120px);

  --ease: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-out-strong: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
}

/* ---- Reset ---- */
/* Progressive enhancement: real cross-page morph for the work-card ->
   case-study transition in browsers that support the View Transitions
   API (Chrome/Edge today). Everywhere else this is simply ignored and
   navigation happens normally, nothing breaks. */
@view-transition { navigation: auto; }

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

body {
  margin: 0;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { max-width: 100%; display: block; }
a { color: inherit; }
ul { margin: 0; padding: 0; list-style: none; }
button { font: inherit; }

.container {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

h1, h2, h3, h4, p { margin: 0; }

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--ink);
  color: var(--cream);
  padding: 0.75rem 1.25rem;
  z-index: 300;
}
.skip-link:focus { left: 0; }

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

/* .work-tag shares this same label styling (mono, uppercase, tracked) but
   is a separate class, grouped in here too so every label on the page
   steps down together on mobile instead of only some shrinking. Fluid
   rather than a breakpoint swap, so it eases up to 14px by 860px instead
   of jumping straight there. (.hero .eyebrow-mono's own smaller version
   below uses the same curve, just from a lower floor.) */
.eyebrow-mono, .work-tag {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: clamp(0.75rem, 1.63vw, 0.875rem);
  line-height: normal;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* ---- Nav ---- */
.site-nav {
  position: sticky;
  top: 0;
  z-index: 100;
  /* matches --cream (#fbfaf9) - was hardcoded to an older, slightly
     warmer off-white that no longer matched the page background */
  background: rgba(251, 250, 249, 0.9);
  backdrop-filter: blur(10px);
  transition: background 0.3s ease;
}
/* the overlay sits at a lower z-index than the nav so the logo and close
   button stay clickable on top of it, but that means the nav's own cream
   background needs to get out of the way, otherwise it paints a cream
   strip across the top of the green overlay */
.site-nav.menu-open {
  background: transparent;
  backdrop-filter: none;
}
.nav-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: 1.2rem;
  position: relative;
  z-index: 6;
}
.nav-logo {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 1rem;
  /* the flex box around this text is already perfectly centred - but at
     the inherited body line-height (1.6) the extra leading sits mostly
     below the letters, so the glyphs themselves read as sitting high,
     especially next to the hamburger icon which has no such leading.
     Tightening it to 1 lets the visible text (not just its box) centre. */
  line-height: 1;
  letter-spacing: -0.01em;
  text-transform: capitalize;
  text-decoration: none;
  color: var(--ink);
  transition: color 0.3s ease;
}
.site-nav.menu-open .nav-logo { color: var(--ink); }

/* Inline links on desktop, hamburger + full-screen overlay on mobile only.
   Function over uniformity: exposing nav on desktop is better UX there,
   the hamburger stays where it earns its keep, on small screens. */
.nav-links { display: none; }
.nav-links a {
  text-decoration: none;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 1rem;
  letter-spacing: -0.16px;
  line-height: normal;
  color: var(--ink);
  transition: color 0.25s ease;
}
.nav-links a:hover { color: var(--accent); }

.menu-btn {
  width: 36px;
  height: 36px;
  background: none;
  border: none;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 0;
  position: relative;
  z-index: 6;
}
.menu-btn span {
  width: 24px;
  height: 2px;
  background: var(--ink);
  transition: transform 0.45s var(--ease-out-strong), opacity 0.25s ease, background 0.3s ease;
}
.menu-btn[aria-expanded="true"] span { background: var(--ink); }
.menu-btn[aria-expanded="true"] span:nth-child(1) { transform: translateY(8px) rotate(45deg); }
.menu-btn[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.menu-btn[aria-expanded="true"] span:nth-child(3) { transform: translateY(-8px) rotate(-45deg); }

/* desktop: inline links replace the hamburger entirely, this must come
   after the base .menu-btn rule above to actually win the cascade */
@media (min-width: 860px) {
  .nav-links { display: flex; align-items: center; gap: 2.2rem; }
  .menu-btn { display: none; }
}

.menu-overlay {
  position: fixed;
  inset: 0;
  background: var(--accent-block);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0 var(--gutter);
  gap: 0.6rem;
  opacity: 0;
  transform: translateY(-16px);
  transition: opacity 0.4s var(--ease-out-strong), transform 0.4s var(--ease-out-strong);
  pointer-events: none;
  z-index: 90;
}
.menu-overlay.is-open { opacity: 1; transform: translateY(0); pointer-events: auto; }
/* Same font, size and weight as .hero-h ("Designing things from idea to
   impact"), just kept white/cream instead of ink for this dark overlay. */
.menu-overlay a {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(52px, 8.5vw, 128px);
  color: var(--cream);
  text-decoration: none;
  line-height: 1.05;
  letter-spacing: -1px;
  transition: color 0.25s ease;
}
.menu-overlay a:hover { color: var(--ink); }
.menu-overlay .menu-foot {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: rgba(249, 246, 241, 0.65);
  margin-top: 1.5rem;
}

/* ---- Buttons ---- */
.btn {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.875rem;
  line-height: normal;
  padding: 0.75rem 1.35rem;
  border-radius: 999px;
  text-decoration: none;
  transition: transform 180ms var(--ease), color 0.2s ease, border-color 0.2s ease;
  min-height: 44px;
}
/* isolation scopes the ::before's z-index:-1 to this button's own local
   stacking order, so the fill sweep can't end up rendering behind other
   page content the way an un-isolated negative z-index would. */
.btn--primary {
  position: relative;
  isolation: isolate;
  background: var(--ink);
  color: #fff;
  overflow: hidden;
}
.btn--ghost { background: none; color: var(--ink); border: 1.5px solid var(--ink); }

/* The fill itself: a circle, behind the label (z-index:-1) and behind
   .btn--primary's own ink background (painted first regardless of
   z-index), positioned at --fill-x/--fill-y and sized by --fill-d (all
   set in script.js - see the "Fill button" block there). Scaling it from
   0 to 1 reads as the button "filling" with colour starting from wherever
   the interaction happened, rather than an instant colour swap or a
   fixed-direction sweep. --fill-d is set to 2x the button's own diagonal,
   so a circle that size fully covers the button (with margin) from any
   origin point, corners included - the ghost/outline buttons don't get
   this, it's specific to the filled style. Width/height are fixed px
   (from script.js), not a %, so it stays a true circle even though the
   button itself is a wide pill, not a square. */
.btn--primary::before {
  content: "";
  position: absolute;
  top: var(--fill-y, 50%);
  left: var(--fill-x, 50%);
  width: var(--fill-d, 300px);
  height: var(--fill-d, 300px);
  z-index: -1;
  background: var(--accent);
  border-radius: 50%;
  transform: translate(-50%, -50%) scale(0);
  transition: transform 500ms var(--ease-out-strong);
  pointer-events: none;
}
/* Triggers the fill: real mouse/keyboard hover+focus (gated, so touch
   can't get stuck in a hover state), :active as an immediate fallback for
   any pointer type including touch (a tap always matches :active on the
   way to firing the click - this is what gives touch a visible fill
   without needing hover, which touchscreens don't have), and
   :focus-visible ungated so a keyboard-focused button still shows it
   even on a device with no hover capability at all. */
@media (hover: hover) and (pointer: fine) {
  .btn:hover { transform: translateY(-2px); }
  .btn--primary:hover::before { transform: translate(-50%, -50%) scale(1); }
  .btn--ghost:hover { color: var(--accent); border-color: var(--accent); }
  .closing-block .btn--primary:hover { color: #fff; }
  .closing-block .btn--ghost:hover { color: var(--accent); border-color: var(--accent); }
}
.btn--primary:focus-visible::before,
.btn--primary:active::before {
  transform: translate(-50%, -50%) scale(1);
}

/* on the ink closing block, the primary button flips to white so it still
   reads as the one call to action, and the ghost outline goes white too */
.closing-block .btn--primary { background: #fff; color: var(--ink); }
.closing-block .btn--primary::before { background: var(--accent-block); }
.closing-block .btn--primary:focus-visible,
.closing-block .btn--primary:active { color: #fff; }
.closing-block .btn--ghost { color: #fff; border-color: #fff; }
/* Press feedback on every button, not just the fill one. Placed after the
   hover rule above (equal specificity - source order decides the winner)
   so pressing while hovering actually shows the press, instead of :hover's
   translateY silently winning the tie and hiding it. Ungated (unlike the
   hover rules), since :active can't get stuck the way :hover can on
   touch, so touch devices should feel this too. */
.btn:active { transform: scale(0.97); }

.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;
}

/* ---- Hero ---- */
.hero { position: relative; overflow: hidden; padding-block: clamp(2rem, 10vw, 120px); }
/* Mobile-only: pin the gap from the sticky nav down to the eyebrow at a
   fixed 132px, independent of the fluid clamp above (which stays exactly
   as it is for desktop, where the top padding already reads correctly at
   every width from the same shared clamp). */
@media (max-width: 859px) {
  .hero { padding-top: 132px; }
}
.hero .container { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 2rem; }

/* Faint dot texture behind the headline, fading out top and bottom so it
   reads as a soft field rather than a hard-edged rectangle of dots.
   46px pitch matches the tighter dot spacing used elsewhere (About
   section); lower alpha keeps it a quiet texture rather than a pattern
   that competes with the headline. */
/* The dots used to be one static CSS background pattern - now a container
   for individual .hero-dot elements (built in script.js) so each one can
   be nudged away from the cursor independently. The container still
   carries the fade mask; that applies to composited children exactly like
   it did to the old background-image. */
.hero-dots {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 14%, black 72%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, black 14%, black 72%, transparent 100%);
}
.hero-dot {
  position: absolute;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: rgba(216, 212, 206, 0.5);
  will-change: transform;
}

/* H1 spec: Gabarito ExtraBold, -1px tracking, 52px floor. On mobile this
   wraps naturally across as many lines as it needs (Figma's mobile frame
   shows three: "Designing" / "things from" / "idea to impact") - the two
   fixed lines ("Designing things from" / "idea to impact") only kick in
   once there's room for them, from the same breakpoint the rest of the
   site treats as desktop. */
.hero-h {
  font-family: var(--font-display);
  font-weight: 800;
  /* 128px puts "Designing things from" right at the edge of the 1200px
     content width available once the container/gutter cap out at
     1440px+ - close enough that sub-pixel rounding could tip it into
     wrapping onto a second line on very wide screens. 122px keeps real
     breathing room. */
  font-size: clamp(52px, 8.5vw, 122px);
  line-height: 1.05;
  letter-spacing: -1px;
}
.hero-line { display: inline; }
/* Explicit join between the two hero-line spans, standing in for the
   incidental whitespace in the HTML source. That source whitespace
   sits between two block-level boxes once .hero-line goes display:block
   below, so it renders fine on a page load that starts narrow - but
   dragging a desktop window down across the 860px breakpoint leaves it
   stuck un-rendered (a browser reflow quirk, not anything width-based),
   silently joining "from" and "idea" with no space. white-space:pre on
   a real element sidesteps that entirely: always exactly one space,
   never trimmed, never dependent on resize history. */
.hero-line-space { white-space: pre-wrap; }
@media (min-width: 860px) {
  .hero-line { display: block; }
  .hero-line-space { display: none; }
}
.accent { color: var(--accent-block); }
.hero-cta { display: flex; flex-wrap: wrap; gap: 1rem; }
/* Figma's mobile hero frame sets this eyebrow smaller than the shared
   .eyebrow-mono default (11px vs 14px) - scoped to the hero specifically
   so every other eyebrow on the page (case studies, About, Contact)
   keeps its normal size. Same 1.63vw curve as the shared rule, just from
   a lower floor, so both land on the same 14px by 860px with no seam
   between "the hero's eyebrow" and "every other eyebrow" right at that
   width. */
.hero .eyebrow-mono { font-size: clamp(0.6875rem, 1.63vw, 0.875rem); }

/* Per-letter hover: each character pops accent-coral on hover. JS (see
   script.js) wraps the text of any [data-hover-letters] element into one
   span per character, hides that split copy from assistive tech, and
   leaves a plain-text .sr-only sibling in its place - so this only runs
   on hover-capable pointers and never affects what a screen reader hears.
   pre-wrap (not pre) on the space characters: pre keeps the space from
   collapsing but also stops the browser treating it as a wrap point, so
   the headline couldn't reflow on any hover-capable device (desktop,
   any width) and just overflowed the viewport instead - pre-wrap keeps
   the visible space AND still allows wrapping there. */
.hero-h .letter { display: inline; white-space: pre-wrap; color: var(--ink); transition: color 0.1s ease; }
@media (hover: hover) and (pointer: fine) {
  .hero-h .letter:hover { color: var(--accent-block); cursor: default; }
}

/* "impact": idle, it matches the rest of the headline (ink text) with a
   coral bar sweeping in and out behind it on a loop; hovering flips both -
   the text turns coral and the bar becomes a solid, full-width pink fill. */
.hero-impact { position: relative; display: inline-block; cursor: default; }
.hero-impact-text {
  position: relative;
  z-index: 1;
  color: var(--ink);
  transition: color 0.35s ease;
}
.hero-impact-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 4%;
  height: 38%;
  z-index: 0;
  background: var(--accent-block);
  animation: hero-impact-sweep 3.2s cubic-bezier(0.77, 0, 0.18, 1) 0.8s infinite;
}
@media (hover: hover) and (pointer: fine) {
  .hero-impact:hover .hero-impact-text { color: var(--accent-block); }
  .hero-impact:hover .hero-impact-bar {
    background: var(--pink);
    animation: none;
    clip-path: inset(0 0 0 0);
  }
}
@keyframes hero-impact-sweep {
  0%   { clip-path: inset(0 100% 0 0); }
  28%  { clip-path: inset(0 0 0 0); }
  62%  { clip-path: inset(0 0 0 0); }
  90%  { clip-path: inset(0 0 0 100%); }
  100% { clip-path: inset(0 100% 0 0); }
}

/* ---- Section shell ---- */
section { padding-block: clamp(3rem, 8vw, 5rem); }

.section-head { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 2.5rem; }
.section-head h2 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.6rem, 4vw, 48px);
  line-height: normal;
}
.section-head .lede { color: var(--ink-soft); font-size: 1.125rem; line-height: 1.556; max-width: 48ch; }

/* margin-top and padding-top used to be the same full clamp value each,
   stacking to ~112-160px on top of .cs-body's own 40px bottom padding -
   halved so the divider still gets clear space above and below it
   without the excess. */
.work-subsection { margin-top: clamp(1.75rem, 4vw, 2.5rem); padding-top: clamp(1.75rem, 4vw, 2.5rem); border-top: 1px solid var(--line); }
/* When a subsection is the last thing before a colour change (e.g. the
   dark closing-block footer), it needs its own bottom space - unlike the
   gap between stacked subsections on the homepage, which is already
   handled by the next subsection's own margin-top, there's nothing after
   the last one on a case study page to provide that breathing room. */
.work-subsection--end { padding-bottom: clamp(2.5rem, 6vw, 4rem); }
.work-subsection-head { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 2.5rem; }
/* Same 32px desktop size and mobile floor as .work-title below - was a
   24px floor (only a 25% reduction from desktop vs. ~40% everywhere else
   in the heading scale), tightened to 22px for consistency with the
   rest of the scale. */
.work-subsection-head h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.375rem, 4vw, 32px);
  line-height: normal;
}
.work-subsection-head .lede { color: var(--ink-soft); max-width: 48ch; font-size: 1.125rem; line-height: 1.556; }

/* ---- Work cards ---- */
.work-grid { display: flex; flex-direction: column; gap: 2.5rem; }
.work-grid--triple { display: grid; gap: 2.5rem; grid-template-columns: 1fr; }
.work-grid--double { display: grid; gap: 2.5rem; grid-template-columns: 1fr; }
@media (min-width: 780px) {
  .work-grid--triple { grid-template-columns: repeat(3, 1fr); }
  .work-grid--double { grid-template-columns: repeat(2, 1fr); }
}

.work-card { display: flex; flex-direction: column; gap: 1rem; text-decoration: none; color: inherit; }
.work-card--static { cursor: default; }

.work-media {
  width: 100%;
  aspect-ratio: 370 / 237;
  background: var(--placeholder-bg);
  border-radius: 16px;
  overflow: hidden;
  position: relative;
}
/* Stroke means "this is clickable" - only cards get it (whether an actual
   link, or a static "coming soon" tile sitting in the same grid as ones
   that are). Plain .work-media images dropped straight into a case
   study's body content aren't wrapped in .work-card, so they're
   unaffected and stay borderless. */
.work-card .work-media { border: 1px solid var(--line); }
/* feature card: copy left, image right on desktop, image simply
   stacked below on mobile */
.work-card--feature {
  display: grid;
  gap: 2rem;
}
@media (min-width: 780px) {
  .work-card--feature { grid-template-columns: 1fr 1fr; gap: 5rem; align-items: center; }
}
.work-feature-info { display: flex; flex-direction: column; gap: 1.5rem; }
.work-media .screen {
  position: absolute;
  inset: 0;
  opacity: 0;
  background-size: cover;
  background-position: center;
  transition: opacity 450ms ease;
}
.work-media .screen.is-active { opacity: 1; }
.work-media img,
.work-media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 450ms var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .work-card:not(.work-card--static):hover .work-media img,
  .work-card:not(.work-card--static):hover .work-media video { transform: scale(1.05); }
}
.media-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--placeholder-bg);
}
.media-placeholder span {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--ink-faint);
}

.work-tag { display: block; }
.work-card--feature .work-tag { color: var(--ink-soft); }
.work-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.375rem, 4vw, 32px);
  line-height: normal;
}
/* Figma's mobile hero frame sizes the featured "tiney" title larger than
   the shared .work-title clamp gives it at this width - own clamp so it
   still meets the same 32px desktop size (at the same 800px point, same
   4vw rate) but starts from a taller 28px floor, matching the mobile
   floor used for .cs-title/.closing-block h2 rather than Figma's literal
   34px, so it doesn't end up reading larger than an actual page heading
   elsewhere on the same phone. */
.work-card--feature .work-title { font-size: clamp(1.75rem, 4vw, 32px); }
.work-body {
  font-size: 1.125rem;
  line-height: 1.556;
  color: var(--ink-soft);
  margin: 0;
  max-width: 46ch;
}
.work-more {
  display: inline-block;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 1.125rem;
  line-height: normal;
  color: var(--ink);
  transition: color 200ms ease;
}
@media (hover: hover) and (pointer: fine) {
  .work-card:not(.work-card--static):hover .work-more {
    color: var(--accent);
    text-decoration: underline;
    text-decoration-color: var(--accent);
    text-underline-offset: 3px;
  }
}

/* ---- Closing block: contact + quote + meta, ink, shared by every page ---- */
.closing-block {
  background: var(--ink);
  color: var(--cream);
  padding-block: clamp(3rem, 10vw, 100px) clamp(1.5rem, 4vw, 40px);
}
.closing-block .eyebrow-mono { color: rgba(249, 246, 241, 0.55); }
/* H2 spec: Gabarito Bold, 48px, auto leading - same 28px mobile floor as
   .cs-title, the other heading that shares this 48px desktop size, so
   the two don't render at visibly different sizes on the same phone */
.closing-block h2 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.75rem, 5vw, 48px);
  line-height: normal;
  color: var(--cream);
}
.closing-block .lede {
  color: rgba(249, 246, 241, 0.8);
  font-size: 1.125rem;
  line-height: 1.556;
  max-width: 40ch;
}
.closing-cta { display: flex; flex-wrap: wrap; gap: 1rem; }
.closing-info { display: flex; flex-direction: column; gap: 1.5rem; align-items: flex-start; }

/* contact copy + CTA on the left, quote on the right, on desktop; stacks
   on mobile with the quote following on beneath */
.closing-grid {
  display: grid;
  gap: 2.5rem;
  margin-bottom: clamp(2.5rem, 8vw, 80px);
}
@media (min-width: 860px) {
  .closing-grid { grid-template-columns: 1fr 1fr; gap: 7.5rem; align-items: center; }
}

/* Quote spec: Gabarito Bold, 44px, auto leading - shared with .about-figure */
.footer-quote {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.75rem, 8vw, 44px);
  line-height: normal;
  color: var(--cream);
  margin: 0;
}
@media (min-width: 860px) {
  /* the ch cap only makes sense once the two-column layout kicks in and
     the quote is sharing the row with something else - on a single mobile
     column it should use the full width available instead of wrapping early */
  .footer-quote { max-width: 32ch; }
}
.foot-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: rgba(249, 246, 241, 0.45);
  padding-top: 2.5rem;
  border-top: 1px solid rgba(249, 246, 241, 0.15);
}

/* Simpler footer used on non-homepage pages: quote + meta, no contact CTA */
.closing-block--simple { padding-block: clamp(2.5rem, 6vw, 3.5rem) clamp(1.6rem, 4vw, 2rem); }

/* ---- About, back to one full-bleed accent-coral ground with the quote
   and body copy side by side inside the normal .container grid (rather
   than the two-tone split panels this used to be) ---- */
.about-block { background: var(--accent); padding-block: clamp(2.5rem, 8vw, 100px); }
.about-block .container { display: flex; flex-direction: column; gap: 2.5rem; }
.about-block .eyebrow-mono { color: #fff; }
.about-flex { display: flex; flex-direction: column; gap: 2.5rem; }
@media (min-width: 860px) {
  .about-flex { flex-direction: row; gap: 5rem; align-items: flex-start; }
}
.about-quote-col { display: flex; flex-direction: column; gap: 2.5rem; flex: 1 1 0; }
.about-body { flex: 1 1 0; }
/* Quote spec: Gabarito Bold, 44px, auto leading - shared with .footer-quote.
   Large bold text only needs 3:1 contrast (WCAG AA large-text) so white
   on this accent is comfortably fine; body text needs the full 4.5:1 for
   normal-size text, which literal #fff (not the slightly duller --cream)
   just clears on this particular accent value - swapping --accent for a
   much lighter shade later would need this rechecked. */
/* position+z-index lifts the quote above .about-pattern below, since the
   pattern now overlaps its bottom edge (see .about-pattern's negative
   margin-top) and would otherwise paint over the last line or two of
   text - a plain static sibling always loses to a positioned one
   regardless of DOM order, so the quote needs this to stay on top. */
.about-figure {
  position: relative;
  z-index: 1;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.75rem, 8vw, 44px);
  line-height: normal;
  color: #fff;
  margin: 0;
  /* The quote's own box would otherwise sit on top of the tiles behind
     it and swallow their hover, even across the transparent gaps around
     the letters - since the text isn't interactive, let hover pass
     straight through to the pattern underneath. */
  pointer-events: none;
}
@media (min-width: 860px) {
  .about-figure { max-width: 16ch; }
}
/* Mobile-only: the unconstrained mobile width let the wrap fall wherever
   the natural line-break landed, which happened to strand "notice" alone
   on its own last line - text-wrap:balance asks the browser to spread
   the same line count more evenly instead. Progressive enhancement only
   (ignored by browsers that don't support it, same wrap as before). */
@media (max-width: 859px) {
  .about-figure { text-wrap: balance; }
}
/* Geometric tile pattern, now 8x5 (was 8x3) with the extra two rows
   pulled up behind the quote via a negative margin-top, rather than
   position:absolute - staying in normal flow this way means the column
   still reserves real height for the whole pattern instead of the
   pattern silently overflowing past a column that's only ever as tall
   as the quote text. Each tile is split diagonally into a darker and a
   lighter coral triangle; not a single repeating background, since a
   diagonal run of tiles nearer the bottom-right gets a third, slightly
   warmer tone instead of the usual lighter one (1 tile on row 3, 2 on
   row 4, 3 on row 5 - unchanged from before, just renumbered now there
   are two plain rows above them). flex + aspect-ratio keeps every tile
   square and the whole grid fluid at any column width. */
.about-pattern { width: 100%; display: flex; flex-direction: column; margin-top: -180px; position: relative; z-index: 0; }
.about-pattern-row { display: flex; }
/* On mobile the quote and pattern stack in one column above the body
   copy (row layout only kicks in at 860px), so the pattern's full height
   sits directly between the quote and the body text instead of running
   alongside it - trimming to 3 of the 5 rows keeps the motif recognisable
   without pushing the body copy so far down it reads as a dead gap. */
@media (max-width: 859px) {
  .about-pattern-row:nth-child(n + 4) { display: none; }
}
.about-pattern-tile {
  flex: 1 1 0;
  aspect-ratio: 1;
  background: linear-gradient(135deg, #ce451f 50%, #d24e29 50%);
}
.about-pattern-tile--accent { background: linear-gradient(135deg, #ce451f 50%, #d14b1e 50%); }

/* Flip-in reveal: each tile starts edge-on (invisible, since a flat plane
   viewed from the side has no width) and rotates flat into place once
   .about-quote-col gets .is-visible from the site's existing scroll-
   reveal observer - no separate trigger needed, this piggybacks on the
   same one already driving the column's own fade-up. A one-time staggered
   cascade rather than a continuous loop: a background texture that never
   stops moving would compete with the quote every single time someone
   scrolls to this section, rather than reading as a considered one-off
   flourish. perspective() is set per-tile (not on a shared parent) since
   these are independent flips, not one connected 3D scene. */
.about-pattern-tile {
  opacity: 0;
  transform: perspective(500px) rotateY(-90deg);
  transition: transform 0.6s var(--ease-out-strong), opacity 0.35s ease;
}
.about-quote-col.is-visible .about-pattern-tile {
  opacity: 1;
  transform: perspective(500px) rotateY(0deg);
}
/* Hover: an individual tile flips again on its own - :hover naturally
   reverses itself when the cursor leaves, so no separate "flip back"
   rule is needed the way the one-time reveal above needed one. The
   mirrored diagonal you see past 90° is just the browser's default
   backface rendering (mirrors the front face) - not a second background
   defined anywhere, which is why this works with zero extra markup. The
   actual speed/easing for this comes from script.js swapping in a
   faster, symmetric ease-in-out transition once the one-time reveal
   finishes (see the "About triangles" block there) - the 0.6s ease-out
   above is right for a single entrance, but felt sluggish reused for a
   repeated hover toggle, so hover gets its own quicker, smoother timing
   instead of sharing the reveal's. Gated to real hover so a tap on touch
   doesn't leave a tile stuck flipped after the tap ends. */
@media (hover: hover) and (pointer: fine) {
  .about-quote-col.is-visible .about-pattern-tile:hover {
    transform: perspective(500px) rotateY(180deg);
  }
}
.about-body p {
  font-size: 1.125rem;
  line-height: 1.556;
  color: #fff;
  max-width: 58ch;
  margin: 0 0 1.3rem;
}
.about-body p:last-child { margin-bottom: 0; }

/* ===================================================================
   Case study pages
   =================================================================== */

.cs-hero {
  padding-block: clamp(3rem, 10vw, 120px) clamp(1.5rem, 4vw, 40px);
  display: flex;
  flex-direction: column;
  gap: 2rem;
}
/* used on pages with their own .cs-back-nav above the hero, so the two
   don't stack to more top space than a page without one would get */
.cs-hero--tight { padding-top: clamp(1.5rem, 4vw, 40px); }
.cs-hero-info { display: flex; flex-direction: column; gap: 1.5rem; }

.cs-back-nav { padding-top: 1.25rem; }
.cs-back-btn {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(21, 19, 15, 0.06), 0 1px 3px rgba(21, 19, 15, 0.08);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
  font-size: 1.125rem;
  text-decoration: none;
  transition: box-shadow 200ms ease, transform 200ms var(--ease);
}
.cs-back-btn-arrow { display: inline-block; transition: transform 200ms var(--ease); }
/* rotate only the glyph, not the button - rotating the button itself would
   flip its box-shadow to sit above it instead of below */
.cs-back-btn--next .cs-back-btn-arrow { transform: rotate(180deg); }
@media (hover: hover) and (pointer: fine) {
  .cs-back-btn:hover {
    box-shadow: 0 2px 6px rgba(21, 19, 15, 0.1), 0 1px 3px rgba(21, 19, 15, 0.08);
  }
  .cs-back-btn:hover .cs-back-btn-arrow { transform: translateX(-2px); }
  /* translateX is applied in the arrow's already-rotated local space, so
     it still nudges toward screen-right, the direction this arrow points */
  .cs-back-btn--next:hover .cs-back-btn-arrow { transform: rotate(180deg) translateX(-2px); }
}
.cs-back {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--ink-soft);
  margin-bottom: 2rem;
}
.cs-back:hover { color: var(--ink); }

/* Same 48px desktop size and 28px mobile floor as .closing-block h2 */
.cs-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.75rem, 6vw, 48px);
  line-height: normal;
  max-width: 16ch;
}
.cs-sub { color: var(--ink-soft); max-width: 580px; font-size: 1.125rem; line-height: 1.556; }

.cs-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 2rem;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--ink-soft);
  margin-bottom: 2rem;
}
.cs-meta strong { color: var(--ink); font-weight: 600; }

/* Inset within the page container (not full-bleed), large rounded corners */
.cs-hero-media {
  width: 100%;
  max-width: 1200px;
  aspect-ratio: 1200 / 768;
  background: var(--placeholder-bg);
  border-radius: 32px;
  overflow: hidden;
}
/* Below 860px, .cs-hero-media and .work-media both end up close to the
   same full-content-width, but keeping the fixed 32px radius there
   makes the hero corners read as roughly twice as rounded as a grid
   card's 16px (32px is ~2.7% of a 1200px desktop hero and 16px is a
   near-identical ~2.8% of a 580px card - consistent - but at ~350px
   wide on mobile that same 32px balloons to ~9%, while the card's 16px
   only drifts to ~4.6%). Matching the grid-card radius here restores
   that same consistency on mobile. */
@media (max-width: 859px) {
  .cs-hero-media { border-radius: 16px; }
}
.cs-hero-media img, .cs-hero-media video { width: 100%; height: 100%; object-fit: cover; }

/* The "Case study" wrapper: py-40 around the text column, with the
   chapters themselves spaced by a 40px gap rather than their own padding */
.cs-body {
  /* shrinks below the 790px reading column once the viewport can't fit
     it plus a gutter on each side, instead of running edge-to-edge -
     a flat max-width here was clipping the side margins on narrow screens */
  width: min(790px, 100% - 2 * var(--gutter));
  margin-inline: auto;
  padding-block: 2.5rem;
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
}

/* Cream stays the ground for every chapter except results, which gets
   pulled onto solid accent so the number reads as the page's headline.
   Standalone use (wrapping a full-width supporting image between
   chapters) keeps its own 40px vertical spacing; nested inside .cs-body
   it becomes a plain flex item spaced by the parent's gap instead.
   margin (not padding) so that two standalone chapters stacked back to
   back - e.g. a double-image grid immediately followed by a triple-image
   grid - collapse their touching 40px margins into one 40px gap, instead
   of adding up to 80px. */
.cs-chapter { margin-block: 2.5rem; }
.cs-body .cs-chapter {
  /* .cs-chapter is a <section>, and the generic `section` rule further
     down also sets its own padding-block - without zeroing that out here
     too, it silently leaked back in and stacked with .cs-body's own 40px
     flex gap, roughly tripling the space between chapters on mobile. */
  margin-block: 0;
  padding-block: 0;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}
/* H5 spec: Gabarito SemiBold, 20px, auto leading */
.cs-chapter h2 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.125rem, 2.33vw, 1.25rem);
  line-height: normal;
  color: var(--ink);
}

/* H4 spec: Gabarito Regular, 24px, auto leading - the statement of intent
   directly under the hero media, ahead of the chapter breakdown, used on
   case studies like YunoJuno */
/* Neither this nor .cs-chapter h2 above scaled down for mobile at all
   before, while every other heading tier did - fluid floors here keep
   the whole page's heading scale consistently "a size down" on mobile,
   easing up to their normal desktop size by 860px like everything else,
   instead of some levels shrinking and others staying desktop-sized. */
.cs-lede {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.25rem, 2.79vw, 1.5rem);
  line-height: normal;
  color: var(--ink);
  max-width: 56ch;
}

/* full-bleed stats strip directly under the hero media, ahead of the
   body chapters - left-aligned, no heading, distinct from the older
   centered .cs-chapter--result treatment further down a case study */
.cs-stats-band { background: var(--accent-block); padding-block: clamp(3rem, 12vw, 140px); }
.cs-stats-band .cs-stats-grid { margin-top: 0; }

.cs-chapter--result {
  background: var(--accent-block);
  margin-inline: calc(-1 * var(--gutter));
  padding-inline: var(--gutter);
  text-align: left;
}
.cs-chapter--result h2 { color: rgba(249, 246, 241, 0.7); }
.result-num {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(3.5rem, 12vw, 7rem);
  line-height: 0.95;
  color: var(--cream);
}
.result-label {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.95rem;
  color: rgba(249, 246, 241, 0.8);
  margin-top: 0.6rem;
}
.cs-list--inverse { max-width: 40ch; margin: 1.8rem auto 0; text-align: left; }
.cs-list--inverse li { color: rgba(249, 246, 241, 0.85); }
.cs-list--inverse li::before { background: var(--cream); }

.cs-stats-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
  text-align: left;
  margin-top: 0.5rem;
}
@media (min-width: 640px) { .cs-stats-grid { grid-template-columns: repeat(4, 1fr); } }
.cs-stats-grid .num {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.8rem, 4vw, 48px);
  color: #fff;
  line-height: normal;
}
/* Stacked (single-column, below the 640px grid breakpoint) numbers get
   a much bigger, H1-scale treatment since each one now has the full
   width to itself - the 48px cap above was tuned for four numbers
   sharing a row, not one at a time. */
@media (max-width: 639px) {
  .cs-stats-grid .num { font-size: clamp(52px, 12vw, 64px); }
}
.cs-stats-grid .label {
  display: block;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.2rem, 3vw, 32px);
  line-height: normal;
  color: #fff;
  margin-top: 0.25rem;
}

.cs-body-text { color: var(--ink-soft); font-size: 1.125rem; line-height: 1.556; }
.cs-body-text p + p { margin-top: 1em; }
.cs-body-text strong { color: var(--ink); font-weight: 700; }

.cs-list li {
  position: relative;
  padding-left: 1.3rem;
  color: var(--ink-soft);
  line-height: 1.55;
}
.cs-list li + li { margin-top: 0.55rem; }
.cs-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.6em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--ink);
}

.cs-callout {
  background: rgba(21, 19, 15, 0.04);
  border-left: 3px solid var(--accent);
  padding: 1.1rem 1.4rem;
  color: var(--ink-soft);
  font-size: 0.95rem;
}

.cs-nav-next {
  border-top: 1px solid var(--line);
  padding-top: 2.5rem;
  padding-bottom: 5rem;
}
.cs-nav-next a { text-decoration: none; display: block; }
.cs-nav-next h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.3rem, 3vw, 32px);
  line-height: normal;
  margin-top: 0.3rem;
}

/* pairs a "back to work" link with "next project" side by side, for case
   studies that want both rather than .cs-nav-next's single next-only link */
.cs-nav-split {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  border-top: 1px solid var(--line);
  padding-top: 2.5rem;
  padding-bottom: 5rem;
}

/* ---- Reveal on scroll ---- */
.reveal {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}
.reveal.is-visible { opacity: 1; transform: translateY(0); }
