/* ==========================================================================
   COMPONENTS
   Every rule here reads from tokens.css. A future case-study page (or the
   About page) pulls the SAME .site-nav / .site-footer / .card-project /
   .pill / .btn / .card-stat classes rather than redeclaring styling —
   page-specific CSS should only ever add new *layout* rules, never new
   color/type/spacing values.
   ========================================================================== */

/* ---- Layout: container -------------------------------------------------
   Figma's own relationship (confirmed by direct measurement against the
   homepage frame): a 1440px frame = 1200px content + 120px gutter on each
   side. --container-max (1200) is the CONTENT width, --space-gutter (120)
   is the gutter — so the container's own max-width must be their sum
   (1440), with the gutter applied as padding *inside* that. The previous
   version capped max-width at 1200 directly and then ALSO padded 120px
   inside it, double-applying the gutter and shrinking real content to
   960px — the root cause of the homepage reading narrow/off-grid relative
   to Figma, and of sections drifting out of alignment with each other. */
.container {
  width: 100%;
  max-width: calc(var(--container-max) + var(--space-gutter) * 2); /* 1440 */
  margin-inline: auto;
  /* 120 / 48 / 24px by tier (tokens.css). The global vertical grid reads the
     same token, so its lines always sit on the real content column. */
  padding-inline: var(--page-margin);
}

/* ---- Site nav ------------------------------------------------------------ */
.site-nav {
  /* Transparent at the top of the page: identical to the original static nav
     (the page behind it is white). Sticky, not fixed, so it stays in normal
     flow and content is never overlapped or shifted at scroll 0. */
  position: sticky;
  top: 0;
  z-index: 100;
  background: transparent;
  /* The rule is a box-shadow, not a border, so toggling it adds no height.
     The header spans the full viewport, so the rule does too. */
  box-shadow: 0 1px 0 transparent;
  transition:
    background-color var(--duration-base) var(--ease-standard),
    box-shadow var(--duration-base) var(--ease-standard),
    -webkit-backdrop-filter var(--duration-base) var(--ease-standard),
    backdrop-filter var(--duration-base) var(--ease-standard);
}
/* main.js toggles .is-scrolled once the page has scrolled at all. */
.site-nav.is-scrolled {
  background: var(--color-nav-scrolled); /* page background at 85% */
  box-shadow: 0 1px 0 var(--color-gray-3);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
/* Anchor/focus scroll targets clear the sticky nav. Tied to the nav's real
   height (--nav-height, set on .site-nav__inner below), not a guess. */
html { scroll-padding-top: var(--nav-height); }
.site-nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--nav-height); /* 24px logo + 2 x 24px padding, unchanged; centred */
}
.site-nav__logo { height: 24px; width: auto; display: block; }
.site-nav__menu { display: flex; align-items: center; gap: var(--space-2xl); } /* 32px, confirmed */
.site-nav__link {
  font-family: var(--font-detail);
  font-size: var(--text-details200-size);
  line-height: var(--text-details200-leading);
  text-transform: uppercase;
  color: var(--color-gray);
  transition: color var(--duration-base) var(--ease-standard);
}
/* Current page renders pure black; everything else (including BOTH links on
   a page with no matching nav item, e.g. a case study) stays gray by
   default — no extra "both gray" rule needed, that's just the fallback. */
.site-nav__link[aria-current="page"] { color: var(--color-ink); }
.site-nav__link:hover,
.site-nav__link:focus-visible { color: var(--color-ink); }

/* ---- Site footer ---------------------------------------------------------- */
.site-footer { background: var(--color-blue); color: var(--color-white); }
.site-footer__row {
  display: flex;
  align-items: center;
  /* Horizontal inset comes from the inner .container (site-footer__container)
     alone now — this row previously ALSO padded 120px inline itself,
     double-applying the gutter on top of .container's own, same bug as
     .container's old self-double-constraint. Vertical padding stays here. */
  min-height: 64px; /* Figma footer bar: 64px, content centred */
  padding-block: var(--space-md);
}
.site-footer__brand {
  flex: 1 0 0;
  font-family: var(--font-body);
  font-weight: var(--weight-semibold);
  letter-spacing: 1.4px;
  text-transform: uppercase;
  font-size: var(--text-details200-size);
}
.site-footer__menu {
  display: flex;
  align-items: center;
  gap: var(--space-2xl);
  font-family: var(--font-detail);
  text-transform: uppercase;
  font-size: var(--text-details200-size);
}
/* No hover/focus color or underline change — the custom cursor (pill/icon)
   is the only hover affordance here; typography stays visually identical
   at rest and on hover. Keyboard users still get the sitewide
   a:focus-visible outline from base.css, untouched. */
