/* ==========================================================================
   Fonts (self-hosted — no external CDN dependency)
   ========================================================================== */

@font-face {
  font-family: "Hind";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("../assets/fonts/hind-300.woff2") format("woff2");
}

@font-face {
  font-family: "Hind";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../assets/fonts/hind-400.woff2") format("woff2");
}

@font-face {
  font-family: "Hind";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../assets/fonts/hind-500.woff2") format("woff2");
}

@font-face {
  font-family: "Hind";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../assets/fonts/hind-600.woff2") format("woff2");
}

@font-face {
  font-family: "Hind";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../assets/fonts/hind-700.woff2") format("woff2");
}

@font-face {
  font-family: "Reenie Beanie";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../assets/fonts/reenie-beanie-400.woff2") format("woff2");
}

/* Used only inside .brain-break, to preview the Sugar game's own type
   system (Itim + Poppins) rather than the sitewide Hind/Reenie Beanie. */
@font-face {
  font-family: "Itim";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../assets/fonts/itim-400.woff2") format("woff2");
}

@font-face {
  font-family: "Poppins";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("../assets/fonts/poppins-300.woff2") format("woff2");
}

@font-face {
  font-family: "Poppins";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../assets/fonts/poppins-600.woff2") format("woff2");
}

/* ==========================================================================
   Reset
   ========================================================================== */

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

/* Guaranteed cross-page fade, driven by js/page-transition.js — not the
   native View Transitions API (Chromium-only, and "best effort": it can
   silently skip to a plain jump if the destination page isn't ready in
   time, which read as inconsistent). html.js is set by an inline script
   at the very top of <head>, before first paint — every other rule here
   is scoped under it, so a page with JS disabled/broken never had
   opacity:0 applied in the first place and just renders normally. */
html.js {
  opacity: 0;
}

html.js.page-fade-in {
  opacity: 1;
  /* Deliberately longer than page-fade-out (and than js/page-transition.js's
     150ms pre-navigation delay) — this is the fade the visitor actually
     watches happen, so it gets more time to read as a fade rather than a
     snap; the fade-out is just brief dead time before navigating away and
     doesn't need to be seen in the same way. */
  transition: opacity 0.32s ease;
}

html.js.page-fade-out {
  opacity: 0;
  transition: opacity 0.15s ease;
}

html {
  -webkit-text-size-adjust: 100%;
  overflow-x: clip;
  /* Reserves the vertical scrollbar's width whether or not a page is
     tall enough to need it, so the header/footer render at the exact
     same effective width on every page. Without this, short pages (no
     scrollbar) got ~15px more layout width than long ones (scrollbar
     present), visibly shifting the logo and nav. */
  scrollbar-gutter: stable;
  /* Matches the cream background instead of the browser's default white
     — the brief flash between page loads (before this stylesheet/body
     background paints) reads as part of the site instead of a stark
     white blink. */
  background-color: var(--color-cream);
}

img,
svg {
  display: block;
  max-width: 100%;
}

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

ul {
  list-style: none;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

/* ==========================================================================
   Design tokens
   ========================================================================== */

:root {
  /* Global colors */
  --color-blue-black: #1e1e24;
  --color-cream: #fff8f0;
  --color-white: #ffffff;
  --color-scarlet: #92140c;

  /* Project accent colors */
  --color-davis-grown: #1f9533;
  --color-home-at-last: #3968b1;
  --color-sville-library: #670919;
  --color-sugar: #ffb5e2;

  /* Typography */
  --font-display: "Reenie Beanie", "Segoe Print", "Bradley Hand", Georgia, serif;
  --font-body: "Hind", sans-serif;

  /* Type scale — desktop sizes are the source of truth from Figma;
     tablet and mobile scale down slightly (see max-width: 1023px override below). */
  --font-size-body: 18px;
  --font-size-nav-link: 20px;

  /* Spacing scale */
  --space-tight: 8px;
  --space-small: 16px;
  --space-medium: 32px;
  --space-large: 80px;
  --space-section: 160px;

  /* Layout */
  --container-max-width: 1280px;
  --container-padding: 40px;

  /* Header */
  --header-height-desktop: 148px;
  --header-height-mobile: 88px;

  /* Motion */
  --transition-fast: 250ms ease-out;
}

body {
  overflow-x: clip;
  font-family: var(--font-body);
  font-size: var(--font-size-body);
  font-weight: 400;
  color: var(--color-blue-black);
  background-color: var(--color-white);
  line-height: 1.5;
  cursor: default;
  caret-color: transparent;
}

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

/* Scroll-reveal — class applied by js/reveal.js only (never in markup),
   so a section always renders normally if JS fails/is disabled instead
   of staying permanently hidden. is-visible is added once, the first
   time each section enters the viewport, and never removed. */
.js-reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}

.js-reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* The About hero's down-arrow fades in on a delay independent of the
   scroll-reveal system (see js/reveal.js) — it needs its own hidden
   state baked into CSS, keyed off the same early html.js class the
   page-fade system uses, rather than js/reveal.js applying "js-reveal"
   itself once it runs. That script executes after the DOM has parsed,
   which is late enough that the arrow could already have painted once
   at full opacity — reveal.js adding the hidden class at that point
   would visibly transition it away and then back in a moment later,
   instead of a single clean entrance. Scoping the hidden state to CSS
   means it's applied at first paint, before anything could ever be seen
   in its default state. */
html.js .about-hero__arrow {
  opacity: 0;
  /* Negative, unlike the shared .js-reveal pattern's +16px (which fades
     content up from below) — the arrow points down, so it fades in
     moving downward instead, reinforcing the "scroll down" cue rather
     than fighting its own direction. */
  transform: translateY(-16px);
  transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}

html.js .about-hero__arrow.is-visible {
  opacity: 1;
  transform: none;
}

/* The homepage hero's text block (name, tagline, and subcopy together,
   as one unit) gets the same one-time delayed fade-in treatment as the
   About hero's arrow above — same reasoning (hidden state baked into
   CSS, not applied later by js/reveal.js, to rule out any flash of the
   default state before the hidden class takes hold). Deliberately the
   whole block now, not just the name — after trying name-only and
   name+tagline, the call landed on all of it fading up together. The
   GIF is untouched, since it's already its own ongoing motion and
   doesn't need a fade on top of it. */
html.js .hero__text {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}

html.js .hero__text.is-visible {
  opacity: 1;
  transform: none;
}

/* Same treatment for the About hero's title + body — deliberately not
   the whole .about-hero__text wrapper, since .about-hero__arrow lives
   nested inside it with its own independent hidden state/timing above;
   hiding the wrapper too would nest the arrow under a second opacity:0
   ancestor, compounding the two fades instead of keeping them separate. */
html.js .about-hero__title,
html.js .about-hero__body {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}

html.js .about-hero__title.is-visible,
html.js .about-hero__body.is-visible {
  opacity: 1;
  transform: none;
}

/* Same treatment for each case study's hero text (title + accent star
   together, as one unit — shared .case-hero__text class, so this one
   rule covers all three case study pages) — except the "slide up" part
   rides on --reveal-offset (folded into .case-hero__text's own
   margin-top, see that rule) instead of transform. .case-hero__star is
   position:absolute and nested directly inside this element; a
   transform here would become its containing block and send it to the
   wrong spot (already the subject of a warning comment on
   .case-hero--davis-grown .case-hero__inner about this exact trap). */
html.js .case-hero__text {
  opacity: 0;
  --reveal-offset: 16px;
  transition: opacity 0.5s ease-out, margin-top 0.5s ease-out;
}

html.js .case-hero__text.is-visible {
  opacity: 1;
  --reveal-offset: 0px;
}

/* The case-study "Summary & Outcomes" card — and the Project
   Details/Focus/Tools column beside it, as one unit with the card, not
   sitting static while the card animates — get a one-time delayed
   fade-up too, timed to start right after .case-hero__text's own
   reveal above finishes settling. A second, deliberate beat rather than
   this content just sitting there fully-formed the whole time while
   everything around it fades in. Same hidden-state-in-CSS reasoning as
   the rest of this group. No absolutely-positioned descendants inside
   either of these (unlike .case-hero__text), so a plain transform is
   safe here — no containing-block trap to work around. */
html.js .case-summary__card,
html.js .case-summary__details {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}

html.js .case-summary__card.is-visible,
html.js .case-summary__details.is-visible {
  opacity: 1;
  transform: none;
}

/* Same reasoning again, applied to everything that can already be in
   view the moment reveal.js runs — either landed on directly via a hash
   jump (Work's #work, the case-study cards; Contact's #page-bottom,
   Skating + the "Let's Connect" banner) or simply because the browser
   restored scroll position on a page refresh while already scrolled
   down (.brain-break, the homepage's last section — reported as a
   jump/glitch on refreshing while scrolled to the bottom, same
   underlying race as the hash-jump cases). This was confirmed with a
   screen recording: the Davis Grown card rendered at full opacity, then
   vanished completely, then faded back in — the flash-before-hide race
   actually happening, not just a theoretical risk. .case-reflection/
   .case-reflection-banner (the case study pages' "Read more of my
   work" pairing) get the same treatment for consistency, even without
   a confirmed report, since they're built on the identical pattern.
   Below-the-fold scroll-reveal sections elsewhere don't need this —
   js/reveal.js has long since run by the time a user actually scrolls
   to them normally, so there's no race to close; it's specifically
   "already in view at the moment the script runs" that's the risk.
   .stitchery joins this group for the same reason — it now gets the
   same hero-adjacent on-load timer treatment as .case-study (Davis
   Grown) when it's fully visible without scrolling, see
   setupHeroAdjacentReveal in js/reveal.js. */
html.js .case-study,
html.js .stitchery,
html.js .skating,
html.js .connect-banner,
html.js .case-reflection,
html.js .case-reflection-banner,
html.js .brain-break {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}

html.js .case-study.is-visible,
html.js .stitchery.is-visible,
html.js .skating.is-visible,
html.js .connect-banner.is-visible,
html.js .case-reflection.is-visible,
html.js .case-reflection-banner.is-visible,
html.js .brain-break.is-visible {
  opacity: 1;
  transform: none;
}

@media (max-width: 1489px) {
  :root {
    --container-padding: 32px;
    --header-height-desktop: 136px;
  }
}

@media (max-width: 1249px) {
  :root {
    --header-height-desktop: 124px;
  }
}

@media (max-width: 1023px) {
  :root {
    --font-size-body: 16px;
    --font-size-nav-link: 18px;
    --header-height-desktop: 112px;
  }
}

@media (max-width: 849px) {
  :root {
    --header-height-desktop: 100px;
  }
}

@media (max-width: 639px) {
  :root {
    --container-padding: 20px;
  }
}

/* ==========================================================================
   Site header
   ========================================================================== */

/* Height matches .site-header__inner's own (js/header.js injects that
   element at runtime, so it doesn't exist yet on first paint) — without
   this, the empty placeholder starts at 0 height, and injecting the real
   header a moment later grows it to full height and shoves everything
   below it down. Landing on a hash further down the page (Contact's
   #page-bottom) made this visible: the browser's anchor-jump locks onto
   the pre-injection (too-short) layout, then the header injection pushes
   the target down, so the page briefly shows the top of the document
   before correcting to the real anchor position — read as the About
   hero flashing before the page settles at the bottom. Reserving the
   same height up front means injecting the header never changes the
   page's height at all. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 250;
  height: var(--header-height-desktop);
  background-color: var(--color-white);
}

.site-header__inner {
  max-width: var(--container-max-width);
  height: var(--header-height-desktop);
  margin-inline: auto;
  padding-inline: var(--container-padding);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.site-header__logo {
  display: inline-flex;
  flex-shrink: 0;
}

.site-header__logo img {
  width: 64px;
  height: auto;
}

/* Desktop nav */

.site-nav__list {
  display: flex;
  align-items: center;
  gap: 56px;
}

.site-nav__item--dropdown {
  position: relative;
}

.site-nav__link {
  font-family: var(--font-body);
  font-size: var(--font-size-nav-link);
  font-weight: 400;
  color: var(--color-blue-black);
  transition: color var(--transition-fast);
}

.site-nav__link:hover,
.site-nav__link:focus-visible {
  color: var(--color-scarlet);
}

.site-nav__dropdown {
  position: absolute;
  top: calc(100% + 16px);
  left: 50%;
  transform: translateX(-50%) translateY(-8px);
  min-width: 220px;
  background-color: var(--color-white);
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(30, 30, 36, 0.1);
  padding: var(--space-small) 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition-fast), transform var(--transition-fast), visibility var(--transition-fast);
}

.site-nav__item--dropdown:hover .site-nav__dropdown,
.site-nav__item--dropdown:focus-within .site-nav__dropdown {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}

.site-nav__dropdown li a {
  display: block;
  padding: var(--space-tight) var(--space-medium);
  font-size: var(--font-size-body);
  color: var(--color-blue-black);
  white-space: nowrap;
  transition: color var(--transition-fast);
}

.site-nav__dropdown li a:hover,
.site-nav__dropdown li a:focus-visible {
  color: var(--color-scarlet);
}

/* Mobile hamburger toggle */

.site-header__toggle {
  display: none;
  position: relative;
  width: 32px;
  height: 24px;
  flex-shrink: 0;
}

.site-header__toggle span {
  position: absolute;
  left: 0;
  width: 100%;
  height: 2px;
  background-color: var(--color-blue-black);
  border-radius: 2px;
  transition: transform var(--transition-fast), opacity var(--transition-fast), top var(--transition-fast);
}

.site-header__toggle span:nth-child(1) {
  top: 0;
}

.site-header__toggle span:nth-child(2) {
  top: 11px;
}

.site-header__toggle span:nth-child(3) {
  top: 22px;
}

.site-header__toggle.is-open span:nth-child(1) {
  top: 11px;
  transform: rotate(45deg);
}

.site-header__toggle.is-open span:nth-child(2) {
  opacity: 0;
}

.site-header__toggle.is-open span:nth-child(3) {
  top: 11px;
  transform: rotate(-45deg);
}

/* Mobile menu drawer */

.mobile-menu {
  position: fixed;
  top: var(--header-height-mobile);
  right: 0;
  bottom: 0;
  width: min(320px, 80vw);
  background-color: var(--color-white);
  z-index: 200;
  transform: translateX(100%);
  transition: transform var(--transition-fast);
  overflow-y: auto;
  padding: var(--space-medium);
  display: flex;
  flex-direction: column;
}

.mobile-menu.is-open {
  transform: translateX(0);
}

.mobile-menu__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-medium);
}

.mobile-menu__link,
.mobile-menu__list > li > a {
  font-size: var(--font-size-nav-link);
  color: var(--color-blue-black);
}

.mobile-menu__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.mobile-menu__caret {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform var(--transition-fast);
}

.mobile-menu__caret[aria-expanded="true"] {
  transform: rotate(180deg);
}

.mobile-menu__submenu {
  display: flex;
  flex-direction: column;
  gap: var(--space-small);
  margin-top: var(--space-small);
  padding-left: var(--space-small);
}

.mobile-menu__submenu[hidden] {
  display: none;
}

.mobile-menu__submenu a {
  font-size: var(--font-size-body);
  color: var(--color-blue-black);
}

.mobile-menu__gif-link {
  display: block;
  align-self: flex-end;
  margin-top: auto;
  padding-top: var(--space-medium);
}

.mobile-menu__gif {
  display: block;
  width: 80px;
  height: auto;
}

.mobile-menu__overlay {
  position: fixed;
  top: var(--header-height-mobile);
  right: 0;
  bottom: 0;
  left: 0;
  background-color: rgba(30, 30, 36, 0.4);
  z-index: 150;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition-fast), visibility var(--transition-fast);
}

.mobile-menu__overlay.is-open {
  opacity: 1;
  visibility: visible;
}

html.menu-open {
  overflow: hidden;
}

/* Responsive header breakpoints — hamburger reserved for phone widths only;
   tablet keeps the full desktop-style nav since three items fit comfortably. */