.site-footer__link,
.site-footer__cat-trigger,
.site-footer__brand {
  color: var(--color-white);
}
/* Pass 4: base.css's sitewide a:focus-visible outline is blue — invisible
   against this footer's own blue background. Scoped override, footer
   only, so every footer link (including the new "Megan Learn" -> /about/
   link) gets a keyboard focus ring that's actually visible here. */
.site-footer :focus-visible {
  outline-color: var(--color-white);
}
.site-footer__legal {
  background: var(--color-surface);
  color: var(--color-blue-text);
  text-align: center;
  /* Horizontal inset comes from the inner .container div (see HTML) —
     this previously ALSO padded 120px inline itself, same double-gutter
     bug as .site-footer__row above. */
  padding-block: var(--space-sm); /* py-8, confirmed */
  font-family: var(--font-detail);
  font-size: var(--text-details200-size);
  text-transform: uppercase;
}
/* ---- Cursor: the one sitewide inverting circle lives in site.css + system.js. */

/* ---- Button ----------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--radius-pill);
  background: var(--color-blue);
  color: var(--color-white);
  font-family: var(--font-body);
  font-size: var(--text-body300-medium-size);
  font-weight: var(--text-body300-medium-weight);
  transition: opacity var(--duration-base) var(--ease-standard);
}
.btn:hover, .btn:focus-visible { opacity: 0.85; }


.site-footer__container { display: flex; align-items: center; width: 100%; gap: var(--space-lg); }
/* Mobile footer: an intentional centred stack. Row 1 is the wordmark, row 2
   the links (wrapping only if a very narrow phone truly cannot fit them). */
@media (max-width: 839px) {
  .site-footer__container { flex-direction: column; gap: var(--space-lg); }
  .site-footer__brand { flex: none; width: 100%; text-align: center; }
  .site-footer__menu { width: 100%; justify-content: center; flex-wrap: wrap; gap: var(--space-sm) var(--space-lg); }
}

/* ---- Modal (footer cat-photo popup) — corrected to use native <dialog>
   semantics instead of custom fixed-position math; the browser already
   centers a showModal() dialog and provides focus trapping + Escape. ------ */
dialog.modal-card {
  /* Native <dialog> UA stylesheets center a modal dialog via
     `position: fixed; inset-block: 0; margin: auto;` in the top layer —
     the previous `position: static` override here broke that (a
     top-layer element rendered with static positioning does not behave
     like an in-flow element the way the old comment assumed), which was
     the root cause of the page jumping/scrolling when the popup opened.
     Setting fixed + inset + margin:auto explicitly reproduces the
     correct centered-over-the-current-viewport behavior regardless of
     UA defaults. */
  position: fixed;
  inset: 0;
  margin: auto;
  border: 0;
  border-radius: var(--radius-card);
  padding: var(--space-lg);
  max-width: 600px;
  width: calc(100vw - var(--space-2xl) * 2);
  /* 32px of breathing room above and below. vh first as the fallback for
     browsers without dvh; dvh is preferred because it tracks the *visible*
     viewport (mobile URL bars, on-screen keyboards) rather than the largest
     possible one, which vh reports. */
  max-height: calc(100vh - var(--space-2xl) * 2);
  max-height: calc(100dvh - var(--space-2xl) * 2);
  box-shadow: var(--shadow-card);
  background: var(--color-surface);
  color: var(--color-ink);
}

/* ---- Photo popup: fit the whole card inside the viewport ------------------
   BUG THIS FIXES: the card's content has a fixed intrinsic height — 16px
   padding + a 568x636 image (width:100% + aspect-ratio, so its height is
   derived from its width and never shrinks) + 12px caption margin + the
   21px caption + 16px padding = 701px. max-height only CAPPED the card at
   calc(100vh - 64px); it could not make the contents smaller. So below a
   765px viewport the content overflowed its own cap and the UA dialog's
   default `overflow: auto` produced an internal scrollbar (measured: none
   at 780px+, scrollbar at 740px and below).
   FIX: lay the card out as a flex column and let the photo be the one item
   that absorbs the shortfall, so the image scales down instead of the card
   overflowing. Nothing is clipped and no content is removed — object-fit:
   cover (already set) keeps the composition centred as it shrinks.
   Scoped with :not(.modal-card--video) so the Strider Fox lightbox, which
   shares .modal-card, is completely untouched. [open] is required: an
   unconditional display:flex would beat the UA's `dialog:not([open])
   { display: none }` and render the closed dialog on the page. */
dialog.modal-card:not(.modal-card--video):not(.modal-card--image)[open] {
  display: flex;
  flex-direction: column;
}
/* The photo absorbs the available height. min-height:0 is required — a flex
   item's default min-height:auto would floor it at its aspect-ratio height
   and reintroduce the overflow. */
dialog.modal-card:not(.modal-card--video):not(.modal-card--image)[open] img {
  flex: 1 1 auto;
  min-height: 0;
}
/* The caption never shrinks; it stays fully visible at every height. */
dialog.modal-card:not(.modal-card--video):not(.modal-card--image)[open] .modal-card__caption {
  flex: 0 0 auto;
}
dialog.modal-card::backdrop {
  background: rgba(0, 0, 0, 0.5);
}
/* Preserves the Figma photo proportions (568x636) even with the asset
   missing, instead of collapsing — same principle as every other
   missing-image slot on the site. Inner radius deliberately smaller than
   the outer card's (16px): nesting the SAME radius on both reads as
   unintentional — half the outer value is a common, clearly-deliberate
   inner/outer ratio. No smaller radius token exists in tokens.css to
   reuse, so this is a scoped one-off rather than a new global token. */
/* NOTE: intentionally NO width/height attributes on this <img> in the HTML.
   This is the one image rule on the site with no explicit CSS height, so an
   HTML height attribute would win over the aspect-ratio below and render the
   photo at its full 1512px intrinsic height (verified live). The
   aspect-ratio here already reserves the box, so the attributes add nothing. */
dialog.modal-card img { border-radius: 8px; width: 100%; aspect-ratio: 568 / 636; object-fit: cover; display: block; }
/* Close button: previously positioned at the same 16px inset as the image
   itself (both measured from the dialog's padding edge), so its top-right
   corner landed exactly ON the image's top-right corner — confirmed live,
   zero clearance, easy to lose against the photo. Figma-preferred
   placement (button in dedicated white space above the photo) would need
   extra vertical room reserved above the image, growing the dialog's
   content height beyond what it needs today — in tension with "preserve
   current popup dimensions," which this pass keeps literal. Using the
   explicitly-given fallback instead: pulled inward to a real margin from
   both the card and image edges, enlarged, and given a solid dark circular
   backdrop so it reads clearly regardless of what's under it in the photo
   (the old bare white-on-photo × had no such guarantee). */
.modal-card__close {
  position: absolute;
  top: calc(var(--space-lg) + var(--space-sm));
  right: calc(var(--space-lg) + var(--space-sm));
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-white);
  background: rgba(0, 0, 0, 0.45);
  border-radius: var(--radius-pill);
  border: none;
}
/* Glyph centering: a font-rendered × (as a text node, then as a ::before
   with content:"\00d7" + a small transform nudge) both still read
   visibly off-center in Megan's actual screenshots — font glyph metrics
   aren't reliable enough here regardless of how the offset is measured.
   Replaced with a purely geometric CSS-drawn X: two pseudo-element lines,
   each independently centered at the button's own 50%/50% point via
   translate(-50%,-50%) BEFORE rotation, so their intersection sits at the
   exact mathematical center of the 32x32 box no matter what — nothing
   here depends on any font's ink/baseline/side-bearing metrics. */
.modal-card__close::before,
.modal-card__close::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 14px;
  height: 2px;
  background: currentColor;
  border-radius: 1px;
}
.modal-card__close::before { transform: translate(-50%, -50%) rotate(45deg); }
.modal-card__close::after { transform: translate(-50%, -50%) rotate(-45deg); }
.modal-card__caption {
  text-align: center;
  margin-top: var(--space-md);
}