@media (max-width: 639px) {
  .site-header {
    height: var(--header-height-mobile);
  }

  .site-header__inner {
    height: var(--header-height-mobile);
    position: relative;
  }

  .site-header__logo {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
  }

  .site-nav {
    display: none;
  }

  .site-header__toggle {
    display: block;
    margin-left: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   Hero
   ========================================================================== */

/* --hero-scale drives only the canvas height here — everything else
   (name/tagline/gif/star sizes) keeps its own hand-tuned per-breakpoint
   values below. The 609px base and 1/.85/.72/.6 steps are shared verbatim
   with .case-hero and .about-hero so all five hero sections (home, about,
   3 case studies) render at the exact same height at every breakpoint. */
.hero {
  --hero-scale: 1;
  position: relative;
  background-color: var(--color-cream);
}

.hero__canvas {
  position: relative;
  width: 100%;
  max-width: 1512px;
  height: calc(609px * var(--hero-scale));
  margin-inline: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-left: 11.11%;
  padding-right: 9.66%;
}

.hero__stars {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

/* Each star's own rotation lives in --star-deg (a bare angle, not a
   full transform function) so .hero__star's shared pulse animation
   below can add a small extra --wobble angle on top of it via calc()
   — a slight rotation, not just scale — without needing to know or
   duplicate that star's specific base angle. The three smaller stars
   (--2, --4, --5 — 150/190/130px) wobble noticeably more (9-10deg)
   than the three big ones (--1, --3, --6 — 260-300px, left at their
   original 5-6deg) — more rotation reads clearly on a small shape
   without the big ones swinging enough to look busy. */
.hero__star {
  position: absolute;
  animation: star-pulse-breathe var(--pulse-duration) ease-in-out var(--pulse-delay) infinite;
}

.hero__star--1 {
  top: -40px;
  left: -12%;
  width: 300px;
  --star-deg: -15deg;
  --wobble: 5deg;
  transform: rotate(var(--star-deg));
  --pulse-duration: 5.2s;
  --pulse-delay: 0.6s;
}

.hero__star--2 {
  top: -20px;
  left: 45%;
  width: 150px;
  --star-deg: 8deg;
  --wobble: -9deg;
  transform: rotate(var(--star-deg));
  --pulse-duration: 4.6s;
  /* Moved into the first batch of movers (alongside --3's 0s) — this
     one sits closest to the hero name/tagline, so it's the most
     noticeable star and reads best as an early mover rather than
     waiting its turn. */
  --pulse-delay: 0.3s;
}

.hero__star--3 {
  top: -30px;
  right: -8%;
  width: 300px;
  --star-deg: -18deg;
  --wobble: -6deg;
  transform: rotate(var(--star-deg));
  --pulse-duration: 5.8s;
  --pulse-delay: 0s;
}

.hero__star--4 {
  bottom: 25px;
  left: -4%;
  width: 190px;
  --star-deg: -10deg;
  --wobble: 9deg;
  transform: rotate(var(--star-deg));
  --pulse-duration: 4.9s;
  --pulse-delay: 3.4s;
}

.hero__star--5 {
  bottom: 16px;
  left: 51%;
  width: 130px;
  --star-deg: 6deg;
  --wobble: 10deg;
  transform: rotate(var(--star-deg));
  --pulse-duration: 5.5s;
  --pulse-delay: 1.4s;
}

.hero__star--6 {
  bottom: 38px;
  right: -11%;
  width: 260px;
  --star-deg: 14deg;
  --wobble: -5deg;
  transform: rotate(var(--star-deg));
  --pulse-duration: 4.3s;
  --pulse-delay: 2.9s;
}

/* Subtle — a small scale breathe plus a slight rotation wobble, not a
   flashy pop or an actual spin. Each star's --pulse-duration/
   --pulse-delay above are deliberately uneven and non-sequential (not
   1-2-3-4-5-6 in order), and --wobble alternates direction star to
   star, so the six of them drift in and out of phase — and rotate
   opposite ways from each other — instead of ever visibly moving in
   unison or a predictable rhythm, matching the same "avoid rigid
   patterns" rule the stars' scattered positions already follow. Slower
   than an earlier version (2-3s cycles) — that read as too fast; this
   settles into an actual twinkle. Scale bumped from 1.1 to 1.2 — at
   1.1 the shrink/expand was too easy to miss at this slower pace.
   --pulse-scale defaults to 1.2 but can be overridden to 1 (no size
   change at all, rotation only — used by .bucknell__star). --star-prefix
   defaults to empty (via the var() fallback) but lets an element with
   its own positioning transform — .bucknell__star's desktop tier uses
   translateY(-50%) for vertical centering — carry that through the
   animation instead of losing it, the same trap .case-hero__text's
   comment already covers for a different property. */
@keyframes star-pulse-breathe {
  0%,
  100% {
    transform: var(--star-prefix, ) rotate(var(--star-deg)) scale(1);
  }
  50% {
    transform: var(--star-prefix, ) rotate(calc(var(--star-deg) + var(--wobble))) scale(var(--pulse-scale, 1.2));
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero__star,
  .footer__star,
  .bucknell__star {
    animation: none;
  }
}

.hero__text {
  position: relative;
  z-index: 2;
  min-width: 0;
}

.hero__name {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 150px;
  line-height: normal;
  letter-spacing: -0.05em;
  color: var(--color-scarlet);
  white-space: nowrap;
}

.hero__tagline {
  margin: var(--space-small) 0 0 36px;
  max-width: 550px;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 28px;
  line-height: 35px;
  letter-spacing: -0.05em;
  color: var(--color-blue-black);
}

.hero__subcopy {
  margin: var(--space-medium) 0 0 36px;
  max-width: 590px;
  font-family: var(--font-body);
  font-weight: 300;
  font-size: 20px;
  line-height: 25px;
  letter-spacing: 0;
  color: var(--color-blue-black);
}

.hero__subcopy strong {
  font-weight: 700;
}

.hero__gif {
  position: relative;
  z-index: 2;
  flex-shrink: 1;
  min-width: 0;
  width: 430px;
  height: auto;
}

/* Tablet-A: 1250-1489. Side-by-side, name stays on one line, gif kept large. */
@media (max-width: 1489px) {
  .hero {
    --hero-scale: 0.85;
  }

  .hero__canvas {
    justify-content: center;
    gap: 40px;
    padding-left: 5%;
    padding-right: 5%;
  }

  .hero__text {
    max-width: 700px;
  }

  .hero__name {
    font-size: 130px;
  }

  .hero__tagline {
    margin-left: 0;
    max-width: 470px;
    font-size: 24px;
    line-height: 30px;
  }

  .hero__subcopy {
    margin-left: 0;
    max-width: 560px;
    font-size: 18px;
    line-height: 23px;
  }

  .hero__gif {
    width: 390px;
  }

  .hero__star--1 {
    left: -13%;
    width: 276px;
  }

  .hero__star--2 {
    top: -30px;
    width: 138px;
  }

  .hero__star--3 {
    right: -8%;
    width: 276px;
  }

  .hero__star--4 {
    left: -7%;
    width: 175px;
  }

  .hero__star--5 {
    width: 120px;
  }

  .hero__star--6 {
    right: -9%;
    width: 239px;
  }

}

/* The 1250-1489 range is wide enough at its top end to bring the edge
   stars in closer, but too tight at its bottom end (name+gif crowd the
   middle) to keep that same inset without clipping into the text —
   split the tier so the narrow end falls back to a safer offset. */
@media (max-width: 1400px) {
  .hero__star--1 {
    left: -19%;
  }

  .hero__star--3 {
    right: -19%;
  }

  .hero__star--4 {
    left: -10%;
  }

  .hero__star--6 {
    right: -16%;
  }
}

/* Tablet-B: 1024-1249. Same idea, sized down to keep the name on one line. */
@media (max-width: 1249px) {
  .hero {
    --hero-scale: 0.72;
  }

  .hero__canvas {
    gap: 24px;
  }

  .hero__text {
    max-width: 600px;
  }

  .hero__name {
    font-size: 112px;
  }

  .hero__gif {
    width: 300px;
  }

  .hero__star--1 {
    left: -19%;
    width: 246px;
  }

  .hero__star--2 {
    width: 123px;
  }

  .hero__star--3 {
    right: -19%;
    width: 246px;
  }

  .hero__star--4 {
    left: -14%;
    width: 156px;
  }

  .hero__star--5 {
    width: 107px;
  }

  .hero__star--6 {
    right: -12%;
    width: 213px;
  }

}

/* Same split logic as Tablet-A: this tier's own narrow end needs a
   safer offset than its wide end can afford to give up. */
@media (max-width: 1150px) {
  .hero__star--1 {
    left: -22%;
  }

  .hero__star--3 {
    right: -22%;
  }

  .hero__star--6 {
    right: -18%;
  }
}

/* Phablet-A: 850-1023. Side-by-side, name stays on one line. */
@media (max-width: 1023px) {
  .hero {
    --hero-scale: 0.6;
  }

  .hero__canvas {
    justify-content: center;
    gap: 12px;
    padding-left: 4%;
    padding-right: 4%;
  }

  .hero__text {
    max-width: 480px;
  }

  .hero__name {
    font-size: 84px;
    line-height: 0.9;
  }

  .hero__tagline {
    max-width: 358px;
    font-size: 18px;
    line-height: 23px;
  }

  .hero__subcopy {
    margin-top: var(--space-small);
    max-width: 460px;
    font-size: 16px;
    line-height: 19px;
  }

  .hero__gif {
    width: 300px;
  }

  .hero__star--1 {
    width: 210px;
  }

  .hero__star--2 {
    width: 105px;
  }

  .hero__star--3 {
    width: 210px;
  }

  .hero__star--4 {
    width: 133px;
  }

  .hero__star--5 {
    width: 91px;
  }

  .hero__star--6 {
    width: 182px;
  }

}

/* Stack-Wide: 640-849. Below Phablet-A's cutoff there isn't room for a
   nowrap name + correct text breaks + a decent gif all side-by-side, so
   this range stacks instead (gif gets its own row, no competition for width). */
@media (max-width: 849px) {
  .hero__canvas {
    height: auto;
    flex-direction: column;
    gap: var(--space-small);
    padding: var(--space-medium) var(--container-padding);
    text-align: center;
    --gif-w: min(280px, 45vw);
  }

  .hero__text {
    max-width: 100%;
  }

  /* Same clamp(30px, 14.5vw, 76px) formula as .case-hero__title/
     .about-hero__title's mobile sizing (kept in sync across all three so
     every hero's display name renders at the exact same size on any given
     phone width) — originally tuned against "Schwenksville Library", the
     longest of the five, so shorter names like this one have room to
     spare and stay comfortably on one line. */
  .hero__name {
    font-size: clamp(30px, 14.5vw, 76px);
    white-space: nowrap;
    line-height: 0.9;
  }

  .hero__tagline {
    margin-top: var(--space-tight);
    margin-inline: auto;
    max-width: 395px;
    font-size: 16px;
    line-height: 20px;
  }

  .hero__subcopy {
    margin-top: var(--space-tight);
    margin-inline: auto;
    max-width: 495px;
    font-size: 14px;
    line-height: 18px;
  }

  .hero__gif {
    width: var(--gif-w);
    order: -1;
  }

  /* Exactly one large star flanking each side of the gif. Positioned off
     the gif's own width via --gif-w so they always clear it with a fixed
     gap, regardless of viewport width within this tier. */
  .hero__star--1 {
    top: 75px;
    left: auto;
    right: calc(50% + (var(--gif-w) / 2) + 28px);
    width: 190px;
  }

  .hero__star--2,
  .hero__star--3,
  .hero__star--4,
  .hero__star--5 {
    display: none;
  }

  .hero__star--6 {
    top: 75px;
    left: calc(50% + (var(--gif-w) / 2) + 28px);
    right: auto;
    width: 190px;
  }
}

/* Stack-Narrow: true mobile, sized so everything but the strip fits without scrolling. */
@media (max-width: 639px) {
  .hero__canvas {
    padding: var(--space-small) var(--container-padding) var(--space-medium);
    --gif-w: min(220px, 58vw);
  }

  .hero__name {
    line-height: 0.85;
  }

  .hero__tagline {
    max-width: 320px;
  }

  .hero__subcopy {
    max-width: 410px;
  }

  .hero__gif {
    width: var(--gif-w);
  }

  .hero__star--1 {
    top: 65px;
    left: auto;
    right: calc(50% + (var(--gif-w) / 2) + 20px);
    width: 125px;
  }

  .hero__star--6 {
    top: 65px;
    left: calc(50% + (var(--gif-w) / 2) + 20px);
    right: auto;
    width: 125px;
  }
}

/* ==========================================================================
   Hero strip
   ========================================================================== */

.hero-strip {
  background-color: var(--color-scarlet);
  min-height: 97px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-small) var(--container-padding);
  text-align: center;
}

.hero-strip__text {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 22px;
  line-height: 1.3;
  color: var(--color-white);
  white-space: nowrap;
}

/* inline-block (not the default inline) so transform applies cleanly to
   the whole link — arrow and text move together as one unit on hover.
   transform-origin:left so the grow expands rightward only, instead of
   also growing left into the "13" text right before it. */
.hero-strip__link {
  display: inline-block;
  font-weight: 300;
  color: var(--color-white);
  transform-origin: left center;
  transition: transform var(--transition-fast);
}

.hero-strip__arrow,
.hero-strip__link-text {
  display: inline-block;
}

/* transform (not font-size) so the hover grow doesn't reflow the nowrap
   line it sits inside or nudge anything around it. translateX nudges it
   further right, away from the preceding text. */
.hero-strip__link:hover,
.hero-strip__link:focus-visible {
  transform: scale(1.05) translateX(4px);
}

/* white-space:nowrap keeps this on one line whenever the container is wide
   enough (verified with margin to spare at every tier down through
   Phablet-A). Below that, Stack-Wide/Stack-Narrow switch to a max-width
   tuned so the only allowed wrap point is right before "running". */
@media (max-width: 1489px) {
  .hero-strip__text {
    font-size: 20px;
  }
}

@media (max-width: 1249px) {
  .hero-strip__text {
    font-size: 19px;
  }
}

@media (max-width: 1023px) {
  .hero-strip {
    min-height: 76px;
  }

  .hero-strip__text {
    font-size: 17px;
  }
}

@media (max-width: 849px) {
  .hero-strip__text {
    white-space: normal;
    max-width: 400px;
    font-size: 16px;
  }
}

@media (max-width: 639px) {
  .hero-strip {
    min-height: 70px;
  }

  .hero-strip__text {
    max-width: 330px;
    font-size: 14px;
  }
}

/* ==========================================================================
   Case studies
   ========================================================================== */

.case-studies {
  --case-scale: 1;
  --case-heading-scale: 1;
  --case-image-scale: 1;
}

/* The #work anchor (used by "My Work") lives directly on the Davis Grown
   <h2>, not the article or image around it, so the scroll lands right at
   the title with only a small fixed gap instead of however much padding
   the card happens to carry at a given breakpoint — less dead whitespace
   above the text. This also sidesteps how title/media reorder at each
   breakpoint (mobile stacks title above media) and keeps the hero-strip's
   scarlet banner from ever coming into view: the card's own top padding
   is white/cream, so scrolling past it only crops empty space, never the
   red strip above it. Desktop's row is align-items: center, so the
   (taller) mockup image pokes a few px above the title's own top edge —
   --space-small rather than --space-tight gives that overhang room to
   clear the sticky header instead of tucking just under it. */
#work {
  scroll-margin-top: calc(var(--header-height-desktop) + var(--space-small));
}

.case-study--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-study--home-at-last {
  --accent: var(--color-home-at-last);
  background-color: var(--color-cream);
}

.case-study--sville-library {
  --accent: var(--color-sville-library);
}

/* Grid with a single "media" + "content" row for desktop/tablet, so the
   text block (title/tags/desc/button) is one grouped column sized by its
   own content. At the stacked breakpoint, .case-study__content switches to
   display:contents so its children become independently placeable grid
   items and the image can be re-slotted between tags and desc. */
.case-study__inner {
  display: grid;
  grid-template-columns: calc(520px * var(--case-image-scale)) max(calc(460px * var(--case-scale)), 350px);
  grid-template-areas: "media content";
  align-items: center;
  justify-content: center;
  column-gap: calc(40px * var(--case-scale));
  padding-block: calc(90px * var(--case-scale));
}

/* Home At Last zigzags: text on the left, image on the right, so all three
   rows in a row don't repeat the exact same layout skeleton. */
.case-study--home-at-last .case-study__inner {
  grid-template-columns: max(calc(460px * var(--case-scale)), 350px) calc(520px * var(--case-image-scale));
  grid-template-areas: "content media";
}

/* Nudged further right than the shared 8px, true desktop width only. */
@media (min-width: 1490px) {
  .case-study--home-at-last .case-study__content {
    margin-left: 60px;
  }
}

/* A bit more breathing room at the very top and bottom of the whole
   case-studies block, beyond the padding shared by all three rows. */
/* Matches .case-study--sville-library's own extra padding-bottom below, so
   the whitespace above the first case study (under the red banner) and
   below the last one (before Brain Break) reads as equal. */
.case-study--davis-grown .case-study__inner {
  padding-top: calc(110px * var(--case-scale));
}

/* Extra beyond Davis Grown's matching top padding, so the Brain Break
   section below (a tonal shift into pink/Sugar) reads as clearly separate
   rather than butting straight up against the last case study. transform
   nudges this row (media+content, centered as a group like the other two)
   slightly left of dead-center on desktop — its higher selector
   specificity overrides the shared tablet-only translateX below at every
   tier, so this holds all the way from full desktop down through tablet;
   reset to none in the stacked breakpoint the same way the shared rule
   is. */
.case-study--sville-library .case-study__inner {
  padding-bottom: calc(110px * var(--case-scale));
  transform: translateX(calc(-30px * var(--case-scale)));
}

.case-study__media {
  grid-area: media;
  display: flex;
  justify-content: center;
}

.case-study__image {
  height: auto;
}

.case-study--davis-grown .case-study__image,
.case-study--home-at-last .case-study__image {
  width: calc(520px * var(--case-image-scale));
}

.case-study--sville-library .case-study__image {
  width: calc(390px * var(--case-image-scale));
  margin-left: calc(20px * var(--case-image-scale));
}

.case-study__content {
  grid-area: content;
  margin-left: calc(8px * var(--case-scale));
}

.case-study__title {
  margin: 0;
  white-space: nowrap;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: calc(75px * var(--case-heading-scale));
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-study__tags {
  margin: var(--space-tight) 0 0;
  width: max-content;
  white-space: nowrap;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 18px;
  line-height: 1.4;
  color: var(--accent);
}

/* 390px is the shared width at which all three descriptions below wrap to
   exactly three lines (verified against each project's actual copy), so the
   column scales with everything else rather than using the sitewide
   body-text breakpoints. Narrower than the 460px content column itself so
   there's room to spare on every card, not just the tightest one. Floored
   at 330px (the narrowest shared width that still holds 3 lines once the
   16px font floor kicks in) so the column doesn't keep shrinking past the
   point the text itself stops shrinking, which is what pushed it to a
   4th line right before the row stacks. */
.case-study__desc {
  margin: var(--space-tight) 0 0;
  max-width: max(calc(390px * var(--case-scale)), 330px);
  font-family: var(--font-body);
  font-weight: 300;
  font-size: clamp(16px, calc(18px * var(--case-scale)), 18px);
  line-height: 1.4;
  color: var(--color-blue-black);
}

.case-study__button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 27px;
  padding: 12px 22px;
  border-radius: 8px;
  background-color: var(--accent);
  box-shadow: 0 2px 6px rgba(30, 30, 36, 0.15);
  transition: transform var(--transition-fast);
}

.case-study__button-text {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 17px;
  color: var(--color-white);
}

.case-study__button-arrow {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 17px;
  color: var(--color-white);
}

/* transform (not font-size) so hovering one card's button doesn't reflow
   anything around it. */
.case-study__button:hover {
  transform: scale(1.05);
}

.case-study__button:active {
  transform: scale(0.97);
}

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

@media (max-width: 1489px) {
  .case-studies {
    --case-scale: 0.85;
    --case-heading-scale: 0.9;
    --case-image-scale: 0.92;
  }

  /* Nudged left of dead-center here (tablet down through the pre-stack
     range), freeing up room on the right for the longer titles/tags
     before they'd otherwise wrap or clip. Not needed at full desktop
     width, where there's plenty of margin either way. */
  .case-study__inner {
    transform: translateX(calc(-45px * var(--case-scale)));
  }

  /* Home At Last is flipped (text left, image right), so its tags line
     would run straight into the image instead of into empty space the way
     the other two cards' tags can. Rather than size it differently from
     the other two cards, all three step down together here. */
  .case-study__tags {
    font-size: 17px;
  }
}

@media (max-width: 1249px) {
  /* The leftward nudge from the tier above is only needed there; below
     this, the fixed-width text column already has plenty of margin on its
     own, and keeping the shift would start crowding the left edge instead. */
  .case-study__inner {
    transform: none;
  }

  .case-studies {
    --case-scale: 0.72;
    --case-heading-scale: 0.8;
    --case-image-scale: 0.85;
  }

  .case-study__tags {
    font-size: 14px;
  }
}

/* Images shrink more slowly than the gap/text here (--case-image-scale vs
   --case-scale) so they don't read as tiny right before the row stacks. */
@media (max-width: 1023px) {
  .case-studies {
    --case-scale: 0.6;
    --case-heading-scale: 0.72;
    --case-image-scale: 0.78;
  }
}

/* Side-by-side holds down through tablet, in step with the hero, which
   also stacks at this breakpoint. Below it, a 600px-wide device mockup
   can't sit beside readable text without crushing one or the other. */
@media (max-width: 849px) {
  .case-studies {
    --case-scale: 0.52;
    --case-heading-scale: 0.64;
  }

  .case-study__inner,
  .case-study--home-at-last .case-study__inner,
  .case-study--sville-library .case-study__inner {
    grid-template-columns: 1fr;
    grid-template-areas:
      "title"
      "tags"
      "media"
      "desc"
      "button";
    justify-items: center;
    text-align: center;
    row-gap: 0;
    transform: none;
  }

  /* Un-groups the text block so title/tags/desc/button become direct grid
     items again, sitting alongside media in the reordered single column. */
  .case-study__content {
    display: contents;
  }

  .case-study__title {
    grid-area: title;
  }

  .case-study__tags {
    grid-area: tags;
  }

  .case-study__media {
    grid-area: media;
    width: 100%;
  }

  /* The three source images each bake in a different amount of solid-color
     padding around the actual device mockup (measured directly from the
     files, by pixel color, not just transparency: Davis Grown ~4.7% top /
     3.3% bottom on white, Home At Last ~9.7% top / 8.2% bottom on cream —
     its own background color, easy to miss by eye — Schwenksville ~3.6%
     top / 1.9% bottom on white). These margins are calibrated per card so
     the VISIBLE gap between text and the actual graphic reads the same
     across all three, not just the CSS gap to each image's bounding box. */
  .case-study--davis-grown .case-study__media {
    margin-top: 14px;
  }

  .case-study--home-at-last .case-study__media {
    margin-top: 3px;
  }

  .case-study--sville-library .case-study__media {
    margin-top: 16px;
  }

  .case-study--davis-grown .case-study__image,
  .case-study--home-at-last .case-study__image {
    width: 100%;
    max-width: 380px;
  }

  .case-study--sville-library .case-study__image {
    width: 100%;
    max-width: 260px;
    margin-left: 0;
  }

  .case-study__desc {
    grid-area: desc;
    max-width: 500px;
  }

  .case-study--davis-grown .case-study__desc {
    margin-top: 13px;
  }

  .case-study--home-at-last .case-study__desc {
    margin-top: 2px;
  }

  .case-study--sville-library .case-study__desc {
    margin-top: 16px;
  }

  .case-study__button {
    grid-area: button;
    margin-top: var(--space-small);
  }
}

@media (max-width: 639px) {
  .case-studies {
    --case-scale: 0.44;
    --case-heading-scale: 0.62;
  }

  #work {
    scroll-margin-top: calc(var(--header-height-mobile) + var(--space-tight));
  }

  .case-study__inner {
    padding-block: var(--space-medium);
  }

  /* A little extra breathing room off the screen edges on true mobile,
     on top of the sitewide container padding. */
  .case-study__inner.container {
    padding-inline: 28px;
  }

  /* The longest tags line (Davis Grown) only clears a true-mobile viewport
     edge-to-edge, with none of the section's own side padding to spare, so
     it bleeds past that padding and tightens slightly here rather than
     wrapping. */
  .case-study__tags {
    font-size: 14px;
    margin-inline: -28px;
    letter-spacing: -0.02em;
  }

  .case-study__button {
    padding: 9px 16px;
  }

  .case-study__button-text,
  .case-study__button-arrow {
    font-size: 14px;
  }
}

/* Below this, even the bleed + letter-spacing tightening above can't fit
   Davis Grown's tags line without clipping, so on genuinely old/small
   phones it wraps instead. */
@media (max-width: 340px) {
  .case-study__tags {
    white-space: normal;
    margin-inline: 0;
    letter-spacing: normal;
    max-width: 260px;
  }
}

/* ==========================================================================
   Case study page hero
   ========================================================================== */

/* Shared hero pattern for the three case study pages: mockup image on one
   side, a two-line Reenie Beanie project name in the accent color on the
   other, with one oversized accent-colored star tucked under the title.
   Each page only sets --accent and --hero-image-width via a modifier class
   below — --hero-scale drives every dimension (container height, title
   size, star size, image width) together as one proportional unit, the
   same pattern .case-studies/.footer/.brain-break already use, so it
   scales across breakpoints exactly like the homepage hero does. */
.case-hero {
  --hero-scale: 1;
  position: relative;
  background-color: var(--color-cream);
  min-height: calc(609px * var(--hero-scale));
  display: flex;
  align-items: center;
}

.case-hero--davis-grown {
  --accent: var(--color-davis-grown);
  --hero-image-width: 810px;
}

.case-hero--home-at-last {
  --accent: var(--color-home-at-last);
  --hero-image-width: 816px;
}

.case-hero--sville-library {
  --accent: var(--color-sville-library);
  --hero-image-width: 530px;
}

/* Image + text are treated as one group and centered together in the
   section (justify-content:center) with a fixed gap between them, so
   the group's overall width can differ per project without leaving a
   lopsided empty gap on one side — a fixed gap keeps image and text
   close together regardless of image width. */
.case-hero__inner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(72px * var(--hero-scale));
  width: 100%;
  max-width: 1512px;
  margin-inline: auto;
  padding-inline: var(--container-padding);
}

/* Davis Grown only: image+text shifted left together as one group, via
   extra right padding that biases the centered flex content leftward
   (NOT transform — transform on this element would create a new
   containing block for the absolutely-positioned star nested inside
   .case-hero__text, dragging it along too; the star needs to stay
   anchored to .case-hero and share the same fixed position as the
   other two projects). */
.case-hero--davis-grown .case-hero__inner {
  padding-right: calc(var(--container-padding) + 140px * var(--hero-scale));
}

/* Home At Last only: smaller whole-group left shift than Davis Grown
   above — moved left, but not as far. */
.case-hero--home-at-last .case-hero__inner {
  padding-right: calc(var(--container-padding) + 90px * var(--hero-scale));
}

.case-hero__media {
  flex-shrink: 0;
}

/* Davis Grown only: image nudged further left than the group-level
   shift above already puts it (that one moves image+text together;
   this moves the image alone a bit further still — text stays put at
   its own margin-left below, unlike this round's change). */
.case-hero--davis-grown .case-hero__media {
  margin-left: calc(-70px * var(--hero-scale));
}

.case-hero__image {
  display: block;
  width: calc(var(--hero-image-width) * var(--hero-scale));
  max-width: 100%;
  height: auto;
}

/* Shared across all three projects so every title sits at the same
   vertical position — nudges the title up from dead-center against
   the (taller) image. --reveal-offset (0 by default, set by the
   html.js reveal rules near .js-reveal above) layers the scroll-reveal
   fade's "slide up" motion into this same margin-top instead of a
   separate transform: .case-hero__star, nested inside this element, is
   position:absolute at this breakpoint (see its own rule + the
   .case-hero--davis-grown comment above about exactly this trap) — a
   transform here would give it a new containing block and send it to
   the wrong spot entirely. Margin doesn't have that side effect. */
.case-hero__text {
  flex-shrink: 0;
  margin-top: calc(-155px * var(--hero-scale) + var(--reveal-offset, 0px));
}

/* Davis Grown only: extra left nudge on top of the whole-group shift
   from .case-hero__inner's padding-right above (that one shifts
   image+text together; this one nudges the text a little further
   still, closer to the image). Vertical position stays the shared
   -155px from .case-hero__text above — not overridden here. */
.case-hero--davis-grown .case-hero__text {
  margin-left: calc(-58px * var(--hero-scale));
}

.case-hero__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: calc(150px * var(--hero-scale));
  line-height: 0.8;
  letter-spacing: -0.05em;
  color: var(--accent);
}

/* Anchored to the .case-hero section itself (not the text or image) —
   .case-hero has a fixed height/width on every case study page, so a
   fixed pixel offset from its corner puts the star in the exact same
   screen position regardless of image width or how long the title
   words are. A *percentage* position here (e.g. top:100%) reliably
   clipped the rotated star's own points in testing — a paint bug
   specific to that combination — hence pixel values throughout.
   right:171px (not 148px — the rotation expands the star's rendered
   bounding box by ~23px past its unrotated position) lines the
   star's actual rendered right edge up with the right edge of
   "Contact" in the header nav — i.e. the star sits under the "t" of
   Contact, on every page.
   Inline SVG (not an <img src="...svg">) so fill can pick up --accent
   via currentColor — no separate colored asset needed per project.
   max-width:none opts out of the sitewide img,svg{max-width:100%} reset
   since this intentionally overflows past the title's own width. */
.case-hero__star {
  position: absolute;
  top: calc(300px * var(--hero-scale));
  right: calc(171px * var(--hero-scale));
  width: calc(241px * var(--hero-scale));
  max-width: none;
  height: auto;
  color: var(--accent);
}

/* Opacity already cascades from .case-hero__text's own fade (this star
   is a DOM child of it, confirmed by directly forcing the parent's
   opacity to 0 and watching the star vanish too), so it was already
   correctly hiding/revealing in sync — it just never had any motion of
   its own, staying perfectly still the whole time while the title slid
   up beside it. This scale-and-rotate-in is the star's own, separate
   from the parent's margin-based slide (which the star, being
   position:absolute relative to .case-hero rather than .case-hero__text,
   was never affected by regardless) — a transform directly on the star
   itself, unlike a transform on an ancestor, doesn't touch its own
   containing block, so this is safe. .case-hero__text.is-visible is
   what's already toggled by js/reveal.js's existing timer — no separate
   JS needed. Starts tilted 7deg short of its resting 12.88deg angle
   (i.e. at 5.88deg) and rotates the rest of the way in alongside a
   subtle scale-up — dialed back from an earlier, more noticeable
   18deg/0.6-scale version per feedback that it read as too much
   motion; barely-there now, just enough to feel like a gentle
   settle-into-place twist rather than a static pop. */
html.js .case-hero__star {
  opacity: 0;
  transform: rotate(5.88deg) scale(0.85);
  transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}

.case-hero__text.is-visible .case-hero__star {
  opacity: 1;
  transform: rotate(12.88deg) scale(1);
}

/* Nudged further from the right edge than the shared 171px, per spec —
   this page only, the other two projects keep the shared value. */
.case-hero--sville-library .case-hero__star {
  right: calc(201px * var(--hero-scale));
}

@media (max-width: 1489px) {
  .case-hero {
    --hero-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-hero {
    --hero-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-hero {
    --hero-scale: 0.6;
  }
}

/* Below this, image + text side-by-side gets cramped at any scale, so the
   hero stacks — image on top, title+star row below it — the same
   breakpoint the homepage hero itself switches to a stacked column at.
   Title/star go left-aligned and switch to a plain side-by-side row
   (star out of absolute positioning, back in normal flow next to the
   title) instead of the desktop stacked-corner treatment, at a size
   independent of --hero-scale (which still drives the image, left
   alone here since it already reads well at that scale). Title+star
   are treated as one group and centered as a unit, same as image+text
   on desktop — not left-aligned against the padding edge. */
@media (max-width: 849px) {
  .case-hero {
    --hero-scale: 0.52;
    min-height: auto;
    padding-block: calc(40px * var(--hero-scale));
  }

  .case-hero__inner {
    flex-direction: column;
    align-items: center;
    gap: calc(28px * var(--hero-scale));
  }

  .case-hero--davis-grown .case-hero__inner {
    padding-right: var(--container-padding);
  }

  .case-hero--home-at-last .case-hero__inner {
    padding-right: var(--container-padding);
  }

  /* Fixed-height box (sized to Davis Grown's own image, the reference)
     with the image contained inside it — so every project's title+star
     row starts at the exact same Y position on mobile regardless of
     that project's own image aspect ratio, instead of drifting per page
     the way normal flow (image height + gap) would. */
  .case-hero__media {
    height: calc(456px * var(--hero-scale));
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .case-hero--davis-grown .case-hero__media {
    margin-left: 0;
  }

  .case-hero__image {
    width: auto;
    height: auto;
    max-width: calc(var(--hero-image-width) * var(--hero-scale));
    max-height: 100%;
  }

  /* Title+star sit side by side as a centered row (not stacked) — both
     back in plain flow, the desktop two-line/absolute-star treatment
     doesn't fit a narrow mobile viewport as one readable unit, but the
     star still hugs the title's right side the same way it hugs
     "Reflection" further down the page. */
  .case-hero__text {
    margin-top: var(--reveal-offset, 0px);
    margin-left: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
  }

  .case-hero--davis-grown .case-hero__text {
    margin-top: var(--reveal-offset, 0px);
    margin-left: 0;
  }

  /* The desktop <br> (splitting the name across two lines) is hidden
     here so the title reads on one line on mobile — the space right
     after it in the markup (invisible on desktop, where whitespace at
     the start of a wrapped line collapses) becomes the word-separating
     space once the <br> itself is gone. font-size is fluid (clamp, not
     a fixed px per breakpoint) so "Schwenksville Library" — the
     longest of the three — still fits one line down to small phones. */
  .case-hero__title br {
    display: none;
  }

  /* Sized off viewport width alone (not per-project), so "Schwenksville
     Library" — the longest of the three — still fits one line beside the
     star with room to spare, and all three projects render at the exact
     same px size at any given screen width. Coefficient is smaller than
     it'd need to be for the title alone (14.5vw) specifically to leave
     room for .case-hero__star sitting next to it — sized/tested against
     "Schwenksville Library" as the worst case. */
  .case-hero__title {
    white-space: nowrap;
    font-size: clamp(26px, 12vw, 64px);
  }

  /* 60px, not .case-reflection__star's own 90px — that size was tuned
     without a title next to it; here it has to share the row with
     "Schwenksville Library" (the longest of the three titles) without
     overflowing at 320px, which 90px didn't leave room for. Sits beside
     the title instead of disappearing, keeping the star motif present at
     every size. */
  .case-hero__star {
    position: static;
    width: 60px;
    height: auto;
    flex-shrink: 0;
  }
}

@media (max-width: 639px) {
  .case-hero {
    --hero-scale: 0.44;
  }
}

/* ==========================================================================
   Case study summary (Davis Grown, etc.)
   ========================================================================== */

/* 80px — standardized across every major content section on this page
   (Summary, Challenge, Understanding, Motivations, Exploration,
   Structure, Screens) so the padding rhythm reads consistently down the
   page instead of each section carrying its own one-off value. */
.case-summary {
  --summary-scale: 1;
  background-color: var(--color-white);
  padding-block: calc(80px * var(--summary-scale));
}

.case-summary--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-summary--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-summary--sville-library {
  --accent: var(--color-sville-library);
}

/* justify-content:center treats the card+details pair as one group and
   centers it in the container, same convention as .bucknell__row/
   .stitchery__inner — the card and its bullets stay paired as a single
   centered unit rather than anchoring to the container's left edge like
   .case-challenge/.stitchery's own text does. Fixed auto/calc columns
   (not fluid minmax) — tried the fluid approach, but once the details
   column is squeezed narrow enough to wrap, it grows taller than the
   card next to it, which looks broken. Stacking earlier (see the 849px
   breakpoint below) avoids that instead of letting it get that narrow. */
.case-summary__inner {
  display: grid;
  grid-template-columns: auto auto;
  grid-template-areas: "card details";
  align-items: start;
  justify-content: center;
  column-gap: calc(120px * var(--summary-scale));
}

.case-summary__card {
  grid-area: card;
  width: calc(588px * var(--summary-scale));
  min-height: calc(433px * var(--summary-scale));
  display: flex;
  flex-direction: column;
  justify-content: center;
  background-color: var(--accent);
  border-radius: 8px;
  padding: calc(48px * var(--summary-scale));
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

.case-summary__card-title {
  margin: 0 0 calc(16px * var(--summary-scale));
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--summary-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--color-white);
}

.case-summary__card-list {
  margin: 0;
  padding-left: calc(22px * var(--summary-scale));
}

.case-summary__card-list li {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--summary-scale)), 14px);
  line-height: calc(29px * var(--summary-scale));
  color: var(--color-white);
}

.case-summary__card-list li::marker {
  font-weight: 700;
}

.case-summary__card-list li + li {
  margin-top: calc(16px * var(--summary-scale));
}

.case-summary__card-list strong {
  font-weight: 700;
}

.case-summary__details {
  grid-area: details;
}

.case-summary__detail-group + .case-summary__detail-group {
  margin-top: calc(40px * var(--summary-scale));
}

.case-summary__detail-title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(22px * var(--summary-scale)), 16px);
  color: var(--accent);
}

.case-summary__detail-list {
  margin-top: calc(8px * var(--summary-scale));
  padding-left: calc(24px * var(--summary-scale));
  list-style: none;
}

/* white-space:nowrap + relative/absolute star bullet — same treatment as
   .stitchery__stats-list, so the details column sizes to its longest line
   instead of wrapping mid-item. line-height kept tight (matching
   .stitchery__stats-list's own 1.6) so the three stacked groups land close
   to the card's own height instead of running noticeably taller. 24px
   (not 22px) matches the ~2-space star-to-text gap standardized across
   every star-bullet list on the site (.stitchery__stats-list,
   .bucknell__card-list, this one). */
.case-summary__detail-list li {
  position: relative;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--summary-scale)), 14px);
  line-height: 1.6;
  white-space: nowrap;
  color: var(--color-blue-black);
}

.case-summary__detail-list li::before {
  content: "★";
  position: absolute;
  left: calc(-24px * var(--summary-scale));
  color: var(--accent);
}