/* ==========================================================================
   CASE STUDY template (shared by every /work/ case study)
   One structure for every case study, on the site's 12-column grid (§19):
     .case-hero    full-bleed header: the nav rule, kicker/title | intro,
                   then the facts row (role / timeline / team) and tags.
     .case-body    the content .container: proof row, hero image, then
                   .case-inner (Challenge → Approach) and the closing.
   Inside the body, every section is its own 12-column grid at >=840px:
   labels (eyebrow / "Approach 0X") sit in columns 1-3, headings run from
   column 4 to the edge, prose runs from column 4 (columns 4-10 at 1200+,
   an ~680-720px measure), and visual evidence spans all 12 columns.
   Below 840px everything stacks in one column, in DOM order.
   ========================================================================== */

/* ---- Hero ------------------------------------------------------------------
   The border is the SAME full-bleed rule the homepage .hero and the About
   bands draw under the nav (home.css / about.css), so the header sits
   outside the content .container. */
.case-hero {
  border-top: var(--border-width) solid var(--color-rule);
  padding-block-start: var(--space-section-y);
}
/* Title in columns 1-3, intro in 4-12 (.case-span-1 / .case-span-3). */
.case-hero__top { row-gap: var(--space-lg); }
.case-hero__kicker { display: block; margin-bottom: var(--space-xs); color: var(--color-ink); }
.case-hero__title { margin-bottom: 0; color: var(--color-blue-text); }
.case-hero__intro { margin-bottom: var(--space-md); color: var(--color-ink); }
.case-hero__context { color: var(--color-ink); }
.case-hero__context + .case-hero__context { margin-top: var(--space-md); }

/* Facts + tags: one quiet block under a hairline (the proof row's own
   rules close it off below). Facts are a <dl> on the
   same grid (role 1-3, timeline 4-6, team 7-12); tags use the site's
   .topic-tag chips (site.css): the blue one with an icon is the category,
   the gray ones are skills and tools. */
.case-meta {
  margin-top: var(--space-3xl);
  padding-top: var(--space-xl);
  border-top: var(--border-width) solid var(--color-rule);
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}
.case-facts { row-gap: var(--space-lg); }
.case-facts__item { grid-column: span 3; }
.case-facts__item--wide { grid-column: span 6; }
.case-facts dt {
  font-family: var(--font-detail);
  font-size: var(--text-details200-size);
  line-height: var(--text-details200-leading);
  text-transform: uppercase;
  color: var(--color-gray);
  margin-bottom: var(--space-xs);
}
.case-facts dd {
  margin: 0;
  font-family: var(--text-body300-font);
  font-size: var(--text-body300-size);
  line-height: var(--text-body300-leading);
  color: var(--color-ink);
}
.case-tags { display: flex; flex-wrap: wrap; gap: var(--space-sm); }
@media (max-width: 839px) {
  .case-facts__item { grid-column: span 2; }
  .case-facts__item--wide { grid-column: 1 / -1; }
}

/* ---- Proof row ----------------------------------------------------------------
   Compact: a large figure over a short label, on hairlines, three columns
   (4 grid columns each). The markup is unchanged from the old stat cards
   (<span class="card-stat__number"> + label text), so every case study's
   stats inherit it. Phones: one stacked row per figure. */
.case-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: var(--grid-gutter);
  margin-top: var(--space-2xl);
}
.card-stat {
  border-top: var(--border-width) solid var(--color-rule);
  padding-top: var(--space-md);
}
.card-stat__text {
  font-family: var(--text-body300-font);
  font-size: var(--text-body300-size);
  line-height: var(--text-body300-leading);
  color: var(--color-ink);
}
.card-stat__number {
  display: block;
  margin-bottom: var(--space-xs);
  font-family: var(--font-body);
  font-size: var(--text-display200-size);
  font-weight: var(--weight-semibold);
  line-height: 1.15;
  color: var(--color-blue-text);
}
@media (max-width: 839px) {
  .case-stats { grid-template-columns: 1fr; }
  .card-stat { padding-block: var(--space-md); }
  .card-stat:last-child { border-bottom: var(--border-width) solid var(--color-rule); }
  .card-stat__number { font-size: var(--text-stat-size); }
}

/* ---- Media boxes ------------------------------------------------------------ */
.case-hero-media,
.case-section__media {
  position: relative;
  border-radius: var(--radius-card);
  border: var(--border-width) solid var(--color-gray-3);
  background: var(--color-light-blue);
  overflow: hidden;
  aspect-ratio: 16 / 9;
}
.case-hero-media { margin-top: var(--space-3xl); aspect-ratio: 12 / 5; } /* 2400x1100 source */
@media (max-width: 839px) {
  /* width:100% is load-bearing: with only min-height and aspect-ratio the
     browser derives the WIDTH from the enforced height (200 x 12/5 = 480px)
     and overflows the page. A definite width keeps height the derived axis. */
  .case-hero-media { width: 100%; min-height: 200px; }
}
.case-hero-media img,
.case-section__media img { width: 100%; height: 100%; object-fit: cover; object-position: top; display: block; }
.case-hero-media--missing[data-expected-asset]::after,
.case-section__media--missing[data-expected-asset]::after {
  content: attr(data-expected-asset);
  position: absolute;
  inset: auto var(--space-md) var(--space-md) auto;
  font-family: var(--font-detail);
  font-size: var(--text-legal-size);
  color: var(--color-blue);
  background: rgba(255, 255, 255, 0.7);
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-pill);
  pointer-events: none;
}

/* ---- Grid primitive -------------------------------------------------------------
   The site grid's own columns and gutter (tokens.css): 12 columns at 840+,
   4 below. The span classes keep their old meaning in quarters of the
   width (span-1 = 3 columns ... span-4 = all 12). */
.case-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gutter);
}
.case-span-1 { grid-column: span 3; }
.case-span-2 { grid-column: span 6; }
.case-span-3 { grid-column: span 9; }
.case-span-4 { grid-column: 1 / -1; }
@media (max-width: 839px) {
  .case-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .case-span-1, .case-span-2, .case-span-3, .case-span-4 { grid-column: 1 / -1; }
}

/* Challenge → last Approach, and the closing: a vertical stack on the major
   section rhythm. */
.case-inner { display: flex; flex-direction: column; gap: var(--space-section-y); }
.case-divider {
  border: 0;
  border-top: var(--border-width) solid var(--color-gray-3);
  margin: 0;
  width: 100%;
}
.case-divider--major { margin-block: var(--space-section-y); }

/* ---- Sections: label rail, heading, prose, evidence -------------------------- */
.case-section__eyebrow,
.approach-section__number {
  display: block;
  color: var(--color-gray);
  margin-bottom: var(--space-sm);
}
.case-section__heading { margin-bottom: var(--space-lg); }
.case-section__body { color: var(--color-ink); }
.case-section__body p + p { margin-top: var(--space-md); }
.case-section__media { margin-top: var(--space-2xl); }
.approach-list { display: flex; flex-direction: column; gap: var(--space-section-y); }
.approach-section__subhead { margin-bottom: var(--space-lg); }
.approach-section__text { color: var(--color-ink); }
.approach-section__text + .approach-section__text,
.approach-section__text + .approach-section__list,
.approach-section__list + .approach-section__text { margin-top: var(--space-md); }
/* A short bridging paragraph between two visuals, or a closing line after
   one, gets the same breathing room as the visual itself. */