@media (max-width: 1489px) {
  .case-summary {
    --summary-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-summary {
    --summary-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-summary {
    --summary-scale: 0.6;
  }
}

/* Stacks in step with the rest of the site's sections: card above details,
   both centered as a unit. Detail text drops the desktop centering so the
   star bullets still read left-aligned. Padding-block tightened from the
   140px desktop value — that much air above the card reads as an
   unintentional gap once the hero and card are stacked directly under the
   sticky nav. */
@media (max-width: 849px) {
  .case-summary {
    padding-block: calc(64px * var(--summary-scale));
  }

  .case-summary__inner {
    grid-template-columns: 1fr;
    grid-template-areas: "card" "details";
    justify-items: center;
    row-gap: calc(48px * var(--summary-scale));
    text-align: center;
  }

  .case-summary__card-title,
  .case-summary__card-list,
  .case-summary__detail-title,
  .case-summary__detail-list {
    text-align: left;
  }

  /* Project Details + Focus sit on one row, Tools drops directly under
     Project Details. Focus spans both rows (grid-area repeated in column 2
     of both rows) so its own height — stretched by "Stakeholder
     Collaboration" wrapping to 2 lines — doesn't push Tools any further
     from Project Details than the row-gap itself; without the span, Tools
     would sit below whichever column is tallest, leaving an uneven gap
     under the shorter Project Details column. */
  .case-summary__details {
    display: grid;
    grid-template-columns: auto auto;
    grid-template-areas: "details focus" "tools focus";
    justify-content: start;
    width: 100%;
    max-width: calc(588px * var(--summary-scale));
    column-gap: calc(56px * var(--summary-scale));
    row-gap: calc(32px * var(--summary-scale));
  }

  .case-summary__detail-group:nth-child(1) {
    grid-area: details;
  }

  .case-summary__detail-group:nth-child(2) {
    grid-area: focus;
  }

  .case-summary__detail-group:nth-child(3) {
    grid-area: tools;
  }

  .case-summary__detail-group + .case-summary__detail-group {
    margin-top: 0;
  }

  /* Tighter than the desktop 8px — each heading sits closer to its own
     bullets here, so the row-gap above (Project Details' bullets to the
     Tools heading) reads as the intentional break, not this smaller one. */
  .case-summary__detail-list {
    margin-top: calc(4px * var(--summary-scale));
  }

  .case-summary__detail-list li {
    white-space: normal;
  }
}

/* Fluid card width with a cap, same pattern as .bucknell__card's own
   639px step — keeps the fixed-px card from overflowing the smallest
   phone widths. */
@media (max-width: 639px) {
  .case-summary__card {
    width: 100%;
    max-width: calc(588px * var(--summary-scale));
  }
}

/* ==========================================================================
   Case study challenge (Davis Grown, etc.)
   ========================================================================== */

/* padding-block (not min-height + flex-centered content, which is what
   .case-summary/.case-hero use) — this section's content is text with its
   own font-size floors, so it doesn't shrink in lockstep with
   --challenge-scale the way a scaled card or image does. min-height's
   centering slack collapses toward zero right where the text is tall
   enough (wrapped more from hitting those floors) to nearly fill a
   shrunk min-height — measured: only 8px of buffer left in the
   1023px-850px range, sandwiched between 43px+ above and 33px below.
   Explicit padding sidesteps that entirely: the buffer is always exactly
   this value, regardless of how tall the text wraps. 68px (down from an
   earlier 98px, which matched this section's old centering slack at
   scale 1) — with only a single paragraph + small logo as content, 98px
   top and bottom was 43% of the section's total height, noticeably airier
   than the ~27-39% padding-to-content ratio the other case-study sections
   run. Applies at every breakpoint including mobile, same as before. */
.case-challenge {
  --challenge-scale: 1;
  background-color: var(--color-cream);
  padding-block: calc(80px * var(--challenge-scale));
}

.case-challenge--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-challenge--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-challenge--sville-library {
  --accent: var(--color-sville-library);
}

/* .case-challenge__inner is the plain .container wrapper (matches every
   other section's outer/inner split). This nested __row is what actually
   centers at 976px*scale — nesting it inside the already-padded container
   is what makes it land in the exact same reference frame as
   .case-understanding__text/.case-findings__text's own 976px boxes.
   (Putting this centering directly on __inner instead — sharing the
   .container class — double-applies container's own padding/centering on
   top of it and throws the math off; learned that the hard way.)
   Fixed 560px content column + max-width:976px*scale centers this same
   box regardless of the media column's actual width — content's left
   edge always lands at the standard inset, instead of the old
   justify-content:center approach where a narrower logo (see the
   per-variant image widths below) shrank the whole centered group and
   dragged the text right. The media column is 1fr so it always fills out
   to the same 976px total regardless of how wide the image inside it
   actually is; justify-self:end on .case-challenge__media keeps the
   image's right edge pinned there too. */
.case-challenge__row {
  display: grid;
  grid-template-columns: calc(560px * var(--challenge-scale)) 1fr;
  grid-template-areas: "content media";
  align-items: center;
  column-gap: calc(96px * var(--challenge-scale));
  max-width: calc(976px * var(--challenge-scale));
  margin-inline: auto;
  width: 100%;
}

.case-challenge__content {
  grid-area: content;
}

.case-challenge__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--challenge-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-challenge__body {
  margin-top: calc(8px * var(--challenge-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--challenge-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-challenge__body p + p {
  margin-top: calc(32px * var(--challenge-scale));
}

.case-challenge__body strong {
  font-weight: 700;
}

.case-challenge__media {
  grid-area: media;
  flex-shrink: 0;
  justify-self: end;
}

.case-challenge__image {
  width: calc(320px * var(--challenge-scale));
  max-width: 100%;
  height: auto;
  display: block;
}

/* Slightly smaller than the shared 320px default so the logo doesn't
   dominate the row next to the text column. */
.case-challenge--davis-grown .case-challenge__image {
  width: calc(300px * var(--challenge-scale));
}

/* Square logo (855x855) reads much bigger than Davis Grown's landscape
   logo at the same width, which also pushed this section's cream block
   taller than Davis Grown's. Scaled down to 246px (matching Davis Grown's
   300px-wide/732:891-aspect logo height) so both the visual weight and
   the section height line up. */
.case-challenge--home-at-last .case-challenge__image {
  width: calc(246px * var(--challenge-scale));
}

/* Screen-mockup tier (12px radius + shadow) — this is an actual website
   screenshot, not a logo like the other two projects' media here, so it
   gets the same treatment as every other screen mockup on the site.
   260px (not the shared 320px default) — smaller across the board, same
   calc(Npx * scale) mechanism as every other tier below, so it keeps
   scaling smoothly with --challenge-scale exactly like it did before,
   just at a smaller base size. */
.case-challenge--sville-library .case-challenge__image {
  width: calc(260px * var(--challenge-scale));
  border-radius: 12px;
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

@media (max-width: 1489px) {
  .case-challenge {
    --challenge-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-challenge {
    --challenge-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-challenge {
    --challenge-scale: 0.6;
  }
}

/* Stacks in step with the rest of the site's sections: content above
   media, both centered as a unit, text dropping back to left-aligned
   (same treatment as .stitchery__title/.stitchery__body at this
   breakpoint). padding-block isn't overridden here — the base rule's
   calc(98px * scale) already applies and doesn't need a separate stacked
   value the way the old min-height approach did. */
@media (max-width: 849px) {
  /* Bottom only — the logo is the last element once stacked (content
     above, media below), so the section's own bottom padding reads as
     extra gap directly under it. Top keeps the shared padding-block
     value from the base rule. */
  .case-challenge {
    padding-bottom: calc(48px * var(--challenge-scale));
  }

  .case-challenge__row {
    grid-template-columns: 1fr;
    grid-template-areas: "content" "media";
    row-gap: calc(20px * var(--challenge-scale));
    justify-items: center;
    max-width: 100%;
    text-align: center;
  }

  .case-challenge__media {
    justify-self: center;
  }

  .case-challenge__title,
  .case-challenge__body {
    text-align: left;
  }

  .case-challenge__content {
    max-width: 100%;
  }

  /* Fluid, not a flat clamp tied to vw alone (that produced a jump: the
     old clamp(200px, 45vw, 300px) evaluates to 300px right at 849px
     viewport, while the desktop formula just above this breakpoint gives
     calc(320px * 0.6) = 192px — logo visibly grew entering the stack
     instead of continuing to shrink). 192px is exactly that pre-stack
     value, so this picks up with zero jump and tapers down to 150px by
     ~320px viewport — same two-point reference width (320/849) and
     formula shape as .bucknell__image's own stacked clamp. */
  .case-challenge__image {
    width: clamp(150px, calc(125px + 7.9vw), 192px);
  }

  /* Overrides the stack above — stays side by side through 849-639px
     instead of the shared breakpoint, per the site's "don't stack
     earlier than the layout actually needs" convention: content (560px
     scaled) + gap + this image (max 156px scaled) still fit comfortably
     together down to 639px, so there's no need to give up the row this
     early. Same values as the base (unstacked) rule. */
  .case-challenge--sville-library .case-challenge__row {
    grid-template-columns: calc(560px * var(--challenge-scale)) 1fr;
    grid-template-areas: "content media";
    row-gap: 0;
    justify-items: initial;
    max-width: calc(976px * var(--challenge-scale));
    text-align: left;
  }

  .case-challenge--sville-library .case-challenge__media {
    justify-self: end;
  }

  .case-challenge--sville-library .case-challenge__content {
    max-width: none;
  }

  .case-challenge--sville-library {
    padding-bottom: calc(80px * var(--challenge-scale));
  }

  /* Same fluid-clamp technique as the shared .case-challenge__image rule
     just above (zero jump entering the stack, taper to a safe floor by
     the narrowest phone), scaled down to match this image's own smaller
     260px base: 156px ceiling = calc(260px * 0.6), the exact pre-stack
     value at this 849px boundary; 146px floor keeps the same ~0.9375
     floor:ceiling ratio the shared clamp uses. */
  .case-challenge--sville-library .case-challenge__image {
    width: clamp(146px, calc(140px + 1.89vw), 156px);
  }
}

/* Site's one true "stack to mobile" breakpoint — .case-challenge--sville-
   library only stacks here, not at the shared 849px point above (see that
   override's own comment). */
@media (max-width: 639px) {
  .case-challenge--sville-library .case-challenge__row {
    grid-template-columns: 1fr;
    grid-template-areas: "content" "media";
    row-gap: calc(40px * var(--challenge-scale));
    justify-items: center;
    max-width: 100%;
    text-align: center;
  }

  .case-challenge--sville-library .case-challenge__media {
    justify-self: center;
  }

  .case-challenge--sville-library .case-challenge__content {
    max-width: 100%;
  }

  .case-challenge--sville-library {
    padding-bottom: calc(48px * var(--challenge-scale));
  }
}

/* ==========================================================================
   Case study library district system (Schwenksville Library only)
   ========================================================================== */

/* Same 976px*scale text-column formula as every other section — keeps
   this section's title at the identical left edge as the rest of the
   page, same as .case-challenge/.case-understanding. */
.case-system {
  --system-scale: 1;
  background-color: var(--color-white);
  padding-block: calc(80px * var(--system-scale));
}

.case-system--sville-library {
  --accent: var(--color-sville-library);
}

.case-system__text {
  max-width: calc(976px * var(--system-scale));
  margin-inline: auto;
}

.case-system__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--system-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-system__body {
  margin-top: calc(8px * var(--system-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--system-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-system__body p + p {
  margin-top: calc(32px * var(--system-scale));
}

.case-system__body strong {
  font-weight: 700;
}

/* Same shared-comparison-row idea as .case-profile__columns, but these
   are small, symmetric square logos (not asymmetric tall screen
   mockups), so there's no need for that section's stack-to-column
   breakpoint — no rule here ever switches flex-direction, so the pair
   stays side by side at every width down to the smallest phone, per
   spec, comparing the two marks directly instead of separating them. */
.case-system__columns {
  display: flex;
  justify-content: center;
  gap: calc(120px * var(--system-scale));
  margin-top: calc(62px * var(--system-scale));
}

.case-system__column {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Same Reenie Beanie floor as .case-profile__column-title/
   .case-wireframes__column-title. */
.case-system__column-title {
  margin: 0 0 calc(10px * var(--system-scale));
  text-align: center;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(calc(40px * var(--system-scale)), 26px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--color-blue-black);
}

.case-system__image {
  display: block;
  width: calc(190px * var(--system-scale));
  max-width: 100%;
  height: auto;
}

@media (max-width: 1489px) {
  .case-system {
    --system-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-system {
    --system-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-system {
    --system-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-system {
    padding-block: calc(64px * var(--system-scale));
  }

  .case-system__text {
    max-width: 100%;
  }
}

/* Site's one true "stack to mobile" breakpoint — .case-system__columns
   deliberately isn't included here (see its own comment): it keeps its
   flex-row layout straight through this breakpoint instead of switching
   to column. */
@media (max-width: 639px) {
  .case-system__columns {
    gap: calc(56px * var(--system-scale));
  }
}

/* ==========================================================================
   Case study inspiration moodboard (Schwenksville Library only)
   ========================================================================== */

.case-inspiration {
  --inspiration-scale: 1;
  background-color: var(--color-white);
  padding-top: calc(80px * var(--inspiration-scale));
  padding-bottom: calc(56px * var(--inspiration-scale));
}

.case-inspiration--sville-library {
  --accent: var(--color-sville-library);
}

.case-inspiration__title {
  margin: 0;
  text-align: center;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--inspiration-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

/* Board (700px) + gap (50px) + text column centered as one unit — wider
   than the standard 976px text-column budget on purpose, since this row
   is image-led rather than a text section. Vertically centered against
   the board, per spec. */
.case-inspiration__row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(50px * var(--inspiration-scale));
  margin-top: calc(10px * var(--inspiration-scale));
}

.case-inspiration__board {
  display: block;
  width: calc(700px * var(--inspiration-scale));
  max-width: 100%;
  height: auto;
  flex-shrink: 0;
}

.case-inspiration__text {
  max-width: calc(300px * var(--inspiration-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--inspiration-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-inspiration__text p + p {
  margin-top: calc(20px * var(--inspiration-scale));
}

.case-inspiration__text strong {
  font-weight: 700;
}

@media (max-width: 1489px) {
  .case-inspiration {
    --inspiration-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-inspiration {
    --inspiration-scale: 0.72;
  }
}

/* Stacks earlier than the site's usual 639px "true mobile" breakpoint —
   by this width the text column has wrapped enough that even top-aligned,
   it reads as noticeably longer than the board beside it; stacking here
   instead of waiting keeps the image and its text feeling paired instead
   of mismatched. align-items:center re-centers the pair now that the row
   is a column (align-items governs the cross/horizontal axis here, not
   the flex-start from the side-by-side layout above, which would leave
   both flush left). */
@media (max-width: 1023px) {
  .case-inspiration {
    --inspiration-scale: 0.6;
  }

  .case-inspiration__row {
    flex-direction: column;
    align-items: center;
  }

  /* Wider than the row's own 300px*scale max — matches .case-system__text
     (the section right above this one) at 976px*scale, instead of
     spanning the section's full width. Centered as a block, text
     left-aligned within it. */
  .case-inspiration__text {
    max-width: calc(976px * var(--inspiration-scale));
    width: 100%;
    margin-inline: auto;
    text-align: left;
  }
}

@media (max-width: 849px) {
  .case-inspiration {
    padding-top: calc(64px * var(--inspiration-scale));
    padding-bottom: calc(45px * var(--inspiration-scale));
  }
}

/* ==========================================================================
   Case study aspirational brands 2 & 3 (Schwenksville Library only)
   ========================================================================== */

.case-brands {
  --brands-scale: 1;
  background-color: var(--color-white);
  padding-block: calc(80px * var(--brands-scale));
}

.case-brands--sville-library {
  --accent: var(--color-sville-library);
}

/* 460px * 2 + 56px gap = 976px total — same combined budget as every
   other section's text column, just split into a labeled pair instead of
   one continuous measure. */
.case-brands__columns {
  display: flex;
  justify-content: center;
  gap: calc(56px * var(--brands-scale));
  max-width: calc(976px * var(--brands-scale));
  margin-inline: auto;
}

.case-brands__column {
  width: calc(460px * var(--brands-scale));
}

/* Same Hind Bold accent-color treatment as every other section title on
   the page (.case-inspiration__title, .case-challenge__title, etc.),
   just centered above its own column instead of left-aligned above a
   full-width row. */
.case-brands__title {
  margin: 0 0 calc(10px * var(--brands-scale));
  text-align: center;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(28px * var(--brands-scale)), 20px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-brands__board {
  display: block;
  width: 100%;
  height: auto;
}

/* Same centered-caption treatment as .case-exploration__caption/
   .case-homepage__note — one shared line underneath both boards instead
   of per-column body text. */
.case-brands__caption {
  margin: calc(40px * var(--brands-scale)) auto 0;
  max-width: calc(976px * var(--brands-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--brands-scale)), 16px);
  line-height: 1.6;
  text-align: center;
  color: var(--color-blue-black);
}

.case-brands__caption strong {
  font-weight: 700;
}

@media (max-width: 1489px) {
  .case-brands {
    --brands-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-brands {
    --brands-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-brands {
    --brands-scale: 0.6;
  }
}

/* Stacks earlier than the site's usual 639px breakpoint — each board is
   already a dense, multi-part moodboard (see .case-inspiration's own
   639px rule for the same reasoning applied there), so two of them side
   by side get illegibly small well before 639px. */
@media (max-width: 849px) {
  .case-brands {
    padding-block: calc(64px * var(--brands-scale));
  }

  .case-brands__columns {
    flex-direction: column;
    align-items: center;
    max-width: 100%;
  }

  .case-brands__column {
    width: 100%;
    max-width: calc(560px * var(--brands-scale));
  }
}

/* ==========================================================================
   Case study brandmark redesign (Schwenksville Library only)
   ========================================================================== */

.case-brandmark {
  --brandmark-scale: 1;
  background-color: var(--color-cream);
  padding-block: calc(80px * var(--brandmark-scale));
}

.case-brandmark--sville-library {
  --accent: var(--color-sville-library);
}

/* Same 976px*scale text-column formula as every other section — keeps
   this section's title at the identical left edge as the rest of the
   page. */
.case-brandmark__text {
  max-width: calc(976px * var(--brandmark-scale));
  margin-inline: auto;
}

.case-brandmark__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--brandmark-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-brandmark__body {
  margin-top: calc(8px * var(--brandmark-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--brandmark-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-brandmark__body p + p {
  margin-top: calc(32px * var(--brandmark-scale));
}

.case-brandmark__body strong {
  font-weight: 700;
}

.case-brandmark__compare {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(56px * var(--brandmark-scale));
  margin-top: calc(62px * var(--brandmark-scale));
}

.case-brandmark__column {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Same Reenie Beanie floor as .case-system__column-title/
   .case-profile__column-title. */
.case-brandmark__column-title {
  margin: 0 0 calc(10px * var(--brandmark-scale));
  text-align: center;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(calc(40px * var(--brandmark-scale)), 26px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--color-blue-black);
}

.case-brandmark__image {
  display: block;
  width: calc(300px * var(--brandmark-scale));
  max-width: 100%;
  height: auto;
}

/* Built as inline SVG (not an exported arrow image), per house rule —
   basic shapes like this stay in code, not asset files. Unlike
   .case-system's own comparison (where an arrow implied the wrong
   relationship), this one genuinely shows a first draft becoming the
   final design, so the directional arrow is accurate here. */
/* margin-top nudges the arrow down from the row's own align-items:center
   position (which centers against the taller title+image column) so its
   center lands on the hexagon's own vertical middle instead. Needs to be
   the full title-height + gap (40px + 10px = 50px at full scale), not
   half — align-items:center centers the item's margin box, so adding
   margin-top only shifts the visible content by half of what's added. */
.case-brandmark__arrow {
  width: calc(70px * var(--brandmark-scale));
  height: auto;
  margin-top: calc(50px * var(--brandmark-scale));
  color: var(--accent);
  flex-shrink: 0;
}

@media (max-width: 1489px) {
  .case-brandmark {
    --brandmark-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-brandmark {
    --brandmark-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-brandmark {
    --brandmark-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-brandmark {
    padding-block: calc(64px * var(--brandmark-scale));
  }

  .case-brandmark__text {
    max-width: 100%;
  }
}

/* Site's one true "stack to mobile" breakpoint — the arrow rotates to
   point down instead of right, matching the new vertical flow (draft
   above, final design below). */
@media (max-width: 639px) {
  .case-brandmark__compare {
    flex-direction: column;
    gap: calc(48px * var(--brandmark-scale));
  }

  .case-brandmark__arrow {
    margin-top: 0;
    transform: rotate(90deg);
  }
}

/* ==========================================================================
   Case study library landscape differentiation (Schwenksville Library only)
   ========================================================================== */

.case-differentiate {
  --differentiate-scale: 1;
  background-color: var(--color-white);
  padding-block: calc(80px * var(--differentiate-scale));
}

.case-differentiate--sville-library {
  --accent: var(--color-sville-library);
}

/* Same 976px*scale text-column formula as every other section — keeps
   this section's title at the identical left edge as the rest of the
   page. */
.case-differentiate__text {
  max-width: calc(976px * var(--differentiate-scale));
  margin-inline: auto;
}

.case-differentiate__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--differentiate-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-differentiate__body {
  margin-top: calc(8px * var(--differentiate-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--differentiate-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-differentiate__body strong {
  font-weight: 700;
}

.case-differentiate__image {
  display: block;
  width: calc(700px * var(--differentiate-scale));
  max-width: 100%;
  height: auto;
  margin: calc(62px * var(--differentiate-scale)) auto 0;
}

@media (max-width: 1489px) {
  .case-differentiate {
    --differentiate-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-differentiate {
    --differentiate-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-differentiate {
    --differentiate-scale: 0.6;
  }
}

/* Simple stack — no side-by-side composition to break apart, just the
   text column going full-width like every other section at this
   breakpoint; the image (already centered, already fluid via
   max-width:100%) keeps scaling down with the container on its own. */
@media (max-width: 849px) {
  .case-differentiate {
    padding-block: calc(64px * var(--differentiate-scale));
  }

  .case-differentiate__text {
    max-width: 100%;
  }
}

/* ==========================================================================
   Case study visual style guide (Schwenksville Library only)
   ========================================================================== */

/* Same white background as .case-differentiate right above it — a
   deliberate same-color run, both part of one continuous "final system"
   arc, same exception CLAUDE.md documents for this pattern. */
/* Top padding smaller than the usual 80px — same reasoning as
   .case-connections: this section runs right into .case-differentiate
   above it with no color change to break up the transition, so a full
   80px+80px pairing reads as an oversized gap where a section boundary
   would normally justify it. */
.case-styleguide {
  --styleguide-scale: 1;
  background-color: var(--color-white);
  padding-top: calc(40px * var(--styleguide-scale));
  padding-bottom: calc(80px * var(--styleguide-scale));
}

.case-styleguide--sville-library {
  --accent: var(--color-sville-library);
}

.case-styleguide__text {
  max-width: calc(976px * var(--styleguide-scale));
  margin-inline: auto;
}

.case-styleguide__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--styleguide-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-styleguide__body {
  margin-top: calc(8px * var(--styleguide-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--styleguide-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-styleguide__body p + p {
  margin-top: calc(32px * var(--styleguide-scale));
}

.case-styleguide__body strong {
  font-weight: 700;
}

.case-styleguide__image {
  display: block;
  width: calc(700px * var(--styleguide-scale));
  max-width: 100%;
  height: auto;
  margin: calc(62px * var(--styleguide-scale)) auto 0;
}

/* Same arrow as .case-screens__arrow (Davis Grown) — same viewBox,
   stroke treatment, and "points down into the banner strip below" role,
   reused verbatim for this page's own transition into Final Touchpoints. */
.case-styleguide__arrow {
  display: block;
  margin: calc(64px * var(--styleguide-scale)) auto 0;
  width: auto;
  height: calc(70px * var(--styleguide-scale));
  fill: none;
  stroke: var(--accent);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (max-width: 1489px) {
  .case-styleguide {
    --styleguide-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-styleguide {
    --styleguide-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-styleguide {
    --styleguide-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-styleguide {
    padding-top: calc(32px * var(--styleguide-scale));
    padding-bottom: calc(64px * var(--styleguide-scale));
  }

  .case-styleguide__text {
    max-width: 100%;
  }
}

/* ==========================================================================
   Case study mobile interface screens (Schwenksville Library only)
   ========================================================================== */

.case-mobile {
  --mobile-scale: 1;
  background-color: var(--color-white);
  padding-block: calc(80px * var(--mobile-scale));
}

.case-mobile--sville-library {
  --accent: var(--color-sville-library);
}

.case-mobile__text {
  max-width: calc(976px * var(--mobile-scale));
  margin-inline: auto;
}

.case-mobile__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--mobile-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-mobile__body {
  margin-top: calc(8px * var(--mobile-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--mobile-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-mobile__body p + p {
  margin-top: calc(32px * var(--mobile-scale));
}

.case-mobile__body strong {
  font-weight: 700;
}

.case-mobile__screens {
  display: flex;
  justify-content: center;
  gap: calc(55px * var(--mobile-scale));
  margin-top: calc(62px * var(--mobile-scale));
}

/* Screen-mockup tier (12px radius + shadow) — height-driven, not width,
   so all three stay the same height regardless of each screen's own
   content length, per spec (660px at full scale). */
.case-mobile__image {
  display: block;
  width: auto;
  height: calc(660px * var(--mobile-scale));
  border-radius: 12px;
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

@media (max-width: 1489px) {
  .case-mobile {
    --mobile-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-mobile {
    --mobile-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-mobile {
    --mobile-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-mobile {
    padding-block: calc(64px * var(--mobile-scale));
  }

  .case-mobile__text {
    max-width: 100%;
  }
}

/* All three screens stay side by side at the shared 0.6 floor scale
   (each ~167px wide, ~568px combined) all the way down to the site's one
   true "stack to mobile" breakpoint — narrower than they first looked
   (height-driven at 660px reads tall, but each is quite narrow), so
   stacking to one column any earlier than this left tablet views with an
   unnecessarily tall single-file column instead of using the width
   available. */
@media (max-width: 639px) {
  .case-mobile__screens {
    flex-direction: column;
    align-items: center;
    gap: calc(40px * var(--mobile-scale));
  }
}

/* ==========================================================================
   Case study identity extension (Schwenksville Library only)
   ========================================================================== */

.case-extend {
  --extend-scale: 1;
  background-color: var(--color-cream);
  padding-block: calc(80px * var(--extend-scale));
}

.case-extend--sville-library {
  --accent: var(--color-sville-library);
}

.case-extend__title {
  margin: 0;
  text-align: center;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--extend-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-extend__columns {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: calc(50px * var(--extend-scale));
  margin-top: calc(64px * var(--extend-scale));
}

/* Each column capped to its own image's width (not left to size freely)
   so the body text wraps within that measure instead of running wider
   than the marketing asset sitting right below it. */
.case-extend__column {
  display: flex;
  flex-direction: column;
}

.case-extend__column:nth-child(1) {
  width: calc(400px * var(--extend-scale));
}

.case-extend__column:nth-child(2) {
  width: calc(324px * var(--extend-scale));
}

.case-extend__column:nth-child(3) {
  width: calc(200px * var(--extend-scale));
}

/* Same Reenie Beanie floor as .case-system__column-title/
   .case-profile__column-title, left-aligned above its own column instead
   of centered. */
.case-extend__column-title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(calc(40px * var(--extend-scale)), 26px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--color-blue-black);
}

.case-extend__body {
  margin-top: calc(10px * var(--extend-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--extend-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

/* Narrowed just enough to push this column's shorter caption onto a
   third line, matching Instagram Post's and App Icon's own 3-line
   wraps (measured: both 86px tall vs. this one's 58px) — otherwise its
   image starts noticeably higher than the other two, since all three
   images sit directly below their own caption in the same row. Desktop
   only; the mobile override below resets it since the row stacks into
   a single column there and this alignment concern doesn't apply. */
.case-extend__column:nth-child(2) .case-extend__body {
  max-width: calc(290px * var(--extend-scale));
}

.case-extend__body strong {
  font-weight: 700;
}

/* Plain-photography/document-export tier (4px radius, no shadow) — these
   are a social post and a printed poster, not screen mockups, so they
   don't get the 12px+shadow screen treatment used elsewhere. */
.case-extend__image {
  display: block;
  height: auto;
  margin-top: calc(28px * var(--extend-scale));
}

.case-extend__image--insta {
  width: calc(400px * var(--extend-scale));
  border-radius: 4px;
}

.case-extend__image--poster {
  width: calc(324px * var(--extend-scale));
  border-radius: 4px;
}

/* No radius override — the source SVG already has its own rounded-square
   shape baked in. */
.case-extend__image--app {
  width: calc(200px * var(--extend-scale));
}

@media (max-width: 1489px) {
  .case-extend {
    --extend-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-extend {
    --extend-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-extend {
    --extend-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-extend {
    padding-block: calc(64px * var(--extend-scale));
  }
}

/* Site's one true "stack to mobile" breakpoint. Every column matches the
   Instagram column's own 400px*scale width here (not each column's own
   narrower image width) — poster/app text can wrap wider than their own
   images once stacked, it just still shouldn't run wider than the
   widest asset on the page (the Instagram post). Images stay their own
   width and center within the shared column via align-items:center. */
@media (max-width: 639px) {
  .case-extend__columns {
    flex-direction: column;
    align-items: center;
    gap: calc(48px * var(--extend-scale));
  }

  .case-extend__column,
  .case-extend__column:nth-child(1),
  .case-extend__column:nth-child(2),
  .case-extend__column:nth-child(3) {
    width: calc(400px * var(--extend-scale));
    max-width: 100%;
    align-items: center;
    text-align: center;
  }

  /* The desktop-only 3-line forcing width doesn't apply once stacked —
     nothing left to align images against in a single column. */
  .case-extend__column:nth-child(2) .case-extend__body {
    max-width: 100%;
  }
}

/* ==========================================================================
   Case study love birds (Schwenksville Library only)
   ========================================================================== */

.case-lovebirds {
  --lovebirds-scale: 1;
  background-color: var(--color-cream);
  padding-top: calc(48px * var(--lovebirds-scale));
  padding-bottom: calc(80px * var(--lovebirds-scale));
}

.case-lovebirds__row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(75px * var(--lovebirds-scale));
  max-width: calc(976px * var(--lovebirds-scale));
  margin-inline: auto;
}

.case-lovebirds__content {
  max-width: calc(400px * var(--lovebirds-scale));
}

/* Reenie Beanie, not the usual Hind Bold accent-color title — matches
   .case-profile__column-title/.case-wireframes__column-title's own
   handwritten-label treatment, per spec. */
.case-lovebirds__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(calc(40px * var(--lovebirds-scale)), 26px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--color-blue-black);
}

.case-lovebirds__body {
  margin-top: calc(10px * var(--lovebirds-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--lovebirds-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-lovebirds__body strong {
  font-weight: 700;
}

.case-lovebirds__media {
  flex-shrink: 0;
}

.case-lovebirds__image {
  display: block;
  width: calc(300px * var(--lovebirds-scale));
  max-width: 100%;
  height: auto;
}

@media (max-width: 1489px) {
  .case-lovebirds {
    --lovebirds-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-lovebirds {
    --lovebirds-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-lovebirds {
    --lovebirds-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-lovebirds {
    padding-block: calc(64px * var(--lovebirds-scale));
  }
}

/* Stays side by side through 849-639px, per spec ("maybe it can remain
   side by side") — content (400px scaled) + 75px gap + seal (300px
   scaled) still fit comfortably together down to 639px. Only stacks at
   the site's true mobile breakpoint, where the seal's own fixed width
   next to the text would otherwise squeeze the text column down to an
   unreadable sliver. */
@media (max-width: 639px) {
  .case-lovebirds__row {
    flex-direction: column;
    gap: calc(24px * var(--lovebirds-scale));
    text-align: center;
  }

  .case-lovebirds__content {
    max-width: 100%;
  }
}

/* ==========================================================================
   Case study understanding/research (Davis Grown, etc.)
   ========================================================================== */

/* Single-column at every breakpoint (title, body, map all stack
   naturally) — unlike .case-summary/.case-challenge there's no side-by-side
   composition here, so this doesn't need the centered-group grid pattern,
   just --understanding-scale driving font-size/spacing the same way. */
.case-understanding {
  --understanding-scale: 1;
  background-color: var(--color-white);
  padding-block: calc(80px * var(--understanding-scale));
}

.case-understanding--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-understanding--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-understanding--sville-library {
  --accent: var(--color-sville-library);
}

/* max-width:976px*scale matches .case-challenge__row's own max-width —
   centering a same-width box here via margin-inline:auto lands this text
   at the exact same left edge as "The Challenge", at every viewport
   width, without needing to hand-replicate challenge's own centering. */
.case-understanding__text {
  max-width: calc(976px * var(--understanding-scale));
  margin-inline: auto;
}

.case-understanding__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--understanding-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-understanding__body {
  margin-top: calc(8px * var(--understanding-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--understanding-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-understanding__body p + p {
  margin-top: calc(32px * var(--understanding-scale));
}

.case-understanding__body strong {
  font-weight: 700;
}

/* margin-inline:auto centers the map within the container independent of
   its own scaled width, same way .case-challenge__image doesn't need
   grid/flex centering of its own. Large margin-top (not the standard
   .space-medium gap) — this is a text-to-image breather matching the
   site's "Section" spacing tier (130-200px), not a tight heading-to-body
   gap. */
.case-understanding__map {
  display: block;
  margin-top: calc(24px * var(--understanding-scale));
  margin-inline: auto;
  width: calc(742px * var(--understanding-scale));
  max-width: 100%;
  height: auto;
}

@media (max-width: 1489px) {
  .case-understanding {
    --understanding-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-understanding {
    --understanding-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-understanding {
    --understanding-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-understanding {
    padding-block: calc(64px * var(--understanding-scale));
  }

  .case-understanding__text {
    max-width: 100%;
  }

  .case-understanding__map {
    margin-top: calc(20px * var(--understanding-scale));
  }
}

/* ==========================================================================
   Case study quote strip (Davis Grown, etc.)
   ========================================================================== */

/* Same red-strip pattern as .hero-strip/.connect-banner — identical
   min-height and font-size steps at every breakpoint, just recolored per
   project via --accent, so this reads as the same family of banner
   across the whole site rather than a one-off. */
.case-quote {
  --accent: var(--color-scarlet);
  background-color: var(--accent);
  min-height: 119px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: var(--space-small) var(--container-padding);
  text-align: center;
}

.case-quote--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-quote--home-at-last {
  --accent: var(--color-home-at-last);
}

/* Extra margin above/below — the strip's own min-height/padding are
   shared with every project, so this is scoped here instead of touching
   .case-quote generally. */
.case-quote--sville-library {
  --accent: var(--color-sville-library);
  margin-block: 24px;
}

.case-quote__line {
  margin: 0;
  max-width: 900px;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 22px;
  line-height: 28px;
  letter-spacing: 0;
  color: var(--color-white);
}

.case-quote__line strong {
  font-weight: 700;
}

@media (max-width: 1489px) {
  .case-quote__line {
    font-size: 20px;
  }
}

@media (max-width: 1249px) {
  .case-quote__line {
    font-size: 19px;
  }
}

@media (max-width: 1023px) {
  .case-quote {
    min-height: 93px;
  }

  .case-quote__line {
    font-size: 17px;
  }
}

/* Below this, the two sentences read as one flowing block of text instead
   of two forced separate lines — display:flex (used above to center the
   two lines as a stacked pair) blockifies its children regardless of their
   own display property, so the switch to display:block here is what
   actually lets .case-quote__line go inline and wrap together. Two
   sentences at 14px each on their own line was reading as noticeably
   taller than this strip needs to be; the whitespace between the two <p>
   tags in the markup collapses to a normal space once both are inline, so
   they wrap exactly like a single paragraph. */
@media (max-width: 849px) {
  .case-quote {
    display: block;
    min-height: 0;
    padding-block: var(--space-small);
  }

  /* line-height:1.3 matches .hero-strip__text's own ratio exactly (same
     value, never overridden at any of its breakpoints either) — same
     line-spacing language as the homepage's red strip. */
  .case-quote__line {
    display: inline;
    font-size: 14px;
    line-height: 1.3;
  }
}

/* ==========================================================================
   Case study customer motivations (Davis Grown, etc.)
   ========================================================================== */

.case-motivations {
  --motivations-scale: 1;
  background-color: var(--color-white);
  padding-block: calc(80px * var(--motivations-scale));
}

.case-motivations--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-motivations--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-motivations--sville-library {
  --accent: var(--color-sville-library);
}

.case-motivations__title {
  margin: calc(20px * var(--motivations-scale)) 0 calc(39px * var(--motivations-scale));
  text-align: center;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--motivations-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-motivations__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  column-gap: calc(75px * var(--motivations-scale));
}

/* 3:2 matches the source photos' own cropped proportions (measured against
   the reference composition), so object-fit:cover only trims a little off
   each edge rather than aggressively cropping to force-fit a mismatched
   ratio. */
.case-motivations__image {
  display: block;
  width: 100%;
  /* height must stay explicit here, even though aspect-ratio is what
     actually sizes it — these three <img> tags carry width/height
     attributes (for layout-shift prevention), which the browser maps to
     a presentational height hint. Nothing else in this rule ever
     touched `height`, so that hint was winning outright and stretching
     every card to a literal 600px tall regardless of its actual
     (much narrower, grid-column) width. Declaring auto here cancels the
     hint and lets aspect-ratio compute height from width again. */
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: 8px;
}

/* Floor raised to 30px (not the usual 24px) — same reasoning as
   .stitchery__role's own floor: Reenie Beanie's thinner script strokes
   read noticeably smaller than a block-letter Hind heading at the same
   px size, so it needs the extra size to hold its own at small scales. */
.case-motivations__card-title {
  margin: calc(20px * var(--motivations-scale)) 0 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(calc(40px * var(--motivations-scale)), 30px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--color-blue-black);
}

.case-motivations__card-body {
  margin-top: calc(12px * var(--motivations-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--motivations-scale)), 16px);
  line-height: 1.5;
  color: var(--color-blue-black);
}

/* Hidden on desktop, where the column is wide enough that "Trust in
   ingredients" reads fine on one line — only activated at the stacked
   mobile breakpoint below, to match "Give back to community"'s own
   natural 2-line wrap in the paired card beside it. */
.case-motivations__break {
  display: none;
}

.case-motivations__card-body p + p {
  margin-top: calc(8px * var(--motivations-scale));
}

.case-motivations__card-body strong {
  font-weight: 700;
}

@media (max-width: 1489px) {
  .case-motivations {
    --motivations-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-motivations {
    --motivations-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-motivations {
    --motivations-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-motivations {
    padding-block: calc(64px * var(--motivations-scale));
  }
}

/* Two cards per row, third centered below — a plain single-column stack
   (one full-width photo per row) measured out to 1111px tall on a 812px
   phone viewport, over a full extra screen of scrolling for this section
   alone. 2-up + square crops (see .case-motivations__image below) cut
   that to ~800px, tested against the actual card copy before landing
   here — the longer "Response" lines still read fine at this width.
   Scoped to ≤639px, matching the site's one true "stack to mobile"
   breakpoint (same as the header hamburger and footer) — the desktop
   3-column grid (repeat(3,1fr), already fluid) has enough room to keep
   all three side by side down to that point without overflowing. */
@media (max-width: 639px) {
  /* max-width caps the columns at the ~160px each they were tested and
     tuned at (phone width) — without it, "1fr 1fr" fills whatever the
     container happens to be, which balloons to 350px+ per card at tablet
     widths within this same ≤767px range, reading oversized. Capping and
     centering the grid itself keeps card size consistent across the
     whole stacked breakpoint instead of only at one specific width. */
  .case-motivations__grid {
    grid-template-columns: 1fr 1fr;
    max-width: 336px;
    margin-inline: auto;
    column-gap: 24px;
    row-gap: 24px;
  }

  /* Third card spans both columns and is capped/centered so it doesn't
     just stretch full-width, matching the two cards' own column width
     above it instead of reading as a mismatched full-width row. */
  /* width:calc((100% - 24px) / 2) — not a fixed px guess — computes to
     exactly one grid column's own width (the same math the 1fr 1fr
     columns above resolve to, given the 24px column-gap), so this card
     is always identically sized to the two above it at any viewport
     width, instead of a hardcoded number that only happened to be close
     at one specific width. */
  .case-motivations__card:nth-child(3) {
    grid-column: 1 / -1;
    width: calc((100% - 24px) / 2);
    margin-inline: auto;
  }

  /* Square (not the desktop 3:2) — at half the container's width, a 3:2
     crop reads noticeably taller than wide and eats into the height
     savings the 2-up layout is meant to buy back. */
  .case-motivations__image {
    aspect-ratio: 1 / 1;
  }

  /* 24px, left-aligned (matching the desktop tier) — measured word-by-word
     so "Shopper" (the shared last word of all three titles) always wraps
     to its own second line: each title's first part ("Responsible Family"
     143px, "Community-Oriented" 148px, "Ethical Health" 116px) fits the
     160px column alone, but adding "Shopper" back in doesn't, so all three
     break the same way and land at the same 2-line height. */
  .case-motivations__card-title {
    margin-top: 12px;
    font-size: 24px;
    text-align: left;
  }

  .case-motivations__card-body {
    margin-top: 6px;
    font-size: 14px;
  }

  .case-motivations__break {
    display: inline;
  }

  .case-motivations__card-body p + p {
    margin-top: 4px;
  }
}

/* ==========================================================================
   Case study design goals (Davis Grown, etc.)
   ========================================================================== */

/* padding-block (not min-height + flex-centered content) — matches every
   other section's sizing convention, giving a consistent, proportional
   buffer above/below content at every scale tier. min-height let the gap
   shrink to as little as ~26px at some tablet widths since it only
   guarantees a floor for the *section*, not the space around its content;
   80px*scale guarantees the actual gap instead. */
.case-goals {
  --goals-scale: 1;
  background-color: var(--color-cream);
  padding-block: calc(80px * var(--goals-scale));
}

.case-goals--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-goals--home-at-last {
  --accent: var(--color-home-at-last);
  /* Base .case-goals is hard-coded cream, which fits Davis Grown (it
     follows a white section there). Here it directly follows
     .case-challenge--home-at-last, which is already cream, so this
     override keeps the white/cream alternation intact instead of running
     two cream sections back to back. */
  background-color: var(--color-white);
}

/* Wider gap than Davis Grown's 5-circle row (26px) — with only 3 circles
   here, 50px still fits comfortably and reads more intentional than the
   tighter default. Scoped to above the 849px stacked breakpoint, which
   already tightens .case-goals__circles' gap for small viewports; letting
   this 50px value win there too would overflow the 3-circle row on phone
   widths, the same problem the 639px/399px tiers below exist to avoid. */
@media (min-width: 850px) {
  .case-goals--home-at-last .case-goals__circles {
    gap: calc(50px * var(--goals-scale));
  }
}

.case-goals--sville-library {
  --accent: var(--color-sville-library);
}


.case-goals__title {
  margin: 0;
  text-align: center;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--goals-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

/* Optional — Davis Grown's goals section has no body copy, just title
   then circles. When present, follows the standard 8px heading-to-body
   spacing; .case-goals__circles' own margin-top below is what's bumped
   from 33px to 39px to clear it, via the adjacent-sibling override. */
.case-goals__body {
  max-width: calc(976px * var(--goals-scale));
  margin: calc(8px * var(--goals-scale)) auto 0;
  text-align: center;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--goals-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

/* flex-wrap (not a grid) is what gives the stacked breakpoint its "2, 2,
   1" layout for free — once the circles shrink past a width where 3 no
   longer fit the container, they wrap after every 2 automatically, and
   justify-content:center keeps the leftover single circle on the last row
   centered rather than stranded on one side. No separate mobile override
   needed as a result. */
.case-goals__circles {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: calc(26px * var(--goals-scale));
  margin-top: calc(33px * var(--goals-scale));
}

.case-goals__body + .case-goals__circles {
  margin-top: calc(39px * var(--goals-scale));
}

.case-goals__circle {
  flex-shrink: 0;
  width: max(calc(198px * var(--goals-scale)), 110px);
  height: max(calc(198px * var(--goals-scale)), 110px);
  border-radius: 50%;
  background-color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: calc(16px * var(--goals-scale));
}

.case-goals__circle span {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(calc(40px * var(--goals-scale)), 22px);
  line-height: calc(45px * var(--goals-scale));
  letter-spacing: -0.05em;
  color: var(--color-white);
}

@media (max-width: 1489px) {
  .case-goals {
    --goals-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-goals {
    --goals-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-goals {
    --goals-scale: 0.6;
  }
}

/* Smaller floors than the desktop-tier values above (110px circle/22px
   text) — tested at 100px/20px against the longest labels ("Strengthen
   Local Connections", "Establish Consistent Brand") and they still wrap
   cleanly inside the circle with room to spare, which is what lets 3 fit
   per row at real phone widths (~375px) instead of only 2. */
@media (max-width: 849px) {
  .case-goals {
    padding-block: calc(64px * var(--goals-scale));
  }

  .case-goals__circles {
    gap: 12px;
  }

  .case-goals__circle {
    width: 100px;
    height: 100px;
    padding: 8px;
  }

  .case-goals__circle span {
    font-size: 20px;
    line-height: 22px;
  }
}

/* Without this cap, plain flex-wrap lets the row fit however many circles
   happen to fit at a given width — measured: at 100px circles/12px gap,
   viewports from 476-587px fit exactly 4 per row, splitting the 5 circles
   as "4, 1" instead of "5" or "3, 2". max-width locked to exactly 3
   circles + 2 gaps (324px) forces the row to always wrap after 3 once it
   can't fit all 5, skipping that in-between 4-fits zone entirely — the
   last 2 then wrap to their own row, centered via the existing
   justify-content:center. Above 639px this isn't needed: 5 already fits
   down to 640px (verified: content width at 640px is 560px, comfortably
   over the 548px 5 circles + 4 gaps need). */
@media (max-width: 639px) {
  .case-goals__circles {
    max-width: 324px;
    margin-inline: auto;
  }
}

/* The exact crossover where the 324px/100px tier above stops fitting is
   364px viewport (324px needed vs. 324px available — zero margin), which
   verified to actually break right at that boundary: browser-reported
   viewport width and the width media queries actually evaluate against
   can differ by a few px (scrollbar accounting), so a zero-margin
   crossover isn't safe. 399px (not 364px) reuses the same breakpoint the
   codebase already has for a similar small-phone tier and leaves a real
   margin on both sides. Smaller circles (84px) + tighter gap (10px) bring
   the 3-circle row down to 272px, comfortably fitting all the way to a
   320px viewport (280px content). Font/line-height and padding scaled
   down to match. */
@media (max-width: 399px) {
  .case-goals__circles {
    max-width: 272px;
    gap: 10px;
  }

  .case-goals__circle {
    width: 84px;
    height: 84px;
    padding: 6px;
  }

  .case-goals__circle span {
    font-size: 16px;
    line-height: 18px;
  }
}

/* ==========================================================================
   Case study process intro banner (Home At Last, etc.)
   ========================================================================== */

.case-testing {
  --accent: var(--color-scarlet);
  --testing-scale: 1;
  background-color: var(--color-cream);
  min-height: calc(203px * var(--testing-scale));
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: calc(8px * var(--testing-scale));
  padding: var(--space-small) var(--container-padding);
  text-align: center;
}

.case-testing--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-testing--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-testing--sville-library {
  --accent: var(--color-sville-library);
}

.case-testing__title {
  margin: 0;
  max-width: calc(700px * var(--testing-scale));
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--testing-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-testing__body {
  margin: 0;
  max-width: calc(700px * var(--testing-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--testing-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-testing__body strong {
  font-weight: 700;
}

@media (max-width: 1489px) {
  .case-testing {
    --testing-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-testing {
    --testing-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-testing {
    --testing-scale: 0.6;
  }
}

/* Fixed min-height (sized for the 2-line desktop copy) doesn't hold up
   once the same text wraps to 3+ lines at narrow widths — same
   min-height:auto + padding-block swap .case-banner uses at this
   breakpoint. */
@media (max-width: 849px) {
  .case-testing {
    min-height: auto;
    padding-block: calc(64px * var(--testing-scale));
  }
}

/* ==========================================================================
   Case study key findings (Home At Last, etc.)
   ========================================================================== */

.case-findings {
  --accent: var(--color-scarlet);
  --findings-scale: 1;
  background-color: var(--color-white);
  padding-block: calc(80px * var(--findings-scale));
}

.case-findings--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-findings--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-findings--sville-library {
  --accent: var(--color-sville-library);
}

/* Same width-matching technique as .case-understanding__text/
   .case-exploration__text — a 976px-wide box (matching
   .case-challenge__content + gap + image) centered inside this same
   .container lands "Key Findings" at the identical left edge as
   "Identifying the Opportunity" above it. */
.case-findings__text {
  max-width: calc(976px * var(--findings-scale));
  margin-inline: auto;
}

.case-findings__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--findings-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-findings__list {
  margin-top: calc(40px * var(--findings-scale));
}

.case-findings__item + .case-findings__item {
  margin-top: calc(32px * var(--findings-scale));
}

/* Extra room after an item carrying a screenshot — that item reads as
   much heavier than the plain-text ones above/below it, so the standard
   32px gap felt abrupt coming off of it specifically. */
.case-findings__item:has(.case-findings__screen) + .case-findings__item {
  margin-top: calc(48px * var(--findings-scale));
}

.case-findings__item-title {
  margin: 0 0 calc(8px * var(--findings-scale));
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(22px * var(--findings-scale)), 18px);
  color: var(--accent);
}

.case-findings__item-list {
  margin: 0;
  padding-left: calc(24px * var(--findings-scale));
  list-style: none;
}

.case-findings__item-list li {
  position: relative;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--findings-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

/* Same star-bullet treatment as .case-summary__detail-list — 24px offset
   matches the standard ~2-space star-to-text gap used everywhere else on
   the site. Positioned (not list-style) so it still works when an item
   wraps to multiple lines, unlike the nowrap detail lists that pattern
   originated from. */
.case-findings__item-list li::before {
  content: "★";
  position: absolute;
  left: calc(-24px * var(--findings-scale));
  color: var(--accent);
}

.case-findings__item-list li + li {
  margin-top: calc(8px * var(--findings-scale));
}

.case-findings__item-list strong {
  font-weight: 700;
}

/* Screen mockup treatment — same 12px radius + soft shadow tier as
   .case-screens__image/.case-homepage__image, since this is an actual
   browser screenshot, not a photo or card. */
/* Same 550px screen-mockup size and scaling as .case-connections__image
   (Davis Grown's own "supporting screen"), not a one-off value — includes
   matching its 639px mobile override below. */
.case-findings__screen {
  display: block;
  width: calc(550px * var(--findings-scale));
  max-width: 100%;
  height: auto;
  margin: calc(32px * var(--findings-scale)) auto 0;
  border-radius: 12px;
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

@media (max-width: 1489px) {
  .case-findings {
    --findings-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-findings {
    --findings-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-findings {
    --findings-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-findings {
    padding-block: calc(64px * var(--findings-scale));
  }

  .case-findings__text {
    max-width: 100%;
  }
}

/* Same 82%/420px mobile size as .case-connections__image — the same
   "supporting screen" scaling all the way down, not just at desktop/tablet. */
@media (max-width: 639px) {
  .case-findings__screen {
    width: 82%;
    max-width: 420px;
  }
}

/* ==========================================================================
   Case study visual exploration (Davis Grown, etc.)
   ========================================================================== */

.case-exploration {
  --exploration-scale: 1;
  background-color: var(--color-white);
  padding-block: calc(80px * var(--exploration-scale));
}

.case-exploration--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-exploration--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-exploration--sville-library {
  --accent: var(--color-sville-library);
}

/* Same width-matching technique as .case-understanding__text — a
   976px-wide box (matching .case-challenge__content + gap + image)
   centered in this same container lands the title/body at the exact same
   x as "The Challenge" and "Understanding Davis Grown" above it. */
.case-exploration__text {
  max-width: calc(976px * var(--exploration-scale));
  margin-inline: auto;
}

.case-exploration__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--exploration-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-exploration__body {
  margin-top: calc(8px * var(--exploration-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--exploration-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-exploration__body strong {
  font-weight: 700;
}

/* grid-template-areas puts each board's title in its own row above the
   images, sharing the same row-gap (title-to-image) on both sides, while
   the arrow occupies a middle column sized to exactly the requested 117px
   gap and centers itself only against the image row (align-self:center),
   not the taller title+image column — so it lines up with the images
   regardless of anything above them. */
/* minmax(0, cap) on the two image columns (not the middle arrow gap,
   which stays a fixed width) — lets both boards keep shrinking fluidly
   with the container instead of jumping straight to the fully-stacked
   layout well before necessary. */
.case-exploration__boards {
  display: grid;
  grid-template-columns:
    minmax(0, calc(450px * var(--exploration-scale))) calc(117px * var(--exploration-scale))
    minmax(0, calc(450px * var(--exploration-scale)));
  grid-template-areas: "title1 . title2" "image1 arrow image2";
  column-gap: 0;
  row-gap: calc(10px * var(--exploration-scale));
  justify-content: center;
  /* Matches .case-exploration__caption's own margin-top exactly — equal
     gap above and below the boards row (intro body to "My Board" title,
     images to the caption paragraph), instead of two different numbers. */
  margin-top: calc(50px * var(--exploration-scale));
}

/* display:contents ungroups each board into the parent grid so its title
   and image become independently placeable — same technique as
   .bucknell__content/.case-study__content use for their own desktop
   grid-area layouts. */
.case-exploration__board {
  display: contents;
}

.case-exploration__board:nth-of-type(1) .case-exploration__board-title {
  grid-area: title1;
}

.case-exploration__board:nth-of-type(1) .case-exploration__board-image {
  grid-area: image1;
}

.case-exploration__board:nth-of-type(2) .case-exploration__board-title {
  grid-area: title2;
}

.case-exploration__board:nth-of-type(2) .case-exploration__board-image {
  grid-area: image2;
}

/* Floor raised to 30px (not the usual 24px) — same Reenie Beanie floor
   reasoning as .stitchery__role/.case-motivations__card-title. */
.case-exploration__board-title {
  margin: 0;
  text-align: center;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(calc(40px * var(--exploration-scale)), 30px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--color-blue-black);
}

/* A subtle 4px, not the 12px "screen mockup" radius — these are photos of
   a physical mood board, not a browser window, so they get just enough
   softening to not look sharp-cornered without reading as a "screen." */
.case-exploration__board-image {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 4px;
}

/* Same arrow as .about-hero__arrow (same viewBox, stroke-width, caps) —
   recolored to this project's accent and rotated to point right, since
   this pair of boards reads left-to-right instead of top-to-bottom. */
.case-exploration__arrow {
  grid-area: arrow;
  align-self: center;
  justify-self: center;
  width: auto;
  height: calc(70px * var(--exploration-scale));
  fill: none;
  stroke: var(--accent);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* -90deg, not 90deg — the source arrow points down (tip at the bottom
     of the viewBox), and rotating a downward vector 90deg clockwise (CSS
     rotate's positive direction) swings the tip to the left, not the
     right. -90deg (counterclockwise) is what actually points it right. */
  transform: rotate(-90deg);
}

.case-exploration__caption {
  margin-top: calc(50px * var(--exploration-scale));
  max-width: calc(976px * var(--exploration-scale));
  margin-inline: auto;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--exploration-scale)), 16px);
  line-height: 1.6;
  text-align: center;
  color: var(--color-blue-black);
}

.case-exploration__caption strong {
  font-weight: 700;
}

@media (max-width: 1489px) {
  .case-exploration {
    --exploration-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-exploration {
    --exploration-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-exploration {
    --exploration-scale: 0.6;
  }
}

/* Stacks in step with the rest of the site's sections. The boards switch
   from the desktop grid to a plain flex column — .case-exploration__board
   goes back to being a real box (title above image, its own natural
   stacking) instead of display:contents, and DOM order (board 1, arrow,
   board 2) puts "My Board" on top and "Converged Board" below with the
   arrow between them. transform:none restores the arrow's native
   downward orientation instead of the desktop's rotated-right version. */
@media (max-width: 849px) {
  .case-exploration {
    padding-block: calc(64px * var(--exploration-scale));
  }

  .case-exploration__text {
    max-width: 100%;
  }

  .case-exploration__caption {
    max-width: 100%;
    text-align: left;
  }
}

/* Boards stack (My Board above, arrow pointing down, Converged Board
   below) only below 639px — with the minmax columns above now shrinking
   both boards fluidly instead of holding a fixed width, they stay side by
   side all the way down to the site's one true "stack to mobile"
   breakpoint (same as the header hamburger and footer), not a wider
   computed threshold. */
@media (max-width: 639px) {
  .case-exploration__boards {
    display: flex;
    flex-direction: column;
    align-items: center;
    row-gap: calc(32px * var(--exploration-scale));
  }

  .case-exploration__board {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 100%;
  }

  .case-exploration__board-title {
    margin-bottom: calc(10px * var(--exploration-scale));
  }

  .case-exploration__board-image {
    width: 100%;
    max-width: calc(450px * var(--exploration-scale));
  }

  .case-exploration__arrow {
    transform: none;
  }
}

/* ==========================================================================
   Case study structuring the experience (Davis Grown, etc.)
   ========================================================================== */

.case-structure {
  --structure-scale: 1;
  background-color: var(--color-cream);
  padding-block: calc(80px * var(--structure-scale));
}

.case-structure--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-structure--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-structure--sville-library {
  --accent: var(--color-sville-library);
}

/* Same width-matching technique as .case-exploration__text/
   .case-understanding__text — keeps this section's title/body at the same
   x as the rest of the page's left-aligned text. */
.case-structure__text {
  max-width: calc(976px * var(--structure-scale));
  margin-inline: auto;
}

.case-structure__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--structure-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-structure__body {
  margin-top: calc(8px * var(--structure-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--structure-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-structure__body strong {
  font-weight: 700;
}

/* margin-top matches .case-structure__caption's own margin-top exactly —
   equal gap above and below the wireframe row, same "equal spacing"
   convention as .case-exploration__boards/.case-exploration__caption. */
/* minmax(0, cap) columns, not a flat flex row of fixed-width images —
   lets all three stay side by side all the way down to 639px (matching
   the site's one true "stack to mobile" breakpoint, same as the header
   hamburger and footer) instead of collapsing to 2-up at a much wider,
   arbitrarily-computed threshold. */
.case-structure__wireframes {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, calc(370px * var(--structure-scale))));
  column-gap: calc(30px * var(--structure-scale));
  justify-content: center;
  margin-top: calc(40px * var(--structure-scale));
}

/* Same subtle 4px as .case-exploration__board-image — a scanned sketch,
   not a browser window, so it gets just enough softening to not look
   sharp-cornered without reading as a "screen." */
.case-structure__wireframe {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 4px;
}

/* max-width matches .case-structure__text's own 976px column — was
   1200px, wider than the title/body/wireframe-row above it, so it spread
   past the rest of the section's established text column instead of
   aligning with it. */
.case-structure__caption {
  margin-top: calc(40px * var(--structure-scale));
  max-width: calc(976px * var(--structure-scale));
  margin-inline: auto;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--structure-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-structure__caption strong {
  font-weight: 700;
}

@media (max-width: 1489px) {
  .case-structure {
    --structure-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-structure {
    --structure-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-structure {
    --structure-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-structure {
    padding-block: calc(64px * var(--structure-scale));
  }

  .case-structure__text {
    max-width: 100%;
  }

  .case-structure__caption {
    max-width: 100%;
  }
}

/* 2-up + centered third, same fix as .case-motivations__grid — a fixed
   max-width on the grid (not just "1fr 1fr") keeps wireframe size
   consistent instead of ballooning at tablet widths within this range.
   639px, not a wider computed threshold — with the minmax columns above
   keeping all 3 side by side fluidly down to that point, this only needs
   to trigger at the site's one true "stack to mobile" breakpoint (same as
   the header hamburger and footer), not earlier. */
@media (max-width: 639px) {
  .case-structure__wireframes {
    display: grid;
    grid-template-columns: 1fr 1fr;
    max-width: 336px;
    margin-inline: auto;
    column-gap: 12px;
    row-gap: 12px;
  }

  .case-structure__wireframe {
    width: 100%;
  }

  .case-structure__wireframe:nth-child(3) {
    grid-column: 1 / -1;
    width: calc((100% - 12px) / 2);
    margin-inline: auto;
  }
}

/* ==========================================================================
   Case study early exploration sketches (Home At Last, etc.)
   ========================================================================== */

/* Centered, not left-aligned like .case-structure's own wireframe section —
   the screenshot shows this one as a centered intro (same treatment as
   .case-testing) followed by a centered pair of photos, not the site's
   usual left-aligned text-column pattern. */
.case-sketches {
  --accent: var(--color-scarlet);
  --sketches-scale: 1;
  background-color: var(--color-cream);
  padding-block: calc(80px * var(--sketches-scale));
  text-align: center;
}

.case-sketches--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-sketches--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-sketches--sville-library {
  --accent: var(--color-sville-library);
}

.case-sketches__text {
  max-width: calc(700px * var(--sketches-scale));
  margin-inline: auto;
}

.case-sketches__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--sketches-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-sketches__body {
  margin-top: calc(8px * var(--sketches-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--sketches-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-sketches__body strong {
  font-weight: 700;
}

/* Same minmax-column sizing as .case-structure__wireframes, just 2
   columns instead of 3 — matches Davis Grown's wireframe treatment
   exactly (same 370px cap, same 30px gap) rather than the larger
   shared-height sizing this used before. */
.case-sketches__images {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, calc(370px * var(--sketches-scale))));
  column-gap: calc(30px * var(--sketches-scale));
  justify-content: center;
  margin-top: calc(48px * var(--sketches-scale));
}

/* Plain-photography tier (scanned sketches, not a screen) — same 4px
   radius, no shadow, as .case-structure__wireframe/.case-exploration__board-image. */
.case-sketches__image {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 4px;
}

@media (max-width: 1489px) {
  .case-sketches {
    --sketches-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-sketches {
    --sketches-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-sketches {
    --sketches-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-sketches {
    padding-block: calc(64px * var(--sketches-scale));
  }

  .case-sketches__text {
    max-width: 100%;
  }
}

/* Unlike .case-structure__wireframes' 2-up 162px tier, these two photos
   each pack 4 separate phone-mockup sketches into one image — at the same
   162px width that content reads as illegible. Stacked instead, so each
   photo gets its own row.
   Fluid clamp (not a flat value) — going from 2-up to solo already means
   this grows from sharing a row (222px right above this breakpoint, at
   640px viewport) to having the whole row to itself; a single flat mobile
   size on top of that still read as one abrupt step rather than a
   graduated size change. This ramps from 260px (a modest, deliberate step
   up from 222px — solo reasonably deserves a bit more room than shared)
   at the top of this tier (639px) down to 220px (roughly matching the
   2-up size again) by the narrowest supported phone (320px) — normal
   "smaller viewport, smaller image" scaling throughout the tier itself,
   same two-point derivation as .case-challenge__image's own clamp. */
@media (max-width: 639px) {
  .case-sketches__images {
    grid-template-columns: 1fr;
    row-gap: calc(20px * var(--sketches-scale));
    justify-items: center;
  }

  .case-sketches__image {
    width: clamp(220px, calc(180px + 12.5vw), 260px);
  }
}

/* ==========================================================================
   Case study initial wireframes (Home At Last, etc.)
   ========================================================================== */

.case-wireframes {
  --accent: var(--color-scarlet);
  --wireframes-scale: 1;
  background-color: var(--color-white);
  padding-block: calc(80px * var(--wireframes-scale));
}

.case-wireframes--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-wireframes--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-wireframes--sville-library {
  --accent: var(--color-sville-library);
}

/* Same width-matching technique as .case-structure__text/
   .case-understanding__text — keeps this section's title/body at the same
   x as the rest of the page's left-aligned text. */
.case-wireframes__text {
  max-width: calc(976px * var(--wireframes-scale));
  margin-inline: auto;
}

.case-wireframes__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--wireframes-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-wireframes__body {
  margin-top: calc(8px * var(--wireframes-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--wireframes-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-wireframes__body p + p {
  margin-top: calc(32px * var(--wireframes-scale));
}

.case-wireframes__body strong {
  font-weight: 700;
}

.case-wireframes__columns {
  display: flex;
  justify-content: center;
  align-items: flex-end;
  column-gap: calc(100px * var(--wireframes-scale));
  margin-top: calc(62px * var(--wireframes-scale));
}

.case-wireframes__column {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Same Reenie Beanie floor as .case-exploration__board-title/
   .case-screens__site-title. */
.case-wireframes__column-title {
  margin: 0 0 calc(20px * var(--wireframes-scale));
  text-align: center;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(calc(40px * var(--wireframes-scale)), 30px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--color-blue-black);
}

/* Wireframe tier (4px radius, no shadow) — same as .case-structure__wireframe,
   not a screen-mockup shadow. Fixed height (not width) is what keeps the
   narrower mobile frame, the mid-width tablet frame, and the wide desktop
   frame reading as one matched row instead of three arbitrarily-sized
   images. */
.case-wireframes__image {
  display: block;
  width: auto;
  height: calc(800px * var(--wireframes-scale));
  max-width: 100%;
  border-radius: 4px;
}

@media (max-width: 1489px) {
  .case-wireframes {
    --wireframes-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-wireframes {
    --wireframes-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-wireframes {
    --wireframes-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-wireframes {
    padding-block: calc(64px * var(--wireframes-scale));
  }

  .case-wireframes__text {
    max-width: 100%;
  }
}

/* These wireframe exports are unusually tall/narrow (full scrolling-page
   mockups, ~390x2400px for mobile) — unlike .case-sketches__image/
   .case-homepage__grid-image's normal-aspect screenshots, capping by
   *width* here would force height up past 1700px on a phone. Staying
   height-driven keeps all three comfortably readable and side by side
   rather than one enormous stacked scroll.
   Fluid clamp (not a flat value) — same "zero jump entering the stack"
   fix as .case-challenge__image's own clamp at this breakpoint. A flat
   px here would either match the calc(800px*scale) value just above
   639px (in which case it's needlessly identical) or, sized for the
   narrowest phones like the old 325px value was, visibly *shrink*
   crossing into this tier and then visibly *grow* again crossing back
   out of it. This anchors both ends instead: ~480px right at 639px
   (exactly matching the calc(800px*0.6) value on the other side of the
   boundary) tapering down to 325px (the narrowest-phone-safe size) by a
   320px viewport, so there's no discontinuity at either edge. Same
   two-point derivation as .case-challenge__image's own clamp. */
@media (max-width: 639px) {
  .case-wireframes__columns {
    column-gap: clamp(16px, calc(-28px + 13.8vw), 60px);
  }

  .case-wireframes__image {
    height: clamp(325px, calc(169px + 48.6vw), 480px);
  }
}

/* ==========================================================================
   Case study refining the experience (Home At Last, etc.)
   ========================================================================== */

.case-refine {
  --accent: var(--color-scarlet);
  --refine-scale: 1;
  background-color: var(--color-cream);
  padding-block: calc(80px * var(--refine-scale));
}

.case-refine--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-refine--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-refine--sville-library {
  --accent: var(--color-sville-library);
}

/* Same fixed-content-column + 1fr-media + 976px-cap technique as
   .case-challenge__row — keeps this section's text at the same left edge
   as every other left-aligned section, independent of the media column's
   actual width. */
.case-refine__row {
  display: grid;
  grid-template-columns: calc(560px * var(--refine-scale)) 1fr;
  grid-template-areas: "content media";
  align-items: center;
  column-gap: calc(96px * var(--refine-scale));
  max-width: calc(976px * var(--refine-scale));
  margin-inline: auto;
  width: 100%;
}

.case-refine__content {
  grid-area: content;
}

.case-refine__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--refine-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-refine__body {
  margin-top: calc(8px * var(--refine-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--refine-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-refine__body p + p {
  margin-top: calc(32px * var(--refine-scale));
}

.case-refine__body strong {
  font-weight: 700;
}

.case-refine__media {
  grid-area: media;
  justify-self: start;
  margin-left: calc(50px * var(--refine-scale));
}

/* position:relative anchors the two arrows to the image's own box, so
   they stay pinned to "Donate"/"Ready to Adopt?" regardless of the
   image's own scaled size. 150px (not the original 260px) — sized to
   read proportionate to the text column beside it instead of towering
   over it; the tall wireframe export was rendering at ~816px tall
   against ~290px of text at desktop scale.
   135px floor (not a flat calc()*scale) — --refine-scale locks to 0.6
   for the whole 640-1023px tablet range, which shrank this down to a
   flat 90px there — noticeably small next to the ~330px-tall text column
   beside it. The floor only kicks in at scale ≤0.9, so desktop (150px,
   scale 1) is unaffected. */
.case-refine__screen-wrap {
  position: relative;
  width: max(calc(150px * var(--refine-scale)), 135px);
  max-width: 100%;
}

/* Same 4px radius as .case-wireframes__image/.case-structure__wireframe
   (the site's "photo" tier, not the 12px "screen mockup" tier — this
   still isn't a final polished screen). Shadow added back on top of that
   radius specifically for this section, since the annotated arrows call
   for a bit more visual weight/elevation than the plain mobile/tablet/
   desktop wireframes get. */
.case-refine__screen {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 4px;
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

/* Same arrow as .case-exploration__arrow/.case-screens__arrow (same
   viewBox, stroke-width, caps), recolored to this project's accent and
   rotated to point left instead of down — 90deg (not -90deg, which
   .case-exploration__arrow uses to point right) swings the source
   arrow's downward-pointing tip to the left, toward the screen.
   Positioned by percentage of the screen's own height (34.6%/92.3%,
   measured directly against the source image) so both arrows stay
   pinned to their buttons if the screen's rendered size changes.
   Negative margin-left (not a gap) — the tip should overlap onto the
   screen itself, pointing right at the button, not stop short beside it. */
.case-refine__arrow {
  position: absolute;
  left: 100%;
  width: auto;
  height: calc(60px * var(--refine-scale));
  margin-left: calc(-10px * var(--refine-scale));
  fill: none;
  stroke: var(--accent);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
  transform: translateY(-50%) rotate(90deg);
}

.case-refine__arrow--donate {
  top: 34.6%;
}

.case-refine__arrow--adopt {
  top: 92.3%;
}

@media (max-width: 1489px) {
  .case-refine {
    --refine-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-refine {
    --refine-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-refine {
    --refine-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-refine {
    padding-block: calc(64px * var(--refine-scale));
  }
}

/* Site's one true "stack to mobile" breakpoint. Arrows switch off percentage
   positioning to a flat px offset — at this narrow a screen width the
   percentage-based gap between arrow and screen edge got visually
   inconsistent with the rest of the stacked single-column content. */
@media (max-width: 639px) {
  .case-refine__row {
    grid-template-columns: 1fr;
    grid-template-areas: "content" "media";
    row-gap: calc(32px * var(--refine-scale));
    max-width: 100%;
    text-align: center;
  }

  .case-refine__content {
    text-align: left;
  }

  .case-refine__media {
    justify-self: center;
    margin-left: 0;
  }

  .case-refine__screen-wrap {
    width: 130px;
  }

  .case-refine__arrow {
    height: 44px;
    margin-left: -7px;
  }
}

/* ==========================================================================
   Case study visual system (Home At Last, etc.)
   ========================================================================== */

/* padding-top matches the 80px standard; padding-bottom stays smaller —
   same reasoning as .case-screens below — the arrow is the last element,
   so full 80px on top of the arrow's own 64px gap above it would read as
   extra dead space before the banner starts. */
.case-visual-system {
  --accent: var(--color-scarlet);
  --visual-system-scale: 1;
  background-color: var(--color-white);
  padding-top: calc(80px * var(--visual-system-scale));
  padding-bottom: calc(64px * var(--visual-system-scale));
}

.case-visual-system--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-visual-system--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-visual-system--sville-library {
  --accent: var(--color-sville-library);
}

/* 780px (not the usual 700px "centered intro" width) — wraps the intro
   to exactly 2 lines at desktop, matching the reference; 700px pushed it
   to 3. */
.case-visual-system__text {
  max-width: calc(780px * var(--visual-system-scale));
  margin-inline: auto;
  text-align: center;
}

.case-visual-system__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--visual-system-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-visual-system__body {
  margin-top: calc(8px * var(--visual-system-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--visual-system-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-visual-system__body strong {
  font-weight: 700;
}

/* Same minmax-column technique as .case-structure__wireframes — 3 equal
   columns that shrink fluidly with the container instead of hard-jumping
   to a stacked layout early. */
.case-visual-system__columns {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, calc(370px * var(--visual-system-scale))));
  column-gap: calc(30px * var(--visual-system-scale));
  justify-content: center;
  margin-top: calc(50px * var(--visual-system-scale));
}

.case-visual-system__column {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Same Reenie Beanie floor as .case-wireframes__column-title/
   .case-exploration__board-title. */
.case-visual-system__column-title {
  margin: 0 0 calc(10px * var(--visual-system-scale));
  text-align: center;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(calc(40px * var(--visual-system-scale)), 30px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--color-blue-black);
}

/* "Photo" tier (4px radius, no shadow) — same as .case-wireframes__image/
   .case-refine__screen, not the 12px "screen mockup" tier. */
.case-visual-system__image {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 4px;
}

/* Same arrow as .case-screens__arrow, recolored to this project's accent. */
.case-visual-system__arrow {
  display: block;
  margin: calc(64px * var(--visual-system-scale)) auto 0;
  width: auto;
  height: calc(70px * var(--visual-system-scale));
  fill: none;
  stroke: var(--accent);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (max-width: 1489px) {
  .case-visual-system {
    --visual-system-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-visual-system {
    --visual-system-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-visual-system {
    --visual-system-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-visual-system__text {
    max-width: 100%;
  }
}

/* Site's one true "stack to mobile" breakpoint — these screenshots carry
   real body copy (style guide labels, type specimens, contrast swatches),
   so squeezing 3 across at phone widths would read as illegible the same
   way the sketches/wireframes did. Stacked instead, same 82%/420px
   "supporting screenshot" size .case-connections__image/.case-homepage__grid-image
   use — these are normal-aspect landscape screenshots (unlike the
   extreme-tall wireframe exports elsewhere on this page), so unlike
   those, this can take the standard size without ballooning to an
   awkward height. Tried a zero-jump fluid clamp here first, but it capped
   out at 180px, which read as too small for the Typography specimen's
   own body copy — legibility wins over a perfectly smooth breakpoint
   transition for this content. */
@media (max-width: 639px) {
  .case-visual-system__columns {
    grid-template-columns: 1fr;
    row-gap: calc(40px * var(--visual-system-scale));
  }

  .case-visual-system__image {
    width: 82%;
    max-width: 420px;
    margin-inline: auto;
  }
}

/* ==========================================================================
   Case study from sketches to screens (Davis Grown, etc.)
   ========================================================================== */

/* padding-top matches the 80px standard every other major section on
   this page uses. padding-bottom stays intentionally smaller — the arrow
   is the last element in this section, so full 80px below it on top of
   the 64px gap the arrow already has above it would still read as extra
   dead space before the green banner starts. */
.case-screens {
  --screens-scale: 1;
  background-color: var(--color-white);
  padding-top: calc(80px * var(--screens-scale));
  padding-bottom: calc(64px * var(--screens-scale));
}

.case-screens--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-screens--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-screens--sville-library {
  --accent: var(--color-sville-library);
}

.case-screens__text {
  max-width: calc(976px * var(--screens-scale));
  margin-inline: auto;
}

.case-screens__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--screens-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-screens__body {
  margin-top: calc(8px * var(--screens-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--screens-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-screens__body strong {
  font-weight: 700;
}

/* Same Reenie Beanie floor as .case-exploration__board-title/
   .stitchery__role. Centered (not left-aligned like the title/body above)
   and independent of .case-screens__text's 976px column — it's centered
   in the full container, matching where the image below it sits. */
.case-screens__site-title {
  margin: calc(70px * var(--screens-scale)) 0 0;
  text-align: center;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(calc(40px * var(--screens-scale)), 30px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--color-blue-black);
}

/* box-shadow matches .bucknell__card's own formula — same "screen
   mockup" shadow treatment used sitewide for elevated visual elements. */
.case-screens__image {
  display: block;
  margin: calc(10px * var(--screens-scale)) auto 0;
  width: calc(933px * var(--screens-scale));
  max-width: 100%;
  height: auto;
  border-radius: 12px;
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

/* Same arrow as .about-hero__arrow/.case-exploration__arrow (same
   viewBox, stroke-width, caps), recolored to this project's accent — no
   rotation needed here since it already points down natively, matching
   this section's single top-to-bottom flow instead of the left-right
   pair in Visual Exploration. */
.case-screens__arrow {
  display: block;
  margin: calc(64px * var(--screens-scale)) auto 0;
  width: auto;
  height: calc(70px * var(--screens-scale));
  fill: none;
  stroke: var(--accent);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (max-width: 1489px) {
  .case-screens {
    --screens-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-screens {
    --screens-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-screens {
    --screens-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-screens {
    padding-block: calc(64px * var(--screens-scale));
  }

  .case-screens__text {
    max-width: 100%;
  }
}

/* ==========================================================================
   Case study full-width banner (Davis Grown, etc.)
   ========================================================================== */

/* Same 119px desktop height as .case-quote, but a completely different
   typographic treatment (40px bold heading vs. 22px regular sentence) —
   not a shared component, just matched container proportions so the two
   green strips read as the same family of banner at a glance. */
.case-banner {
  --accent: var(--color-scarlet);
  --banner-scale: 1;
  background-color: var(--accent);
  min-height: calc(119px * var(--banner-scale));
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-small) var(--container-padding);
  text-align: center;
}

.case-banner--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-banner--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-banner--sville-library {
  --accent: var(--color-sville-library);
}

.case-banner__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--banner-scale)), 24px);
  line-height: 1.05;
  letter-spacing: -0.05em;
  color: var(--color-white);
}

@media (max-width: 1489px) {
  .case-banner {
    --banner-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-banner {
    --banner-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-banner {
    --banner-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-banner {
    min-height: auto;
    padding-block: var(--space-medium);
  }
}

/* ==========================================================================
   Case study homepage walkthrough (Davis Grown, etc.)
   ========================================================================== */

.case-homepage {
  --homepage-scale: 1;
  background-color: var(--color-white);
  padding-block: calc(80px * var(--homepage-scale));
}

.case-homepage--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-homepage--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-homepage--sville-library {
  --accent: var(--color-sville-library);
}

/* Same width-matching technique as .case-screens__text/
   .case-understanding__text — keeps this section's title/body at the same
   x as the rest of the page's left-aligned text. */
.case-homepage__text {
  max-width: calc(976px * var(--homepage-scale));
  margin-inline: auto;
}

.case-homepage__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--homepage-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-homepage__body {
  margin-top: calc(8px * var(--homepage-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--homepage-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

/* Davis Grown's own .case-homepage__body only ever had one paragraph, so
   this was never needed there — Home At Last's two-paragraph body
   exposed the gap. Same 32px*scale rhythm as .case-understanding__body/
   .case-refine__body's own p+p rule. */
.case-homepage__body p + p {
  margin-top: calc(32px * var(--homepage-scale));
}

.case-homepage__body strong {
  font-weight: 700;
}

/* Same width, corner radius, and shadow as .case-screens__image — same
   "screen mockup" treatment reused rather than a one-off. */
.case-homepage__image {
  display: block;
  margin: calc(70px * var(--homepage-scale)) auto 0;
  width: calc(933px * var(--homepage-scale));
  max-width: 100%;
  height: auto;
  border-radius: 12px;
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

/* A second body paragraph continuing the section without repeating the
   title — same width/alignment as .case-homepage__text, but with its own
   margin-top (matching the equal-spacing convention already used above,
   below the previous image) instead of the 8px title-to-body gap that
   .case-homepage__body carries. */
.case-homepage__note {
  margin-top: calc(70px * var(--homepage-scale));
  max-width: calc(976px * var(--homepage-scale));
  margin-inline: auto;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--homepage-scale)), 16px);
  line-height: 1.6;
  text-align: center;
  color: var(--color-blue-black);
}

.case-homepage__note strong {
  font-weight: 700;
}

.case-homepage__grid {
  margin-top: calc(70px * var(--homepage-scale));
}

/* minmax(0, cap) instead of a flat calc() — a fixed-px column can't keep
   two 550px-capped images side by side once the container gets much
   narrower than 1150px, forcing a stack far earlier than necessary.
   minmax lets both columns shrink fluidly with the container (down from
   the 550px*scale cap) instead, so this stays 2-up all the way down to
   where .case-goals__circle actually needs to wrap (measured: 615-640px
   viewport) rather than collapsing well before that. */
.case-homepage__grid-row {
  display: grid;
  grid-template-columns: minmax(0, calc(550px * var(--homepage-scale))) minmax(0, calc(550px * var(--homepage-scale)));
  column-gap: calc(50px * var(--homepage-scale));
  justify-content: center;
  align-items: center;
}

.case-homepage__grid-row + .case-homepage__grid-row {
  margin-top: calc(50px * var(--homepage-scale));
}

/* Same corner radius + shadow as .case-homepage__image/.case-screens__image
   — same "screen mockup" treatment, just at a smaller size for this row of
   three supporting screenshots. */
.case-homepage__grid-image {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 12px;
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

.case-homepage__grid-caption p {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--homepage-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-homepage__grid-caption strong {
  font-weight: 700;
}

/* Home At Last only: three screenshots at once (wide hero + two narrower
   ones), instead of Davis Grown's single hero image followed by a
   separate 2-up grid. Grid with named areas (not flex) — same three
   images as the 639px breakpoint's stacked layout below, just "hero |
   about | footer" side by side here instead of "hero hero" / "about
   footer".
   Tried matching Davis Grown's 933px hero width here too, but that only
   fits sharing one row with both narrow crops if the row runs to
   ~1670px, well past the 1200px container — reverted back to this
   shared-height layout instead, which is what actually matches the
   reference. Shared height (not equal-width columns) — the three source
   images are all cropped to the exact same natural height (1442px) by
   design, at 1.30:1 (hero) and 0.49:1 (the other two) aspect ratios, so
   matching height is what reproduces their intended proportions instead
   of forcing them into even columns. */
/* 48px (not the usual 70px gap .case-homepage__image/.case-homepage__note
   use elsewhere) — this row sits noticeably taller/heavier than a single
   image, so the standard 70px gap above it read as more space than the
   body text needed. */
.case-homepage__trio {
  display: grid;
  grid-template-columns: auto auto auto;
  grid-template-areas: "hero about footer";
  align-items: end;
  justify-content: center;
  column-gap: calc(32px * var(--homepage-scale));
  margin-top: calc(48px * var(--homepage-scale));
}

.case-homepage__trio-hero {
  grid-area: hero;
}

.case-homepage__trio-about {
  grid-area: about;
}

.case-homepage__trio-footer {
  grid-area: footer;
}

/* Same screen-mockup treatment as .case-homepage__image/.case-homepage__grid-image
   — these are the final polished screens, not wireframes.
   480px height (not width) — sized so the row's total width (~1155px at
   desktop scale) expands past the 976px text column above it, matching
   how Davis Grown's own case-homepage__grid-row is allowed to run wider
   than its text. */
/* object-fit:contain is a safety net, not the primary fix — the real fix
   is the dedicated 780px breakpoint below switching to the stacked
   layout before the row ever runs out of room. Without either, a grid
   track this constrained silently overrides the img's own width:auto
   with whatever narrower width the track has left, and a plain <img>
   stretches to fill that box instead of respecting its aspect ratio —
   confirmed distorting the hero image (rendered 1.10:1 against its true
   1.30:1 natural ratio) at viewport widths between 640-757px, where the
   three images' combined natural width already exceeds the container. */
.case-homepage__trio-image {
  display: block;
  width: auto;
  height: calc(480px * var(--homepage-scale));
  object-fit: contain;
  border-radius: 12px;
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

@media (max-width: 1489px) {
  .case-homepage {
    --homepage-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-homepage {
    --homepage-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-homepage {
    --homepage-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-homepage {
    padding-block: calc(64px * var(--homepage-scale));
  }

  .case-homepage__text {
    max-width: 100%;
  }

  .case-homepage__note {
    max-width: 100%;
    text-align: left;
  }
}

/* With minmax columns above, the 2-column row now stays side by side all
   the way down to where .case-goals__circle actually needs to wrap
   (measured empirically: 615-640px viewport) instead of collapsing at a
   much wider, arbitrarily-computed threshold — 639px reuses the site's
   own existing breakpoint value, already just above that measured range. */
@media (max-width: 639px) {
  /* All images stack one per row — row-gap fills in for the desktop
     column-gap, which doesn't apply once there's only one column, and
     .case-homepage__grid-caption just becomes the next stacked item
     instead of sitting beside its image. justify-items:center matters
     specifically because the image below is capped narrower than this
     now-full-width single column — without it, grid's default
     "stretch/start" alignment leaves the capped-width image flush against
     the column's left edge instead of centered, with all the unused
     width sitting empty on the right. */
  .case-homepage__grid-row {
    grid-template-columns: 1fr;
    row-gap: calc(32px * var(--homepage-scale));
    justify-items: center;
  }

  .case-homepage__grid-row + .case-homepage__grid-row {
    margin-top: calc(32px * var(--homepage-scale));
  }

  /* Extra space specifically before the caption, on top of the row-gap
     it already inherits (32px*scale) — combined, this brings the total
     gap below the images to calc(70px*scale), exactly matching
     .case-homepage__grid's own margin-top above the images, so the
     images sit with equal breathing room on both sides once stacked. */
  .case-homepage__grid-caption {
    margin-top: calc(38px * var(--homepage-scale));
  }

  /* Matches .case-ecommerce__image's own mobile size exactly — every
     "smaller supporting screenshot" across the case study (this row's 3
     plus the e-commerce section's 4) should read as one consistent size,
     not vary by which section they happen to sit in. Still comfortably
     smaller than .case-homepage__image's own stacked width (~68-70%
     ratio to it, same hierarchy the 550px vs 933px desktop sizes
     establish), just no longer using a separate, slightly smaller value. */
  .case-homepage__grid-image {
    width: 82%;
    max-width: 420px;
  }

}

/* Dedicated breakpoint (not the usual 639px) — computed to be the exact
   point where the desktop 3-across row can no longer fit its natural
   width. At --homepage-scale's 0.6 floor (flat across 640-1023px), the
   three images' combined natural width is ~693px; the container has
   enough room for that down to a 757px viewport (container width minus
   its 64px padding). Below 757px the grid track would otherwise squeeze
   the images narrower than their aspect ratio allows — silently
   distorting them (see .case-homepage__trio-image's own comment) rather
   than just shrinking. 780px adds a ~23px safety margin above that exact
   crossover. Switching layouts here, before any compression can happen,
   is what actually prevents the distortion — object-fit:contain above is
   just a backstop.
   Hero keeps its own full-width row; About/Contact pair up side by side
   beneath it instead of all three stacking individually — same
   "hero | about | footer" grid areas as desktop, just reflowed into
   "hero hero" / "about footer". */
@media (max-width: 780px) {
  /* max-width + margin-inline:auto on the whole grid (not just the hero)
     — same 933px*scale formula as Davis Grown's own .case-homepage__image,
     but applied to the row's own box instead of just capping the hero
     inside a still-full-width row. That approach left About/Contact
     spanning the full container while the hero sat narrower above them,
     visibly wider than the hero's own edges. Capping the grid itself
     means About/Contact (each still just width:100% of their own 1fr
     column) automatically shrink to match, so the whole row's edges —
     hero included — line up as one aligned block instead of the pair
     overhanging past the hero on both sides. */
  .case-homepage__trio {
    grid-template-columns: 1fr 1fr;
    grid-template-areas: "hero hero" "about footer";
    column-gap: calc(20px * var(--homepage-scale));
    row-gap: calc(24px * var(--homepage-scale));
    align-items: start;
    max-width: calc(933px * var(--homepage-scale));
    margin-inline: auto;
  }

  .case-homepage__trio-hero {
    width: 100%;
    height: auto;
  }

  .case-homepage__trio-about,
  .case-homepage__trio-footer {
    width: 100%;
    height: auto;
  }
}

/* 8px (not the 20px*scale gap the 780px breakpoint above sets) — once
   container-padding itself drops (at this same 639px threshold),
   About/Contact have less room to share; tightening the gap is what
   pushes them from ~150px up toward the ~165px ceiling this narrower
   container still allows for a true side-by-side fit. */
@media (max-width: 639px) {
  .case-homepage__trio {
    column-gap: 8px;
  }
}

/* ==========================================================================
   Case study e-commerce tradeoff (Davis Grown, etc.)
   ========================================================================== */

/* --goals-scale is the same custom property .case-goals__circle already
   reads from its own section — defining it here (with the same tier
   values below) is what lets .case-goals__circle be reused verbatim as
   .case-ecommerce__circle's second class, with zero duplicated circle CSS,
   instead of copying that whole component under a new name. */
.case-ecommerce {
  --ecommerce-scale: 1;
  --goals-scale: 1;
  position: relative;
  background-color: var(--color-white);
  padding-block: calc(80px * var(--ecommerce-scale));
}

.case-ecommerce--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-ecommerce--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-ecommerce--sville-library {
  --accent: var(--color-sville-library);
}

/* Positioning only — .case-goals__circle (the shared second class) owns
   size, color, and type. A float (not position:absolute): the title is
   short and left-aligned so it never reaches the circle's column, but
   the body copy right below it runs full-width and its first line does
   land under the circle (the line break lands right around "...aligned
   with" / "Davis Grown's..." at the default scale) — a float wraps that
   line around the circle automatically instead of needing to hand-check
   every scale tier for where the body text happens to break. Negative
   margin-top pulls it up so its lower half overlaps the section's own
   top edge/title row, the same "floating accent" placement Circle
   Callouts use elsewhere and what the Figma reference shows. margin-right
   is negative to reach past the text column's own (976px*scale) right
   edge out to the images' own right edge — the gap between those two
   edges (~87px at full scale) is smaller than the circle, which is why
   plain in-column floating isn't enough on its own here. */
.case-ecommerce__circle {
  float: right;
  margin: calc(-80px * var(--ecommerce-scale) - 60px * var(--goals-scale)) calc(-87px * var(--ecommerce-scale)) 0
    calc(20px * var(--ecommerce-scale));
}

.case-ecommerce__text {
  display: flow-root;
  max-width: calc(976px * var(--ecommerce-scale));
  margin-inline: auto;
}

.case-ecommerce__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--ecommerce-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-ecommerce__body {
  margin-top: calc(8px * var(--ecommerce-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--ecommerce-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-ecommerce__body p + p {
  margin-top: calc(24px * var(--ecommerce-scale));
}

.case-ecommerce__body strong {
  font-weight: 700;
}

.case-ecommerce__grid {
  margin-top: calc(70px * var(--ecommerce-scale));
}

/* Same minmax(0, cap) + fluid-shrink technique as .case-homepage__grid-row
   — stays side by side down to 639px (the site's one true "stack to
   mobile" breakpoint) instead of a wider, one-off threshold. */
.case-ecommerce__row {
  display: grid;
  grid-template-columns: minmax(0, calc(550px * var(--ecommerce-scale))) minmax(0, calc(550px * var(--ecommerce-scale)));
  column-gap: calc(50px * var(--ecommerce-scale));
  justify-content: center;
  align-items: start;
}

.case-ecommerce__row + .case-ecommerce__row {
  margin-top: calc(50px * var(--ecommerce-scale));
}

/* Same corner radius + shadow as .case-homepage__grid-image/.case-screens__image
   — same "screen mockup" treatment reused rather than a one-off. */
.case-ecommerce__image {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 12px;
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

/* The caption sits under the left ("footer-davis") image specifically,
   not centered under the pair or spanning full width — an explicit
   sub-grid area under column 1 only, with column 2 left empty beside it,
   instead of a separate full-width element that would need its own
   left-alignment math to line up with that column. */
.case-ecommerce__row--caption {
  grid-template-areas: "img1 img2" "caption .";
  row-gap: calc(16px * var(--ecommerce-scale));
}

.case-ecommerce__row--caption .case-ecommerce__image:first-child {
  grid-area: img1;
}

.case-ecommerce__row--caption .case-ecommerce__image:last-of-type {
  grid-area: img2;
}

.case-ecommerce__caption {
  grid-area: caption;
  margin: 0;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--ecommerce-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-ecommerce__caption strong {
  font-weight: 700;
}

@media (max-width: 1489px) {
  .case-ecommerce {
    --ecommerce-scale: 0.85;
    --goals-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-ecommerce {
    --ecommerce-scale: 0.72;
    --goals-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-ecommerce {
    --ecommerce-scale: 0.6;
    --goals-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-ecommerce {
    padding-block: calc(64px * var(--ecommerce-scale));
  }

  .case-ecommerce__text {
    max-width: 100%;
  }

  /* .case-ecommerce__text is 100%-wide at this tier (not the 976px*scale
     column the desktop margin-right was calibrated against), so its own
     right edge already sits at the container's content edge — margin-right
     just needs to be the positive inset from that edge instead of a
     negative reach-past-the-column value. padding-block also dropped to
     64px here, so margin-top's poke needs the matching base value. */
  .case-ecommerce__circle {
    margin-top: calc(-64px * var(--ecommerce-scale) - 40px * var(--goals-scale));
    margin-right: calc((25px * var(--ecommerce-scale)) + (60px * var(--ecommerce-scale)));
  }
}

/* Stacks in step with .case-homepage__grid-row — one image per row below
   639px, with the same fluid-then-stack sizing (70% / 380px cap) and the
   same equal-spacing-above/below-images convention. */
@media (max-width: 639px) {
  .case-ecommerce__row {
    grid-template-columns: 1fr;
    row-gap: calc(32px * var(--ecommerce-scale));
    justify-items: center;
  }

  .case-ecommerce__row--caption {
    grid-template-areas: "img1" "img2" "caption";
  }

  .case-ecommerce__row + .case-ecommerce__row {
    margin-top: calc(32px * var(--ecommerce-scale));
  }

  .case-ecommerce__caption {
    margin-top: calc(38px * var(--ecommerce-scale));
  }

  .case-ecommerce__image {
    width: 82%;
    max-width: 420px;
  }

  /* At this width the title itself runs full-width (no room left over to
     its right), so unlike desktop/tablet it also needs to wrap around the
     circle, not just the body copy below it — a smaller poke-up than the
     desktop/tablet tiers keeps it from riding up too far given how little
     vertical room a stacked mobile layout has to spare. */
  .case-ecommerce__circle {
    margin: calc(-40px * var(--goals-scale)) 8px 8px 8px;
  }
}

/* ==========================================================================
   Case study finding a forever home (Home At Last, etc.)
   ========================================================================== */

/* --goals-scale defined here (with matching tier values below) the same
   way .case-ecommerce does, so .case-goals__circle can be reused verbatim
   as .case-adoption__circle's second class. */
/* White (not the alternating cream this section's own accent scale would
   otherwise get) — .case-homepage right above it is also white; a
   deliberate same-color run here, both part of one continuous "final
   result" arc, same exception CLAUDE.md documents for this pattern.
   130px top / 80px bottom (not symmetric) — .case-homepage right above
   already carries its own bottom padding, so a matching 80px top here
   read as too tight against its image row; the extra 50px opens up real
   breathing room above the title/circle without touching the circle's
   own position relative to the two paragraphs below it (the whole
   text+circle block just starts lower, together). */
.case-adoption {
  --accent: var(--color-scarlet);
  --adoption-scale: 1;
  --goals-scale: 1;
  position: relative;
  background-color: var(--color-white);
  /* Top padding is smaller than the usual 80px, same reasoning as
     .case-connections — this section runs right into .case-homepage
     above it with no color change to break up the transition, so a
     full-size top padding reads as an oversized gap where a section
     boundary would normally justify it. */
  padding-top: calc(40px * var(--adoption-scale));
  padding-bottom: calc(80px * var(--adoption-scale));
}

.case-adoption--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-adoption--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-adoption--sville-library {
  --accent: var(--color-sville-library);
}

/* Started as the exact .case-ecommerce__circle formula (a negative
   top-margin poking it up above the title), then pulled the other way
   entirely — a positive top margin (57px) now pushes it *down* past the
   title into the body copy, centered in the gap between the two
   paragraphs (measured: circle center at title-top+156, paragraph gap's
   own center at title-top+156.4) instead of sitting mostly over the
   first paragraph. */
.case-adoption__circle {
  float: right;
  margin: calc(57px * var(--adoption-scale)) calc(-87px * var(--adoption-scale)) 0 calc(20px * var(--adoption-scale));
}

.case-adoption__text {
  display: flow-root;
  max-width: calc(976px * var(--adoption-scale));
  margin-inline: auto;
}

.case-adoption__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--adoption-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-adoption__body {
  margin-top: calc(8px * var(--adoption-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--adoption-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-adoption__body p + p {
  margin-top: calc(32px * var(--adoption-scale));
}

.case-adoption__body strong {
  font-weight: 700;
}

/* Same Reenie Beanie floor as .case-wireframes__column-title/
   .case-exploration__board-title. */
.case-adoption__label {
  margin: 0;
  text-align: center;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(calc(40px * var(--adoption-scale)), 30px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--color-blue-black);
}

/* 888px — spans exactly the combined width of the desktop+mobile pair
   below it (622 + 32 gap + 234 ≈ 888), not the desktop image alone, per
   spec — same left/right edges as that whole row, not just its first
   image. */
.case-adoption__filter-wrap {
  width: calc(888px * var(--adoption-scale));
  max-width: 100%;
  margin: calc(62px * var(--adoption-scale)) auto 0;
}

.case-adoption__filter-label {
  margin-bottom: calc(10px * var(--adoption-scale));
}

/* Screen-mockup tier (12px radius + shadow) — this is the actual live
   "before" UI, not a wireframe. */
.case-adoption__filter-image {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 12px;
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

.case-adoption__vs-label {
  margin-top: calc(64px * var(--adoption-scale));
  margin-bottom: calc(10px * var(--adoption-scale));
}

/* Shared height (not equal-width columns) — same technique as
   .case-homepage__trio: the desktop and mobile "after" screenshots are
   cropped to the exact same natural height (1464px) by design, at
   1.30:1 (desktop) and 0.49:1 (mobile) aspect ratios, so matching height
   is what reproduces that relationship instead of forcing even columns. */
.case-adoption__vs-row {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: calc(32px * var(--adoption-scale));
}

.case-adoption__vs-image {
  display: block;
  border-radius: 12px;
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

/* 478px height — at this image's 1.30:1 aspect ratio, resolves its width
   to ~622px, matching .case-homepage__trio-hero (the "Meet Jovie"
   desktop screenshot in the section above this one) exactly, per spec. */
.case-adoption__vs-desktop {
  width: auto;
  height: calc(478px * var(--adoption-scale));
}

/* 234.72px width — not shared height with the desktop image beside it
   (they only coincidentally nearly matched that way) — matches
   .case-homepage__trio-about/__trio-footer's own width exactly, per
   spec, since those are the "About Us"/"Contact Information" screens
   this is meant to size against. */
.case-adoption__vs-mobile {
  width: calc(234.72px * var(--adoption-scale));
  height: auto;
}

@media (max-width: 1489px) {
  .case-adoption {
    --adoption-scale: 0.85;
    --goals-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-adoption {
    --adoption-scale: 0.72;
    --goals-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-adoption {
    --adoption-scale: 0.6;
    --goals-scale: 0.6;
  }
}

@media (max-width: 849px) {
  /* Same values as .case-connections' matching breakpoint override. */
  .case-adoption {
    padding-top: calc(32px * var(--adoption-scale));
    padding-bottom: calc(64px * var(--adoption-scale));
  }

  .case-adoption__text {
    max-width: 100%;
  }

  /* Margin-top NOT overridden here — reuses the base rule's own 57px*scale
     value so the circle hugs the paragraph text the same way it does at
     full desktop width, instead of poking up above the section (that
     older "cancel the top padding" approach left it stranded in the gap
     above the title once the section's own top padding grew). Margin-right
     is still overridden: same adjustment as .case-ecommerce__circle's own
     849px override — .case-adoption__text is 100%-wide at this tier, so it
     needs to be a positive inset from the container's own edge instead of
     a negative reach-past-the-column value. */
  .case-adoption__circle {
    margin-right: calc((25px * var(--adoption-scale)) + (60px * var(--adoption-scale)));
  }
}

/* Site's one true "stack to mobile" breakpoint. */
@media (max-width: 639px) {
  /* Same reduced poke-up as .case-ecommerce__circle's own 639px
     override — the title runs full-width here too, so it wraps around
     the circle same as the body does. Right margin pulled negative
     (bleeding slightly past the container's own right edge, the same
     "reach past the edge" idea the desktop margin already uses) instead
     of the previous 8px inset — that read as sitting too close to the
     title/heading text on its left; shifting it further right opens up
     more breathing room between the two. */
  .case-adoption__circle {
    margin: calc(-40px * var(--goals-scale)) 24px 8px 8px;
  }

  .case-adoption__vs-row {
    flex-direction: column;
    align-items: center;
    gap: calc(32px * var(--adoption-scale));
  }

  /* Drops the shared-height rule once stacked. Same width formula as
     .case-homepage__trio-hero's own stacked-mode sizing (933px there,
     622px here to match its own target) — matches the homepage hero at
     every width instead of switching to the generic 82%/420px
     "supporting screenshot" size once stacked. */
  .case-adoption__vs-desktop {
    width: calc(622px * var(--adoption-scale));
    max-width: 100%;
    height: auto;
  }

  /* 50% (minus half the row-gap) instead of a fixed px value — matches
     .case-homepage__trio-about/__trio-footer's own mobile width, which
     is itself just "half the container" (they're paired side by side in
     a 2-up grid). This image is alone in its own stacked row here, not
     paired, so this reproduces that same half-container proportion
     directly rather than needing a matching grid. Centered underneath
     the desktop screen above it via .case-adoption__vs-row's own
     align-items:center. */
  .case-adoption__vs-mobile {
    width: calc(50% - 4px);
    height: auto;
    margin-inline: auto;
  }
}

/* Matches .case-adoption__vs-desktop's own mobile width exactly (not the
   888px "combined row" value the base rule uses) — once the row above
   stacks, "spans the width of the two below" no longer means a
   side-by-side pair, just the desktop screen alone. */
@media (max-width: 639px) {
  .case-adoption__filter-wrap {
    width: calc(622px * var(--adoption-scale));
    max-width: 100%;
  }
}

/* ==========================================================================
   Case study dog profile experience (Home At Last, etc.)
   ========================================================================== */

.case-profile {
  --accent: var(--color-scarlet);
  --profile-scale: 1;
  background-color: var(--color-cream);
  padding-block: calc(80px * var(--profile-scale));
}

.case-profile--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-profile--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-profile--sville-library {
  --accent: var(--color-sville-library);
}

.case-profile__text {
  max-width: calc(976px * var(--profile-scale));
  margin-inline: auto;
}

.case-profile__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--profile-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-profile__body {
  margin-top: calc(8px * var(--profile-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--profile-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-profile__body p + p {
  margin-top: calc(32px * var(--profile-scale));
}

.case-profile__body strong {
  font-weight: 700;
}

.case-profile__columns {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: calc(40px * var(--profile-scale));
  margin-top: calc(62px * var(--profile-scale));
}

.case-profile__column {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Same Reenie Beanie floor as .case-wireframes__column-title/
   .case-visual-system__column-title. */
.case-profile__column-title {
  margin: 0 0 calc(10px * var(--profile-scale));
  text-align: center;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(calc(40px * var(--profile-scale)), 30px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--color-blue-black);
}

/* Screen-mockup tier (12px radius + shadow) — these are the final
   interactive prototypes, not wireframes. */
.case-profile__image {
  display: block;
  border-radius: 12px;
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

/* Width-locked to 622px — matches .case-adoption__vs-desktop's own width
   exactly (which itself matches .case-homepage__trio-hero), per spec.
   Height follows from this image's own ~0.876:1 aspect ratio. */
.case-profile__image--desktop {
  width: calc(622px * var(--profile-scale));
  height: auto;
}

/* Shared height (not equal-width columns) — matched to the desktop image's
   own computed height at 622px wide (709.81px, from its 0.876:1 aspect
   ratio) so the two stay a matched pair, same technique as
   .case-homepage__trio and the row above. */
.case-profile__image--mobile {
  width: auto;
  height: calc(709.81px * var(--profile-scale));
}

@media (max-width: 1489px) {
  .case-profile {
    --profile-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-profile {
    --profile-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-profile {
    --profile-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-profile {
    padding-block: calc(64px * var(--profile-scale));
  }

  .case-profile__text {
    max-width: 100%;
  }
}

/* Site's one true "stack to mobile" breakpoint. Drops the shared-height
   rule once stacked, same as .case-adoption__vs-row above — each image
   gets its own width matched to that section's equivalent screen instead,
   per spec, even though the mobile/tablet mockup is naturally much
   taller. */
@media (max-width: 639px) {
  .case-profile__columns {
    flex-direction: column;
    align-items: center;
    gap: calc(40px * var(--profile-scale));
  }

  /* Matches .case-adoption__vs-mobile's own mobile width exactly (50% of
     the container, minus half the row-gap) — same "About Us/Contact
     Information" proportion, reused here for the equivalent mobile/tablet
     screen. */
  .case-profile__image--mobile {
    width: calc(50% - 4px);
    height: auto;
    margin-inline: auto;
  }

  /* Matches .case-adoption__vs-desktop's own mobile width exactly
     (622px, capped to the container). */
  .case-profile__image--desktop {
    width: calc(622px * var(--profile-scale));
    max-width: 100%;
    height: auto;
  }
}

/* ==========================================================================
   Case study supporting local connections (Davis Grown, etc.)
   ========================================================================== */

.case-connections {
  --connections-scale: 1;
  background-color: var(--color-white);
  /* Top padding is smaller than the usual 80px — this section runs right
     into .case-ecommerce above it with no color change to break up the
     transition, so a full 80px+80px pairing reads as an oversized gap
     where a section boundary would normally justify it. */
  padding-top: calc(40px * var(--connections-scale));
  padding-bottom: calc(80px * var(--connections-scale));
}

.case-connections--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-connections--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-connections--sville-library {
  --accent: var(--color-sville-library);
}

.case-connections__title {
  margin: 0;
  text-align: center;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--connections-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

/* image + text combined width matches .case-challenge__content + gap +
   .case-challenge__image's own 976px total (550 + 50 + 376) — same
   x-alignment convention as the rest of the page's two-column rows,
   just mirrored (image left, text right) to match this section's own
   screenshot. minmax(0, cap) on both columns, not flat calc() — lets the
   row shrink fluidly with the container instead of hard-jumping to the
   639px stacked layout early. */
.case-connections__row {
  display: grid;
  grid-template-columns: minmax(0, calc(550px * var(--connections-scale))) minmax(0, calc(376px * var(--connections-scale)));
  column-gap: calc(50px * var(--connections-scale));
  justify-content: center;
  align-items: center;
  margin-top: calc(42px * var(--connections-scale));
}

/* Same corner radius + shadow as .case-homepage__grid-image/
   .case-ecommerce__image — same "screen mockup" treatment, same 550px
   width, reused rather than a one-off size for this section's image. */
.case-connections__image {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 12px;
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

.case-connections__text p {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--connections-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-connections__text strong {
  font-weight: 700;
}

@media (max-width: 1489px) {
  .case-connections {
    --connections-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-connections {
    --connections-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-connections {
    --connections-scale: 0.6;
  }
}

/* Same reasoning as .case-handoff__inner's own 849px switch — below this,
   the text column picks up extra line wraps faster than the image
   shrinks, so centering starts reading odd against a noticeably taller
   text block. */
@media (max-width: 849px) {
  .case-connections {
    padding-top: calc(32px * var(--connections-scale));
    padding-bottom: calc(64px * var(--connections-scale));
  }

  .case-connections__row {
    align-items: start;
  }
}

/* Stacks in step with .case-homepage__grid-row/.case-ecommerce__row — one
   column below 639px (the site's one true "stack to mobile" breakpoint),
   image above text, with the same 82%/420px "supporting screen" sizing
   used everywhere else. */
@media (max-width: 639px) {
  .case-connections__row {
    grid-template-columns: 1fr;
    row-gap: calc(32px * var(--connections-scale));
    justify-items: center;
  }

  .case-connections__image {
    width: 82%;
    max-width: 420px;
  }

  .case-connections__text {
    text-align: left;
  }
}

/* ==========================================================================
   Case study hand off (Davis Grown, etc.)
   ========================================================================== */

/* flex, not grid — .case-handoff__image is a tall document mockup sized by
   height (502px), not the usual fixed-width "screen" treatment, so its own
   width is whatever its natural aspect ratio resolves to instead of a
   column track. justify-content:center keeps the image+text pair centered
   as a unit within the wider container, matching every other paired row's
   own centering convention. */
.case-handoff {
  --handoff-scale: 1;
  background-color: var(--color-cream);
  padding-block: calc(80px * var(--handoff-scale));
}

.case-handoff--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-handoff--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-handoff--sville-library {
  --accent: var(--color-sville-library);
}

.case-handoff__inner {
  display: flex;
  justify-content: center;
  align-items: center;
  column-gap: calc(107px * var(--handoff-scale));
}

/* Subtle 4px, not the 12px "screen mockup" radius — a document page, not
   a browser window, so it gets just enough softening to not look
   sharp-cornered without reading as a "screen." Shadow kept (unlike
   .case-structure__wireframe/.case-exploration__board-image) so it lifts
   off the cream background instead of blending flat into it. */
.case-handoff__image {
  display: block;
  flex-shrink: 0;
  height: calc(502px * var(--handoff-scale));
  width: auto;
  max-width: 100%;
  border-radius: 4px;
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

.case-handoff__text {
  max-width: calc(420px * var(--handoff-scale));
}

.case-handoff__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--handoff-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-handoff__body {
  margin-top: calc(8px * var(--handoff-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--handoff-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-handoff__body p + p {
  margin-top: calc(24px * var(--handoff-scale));
}

.case-handoff__body strong {
  font-weight: 700;
}

@media (max-width: 1489px) {
  .case-handoff {
    --handoff-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-handoff {
    --handoff-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-handoff {
    --handoff-scale: 0.6;
  }
}

@media (max-width: 849px) {
  .case-handoff {
    padding-block: calc(64px * var(--handoff-scale));
  }
}

/* Stacks in step with the rest of the site's paired rows — one column
   below 639px, text then the manual, matching source order. Image keeps
   the same height-driven sizing as desktop (no flat width/max-width
   override) so it keeps scaling down proportionally with --handoff-scale
   instead of jumping to a flat mobile size that reads oversized next to a
   document this tall. */
@media (max-width: 639px) {
  .case-handoff__inner {
    flex-direction: column;
    align-items: center;
    row-gap: calc(32px * var(--handoff-scale));
  }

  .case-handoff__text {
    max-width: 100%;
  }
}

/* ==========================================================================
   Case study reflection (Davis Grown, etc.)
   ========================================================================== */

.case-reflection {
  --reflection-scale: 1;
  position: relative;
  background-color: var(--color-white);
  padding-block: calc(80px * var(--reflection-scale));
}

.case-reflection--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-reflection--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-reflection--sville-library {
  --accent: var(--color-sville-library);
}

/* Same max-width:976px*scale + margin-inline:auto pattern every other
   section's text column uses — keeps "Reflection" at the exact same left
   edge as the rest of the page. display:flow-root contains the floated
   star below (so the box's own height still accounts for it) without
   clipping anything, same reason .case-ecommerce__text uses it. */
.case-reflection__text {
  display: flow-root;
  max-width: calc(976px * var(--reflection-scale));
  margin-inline: auto;
}

.case-reflection__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--reflection-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--accent);
}

.case-reflection__body {
  margin-top: calc(8px * var(--reflection-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--reflection-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.case-reflection__body strong {
  font-weight: 700;
}

/* assets/icons/green-star.svg — the same hand-drawn star used elsewhere,
   pre-colored in the file itself rather than a currentColor asset, so
   this reads as a fixed Davis Grown green accent rather than
   per-project-recolorable like .case-hero__star's inline version. Same
   12.88deg rotation .case-hero__star already uses. Treated like
   .case-ecommerce__circle: a float inside the text column (not an
   absolutely-positioned element bleeding past the container edge) — the
   title/body wrap around it naturally, sitting right up against the
   heading the same way the hero's own star sits close beside "Davis
   Grown" on desktop. */
.case-reflection__star {
  float: right;
  width: calc(150px * var(--reflection-scale));
  height: auto;
  margin: 0 0 calc(8px * var(--reflection-scale)) calc(10px * var(--reflection-scale));
  transform: rotate(12.88deg);
}

@media (max-width: 1489px) {
  .case-reflection {
    --reflection-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .case-reflection {
    --reflection-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .case-reflection {
    --reflection-scale: 0.6;
  }
}

/* Being a float (not the old absolute/flex-stack treatment), the star
   doesn't need a breakpoint-specific "hide" or "stack below" rule at
   all — same as .case-ecommerce__circle, the title/body just keep
   wrapping around it at every width down to mobile, keeping the motif
   present everywhere instead of only on wide screens. */
@media (max-width: 849px) {
  .case-reflection {
    padding-block: calc(64px * var(--reflection-scale));
  }

  .case-reflection__text {
    max-width: 100%;
  }
}

/* ==========================================================================
   Case study reflection banner (Davis Grown, etc.)
   ========================================================================== */

/* Same red-strip family as .hero-strip/.case-quote/.connect-banner —
   119px min-height like .case-quote/.connect-banner, but .hero-strip's
   own leading-text-plus-link structure and hover motion (scale+translateX
   on the link only, not the whole strip) since this needs an actual link
   to the homepage's case studies, not just a plain line or a JS-driven
   full-strip click target. */
.case-reflection-banner {
  --accent: var(--color-scarlet);
  background-color: var(--accent);
  min-height: 119px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-small) var(--container-padding);
  text-align: center;
}

.case-reflection-banner--davis-grown {
  --accent: var(--color-davis-grown);
}

.case-reflection-banner--home-at-last {
  --accent: var(--color-home-at-last);
}

.case-reflection-banner--sville-library {
  --accent: var(--color-sville-library);
}

.case-reflection-banner__text {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 22px;
  line-height: 1.3;
  color: var(--color-white);
}

.case-reflection-banner__link {
  display: inline-block;
  font-weight: 300;
  color: var(--color-white);
  transform-origin: left center;
  transition: transform var(--transition-fast);
}

.case-reflection-banner__arrow,
.case-reflection-banner__link-text {
  display: inline-block;
}

.case-reflection-banner__link:hover,
.case-reflection-banner__link:focus-visible {
  transform: scale(1.05) translateX(4px);
}

@media (max-width: 1489px) {
  .case-reflection-banner__text {
    font-size: 20px;
  }
}

@media (max-width: 1249px) {
  .case-reflection-banner__text {
    font-size: 19px;
  }
}

@media (max-width: 1023px) {
  .case-reflection-banner {
    min-height: 93px;
  }

  .case-reflection-banner__text {
    font-size: 17px;
  }
}

@media (max-width: 849px) {
  .case-reflection-banner__text {
    white-space: normal;
    max-width: 400px;
    font-size: 16px;
  }
}

@media (max-width: 639px) {
  .case-reflection-banner {
    min-height: 70px;
  }

  .case-reflection-banner__text {
    max-width: 330px;
    font-size: 14px;
  }
}

/* ==========================================================================
   Brain break (Sugar homepage entry point)
   ========================================================================== */

/* This section previews the Sugar game itself, so it deliberately borrows
   the game's own type system (Itim + Poppins) and palette instead of the
   sitewide Hind/Reenie Beanie — a contained, intentional exception. */
.brain-break {
  --game-brown: #6e3731;
  --bb-scale: 1;
  --bb-image-scale: 1;
  /* Continuous (not stepped) — fluidly interpolates between 0.85 at 1489px
     and a 0.7 floor at 639px and below, instead of jumping between fixed
     per-breakpoint values. Drives the banner, both Itim headings, and the
     peeking logo (see .brain-break__peek) below 1489px, so their
     proportions relative to each other never change — the logo stays
     correctly "peeking" at every width instead of needing hand-tuned
     positions per breakpoint. */
  --bb-text-scale: clamp(0.7, calc(0.7 + 0.15 * (100vw - 639px) / 850px), 0.85);
  background-color: #fee4ff;
}

/* All four values here (height, title size/line-height, padding) were
   scaled down ~10% together — the section was reading a bit overwhelming
   at full desktop size. */
.brain-break__banner {
  position: relative;
  height: calc(166px * var(--bb-scale));
  background-color: var(--color-sugar);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: calc(31px * var(--bb-scale));
}

.brain-break__banner-title {
  margin: 0;
  font-family: "Itim", cursive;
  font-weight: 400;
  font-size: calc(40px * var(--bb-scale));
  line-height: calc(25px * var(--bb-scale));
  letter-spacing: 0;
  color: var(--color-white);
}

/* Mirrors the proportions of the Sugar game's own header logo (peeking dog
   mostly inside its pink band, only its chin dipping below): visible-dog
   height ≈ 69% of the banner height, positioned so ~37% of the banner sits
   above it and it dips ~6% of the banner height past the bottom edge.
   Cropped to its visible artwork (not the source SVG's full canvas), so
   this width is the actual drawn width, not a bounding box. */
.brain-break__peek {
  position: absolute;
  top: calc(68px * var(--bb-scale));
  left: 50%;
  transform: translateX(-50%);
  width: calc(162px * var(--bb-scale));
  height: auto;
  z-index: 3;
}

/* padding-top clears the small portion of .brain-break__peek that dips past
   the banner (see its comment above) so neither column starts underneath it.
   Floored at 40px so tablet widths (where --bb-scale shrinks toward 0.6)
   still keep a clear, visible gap above the pancake image instead of the
   formula alone squeezing it thin. */
.brain-break__row {
  display: grid;
  grid-template-columns: max(calc(460px * var(--bb-scale)), 350px) calc(330px * var(--bb-image-scale));
  grid-template-areas: "content media";
  align-items: center;
  justify-content: center;
  column-gap: calc(40px * var(--bb-scale));
  padding-top: max(calc(40px * var(--bb-scale)), 40px);
  padding-bottom: calc(32px * var(--bb-scale));
}

.brain-break__content {
  grid-area: content;
}

/* font-size intentionally matches .brain-break__banner-title exactly at
   every tier (same value/formula duplicated at each breakpoint below) so
   "A Day in the Life of Sugar" and "Need a Brain Break?" always read as
   the same size. */
.brain-break__title {
  margin: 0;
  font-family: "Itim", cursive;
  font-weight: 400;
  font-size: calc(40px * var(--bb-scale));
  line-height: 1.2;
  letter-spacing: 0;
  color: var(--game-brown);
  /* Always stays on one line — checked against the available column width
     at every breakpoint down to 320px. */
  white-space: nowrap;
}

/* Fluid between 16px (1489px+) and a 14px floor (639px and below) instead
   of a flat 16px that then snaps straight to 14px at one breakpoint. */
.brain-break__desc {
  margin: 19px 0 0;
  max-width: max(calc(390px * var(--bb-scale)), 330px);
  font-family: "Poppins", sans-serif;
  font-weight: 300;
  font-size: clamp(14px, calc(14px + 2px * (100vw - 639px) / 850px), 16px);
  line-height: 1.4;
  color: #000000;
}

.brain-break__desc strong {
  font-weight: 300;
}

/* Same shape/feel as the game's own .button — rounded, brown, no shadow —
   and scales down with the rest of the section via --bb-scale instead of
   staying at full desktop size until a single breakpoint far down, floored
   at the game's own current mobile button dimensions (10px 20px / 1.35rem)
   so it never shrinks past what the game itself uses on phones. */
.brain-break__button {
  display: inline-flex;
  align-items: center;
  margin-top: calc(30px * var(--bb-scale));
  padding: max(calc(12px * var(--bb-scale)), 10px) max(calc(24px * var(--bb-scale)), 20px);
  border-radius: 12px;
  background-color: var(--game-brown);
  transition: transform var(--transition-fast);
}

.brain-break__button-text {
  font-family: "Itim", cursive;
  font-weight: 400;
  font-size: max(calc(24px * var(--bb-scale)), 21.6px);
  /* The sitewide body{line-height:1.5} would otherwise inherit here and
     make this button noticeably taller than the game's own (which relies
     on the browser default) at the same font-size/padding. */
  line-height: normal;
  color: var(--color-white);
}

.brain-break__button:hover {
  transform: scale(1.05);
}

.brain-break__button:active {
  transform: scale(0.97);
}

.brain-break__button:focus-visible {
  outline: 2px solid var(--game-brown);
  outline-offset: 3px;
}

.brain-break__media {
  grid-area: media;
  display: flex;
  justify-content: center;
}

.brain-break__pancakes {
  width: calc(330px * var(--bb-image-scale));
  height: auto;
  animation: brain-break-float 2.8s ease-in-out infinite;
}

@keyframes brain-break-float {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-14px);
  }
}

/* Below 1489px, the banner/both headings/peek logo all switch to
   --bb-text-scale (continuous, see its definition above) instead of the
   stepped --bb-scale — this is what keeps the logo genuinely peeking at
   every width in this whole range down to 300px, since its position and
   the banner height it's positioned against always move by the exact same
   proportion. --bb-scale (still stepped) continues to drive everything
   else in this section (image, spacing, button). */
@media (max-width: 1489px) {
  .brain-break {
    --bb-scale: 0.85;
    --bb-image-scale: 0.92;
  }

  .brain-break__banner {
    height: calc(184px * var(--bb-text-scale));
    padding-top: calc(34px * var(--bb-text-scale));
  }

  .brain-break__banner-title {
    font-size: calc(40px * var(--bb-text-scale));
    line-height: calc(25px * var(--bb-text-scale));
  }

  .brain-break__title {
    font-size: calc(40px * var(--bb-text-scale));
  }

  .brain-break__peek {
    top: calc(82px * var(--bb-text-scale));
    width: calc(172px * var(--bb-text-scale));
  }
}

@media (max-width: 1249px) {
  .brain-break {
    --bb-scale: 0.72;
    --bb-image-scale: 0.85;
  }
}

@media (max-width: 1023px) {
  .brain-break {
    --bb-scale: 0.6;
    --bb-image-scale: 0.78;
  }
}

/* Stacks in step with the case studies above it. Banner/heading/peek
   sizing all comes from the continuous --bb-text-scale rule above — this
   tier only changes the layout (grid-template-areas etc.) and the things
   that rule doesn't cover (image, row spacing). */
@media (max-width: 849px) {
  .brain-break {
    --bb-scale: 0.52;
    --bb-image-scale: 0.7;
  }

  .brain-break__row {
    grid-template-columns: 1fr;
    grid-template-areas:
      "title"
      "desc"
      "media"
      "button";
    justify-items: center;
    text-align: center;
    row-gap: 0;
    /* Matches the bottom buffer below, so there's the same breathing room
       between the banner and the title as there is under the button. */
    padding-top: 56px;
    padding-bottom: 56px;
  }

  .brain-break__content {
    display: contents;
  }

  .brain-break__title {
    grid-area: title;
  }

  .brain-break__desc {
    grid-area: desc;
    margin-top: var(--space-small);
    max-width: 460px;
  }

  .brain-break__media {
    grid-area: media;
    margin-top: var(--space-small);
  }

  .brain-break__button {
    grid-area: button;
    margin-top: var(--space-small);
  }
}

@media (max-width: 639px) {
  .brain-break {
    --bb-scale: 0.44;
    --bb-image-scale: 0.58;
  }

  .brain-break__row {
    padding-top: 48px;
    padding-bottom: 48px;
  }

  /* Narrower than the 460px inherited from the tier above, so the body
     copy reads with clearer side margins instead of nearly spanning the
     row's own padding. */
  .brain-break__desc {
    max-width: 320px;
  }

  /* More breathing room before the image than the inherited
     var(--space-small). */
  .brain-break__media {
    margin-top: var(--space-medium);
  }
}

/* ==========================================================================
   About page hero
   ========================================================================== */

/* Matches .case-hero's min-height/scale steps (609px, same 1/.85/.72/.6/.52/.44
   progression) rather than the taller homepage .hero (618px) — About and the
   case studies share the same simple image+title shape, while the homepage
   hero is the intentionally distinct, denser section (three tiers of text
   plus the gif), so it's the one allowed to sit a bit taller. */
.about-hero {
  --about-scale: 1;
  position: relative;
  background-color: var(--color-cream);
  min-height: calc(609px * var(--about-scale));
  display: flex;
  align-items: center;
}

/* Extra right padding biases the centered image+text group left, same
   technique as .case-hero--davis-grown/.case-hero--home-at-last. */
.about-hero__inner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(64px * var(--about-scale));
  width: 100%;
  padding-right: calc(var(--container-padding) + 56px * var(--about-scale));
}

.about-hero__media {
  flex-shrink: 0;
  margin-left: calc(44px * var(--about-scale));
}

/* Same subtle 4px as the case-study wireframes/moodboards — plain
   photography sitting directly on the page, not an elevated card or
   screen mockup, so it gets just enough softening to not look
   sharp-cornered. */
.about-hero__image {
  width: calc(506px * var(--about-scale));
  height: auto;
  border-radius: 4px;
}

/* position:relative + left (not margin) nudges the text column left as a
   pure visual offset that doesn't feed back into the flex row's centering
   math — a margin here would shrink the row's total width and drag the
   headshot along with it since the group is centered as a whole. */
.about-hero__text {
  display: flex;
  flex-direction: column;
  position: relative;
  left: calc(-60px * var(--about-scale));
}

/* nowrap so the name never breaks onto a second line — sized down together
   with everything else via --about-scale, the same way .hero__name stays
   nowrap across every breakpoint. */
.about-hero__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: calc(150px * var(--about-scale));
  line-height: 0.95;
  letter-spacing: -0.05em;
  color: var(--color-scarlet);
  white-space: nowrap;
}

/* nowrap keeps each <br>-separated sentence on its own single line rather
   than letting the longest one wrap — same intent as .hero-strip__text.
   line-height is unitless (not a px value) so it stays proportional no
   matter what the font-size resolves to, including the fluid clamp() this
   gets at the mobile breakpoint below — a fixed px line-height doesn't
   scale cleanly against a fluid font-size. 1.25 is deliberately tighter
   than the 1.6 used for the long-form paragraphs in .stitchery__body:
   short, display-adjacent lines like these read better with tight,
   intentional leading; the looser ratio is reserved for actual
   paragraph-length reading copy. */
/* margin-left nudges the body copy right of the text column's own edge —
   Reenie Beanie's cursive "I" glyph has a small leading loop/flourish
   before its main downstroke (measured via canvas pixel-sampling: the
   glyph's ink starts at x:0, but the thick stroke itself doesn't begin
   until ~30px in at this font's 150px base size), so lining the body up
   with the column edge put it visibly left of where the "I" actually
   reads as starting. 30px is that stroke's own left edge, scaled with
   everything else via --about-scale. */
.about-hero__body {
  margin-top: calc(32px * var(--about-scale));
  margin-left: calc(30px * var(--about-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(28px * var(--about-scale)), 16px);
  line-height: 1.25;
  letter-spacing: -0.05em;
  color: var(--color-blue-black);
  white-space: nowrap;
}

.about-hero__body strong {
  font-weight: 700;
}

.about-hero__arrow {
  align-self: flex-end;
  margin-top: calc(56px * var(--about-scale));
  width: auto;
  height: calc(70px * var(--about-scale));
  fill: none;
  stroke: var(--color-scarlet);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (max-width: 1489px) {
  .about-hero {
    --about-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .about-hero {
    --about-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .about-hero {
    --about-scale: 0.6;
  }
}

/* Stacks in step with the homepage hero and case-hero, which both switch to
   a single column at this same breakpoint. */
@media (max-width: 849px) {
  .about-hero {
    --about-scale: 0.52;
    min-height: auto;
    padding-top: calc(32px * var(--about-scale));
    padding-bottom: calc(88px * var(--about-scale));
  }

  .about-hero__inner {
    flex-direction: column;
    gap: calc(10px * var(--about-scale));
    padding-right: var(--container-padding);
    text-align: center;
  }

  /* Both desktop-only offsets (headshot nudged right, text nudged left)
     are reset here so everything sits dead-center in the stacked column. */
  .about-hero__media {
    margin-left: 0;
  }

  .about-hero__text {
    align-items: center;
    left: 0;
  }

  /* Same clamp formula as .hero__name/.case-hero__title's mobile sizing —
     see the comment on .hero__name's Stack-Wide rule for why. */
  .about-hero__title {
    white-space: nowrap;
    font-size: clamp(30px, 14.5vw, 76px);
  }

  /* Fluid vw-based size (not the stepped --about-scale formula used above
     this breakpoint) so nowrap can hold on every phone width without
     wrapping or overflowing — measured directly against the longest of
     the three sentences ("Today, I bring those experiences into every
     product I design.", ~362px wide at 16px/-5% letter-spacing) against
     available width at 320/375/639/849px, each with margin to spare. The
     11px floor only actually gets hit on screens narrower than real
     phones ever go; from ~430px up this already reads at a full 16px+.
     Ceiling lowered to 20px (was 28px, matching the pre-stack desktop
     size) — at 28px this hit its ceiling by ~714px and then sat flat all
     the way to 849px, reading oversized in that tablet-ish portion of the
     stack range relative to the title above it (which keeps shrinking
     the whole way, per its own clamp). Lowering the ceiling doesn't risk
     the nowrap-fit guarantee — a smaller size only ever needs less width,
     never more. */
  .about-hero__body {
    margin-left: 0;
    white-space: nowrap;
    font-size: clamp(11px, calc(4.2vw - 2px), 20px);
  }

  .about-hero__arrow {
    align-self: center;
  }
}

@media (max-width: 639px) {
  .about-hero {
    --about-scale: 0.44;
  }
}

/* ==========================================================================
   Scarlet's Stitchery (About page)
   ========================================================================== */

.stitchery {
  --stitchery-scale: 1;
  background-color: var(--color-white);
  padding-block: calc(140px * var(--stitchery-scale));
}

/* justify-content:center treats the content+media pair as one group and
   centers it in the container, the same way .about-hero__inner centers
   its own image+text group above. align-items:stretch (the grid default)
   makes both columns match the row's height (set by whichever column is
   naturally taller — normally .stitchery__media, image+stats) — combined
   with .stitchery__content being a flex column and its button using
   margin-top:auto below, this is what lines the button's bottom edge up
   with the bottom of the stats list, instead of a fixed gap. */
/* 540px (not the original 580px) — narrow enough to wrap the paragraphs to
   roughly 2 lines more than at 580px (verified: 14 lines total at 580px vs
   16px at 540px), so there's more copy above the button before it. */
/* justify-content:center treats content+media as one group and centers it
   in the container — .sbdc__content and .skating__media/.bucknell__media
   land within a few px of this same x too (their own centered-group math,
   or in sbdc's case a flex-shrink quirk that happens to net out close),
   so this is what actually keeps Stitchery in line with the rest of the
   About page's sections rather than sitting flush at the container's raw
   edge on its own. */
/* position:relative + left is a small nudge on top of the centered-group
   position above — brings Stitchery's own centered-group math (208px at
   1512px viewport) those last few px in line with .sbdc__content (213px)
   and .skating__media/.bucknell__media (217px), which don't land at
   exactly the same x as Stitchery on their own. Reset to 0 in the stacked
   breakpoint below, where centering works differently (justify-items on a
   single column) and this offset would just bias it off-center. */
.stitchery__inner {
  display: grid;
  grid-template-columns: max(calc(540px * var(--stitchery-scale)), 350px) calc(430px * var(--stitchery-scale));
  grid-template-areas: "content media";
  column-gap: calc(110px * var(--stitchery-scale));
  justify-content: center;
  position: relative;
  left: calc(5px * var(--stitchery-scale));
}

.stitchery__content {
  grid-area: content;
  display: flex;
  flex-direction: column;
}

.stitchery__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--stitchery-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--color-scarlet);
}

/* Floor bumped higher than .stitchery__title's (30px vs 24px) — Reenie
   Beanie's thinner script strokes read noticeably smaller than bold Hind
   at the same px size, so it needs the extra size to hold its own at the
   mobile floor. */
.stitchery__role {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(calc(40px * var(--stitchery-scale)), 30px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--color-scarlet);
}

.stitchery__body {
  margin-top: calc(28px * var(--stitchery-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--stitchery-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.stitchery__body p + p {
  margin-top: calc(20px * var(--stitchery-scale));
}

.stitchery__body strong {
  font-weight: 700;
}

/* margin-top:auto (not a fixed gap) pushes the button to the bottom of
   .stitchery__content's now-stretched height — see .stitchery__inner's
   comment. align-self:flex-start keeps the button itself from stretching
   to the column's full width, since it's inline content, not a block. */
.stitchery__button {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  gap: 6px;
  margin-top: auto;
  padding: 12px 22px;
  border-radius: 8px;
  background-color: var(--color-scarlet);
  box-shadow: 0 2px 6px rgba(30, 30, 36, 0.15);
  transition: transform var(--transition-fast);
}

.stitchery__button-text {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 17px;
  color: var(--color-white);
}

.stitchery__button-arrow {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 17px;
  color: var(--color-white);
}

.stitchery__button:hover {
  transform: scale(1.05);
}

.stitchery__button:active {
  transform: scale(0.97);
}

.stitchery__button:focus-visible {
  outline: 2px solid var(--color-scarlet);
  outline-offset: 3px;
}

.stitchery__media {
  grid-area: media;
}

/* Same subtle 4px as the plain-photography tier used sitewide. */
.stitchery__image {
  width: calc(430px * var(--stitchery-scale));
  max-width: 100%;
  height: auto;
  border-radius: 4px;
}

.stitchery__stats {
  margin-top: calc(34px * var(--stitchery-scale));
}

.stitchery__stats-title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(22px * var(--stitchery-scale)), 16px);
  letter-spacing: -0.05em;
  color: var(--color-scarlet);
}

.stitchery__stats-list {
  margin-top: calc(12px * var(--stitchery-scale));
  padding-left: 24px;
  list-style: none;
}

/* nowrap + a 14px floor (never lower). At the 0.6 scale tier the floored
   14px is still ~16px wider than that tier's own stats column, but that
   column sits well clear of the container's right edge (32px of padding
   to spare there), so the small overflow bleeds harmlessly into existing
   whitespace rather than colliding with anything or clipping. */
.stitchery__stats-list li {
  position: relative;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--stitchery-scale)), 14px);
  line-height: 1.6;
  white-space: nowrap;
  color: var(--color-blue-black);
}

.stitchery__stats-list li::before {
  content: "★";
  position: absolute;
  left: -24px;
  color: var(--color-scarlet);
}

@media (max-width: 1489px) {
  .stitchery {
    --stitchery-scale: 0.85;
  }
}

/* Below 1250px, the content column narrows enough (per --stitchery-scale)
   that the 3 paragraphs can wrap to more lines than the image+stats column
   is tall — content becomes the taller of the two rather than media, which
   flips the stretch/margin-auto bottom-alignment above into a large gap
   instead of a match. Reverting to align-items:start + a fixed button gap
   here avoids that; the auto-align trick only runs at 1250px+, where
   content reliably stays shorter than media (verified). */
@media (max-width: 1249px) {
  .stitchery {
    --stitchery-scale: 0.72;
  }

  .stitchery__inner {
    align-items: start;
  }

  .stitchery__button {
    margin-top: calc(59px * var(--stitchery-scale));
  }
}

@media (max-width: 1023px) {
  .stitchery {
    --stitchery-scale: 0.6;
  }
}

/* Stacks in step with the rest of the page's sections — image+stats drop
   below the text column, centered as a unit rather than left-aligned
   against the padding edge. */
@media (max-width: 849px) {
  .stitchery {
    --stitchery-scale: 0.6;
    padding-block: calc(90px * var(--stitchery-scale));
  }

  .stitchery__inner {
    grid-template-columns: 1fr;
    grid-template-areas: "content" "media";
    row-gap: calc(48px * var(--stitchery-scale));
    justify-items: center;
    text-align: center;
    left: 0;
  }

  /* Left-aligned to match .stitchery__body below, instead of the centered
     text inherited from .stitchery__inner's text-align:center. */
  .stitchery__title,
  .stitchery__role {
    text-align: left;
  }

  .stitchery__body {
    text-align: left;
    max-width: 500px;
  }

  /* The bottom-alignment trick above (align-items:stretch + margin-top:auto)
     only makes sense in the two-column desktop layout, where content and
     media share one grid row. Stacked, they're separate rows with no shared
     height to stretch against, so margin-top:auto would collapse to 0 —
     restore a normal fixed gap here instead. */
  .stitchery__button {
    align-self: center;
    margin-top: calc(40px * var(--stitchery-scale));
  }

  /* No max-width here — .stitchery__media spans the full stacked column
     (same width the body text and bullets use), so shrinking the image
     below doesn't also cramp the stats list into a narrower box. */
  .stitchery__media {
    width: 100%;
  }

  /* Fluid, not tied to --stitchery-scale (which stays flat at 0.6 across
     this entire 849px-320px range) — a fluid vw-based width instead of a
     flat one is what makes the image keep shrinking gradually as the
     screen narrows, rather than staying frozen then suddenly changing
     size at one breakpoint. Continuous with the pre-stack side: 258px at
     849px viewport matches calc(430px * 0.6) exactly, so there's no jump
     entering the stack. Margin auto centers it since it's narrower than
     the full-width .stitchery__media it sits in. */
  /* Same clamp values as .bucknell__image's own mobile sizing below, so
     the two photos read as the same size on any given phone width. */
  .stitchery__image {
    width: clamp(210px, calc(182px + 8.7vw), 256px);
    margin-inline: auto;
  }

  /* width:fit-content + margin-inline:auto centers the whole stats block
     (title + list together, sized to whichever is wider) under the image
     above, while both children stay left-aligned inside that shared box —
     this is what keeps the title's text flush with the bullets' text
     rather than each centering independently at a different width. */
  .stitchery__stats {
    width: fit-content;
    margin-inline: auto;
    text-align: left;
  }

}

/* Tablet-stack: 640-849px. The text above still stacks full-width, but
   there's enough room in this sub-range to put the image and bullets side
   by side instead of stacking those too — image on the left, stats on the
   right, centered together as a group. Reverts to the fully-stacked
   image-above-stats layout below 640px, where there isn't room for both
   side by side at a readable size. */
@media (min-width: 640px) and (max-width: 849px) {
  /* More breathing room between the text column and the photo/bullets row
     than the general stack's row-gap — called out specifically for this
     sub-range. */
  .stitchery__inner {
    row-gap: 80px;
  }

  .stitchery__media {
    display: flex;
    align-items: flex-start;
    justify-content: center;
    gap: 24px;
  }

  .stitchery__image {
    margin-inline: 0;
  }

  /* width:auto + margin-inline:0 cancels the fit-content/auto-margin
     centering from the general ≤849 rule above — that's meant for the
     stacked (<640px) layout, where .stitchery__stats is a lone block that
     needs to center itself. Here it's a flex child of .stitchery__media
     instead, and margin-inline:auto on a flex item consumes free space in
     the row, which was pushing it away from the image — .stitchery__media's
     own justify-content:center already centers the image+stats pair as a
     group, so .stitchery__stats doesn't need to do any centering itself. */
  .stitchery__stats {
    width: auto;
    margin-inline: 0;
    margin-top: 0;
  }

  /* Left-aligned here (matching the title/role/body above it) rather than
     the centered treatment used below 640px — see the general ≤849 rule
     for the centered mobile default this overrides. */
  .stitchery__button {
    align-self: flex-start;
  }
}

/* Matches .case-study__button's own breakpoint behavior exactly: flat at
   17px/12px 22px through every tier above this, dropping only once, here,
   at the same 639px cutoff and to the same values. */
@media (max-width: 639px) {
  .stitchery__button {
    padding: 9px 16px;
  }

  .stitchery__button-text,
  .stitchery__button-arrow {
    font-size: 14px;
  }
}

/* ==========================================================================
   Bucknell (About page)
   ========================================================================== */

.bucknell {
  --bucknell-scale: 1;
  background-color: var(--color-white);
}

.bucknell__banner {
  height: calc(160px * var(--bucknell-scale));
  background-color: var(--color-scarlet);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: calc(8px * var(--bucknell-scale));
  text-align: center;
}

.bucknell__banner-name {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(calc(60px * var(--bucknell-scale)), 14px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--color-white);
}

.bucknell__banner-tagline {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--bucknell-scale)), 14px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--color-white);
}

/* Grid (not flex) so the tablet breakpoint below can reassign
   .bucknell__card and .bucknell__body to their own named areas —
   .bucknell__content switches to display:contents there, which only
   works when its parent is laying out by named grid areas. At this
   (desktop) tier it's just "media content" side by side, same result a
   flex row would give since both columns already have explicit/max
   widths. */
/* justify-content:center treats media+content as one group and centers
   it as a unit, the same way .stitchery__inner centers its own image+text
   group — this is what keeps the two sides moving together smoothly as
   the viewport shrinks, instead of staying left-anchored while a growing
   gap opens up on the right (which is what a fixed left offset like the
   old .bucknell__media margin-left did). */
.bucknell__row {
  display: grid;
  grid-template-columns: auto auto;
  grid-template-areas: "media content";
  align-items: start;
  justify-content: center;
  column-gap: calc(95px * var(--bucknell-scale));
  padding-block: calc(96px * var(--bucknell-scale));
}

.bucknell__media {
  grid-area: media;
  flex-shrink: 0;
}

/* Same subtle 4px as the plain-photography tier used sitewide. */
.bucknell__image {
  display: block;
  width: calc(427px * var(--bucknell-scale));
  height: auto;
  border-radius: 4px;
}

/* Wider than .bucknell__card (540 vs 462) — this is what gets the second
   paragraph down to 4 lines; the card keeps its own fixed Figma width via
   .bucknell__card's own width below, so widening this parent only gives
   the body paragraphs (not the card) the extra room. */
/* position:relative + left nudges the card+star and the body paragraph
   beneath it (both live inside .bucknell__content) slightly right of the
   centered-group position, without dragging .bucknell__image along with
   it — the same technique .about-hero__text/.about-hero__body use for
   their own small nudges. Reset to 0 in the stacked breakpoint below,
   where this would just throw off the centered stacked column. */
.bucknell__content {
  grid-area: content;
  max-width: calc(540px * var(--bucknell-scale));
  position: relative;
  left: calc(32px * var(--bucknell-scale));
}

/* min-height (not a hard height) so the shortened card still fits its
   content without clipping if internal spacing ever needs to grow a
   little. Padding/title/group spacing below were tightened specifically
   to bring this down near the original 320px Figma height while keeping
   the card's total height + gap + body height flush with the image's
   height beside it. */
.bucknell__card {
  position: relative;
  width: calc(462px * var(--bucknell-scale));
  min-height: calc(320px * var(--bucknell-scale));
  background-color: var(--color-cream);
  border-radius: 8px;
  padding: calc(28px * var(--bucknell-scale));
  /* Directional (not symmetric) — offset right+down so the shadow reads
     more prominently on the card's bottom and right edges specifically.
     A tighter blur than a wide/diffuse radius keeps this reading as a
     defined drop shadow rather than a soft ambient glow, while the
     moderate opacity keeps it from going back to the earlier, too-heavy
     version. */
  box-shadow: 5px 7px 9px rgba(30, 30, 36, 0.14);
}

.bucknell__card-title {
  margin: 0 0 calc(16px * var(--bucknell-scale));
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--bucknell-scale)), 14px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--color-blue-black);
}

.bucknell__card-group + .bucknell__card-group {
  margin-top: calc(16px * var(--bucknell-scale));
}

.bucknell__card-label {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(18px * var(--bucknell-scale)), 14px);
  color: var(--color-blue-black);
}

.bucknell__card-list {
  margin-top: calc(8px * var(--bucknell-scale));
  padding-left: 24px;
  list-style: none;
}

/* Star bullets matching .stitchery__stats-list's own treatment — same
   24px indent/offset pair (star flush at the list's left edge, ~2 spaces
   of gap to the text) for a consistent bullet language between the two
   sections. Scarlet (not blue-black) to tie in with the card's own red
   star decoration bleeding off its right edge, rather than competing with
   a second, different star color on the same card. */
.bucknell__card-list li {
  position: relative;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--bucknell-scale)), 14px);
  line-height: 1.5;
  color: var(--color-blue-black);
}

.bucknell__card-list li::before {
  content: "★";
  position: absolute;
  left: -24px;
  color: var(--color-scarlet);
}

/* Mobile-stacked default: sits below the "Major: Markets, Innovation &
   Design" bullet, overlapping the card's right edge. top/right are flat
   px (not scaled) — this treatment only ever runs at --bucknell-scale:0.6
   (it's used below the 850px breakpoint, and scale is already locked to
   0.6 by then), so there's no cross-tier proportion to preserve here,
   unlike the desktop star below. rotate(15deg) — same clockwise direction
   as the desktop star (rotate(-12deg) was the opposite, counter-clockwise,
   direction; fixed for consistency). Position verified via actual pixel
   sampling of the rotated star image against each text line's real
   rendered extent (not bounding-box math, which overstates overlap risk
   for a concave star shape) at this specific rotation — the rotation
   direction changes which points of the star extend furthest toward the
   text, so the safe position had to be re-verified after the angle
   changed, not just carried over from the old -12deg numbers. max-width:
   none opts out of the sitewide img{max-width:100%} reset since this
   intentionally overflows past the card's own edge. Overridden for wider
   (desktop/tablet) viewports below, where there's more room to bleed off
   the right edge instead. */
.bucknell__star {
  position: absolute;
  top: 155px;
  right: -60px;
  width: calc(137px * var(--bucknell-scale));
  max-width: none;
  height: auto;
  --star-deg: 15deg;
  --wobble: -5deg;
  /* Rotation only, same slow/scattered timing as .hero__star — no
     shrink/expand, so --pulse-scale is pinned to 1 (see the shared
     @keyframes star-pulse-breathe comment). */
  --pulse-scale: 1;
  --pulse-duration: 5s;
  --pulse-delay: 0.9s;
  animation: star-pulse-breathe var(--pulse-duration) ease-in-out var(--pulse-delay) infinite;
  transform: rotate(var(--star-deg));
}

/* Below ~340px, the card sits close enough to the true viewport edge that
   more bleed would visually crop the star's tip against the site's global
   overflow-x:clip (see html{} at the top of this file). Verified via actual
   rendered-pixel sampling (not bounding-box math, which is mostly
   transparent padding for this star image and overstates both overlap and
   clipping risk) that -20px is the largest bleed that stays clear of the
   viewport at the narrowest phone widths this site supports. */
@media (max-width: 339px) {
  .bucknell__star {
    right: -20px;
  }
}

/* 340px-400px: the star's actual visible tip (not its bounding box, which
   has empty padding baked into the source SVG) sits well short of the
   card here at -20px. -35px was verified via the same pixel-sampling
   technique to land the visible tip within a fraction of a pixel of the
   header hamburger's right edge at the standard 375px width, while still
   keeping several pixels of clipping safety at the 340px lower bound. */
@media (min-width: 340px) and (max-width: 400px) {
  .bucknell__star {
    right: -35px;
  }
}

/* Desktop/tablet: bleeds off the card's right edge instead, pinned to a
   percentage down the card rather than a corner. top:62% (not 50%, shifted
   down from dead-center) + translateY(-50%) positions it regardless of the
   card's actual rendered height (which can vary slightly beyond its
   min-height depending on content), rather than a fixed pixel offset that
   would only be correct at one exact height. right is a flat px value
   (not scaled by --bucknell-scale like everything else) — deliberately,
   not an oversight. Verified with actual pixel sampling of the star image
   (not just its bounding box, which is mostly transparent for a concave
   star shape and way overstates real overlap risk): at this vertical
   position, -60px is the closest safe clearance across every scale tier
   (1, 0.85, 0.72, 0.6) — this adds a small buffer past it. */
@media (min-width: 850px) {
  .bucknell__star {
    top: 62%;
    right: -65px;
    bottom: auto;
    /* --star-prefix carries this translateY through the pulse animation
       (see the shared keyframe's comment) — a bare transform here would
       get replaced outright during the animation and lose the vertical
       centering the moment it started playing. */
    --star-prefix: translateY(-50%);
    transform: var(--star-prefix) rotate(var(--star-deg));
  }
}

/* margin-top is a bit more than the visual card-to-body gap alone calls
   for — the extra ~9px (at scale:1) brings the body's own bottom edge down
   to line up with .bucknell__image's bottom edge beside it, since the card
   above it is shorter than the image and the two columns don't otherwise
   share a bottom anchor. */
.bucknell__body {
  margin-top: calc(49px * var(--bucknell-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--bucknell-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.bucknell__body p + p {
  margin-top: calc(24px * var(--bucknell-scale));
}

.bucknell__body strong {
  font-weight: 700;
}

@media (max-width: 1489px) {
  .bucknell {
    --bucknell-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .bucknell {
    --bucknell-scale: 0.72;
  }
}

/* Below this, the card narrows enough that "Freeman College of
   Management | 2023 - 2027" wraps to 2 lines (verified: single line down
   to 1100px, 2 lines at 900px and every narrower width tested, all within
   this same --bucknell-scale:0.6 tier) — once it wraps, the "|" separator
   just hangs at the end of the first line for no reason, since the line
   break itself already separates the two parts. Hiding it and forcing
   "2023 - 2027" onto its own line reads cleaner than the orphaned pipe. */
@media (max-width: 1023px) {
  .bucknell {
    --bucknell-scale: 0.6;
  }

  .bucknell__card-label-sep {
    display: none;
  }

  .bucknell__card-label-year {
    display: block;
  }
}

/* Stacks in step with the rest of the page's sections. */
@media (max-width: 849px) {
  .bucknell {
    --bucknell-scale: 0.6;
  }

  /* Vertical stack: media above content, both centered as a unit. Gap
     tightened from the desktop 95px column-gap — that was sized for a
     side-by-side row, not the space between a stacked image and the
     content below it. */
  .bucknell__row {
    grid-template-columns: 1fr;
    grid-template-areas: "media" "content";
    justify-items: center;
    row-gap: calc(32px * var(--bucknell-scale));
    text-align: center;
    padding-block: calc(64px * var(--bucknell-scale));
  }

  /* The pre-stack alignment offset (matching .stitchery__text above) is
     reset here — .bucknell__row's own align-items:center handles centering
     the stacked image instead. */
  .bucknell__media {
    margin-left: 0;
  }

  /* Fluid, not tied to --bucknell-scale (which stays flat at 0.6 across
     this entire 849px-320px range) — same fix as .stitchery__image's own
     mobile sizing, for the same reason: a flat width means the image stops
     shrinking at all below 849px, reading too large again on small phones.
     256px at 849px viewport matches calc(427px * 0.6) exactly, so there's
     no jump entering the stack. Floor raised to 210px (was 179px) — at a
     true personal photo (not a decorative graphic), the narrower floor
     read as too small on the smallest phones. */
  .bucknell__image {
    width: clamp(210px, calc(182px + 8.7vw), 256px);
  }

  .bucknell__content {
    max-width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    left: 0;
  }

  /* Left-aligned, overriding the centered text inherited from
     .bucknell__row — the card box itself stays centered as a unit
     (via .bucknell__content's align-items:center above), but its title,
     labels, and bullets read left-aligned within it, same as the body
     copy below. */
  .bucknell__card-title,
  .bucknell__card-label,
  .bucknell__card-list {
    text-align: left;
  }

  .bucknell__body {
    text-align: left;
    max-width: calc(462px * var(--bucknell-scale));
  }
}

/* Tablet-stack: 640-849px. Mirrors .stitchery's own 640-849 sub-mode —
   image and card sit side by side again (like desktop), centered as a
   group, while the paragraphs drop to their own full-width row below
   spanning underneath both. Reverts to the fully-stacked layout above
   (media/content/card/body all vertical) below 640px, where there isn't
   room for image+card side by side at a readable size. */
@media (min-width: 640px) and (max-width: 849px) {
  /* auto columns, sized by media/card's own content — max-content was
     tried here first but broke badly: browsers fall back to the <img>'s
     actual intrinsic pixel size (not its fluid vw-based CSS width) when
     computing max-content contribution for a replaced element, blowing
     the column way out. auto avoids that. The remaining risk with auto —
     the spanning "body" row below stretching these columns to fill the
     container — is avoided by giving .bucknell__body an explicit
     calculated width (below) instead of letting it size intrinsically
     from its own text, which is what was actually causing the stretch. */
  .bucknell__row {
    grid-template-columns: auto auto;
    grid-template-areas: "media card" "body body";
    column-gap: 24px;
    justify-content: center;
    justify-items: start;
    row-gap: calc(32px * var(--bucknell-scale));
  }

  /* Ungroups .bucknell__card and .bucknell__body so they become
     independent grid items placeable into the areas above, instead of a
     single "content" block. */
  .bucknell__content {
    display: contents;
  }

  .bucknell__card {
    grid-area: card;
  }

  /* Explicit width — the same formula as .bucknell__image's own fluid
     clamp, plus the column-gap and the card's fixed width — so this
     matches the media+card row's actual combined width exactly, rather
     than sizing from its own text content (which was stretching the
     columns above to fill the full container; see .bucknell__row's
     comment). */
  .bucknell__body {
    grid-area: body;
    width: calc(clamp(210px, 182px + 8.7vw, 256px) + 24px + 462px * var(--bucknell-scale));
    max-width: none;
    margin-top: 0;
    text-align: left;
  }
}

@media (max-width: 639px) {
  .bucknell {
    --bucknell-scale: 0.6;
  }

  /* Only the card narrows to match its own content width now —
     .bucknell__content itself stays full width (explicit here since
     .bucknell__row's justify-items:center at this breakpoint would
     otherwise shrink an unconstrained grid item to fit its content
     instead of filling the column). This block used to narrow
     .bucknell__content to the card's width too, which pulled
     .bucknell__body's box in along with it, since align-items:center
     sizes an unconstrained flex child to its content. That left the
     body copy's left edge ~29px further right than every other
     section's body text on the page (.stitchery__body, .skating__body,
     .sbdc__body all sit flush with the container edge) — narrower box,
     correctly left-aligned text within it, but the box itself wasn't
     lined up with its siblings. */
  .bucknell__content {
    width: 100%;
  }

  .bucknell__card {
    width: 100%;
    max-width: calc(462px * var(--bucknell-scale));
  }

  /* Stretches across .bucknell__content's now-full width instead of
     shrinking to fit like a centered flex child would by default —
     this is what actually puts its left edge back in line with the
     rest of the page's body text. */
  .bucknell__body {
    align-self: stretch;
    max-width: 500px;
    margin-top: calc(40px * var(--bucknell-scale));
  }
}

/* ==========================================================================
   SBDC (About page)
   ========================================================================== */

.sbdc {
  --sbdc-scale: 1;
  background-color: var(--color-cream);
  min-height: calc(662px * var(--sbdc-scale));
  display: flex;
  align-items: center;
}

.sbdc__inner {
  display: flex;
  align-items: center;
  gap: calc(80px * var(--sbdc-scale));
  padding-block: calc(64px * var(--sbdc-scale));
}

.sbdc__content {
  max-width: calc(640px * var(--sbdc-scale));
}

/* Same font rules as .stitchery__title/__role/__body — reused verbatim
   for consistency between the two About-page sections. */
.sbdc__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--sbdc-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--color-scarlet);
}

.sbdc__role {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(calc(40px * var(--sbdc-scale)), 30px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--color-scarlet);
}

.sbdc__body {
  margin-top: calc(28px * var(--sbdc-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--sbdc-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.sbdc__body p + p {
  margin-top: calc(20px * var(--sbdc-scale));
}

.sbdc__body strong {
  font-weight: 700;
}

/* isolation:isolate (not just position:relative) is what actually matters
   here — without an explicit stacking context, the ::after's z-index:-1
   below has no local "behind the image" to sink to, so combined with
   filter:blur() (which forces its own compositing layer) it was escaping
   all the way past this section's own background and disappearing
   entirely, rather than just sitting behind the image as intended. */
.sbdc__media {
  position: relative;
  isolation: isolate;
  flex-shrink: 0;
}

/* A soft ground-shadow blob instead of a box-shadow around the whole
   image — the nametag now has its own cream background baked into the
   file (matching this section's cream background), so a box-shadow would
   outline the entire rectangle; this only grounds the bottom-middle,
   reading like the tag is resting on the page rather than framed by it.
   Unrotated (sits on .sbdc__media, not .sbdc__image) so it stays flat
   like a real cast shadow instead of tilting with the card. z-index:-1
   drops it behind the (unpositioned, normal-flow) image without needing
   a z-index on the image itself. */
.sbdc__media::after {
  content: "";
  position: absolute;
  bottom: calc(-2px * var(--sbdc-scale));
  left: 50%;
  transform: translateX(-50%);
  width: 95%;
  height: calc(20px * var(--sbdc-scale));
  background: rgba(30, 30, 36, 0.3);
  filter: blur(calc(8px * var(--sbdc-scale)));
  border-radius: 50%;
  z-index: -1;
}

/* 14px at the base 350px width matches the badge graphic's own corner
   radius (measured directly from the source image: ~70px arc at its
   native 1785px width, scaled down) — the badge sits almost flush with
   the image's own edges, so rounding the image itself reveals the
   badge's real rounded corner rather than just clipping a mismatched one. */
.sbdc__image {
  position: relative;
  width: calc(350px * var(--sbdc-scale));
  height: auto;
  border-radius: calc(14px * var(--sbdc-scale));
  transform: rotate(6deg);
}

@media (max-width: 1489px) {
  .sbdc {
    --sbdc-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .sbdc {
    --sbdc-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .sbdc {
    --sbdc-scale: 0.6;
  }
}

/* Stacks in step with the rest of the page's sections. */
@media (max-width: 849px) {
  .sbdc {
    --sbdc-scale: 0.6;
    min-height: auto;
    padding-block: calc(56px * var(--sbdc-scale));
  }

  /* align-items:center replaces the centering .sbdc__content used to do as
     a grouped flex column — now that its children are ungrouped (see
     display:contents below), .sbdc__inner itself has to center each of
     them (title, role, media, body) individually instead. */
  /* gap:0 overrides the desktop row's 80px column-gap — that gap now sits
     between every ungrouped item (see display:contents below), including
     title-to-role, which needs to stay tight. Spacing between the actual
     sandwich layers (role-to-media, media-to-body) is set explicitly on
     each item's own margin-top instead. */
  .sbdc__inner {
    flex-direction: column;
    align-items: center;
    gap: 0;
    text-align: center;
  }

  /* display:contents ungroups title/role/body so they become independent
     flex items directly in .sbdc__inner, orderable individually — this is
     what makes the "sandwich" (title/role, then the nametag, then body)
     possible; grouped as one block, the image could only go before or
     after the whole thing, never in the middle of it. */
  .sbdc__content {
    display: contents;
  }

  .sbdc__title,
  .sbdc__role {
    order: 0;
  }

  .sbdc__media {
    order: 1;
    margin-top: calc(48px * var(--sbdc-scale));
    margin-bottom: calc(32px * var(--sbdc-scale));
  }

  .sbdc__body {
    order: 2;
    text-align: left;
    max-width: 500px;
  }
}

/* Tablet (640-849px): bigger than the true-mobile size below, since
   there's more width to spare here for the nametag to hold its own
   between the role line and the body copy. */
@media (min-width: 640px) and (max-width: 849px) {
  .sbdc__image {
    width: calc(340px * var(--sbdc-scale));
  }
}

/* True mobile: only a small bit bigger than before (280px), not the
   full tablet bump above — there's less room to spare at this width. */
@media (max-width: 639px) {
  .sbdc__image {
    width: calc(300px * var(--sbdc-scale));
  }
}

/* ==========================================================================
   Skating (About page)
   ========================================================================== */

.skating {
  --skating-scale: 1;
  background-color: var(--color-white);
}

/* justify-content:center (not a fixed offset on .skating__media) is what
   actually keeps this image aligned with .bucknell__image above at every
   viewport width — .bucknell__row is also centered (not left-anchored +
   margin), and a fixed margin here only coincidentally matched its left
   edge at the exact width it was measured against. Two centered groups
   whose total widths are close (see .skating__content's max-width below,
   tuned to roughly match .bucknell__row's own media+gap+content width)
   track each other continuously as the container's own width changes,
   instead of drifting tier to tier the way a static margin did. */
.skating__inner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(130px * var(--skating-scale));
  padding-top: calc(100px * var(--skating-scale));
  padding-bottom: calc(96px * var(--skating-scale));
}

.skating__media {
  flex-shrink: 0;
}

/* Sized so this section's total group width (media + gap + content) comes
   out equal to .bucknell__row's own — 539px is what closed the gap exactly
   at full scale (measured: bucknell's group was 1062px, this content was
   rendering unconstrained at 677px with no max-width at all, an oversight
   from the first pass). Matching group widths is what makes two
   independently-centered rows track each other's left edge continuously,
   rather than a fixed offset that only worked at one specific viewport. */
.skating__content {
  max-width: calc(539px * var(--skating-scale));
}

/* Same subtle 4px as the plain-photography tier used sitewide. */
.skating__image {
  width: calc(393px * var(--skating-scale));
  height: auto;
  border-radius: 4px;
}

/* Same title/body font rules as .stitchery__title/__body — the only
   difference from the other About-page sections is the 8px heading-to-body
   gap below (theirs use 28px) and there's no script role line here. */
.skating__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: max(calc(40px * var(--skating-scale)), 24px);
  line-height: 1.15;
  letter-spacing: -0.05em;
  color: var(--color-scarlet);
}

.skating__body {
  margin-top: calc(8px * var(--skating-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: max(calc(18px * var(--skating-scale)), 16px);
  line-height: 1.6;
  color: var(--color-blue-black);
}

.skating__body p + p {
  margin-top: calc(20px * var(--skating-scale));
}

.skating__body strong {
  font-weight: 700;
}

@media (max-width: 1489px) {
  .skating {
    --skating-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .skating {
    --skating-scale: 0.72;
  }
}

@media (max-width: 1023px) {
  .skating {
    --skating-scale: 0.6;
  }
}

/* Stacks in step with the rest of the page's sections. */
@media (max-width: 849px) {
  .skating {
    --skating-scale: 0.6;
  }

  /* gap overrides the desktop row's 130px column-gap, which otherwise
     carries over unchanged into the stacked column — this is the space
     between the image and the title/text following it, a separate
     concern from padding-top/bottom below (the space above the image and
     below the last paragraph, now equal on both ends of the section). */
  .skating__inner {
    flex-direction: column;
    gap: calc(60px * var(--skating-scale));
    text-align: center;
    padding-top: calc(72px * var(--skating-scale));
    padding-bottom: calc(72px * var(--skating-scale));
  }

  /* Same fluid clamp as .stitchery__image/.bucknell__image's own mobile
     sizing, for the same reason: --skating-scale is flat at 0.6 across
     this entire 849px-320px range, so without this the image stops
     shrinking at all below 849px and reads too large on small phones. */
  .skating__image {
    width: clamp(210px, calc(182px + 8.7vw), 256px);
  }

  /* No align-items:center here (unlike most of this section's other
     centered blocks) — .stitchery__content doesn't set it either,
     defaulting to stretch, which is what actually makes text-align:left
     below line up with .stitchery__title's own left edge. align-items:
     center would center the title/body *blocks* themselves within
     .skating__content, which text-align alone can't undo. */
  .skating__content {
    max-width: 100%;
    display: flex;
    flex-direction: column;
  }

  /* Left-aligned, matching how .stitchery__title reads in its own
     stacked view. */
  .skating__title {
    text-align: left;
  }

  .skating__body {
    text-align: left;
    max-width: 500px;
  }
}

/* ==========================================================================
   Connect banner (About page)
   ========================================================================== */

/* Same red strip pattern as the homepage hero-strip, and both start from
   the same 22px/Hind base, so this reuses the exact same font-size scale
   across breakpoints for one consistent banner language site-wide. */
.connect-banner {
  background-color: var(--color-scarlet);
  min-height: 119px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: var(--space-small) var(--container-padding);
  text-align: center;
}

.connect-banner__title,
.connect-banner__arrow {
  margin: 0;
  font-family: var(--font-body);
  font-size: 22px;
  line-height: 1.27;
  letter-spacing: -0.05em;
  color: var(--color-white);
}

.connect-banner__title {
  font-weight: 500;
}

.connect-banner__arrow {
  font-weight: 300;
  display: inline-block;
}

@media (max-width: 1489px) {
  .connect-banner__title,
  .connect-banner__arrow {
    font-size: 20px;
  }
}

@media (max-width: 1249px) {
  .connect-banner__title,
  .connect-banner__arrow {
    font-size: 19px;
  }
}

@media (max-width: 1023px) {
  .connect-banner {
    min-height: 93px;
  }

  .connect-banner__title,
  .connect-banner__arrow {
    font-size: 17px;
  }
}

@media (max-width: 849px) {
  .connect-banner__title,
  .connect-banner__arrow {
    font-size: 16px;
  }
}

@media (max-width: 639px) {
  .connect-banner {
    min-height: 86px;
  }

  .connect-banner__title,
  .connect-banner__arrow {
    font-size: 14px;
  }
}

/* ==========================================================================
   Footer
   ========================================================================== */

.site-footer {
  position: relative;
  background-color: var(--color-cream);
  --footer-scale: 1;
}

.footer__stars {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

/* Same one-time-tuned "twinkle" as .hero__star (see that rule and the
   shared @keyframes star-pulse-breathe near it) — a slow, subtle scale
   breathe plus rotation wobble, staggered and non-sequential per star
   so all four never move in visible unison. */
.footer__star {
  position: absolute;
  animation: star-pulse-breathe var(--pulse-duration) ease-in-out var(--pulse-delay) infinite;
}

.footer__star--1 {
  top: calc(35px * var(--footer-scale));
  left: -11%;
  /* Matches .hero__star--5, the smallest hero star at desktop width —
     same for .footer__star--4 below, so the mirrored outer pair shrinks
     together rather than throwing off the left/right balance. */
  width: calc(130px * var(--footer-scale));
  --star-deg: -12deg;
  --wobble: 4deg;
  transform: rotate(var(--star-deg));
  --pulse-duration: 4.7s;
  --pulse-delay: 1.8s;
}

/* Anchored off the icon-column/copyright width and the tagline+gif
   block width (both scale with --footer-scale) rather than a fixed
   viewport percentage, so these two sit inside the actual middle gap
   between the two content columns instead of drifting into either a
   cluster or a blank stretch as that gap's width changes with layout. */
.footer__star--2 {
  top: calc(14px * var(--footer-scale));
  left: calc(var(--container-padding) + (354px * var(--footer-scale)) + 20px);
  width: calc(150px * var(--footer-scale));
  --star-deg: 9deg;
  --wobble: -5deg;
  transform: rotate(var(--star-deg));
  --pulse-duration: 5.4s;
  --pulse-delay: 0.4s;
}

.footer__star--3 {
  top: calc(60px * var(--footer-scale));
  right: calc(var(--container-padding) + (436px * var(--footer-scale)) + 20px);
  width: calc(160px * var(--footer-scale));
  --star-deg: -8deg;
  --wobble: 5deg;
  transform: rotate(var(--star-deg));
  --pulse-duration: 4.4s;
  --pulse-delay: 2.6s;
}

.footer__star--4 {
  top: calc(40px * var(--footer-scale));
  right: -11%;
  width: calc(130px * var(--footer-scale));
  --star-deg: 14deg;
  --wobble: -4deg;
  transform: rotate(var(--star-deg));
  --pulse-duration: 5.1s;
  --pulse-delay: 1.1s;
}

.footer__inner {
  position: relative;
  z-index: 2;
  height: calc(253px * var(--footer-scale));
}

.footer__social {
  position: absolute;
  left: var(--container-padding);
  top: calc(90px * var(--footer-scale));
  display: flex;
  align-items: center;
  gap: calc(35px * var(--footer-scale));
}

.footer__social img {
  display: block;
  width: clamp(32px, calc(44px * var(--footer-scale)), 44px);
  height: clamp(32px, calc(44px * var(--footer-scale)), 44px);
  transition: opacity var(--transition-fast);
}

.footer__social a:hover img,
.footer__social a:focus-visible img {
  opacity: 0.75;
}

/* Same lightened look as the hover state above (opacity:0.75), plus a
   subtle scale (transform, not width/height, so it never reflows the
   row) — played as a one-time pulse per icon instead of waiting for a
   hover. Triggered by JS (see js/connect-banner.js) when arriving via the
   header's Contact link — this is now the only motion cue for that
   moment, the connect-banner arrow bounce was removed in favor of it (the
   icons are the actual payoff of clicking Contact; the arrow was pointing
   at not much once you're already scrolled to the bottom). Each icon's
   delay (below) is set to its own index times the 0.5s duration, so they
   run one at a time in sequence rather than overlapping. */
@keyframes footer-social-highlight {
  0%,
  100% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 0.75;
    transform: scale(1.12);
  }
}

.footer__social.is-shining li:nth-child(1) img {
  animation: footer-social-highlight 0.5s ease-in-out;
  animation-delay: 0s;
}

.footer__social.is-shining li:nth-child(2) img {
  animation: footer-social-highlight 0.5s ease-in-out;
  animation-delay: 0.5s;
}

.footer__social.is-shining li:nth-child(3) img {
  animation: footer-social-highlight 0.5s ease-in-out;
  animation-delay: 1s;
}

.footer__social.is-shining li:nth-child(4) img {
  animation: footer-social-highlight 0.5s ease-in-out;
  animation-delay: 1.5s;
}

@media (prefers-reduced-motion: reduce) {
  .footer__social.is-shining img {
    animation: none;
  }
}

.footer__copyright {
  position: absolute;
  left: var(--container-padding);
  top: calc(179px * var(--footer-scale));
  font-family: var(--font-body);
  font-weight: 400;
  font-size: clamp(14px, calc(20px * var(--footer-scale)), 20px);
  color: var(--color-blue-black);
}

.footer__copyright-star {
  color: var(--color-scarlet);
}

.footer__copyright a {
  transition: color var(--transition-fast);
}

.footer__copyright a:hover,
.footer__copyright a:focus-visible {
  color: var(--color-scarlet);
}

.footer__right {
  position: absolute;
  right: var(--container-padding);
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: calc(var(--space-medium) * var(--footer-scale));
}

.footer__tagline {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(18px, calc(30px * var(--footer-scale)), 30px);
  line-height: clamp(20px, calc(28px * var(--footer-scale)), 28px);
  color: var(--color-blue-black);
  white-space: nowrap;
}

.footer__gif-link {
  display: block;
}

.footer__gif {
  display: block;
  width: calc(174px * var(--footer-scale));
  height: auto;
}

/* Footer scales down as one proportional unit as the viewport narrows —
   same breakpoints AND same ratios as .case-hero's own 5-step scale
   (0.85/0.72/0.6/0.52/0.44), so the footer and a case-study hero shrink
   at an identical rate rather than drifting apart at any given width.
   Height, icons, gaps, type, gif, and star sizes are all driven off
   --footer-scale so everything shrinks together instead of via separate
   hand-picked values per tier. */
@media (max-width: 1489px) {
  .site-footer {
    --footer-scale: 0.85;
  }
}

@media (max-width: 1249px) {
  .site-footer {
    --footer-scale: 0.72;
  }

  .footer__tagline {
    white-space: normal;
    max-width: 160px;
  }
}

/* Two-column layout stays intact down to the hamburger breakpoint, but
   past this point the middle gap is too narrow for 4 stars beside the
   content. Keep 2, anchored via calc() off the known icon-column and
   gif widths at this scale so they clear both at any width in range. */
@media (max-width: 1023px) {
  .site-footer {
    --footer-scale: 0.6;
  }

  .footer__right {
    gap: calc(var(--space-small) * var(--footer-scale));
  }

  .footer__star--1 {
    top: calc(60px * var(--footer-scale));
    left: auto;
    right: calc(100% - 17px);
  }

  .footer__star--3 {
    right: calc(var(--container-padding) + (436px * var(--footer-scale)) - 14px);
  }

  .footer__star--4 {
    top: calc(60px * var(--footer-scale));
    right: auto;
    left: calc(100% - var(--container-padding) + 15px);
  }
}

@media (max-width: 849px) {
  .site-footer {
    --footer-scale: 0.52;
  }
}

/* True mobile only (hamburger-nav breakpoint): one centered column, in
   source order — social icons (horizontal), then gif+tagline side by
   side, then the copyright line last, centered underneath everything.
   Above this, the footer stays in its two-column desktop layout. */
@media (max-width: 639px) {
  .site-footer {
    --footer-scale: 0.44;
  }

  .footer__inner {
    height: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(var(--space-medium) * var(--footer-scale) * 1.3);
    padding-block: calc(var(--space-large) * var(--footer-scale) * 0.85);
    text-align: center;
  }

  .footer__social {
    position: static;
  }

  .footer__right {
    position: static;
    transform: none;
    justify-content: center;
  }

  .footer__copyright {
    position: static;
  }

  .footer__tagline {
    white-space: normal;
    max-width: 170px;
    text-align: left;
  }

  .footer__star--1 {
    top: 69px;
    left: auto;
    right: 87%;
    width: 95px;
  }

  .footer__star--2,
  .footer__star--3 {
    display: none;
  }

  .footer__star--4 {
    display: block;
    top: 69px;
    bottom: auto;
    right: auto;
    left: 87%;
    width: 95px;
  }
}

/* PREVIEW: un-bolding the emphasized phrases in every About page section
   except the hero (which stays bold per request). Placed last in the
   cascade so it wins over each section's own "strong { font-weight: 700 }"
   rule without editing those rules directly — delete this block to revert
   instantly, no HTML/other CSS was touched to make this preview. */
.stitchery__body strong,
.bucknell__body strong,
.sbdc__body strong,
.skating__body strong {
  font-weight: 400;
}

/* CRITICAL: must stay last in the file. Every selector below is also
   targeted by an "html.js .selector { opacity: 0; ... }" scroll-reveal
   hidden-state rule defined earlier in this file (js/reveal.js bails
   out entirely, its very first line, for prefers-reduced-motion users,
   so it never runs the code that would add "is-visible" to any of
   these — without this override they'd stay stuck at that opacity:0
   starting state forever). Both this block and those hidden-state rules
   use the exact same specificity, so whichever is LATER in the
   cascade wins regardless of the media query — this used to live near
   the top of the file (right after the reduced-motion block that kills
   transition durations) and silently lost that tie-break to every
   hidden-state rule defined after it, leaving reduced-motion visitors
   looking at a permanently blank page (hero text, case studies, About
   hero, Stitchery, Skating, the connect banner, case summary/reflection
   content — all of it). Confirmed live: the site was fully invisible on
   a phone with Reduce Motion enabled. If you add a new scroll-reveal
   hidden-state selector anywhere in this file, add its reduced-motion
   override here too, not next to the new rule. */
@media (prefers-reduced-motion: reduce) {
  html.js {
    opacity: 1;
  }

  html.js .about-hero__arrow,
  html.js .hero__text,
  html.js .about-hero__title,
  html.js .about-hero__body,
  html.js .case-hero__text,
  html.js .case-summary__card,
  html.js .case-summary__details,
  html.js .case-study,
  html.js .stitchery,
  html.js .skating,
  html.js .connect-banner,
  html.js .case-reflection,
  html.js .case-reflection-banner,
  html.js .brain-break {
    opacity: 1;
    transform: none;
  }

  /* Separate from the block above — "none" would strip this star's own
     base rotation instead of just cancelling the reveal's scale. */
  html.js .case-hero__star {
    opacity: 1;
    transform: rotate(12.88deg);
  }
}