.case-section__media + .approach-section__text,
.approach-section__media-row + .approach-section__text,
.approach-section__media-filmstrip + .approach-section__text { margin-top: var(--space-2xl); }
.approach-section__list {
  color: var(--color-ink);
  list-style: disc;
  padding-left: var(--space-xl);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

@media (min-width: 840px) {
  .case-section,
  .approach-section {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: var(--grid-gutter);
    align-items: start;
  }
  /* The Approach body wrapper dissolves so its heading and paragraphs place
     on the section grid directly. */
  .approach-section__body { display: contents; }
  .case-section > *,
  .approach-section > *,
  .approach-section__body > * { grid-column: 4 / -1; }
  /* Label rail: same row as the heading, sharing its first baseline. */
  .case-section > .case-section__eyebrow,
  .approach-section > .approach-section__number {
    grid-column: 1 / span 3;
    grid-row: 1;
    align-self: baseline;
    margin-bottom: 0;
  }
  .case-section > .case-section__heading,
  .approach-section__body > .approach-section__subhead { grid-row: 1; align-self: baseline; }
  /* Evidence, card grids and list wrappers use all 12 columns. */
  .case-section > .case-section__media,
  .case-section > .insights-grid,
  .case-section > .approach-list,
  .case-section > .case-stats,
  .case-section > .callout-reflection,
  .approach-section > .case-section__media,
  .approach-section > .approach-section__media-row,
  .approach-section > .approach-section__media-filmstrip { grid-column: 1 / -1; }
}
/* Editorial measure for running prose (~680-720px). The grid's columns
   widen with the viewport, so the span narrows in steps to hold the
   measure: columns 4-12 below 1280px (~700px at 1200), 4-11 at
   1280-1399px, and 4-10 from 1400px (~680px at 1440). */
@media (min-width: 1280px) {
  .case-section > .case-section__body,
  .approach-section__body > .approach-section__text,
  .approach-section__body > .approach-section__list,
  .approach-section > .approach-section__text { grid-column: 4 / span 8; }
}
@media (min-width: 1400px) {
  .case-section > .case-section__body,
  .approach-section__body > .approach-section__text,
  .approach-section__body > .approach-section__list,
  .approach-section > .approach-section__text { grid-column: 4 / span 7; }
}

/* ---- Key Insights -------------------------------------------------------------
   2x2 on the 12-column grid (each card 6 columns). With subgrid, each card's
   label, heading and body share row tracks with the card beside it, so they
   line up across the pair; each pair of rows is only as tall as its own
   content (no forced page-wide height). */
.insights-grid { margin-top: var(--space-2xl); }
.insights-grid > .card-insight + .card-insight { margin-top: var(--space-2xl); }
@media (min-width: 840px) {
  .insights-grid > .card-insight:nth-child(2) { margin-top: 0; }
}
@supports (grid-template-rows: subgrid) {
  .card-insight {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: span 3;
    row-gap: 0;
    align-content: start;
  }
}
.card-insight {
  background: var(--color-surface);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-card);
  padding: var(--space-xl);
  box-shadow: var(--shadow-card);
}
.card-insight__eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font-family: var(--font-detail);
  font-size: var(--text-details200-size);
  font-weight: var(--text-details200-weight);
  line-height: var(--text-details200-leading);
  text-transform: uppercase;
  color: var(--color-gray);
  margin-bottom: var(--space-md);
}
.card-insight__eyebrow > span { color: var(--color-blue-text); }
.card-insight__heading {
  font-family: var(--text-h400-font);
  font-size: var(--text-h400-size);
  font-weight: var(--text-h400-weight);
  line-height: var(--text-h400-leading);
  color: var(--color-ink);
  margin-bottom: var(--space-md);
}
.card-insight ul { display: flex; flex-direction: column; }
.card-insight li {
  padding-block: var(--space-lg);
  border-top: var(--border-width) solid var(--color-gray-3);
}
.card-insight li:first-child { padding-top: 0; border-top: 0; }
.card-insight li:last-child { padding-bottom: 0; }
/* :not(.card-insight__eyebrow) is a real, confirmed specificity fix: without
   it this (class + element) selector outranks the eyebrow's class-only rule
   and silently turns the eyebrow into ink body text. Do not simplify it. */
.card-insight li,
.card-insight p:not(.card-insight__eyebrow) {
  font-family: var(--text-body300-font);
  font-size: var(--text-body300-size);
  line-height: var(--text-body300-leading);
  color: var(--color-ink);
}
/* Two body paragraphs in one card: same hairline divider as the list. */
.card-insight p + p {
  margin-top: var(--space-lg);
  padding-top: var(--space-lg);
  border-top: var(--border-width) solid var(--color-gray-3);
}

/* ---- Approach visual groups ----------------------------------------------------
   Wide + narrow pair: 8 + 4 columns at 840+ (an exact 2:1 on the grid);
   stacked on phones, each image at its own aspect ratio. Never scrolls
   horizontally. The narrow image switches to object-fit:contain on a white
   letterbox at 840+, because stretch-matched height won't equal its own
   ratio and cropping into real UI to fill the box is worse. */
.approach-section__media-row { margin-top: var(--space-2xl); align-items: stretch; }
.approach-section__media-row .case-section__media { margin-top: 0; min-width: 0; max-width: 100%; }
.approach-section__media-row--03 .case-section__media:first-child { aspect-ratio: 1374 / 1000; } /* All Courses grid */
.approach-section__media-row--03 .case-section__media:last-child { aspect-ratio: 614 / 1064; } /* mobile course list, portrait */
.approach-section__media-row--04 .case-section__media:first-child { aspect-ratio: 1374 / 400; } /* Welcome/Nursing banner */
.approach-section__media-row--04 .case-section__media:last-child { aspect-ratio: 550 / 400; } /* Account Settings phone */
@media (max-width: 839px) {
  .approach-section__media-row { row-gap: var(--space-xl); }
}
@media (min-width: 840px) {
  .approach-section__media-row > .case-section__media:first-child { grid-column: span 8; }
  .approach-section__media-row > .case-section__media:last-child {
    grid-column: span 4;
    aspect-ratio: auto;
    background: var(--color-white);
  }
  /* The wide image alone sets the row height (its own aspect ratio); the
     narrow image is taken out of flow so its intrinsic height can't
     stretch the row and crop the wide image. */
  .approach-section__media-row .case-section__media:last-child img {
    position: absolute;
    inset: 0;
    object-fit: contain;
  }
  /* Student Dashboard 03B keeps its approved light-blue letterbox at every width. */
  .approach-section__media-row--03 .case-section__media:last-child { background: var(--color-light-blue); }
}
/* Six-state grid (Student Dashboard Approach 02): 3x2, 4 columns each. */
.approach-section__media-filmstrip {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--grid-gutter);
  margin-top: var(--space-2xl);
}
.approach-section__media-filmstrip .case-section__media { margin-top: 0; aspect-ratio: 620 / 500; }
@media (max-width: 839px) {
  .approach-section__media-filmstrip { grid-template-columns: 1fr; row-gap: var(--space-xl); }
}

/* ---- Per-image aspect ratios (exact source dimensions) ------------------------ */
/* Student Dashboard */
.case-section__media--collage { aspect-ratio: 1988 / 1000; }
.case-section__media--challenge { aspect-ratio: 1988 / 1000; }
.case-section__media--approach01 { aspect-ratio: 1988 / 1100; }
/* K–12 Family Onboarding */
.case-hero-media.case-hero-media--k12 { aspect-ratio: 8 / 3; } /* 2400x900 */
.case-section__media--k12-routing { aspect-ratio: 2854 / 1252; }
.case-section__media--k12-concepts { aspect-ratio: 1988 / 1000; }
.case-section__media--k12-views { aspect-ratio: 1988 / 1000; }
.case-section__media--k12-lessons { aspect-ratio: 1988 / 1000; }
.case-section__media--k12-tracker { aspect-ratio: 1988 / 1793; }
.case-section__media--k12-entry { aspect-ratio: 1988 / 900; }
.approach-section__media-row--k12-01 .case-section__media:first-child { aspect-ratio: 1374 / 500; }
@media (max-width: 839px) {
  /* Narrow images use their own ratio only when stacked; at 840+ the pair
     rule above sizes them to the wide image. */
  .approach-section__media-row--k12-01 .case-section__media:last-child { aspect-ratio: 550 / 500; }
}

/* ---- Closing: reflection -----------------------------------------------------------
   Full width, brand blue in both themes, sized to its content. It follows
   the Solution / Launch section as the page's last narrative beat. */
.callout-reflection {
  background: var(--color-blue);
  color: var(--color-white);
  border-radius: var(--radius-card);
  padding: var(--space-3xl) var(--space-2xl);
  box-shadow: var(--shadow-card);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
  text-align: center;
}
.callout-reflection__kicker {
  font-family: var(--font-detail);
  font-size: var(--text-details200-size);
  text-transform: uppercase;
  color: var(--color-white);
  opacity: 0.75;
}
.callout-reflection__quote {
  font-family: var(--text-h300-font);
  font-size: var(--text-h300-size);
  font-weight: var(--text-h300-weight);
  line-height: var(--text-h300-leading);
  color: var(--color-white);
  max-width: 36em;
}
@media (max-width: 839px) {
  .callout-reflection { padding: var(--space-2xl) var(--space-xl); }
}
/* A recap proof row inside the closing sits on the closing's own gap. */
.case-inner > .case-stats { margin-top: 0; }

/* ---- Next case study + View All Work -------------------------------------------
   Text-only, shared. Order follows the homepage's featured projects and wraps
   to the start; each page links its successor in static markup. */
.case-next {
  margin-top: var(--space-section-y);
  border-top: var(--border-width) solid var(--color-rule);
}
.case-next__inner {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-xl);
  padding-block: var(--space-2xl) var(--space-section-y);
}
.case-next__link { display: flex; flex-direction: column; gap: var(--space-xs); }
.case-next__label {
  font-family: var(--font-detail);
  font-size: var(--text-details200-size);
  text-transform: uppercase;
  color: var(--color-gray);
}
.case-next__title {
  font-family: var(--text-h300-font);
  font-size: var(--text-h300-size);
  font-weight: var(--text-h300-weight);
  line-height: var(--text-h300-leading);
  color: var(--color-blue-text);
}
.case-next__arrow { display: inline-block; transition: transform var(--duration-base) var(--ease-standard); }
.case-next__link:hover .case-next__arrow,
.case-next__link:focus-visible .case-next__arrow { transform: translateX(4px); }
.case-cta__link {
  font-family: var(--font-detail);
  font-size: var(--text-details200-size);
  text-transform: uppercase;
  color: var(--color-blue-text);
  transition: opacity var(--duration-base) var(--ease-standard);
}
.case-cta__link:hover, .case-cta__link:focus-visible { opacity: 0.7; }

/* ---- Video lightbox (Strider Fox expand) — reuses native <dialog> modal
   semantics, same pattern as the footer cat-popup. ------------------------ */
dialog.modal-card--video {
  max-width: 960px;
  width: calc(100vw - var(--space-2xl) * 2);
  padding: 0;
  background: var(--color-black);
}
dialog.modal-card--video .modal-card__close {
  color: var(--color-white);
  z-index: 1;
}
.video-lightbox__body { width: 100%; }
/* aspect-ratio reserves the 16:9 box before metadata exists. Without it an
   unloaded <video> falls back to the 300x150 replaced-element default, so the
   dialog opens badly proportioned and then jumps when the metadata arrives. */
.video-lightbox__body video { width: 100%; height: auto; aspect-ratio: 16 / 9; display: block; }

/* ---- Case-study image inspection ------------------------------------------
   Product-design screenshots carry detail that cannot be read at mobile inline
   size. Each case-study media box gets a small expand affordance that opens the
   same production image in a lightbox at its natural proportions.
   The control is position:absolute inside the already-position:relative media
   box, so it causes zero layout shift, and it is mobile-only: desktop inline
   images are already large enough and the brief was explicit about not
   cluttering them. Icon reuses the existing Font Awesome solid set
   (fa-expand) — the same family the cursor system already loads. */
.media-expand {
  display: none;
  position: absolute;
  right: var(--space-md);
  bottom: var(--space-md);
  z-index: 1;
  width: 32px;
  height: 32px;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, 0.55);
  color: var(--color-white);
  font-size: 13px;
  line-height: 1;
  transition: opacity var(--duration-base) var(--ease-standard);
}
.media-expand:hover, .media-expand:focus-visible { opacity: 0.85; }
@media (max-width: 839px) {
  .media-expand { display: inline-flex; }
}

/* Lightbox shell: same .modal-card component and the same CSS-drawn close X as
   the cat popup and the Strider lightbox. Width/height are driven by the image
   so its natural aspect ratio is preserved exactly — it is never cropped or
   stretched here, only fitted. */
dialog.modal-card--image {
  max-width: calc(100vw - var(--space-2xl));
  width: auto;
  padding: var(--space-sm);
  background: var(--color-surface);
}
.image-lightbox__body { display: flex; align-items: center; justify-content: center; }
/* Selector is deliberately specific (0,2,2). The cat popup's
   `dialog.modal-card img` rule (0,1,2) also matches images inside this dialog
   and forces `width:100%; aspect-ratio:568/636; object-fit:cover` — which
   rendered a 1374x1000 screenshot as a distorted, cropped 327x366 box.
   aspect-ratio and object-fit are reset explicitly so the image keeps its own
   natural proportions and is only ever scaled down to fit, never cropped. */
dialog.modal-card--image .image-lightbox__body img {
  display: block;
  aspect-ratio: auto;
  object-fit: contain;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: calc(100vh - var(--space-2xl) * 2 - var(--space-lg));
  max-height: calc(100dvh - var(--space-2xl) * 2 - var(--space-lg));
  border-radius: 8px;
}
