/* ==========================================================================
   Project detail template  (Figma 2001:9661 — "Project details: UNC Health")

   Page rhythm on the 1920 canvas:
     header            0…123
     masthead marquee  123…383   (260)
     meta row          463
     hero image        575…1595  (1920×1020)
     intro text block  1595…2439 (padding 180/120)
     white body        2439…11559 (padding 180/120, 180 gap between blocks)
     full-bleed image  11559…12639
     next project      12639…13719
     footer            13719…14056
   ========================================================================== */

.page-project {
  --color-ink: #1b1a18; /* Black / Border-Dark */
  --color-rule-dark: rgb(27 26 24 / 0.4);
}

/* --------------------------------------------------------------------------
   Page Masthead + section MASTHEAD  (Figma 2001:9664)
   -------------------------------------------------------------------------- */

.pmast {
  position: relative;
  padding-top: var(--header-h);
  overflow: hidden;
}

/* [derived] Aura + grid behind the title, reusing the home hero's classes
   so the light theme and reduced-motion handling come for free.
   Kept quieter than the home hero: the marquee type is the focal point. */
.pmast__fx {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.pmast .hero__aura {
  opacity: 0.38;
}

.pmast__grid {
  /* The grid fades out before the hero image so it never prints on it. */
  -webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent 85%);
  mask-image: linear-gradient(to bottom, #000 55%, transparent 85%);
}

.pmast__title,
.pmast__media {
  position: relative;
}

.pmast__media {
  margin: 0;
  aspect-ratio: 1920 / 1020;
  /* The figure drifts on scroll (data-parallax); a small shadow keeps it
     reading as a card above the page rather than a seam. */
  box-shadow: var(--shadow-card);
}

/* [derived] The header no longer carries a permanent hairline on these
   pages (it condenses the same way it does on the home page). The
   masthead title takes over that separating line, on its top edge, so the
   header still reads as visually closed off at the top of the page. */
.pmast__title {
  height: clamp(140px, calc(13.542 * var(--vw)), 260px);
  margin: 0;
  display: flex;
  align-items: center;
  /* Butler's visible glyphs sit high inside their line box. This optical
     inset shifts that centred line box down by 0.13em, balancing the visible
     space above and below the marquee without changing the section height. */
  padding-top: 0.26em;
  border-top: 1px solid var(--color-rule);
  border-bottom: 1px solid var(--color-rule);
  font-family: var(--font-display);
  font-weight: 200;
  font-size: var(--fs-display-lg);
  line-height: var(--lh-display-lg);
  letter-spacing: var(--ls-display);
  color: var(--color-yellow);
}

.pmast__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* --------------------------------------------------------------------------
   Text Block  (Figma 2001:9670 dark / 2001:9686 light)
   padding 180/120 · label flush left · 1186 column flush right
   -------------------------------------------------------------------------- */

.tblock {
  padding-inline: var(--gutter);
}

.tblock--dark {
  padding-block: var(--sp-180);
}

.tblock__inner {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-40);
  max-width: var(--content-w);
  margin-inline: auto;
}

.tblock__label {
  flex-shrink: 0;
  margin: 0;
  padding-top: 8px; /* 12/16 caps optically aligned to the 64px lede */
}

.tblock__body {
  width: 100%;
  max-width: clamp(320px, calc(61.771 * var(--vw)), 1186px);
}

.tblock__lede {
  margin: 0;
  letter-spacing: 0.5px;
  color: var(--color-text);
}

.tblock__copy {
  margin: 0;
  color: var(--color-ink);
}

.tblock__copy + .tblock__copy {
  margin-top: var(--sp-32); /* one blank 32px line, per Figma */
}

.tblock--center .tblock__inner {
  justify-content: center;
}

/* .tblock--center .tblock__copy {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
} */

/* --- Meta columns (Services / Deliverables / Links) -----------------------
   Three 382px columns at x0, x470, x940 with an 88px gutter. The third
   column overflows its 1186 parent in Figma; giving it the remaining space
   keeps the x-positions exact without pushing the page wider. */
.pmeta {
  display: grid;
  grid-template-columns:
    clamp(160px, calc(19.896 * var(--vw)), 382px)
    clamp(160px, calc(19.896 * var(--vw)), 382px)
    1fr;
  gap: clamp(24px, calc(4.583 * var(--vw)), 88px);
  margin-top: var(--sp-80);
}

.pmeta__heading {
  margin: 0;
  color: var(--color-cream);
}

.pmeta__list {
  /* 16px gap after the heading plus the blank line Figma leaves in the
     text layer = 48px to the first entry. */
  margin-top: var(--sp-48);
  color: var(--color-text);
}

.pmeta__list li + li {
  margin-top: var(--sp-32); /* blank line between entries */
}

.pmeta__col--link .btn-circle {
  margin: var(--sp-40) 0 0;
}

/* --------------------------------------------------------------------------
   White project body  (Figma 2001:9685)
   -------------------------------------------------------------------------- */

/* NAV PROJECTS runs straight into the footer here — no 200px gap. */
.page-project .site-footer {
  margin-top: 0;
}

/* The contact CTA no longer reaches a project footer at all: it lives only on
   home/404, and the router now reconciles it on every route change instead of
   leaving the previous page's copy behind (see syncFooter in js/router.js).
   The `.page-project .site-footer__cta { border-top: 0 }` override that used
   to sit here only ever existed to patch that leak, so it has been removed. */

.pbody {
  display: flex;
  flex-direction: column;
  gap: var(--pbody-gap, var(--sp-180));
  padding: var(--pbody-padding, var(--sp-180)) 0;
  background-color: var(--color-white);
  color: var(--color-ink);
}

.case-inova,
.case-lucid-motors {
  --pbody-gap: var(--sp-200);
  --pbody-padding: var(--sp-200);
}

.case-inova .tblock--dark,
.case-lucid-motors .tblock--dark {
  margin-block: var(--sp-20);
}

.pbody .tblock__label {
  color: var(--color-ink);
}

.pfigure {
  width: 100%;
  max-width: calc(var(--content-w) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.pfigure img {
  width: 100%;
  height: auto;
  aspect-ratio: var(--pfigure-ratio, 1680 / 945);
  object-fit: cover;
}

.pgrid-1x1 img {
  width: 100%;
  height: auto;
  aspect-ratio: var(--pfigure-ratio, 1680 / 1680);
  object-fit: cover;
}

/* Video figure (Figma 2063:7751 — Lucid). Muted, looping, chromeless; playback
   is driven by visibility — see js/fx.js. */
.pfigure--video .pfigure__video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: var(--pfigure-ratio, 1680 / 945);
  object-fit: cover;
  background: #e8e6e0; /* sampled from the Figma node's base fill */
}

.pfigure--portrait {
  --pfigure-ratio: 1680 / 1091;
}

.pfigure--feature-flat {
  --pfigure-ratio: 1680 / 740;
}

/* DIRECTV title-art row (Figma 2052:5551 — 1680×763). */
.pfigure--posters {
  --pfigure-ratio: 1680 / 763;
}

/* Inova Pediatrics feature (Figma 2001:10587): the source export flattened
   type and photography into one bitmap. Rebuild the 940/740 split so the
   heading and copy remain responsive, selectable and accessible. */
.pfigure__feature-inner {
  display: grid;
  grid-template-columns: 573fr 451fr;
  gap: var(--sp-60);
  aspect-ratio: var(--pfigure-ratio);
  overflow: hidden;
}

.pfigure__feature-copy {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-width: 0;
  /* This panel stays on the white project surface in both themes. Unlike
     --color-navy, --color-ink does not invert to the light page ground. */
  color: var(--color-ink);
}

.pfigure__feature-title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 200;
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
}

.pfigure__feature-copy p {
  width: min(100%, 780px);
  margin: 0 0 4px;
  /* font-size: var(--fs-body);
  line-height: var(--lh-body); */
}

.pfigure__feature-media {
  min-width: 0;
  margin: 0;
  overflow: hidden;
}

.pfigure__feature-media img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
  object-fit: cover;
}

/* [derived] The Figma frame (2001:10235) is a long site concept sitting in
   a navy panel. As a static export it reads as one crop; making the panel a
   viewport and letting the concept scroll freely inside it is the closest
   web equivalent — visitors see the whole page, not just its hero.
   The frame is the navy panel itself: the concept sits inside it with a
   proportional inset (≈145px side / 128px top on the 1680 design) and the
   bottom of the page bleeds out of view, matching Figma 2001:10235.
   Wheel / touch / keyboard scrolling works inside the frame;
   data-lenis-prevent keeps Lenis from hijacking the inner scroll. */
.pfigure__scroll {
  aspect-ratio: var(--pfigure-ratio, 1680 / 1091);
  overflow-y: auto;
  border-radius: 2px;
  scrollbar-width: none;
  background: #0e296b; /* sampled from the frame's navy panel */
  padding: 7.62% 0 0;
  -webkit-overflow-scrolling: touch;
}

.pfigure__scroll::-webkit-scrollbar {
  display: none;
}

/* The concept is 3360×16866, so it must keep its own tall intrinsic ratio
   rather than the 1680/1091 frame ratio inherited from `.pfigure img`.

   Do NOT write `aspect-ratio: auto` here. The UA stylesheet derives a
   placeholder ratio from the img's width/height attributes
   (`img[width][height] { aspect-ratio: attr(width) / attr(height) }`), and an
   author `auto` overrides it. That left this figure at 1304×33px until the
   16866px-tall JPEG finished decoding, so on a cold load the partially
   decoded image painted into a near-zero box — it read as a smeared/stretched
   band before snapping to full height. Declaring the true ratio reserves the
   correct box on the very first paint. */
.pfigure--scrollable img {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  aspect-ratio: var(--scroll-img-ratio, 3360 / 16866);
  object-fit: contain;
}

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

/* --- Feature block: big number/title beside a square image, 200px gutter -- */

.pfeature {
  display: grid;
  grid-template-columns: clamp(280px, calc(38.542 * var(--vw)), 740px) 1fr;
  gap: var(--sp-200);
  width: 100%;
  max-width: calc(var(--content-w) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.pfeature__text {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.pfeature__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 200;
  font-size: var(--fs-display-lg);
  line-height: var(--lh-display-lg);
  letter-spacing: var(--ls-display);
  color: var(--color-ink);
}

/* Figma "Heading 4" at the 38px line-height variant (24/38) — the copy in
   this block is set larger than body text so the six measured lines fall
   inside the 228px text box beside the title. */
.pfeature__copy {
  margin: 0;
  font-size: var(--fs-h4);
  line-height: 1.5833; /* 38/24 */
  color: var(--color-ink);
}

.pfeature__media {
  margin: 0;
  aspect-ratio: 1;
}

.pfeature__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* --------------------------------------------------------------------------
   Template-anatomy blueprint  (Figma 2052:5303 — 740×740, DIRECTV)

   Rebuilt as markup instead of exported as a bitmap: the source frame is
   pure vector — rectangles, 1px lines, dots and eight labels — so CSS
   reproduces it exactly, stays crisp at any zoom, and keeps the module
   names as real text rather than pixels.

   Every number in the markup is the raw Figma coordinate inside the 740px
   frame; `--u` converts that design space into container-relative units so
   the whole diagram scales as one piece. Colours were sampled from the
   frame render — this is one diagram, not a reusable brand palette, so it
   deliberately does not reference the site tokens.
   -------------------------------------------------------------------------- */

.bp {
  position: relative;
  overflow: hidden;
  container-type: inline-size;
  background: var(--bp-ground);

  --bp-ground: #ecf3fb;
  --bp-surface: #253551;
  --bp-stroke: rgb(0 109 255 / 0.6);
  --bp-accent: #006dff;
  --bp-highlight: #fff9b6;

  /* Viewport onto the 740×740 frame. The mobile query below narrows it to
     the wireframe column alone. */
  --bp-frame: 740;
  --bp-height: 740;
  --bp-origin-x: 83;
  --bp-origin-y: 74.859;

  --u: calc(100cqw / var(--bp-frame));
  aspect-ratio: var(--bp-frame) / var(--bp-height);
}

.bp__grid {
  position: absolute;
  left: calc(var(--bp-origin-x) * var(--u));
  top: calc(var(--bp-origin-y) * var(--u));
  width: calc(574 * var(--u));
  height: calc(590.283 * var(--u));
}

.bp-row,
.bp-block,
.bp-bar,
.bp-card,
.bp-cta,
.bp-rule,
.bp-plus,
.bp-callout,
.bp-label {
  position: absolute;
  left: calc(var(--x) * var(--u));
  top: calc(var(--y) * var(--u));
  width: calc(var(--w) * var(--u));
  height: calc(var(--h) * var(--u));
}

.bp-block {
  background: var(--bp-surface);
  border-radius: calc(3 * var(--u));
  box-shadow: inset 0 0 0 max(1px, calc(1 * var(--u))) var(--bp-stroke);
}

.bp-bar {
  background: rgb(255 255 255 / var(--a, 1));
  border-radius: calc(1.5 * var(--u));
}

.bp-bar--accent {
  background: var(--bp-highlight);
}

.bp-bar--solid {
  background: #fff;
}

.bp-cta {
  background: var(--bp-accent);
  border-radius: calc(2 * var(--u));
}

.bp-card {
  border-radius: calc(2 * var(--u));
  box-shadow: inset 0 0 0 max(1px, calc(0.75 * var(--u))) rgb(255 255 255 / 0.22);
}

.bp-card--featured {
  background: rgb(255 255 255 / 0.08);
  box-shadow: inset 0 0 0 max(1px, calc(0.75 * var(--u))) rgb(255 255 255 / 0.55);
}

.bp-rule {
  height: max(1px, calc(0.9 * var(--u)));
  background: rgb(255 255 255 / 0.14);
}

.bp-plus::before,
.bp-plus::after {
  content: "";
  position: absolute;
  background: rgb(255 255 255 / 0.6);
}

.bp-plus::before {
  inset-inline: 0;
  top: 50%;
  height: max(1px, calc(0.9 * var(--u)));
  transform: translateY(-50%);
}

.bp-plus::after {
  inset-block: 0;
  left: 50%;
  width: max(1px, calc(0.9 * var(--u)));
  transform: translateX(-50%);
}

.bp-callout {
  display: flex;
  align-items: center;
}

.bp-callout::before {
  content: "";
  flex: none;
  width: calc(4.131 * var(--u));
  height: calc(4.54 * var(--u));
  border-radius: 50%;
  background: var(--bp-accent);
}

.bp-callout::after {
  content: "";
  flex: 1;
  height: max(1px, calc(0.9 * var(--u)));
  background: var(--bp-accent);
}

.bp-label {
  display: flex;
  align-items: center;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: calc(12.8 * var(--u));
  line-height: 1.2;
  letter-spacing: calc(0.95 * var(--u));
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--bp-accent);
}

/* --- 4-up image grid, 840×840 cells, no gutter --------------------------- */

.pgrid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  width: 100%;
  max-width: calc(var(--content-w) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.pgrid figure {
  margin: 0;
  aspect-ratio: 1;
}

.pgrid img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.pgrid--mixed .pgrid__wide {
  grid-column: 1 / -1;
  aspect-ratio: 2 / 1;
}

.pgrid--diagrams {
  grid-template-columns: repeat(2, 790fr);
  gap: clamp(24px, calc(5.208 * var(--vw)), 100px);
}

/* --------------------------------------------------------------------------
   TAXONOMY / SITEMAP DIAGRAMS — rebuilt as real markup underneath the two
   reference PNGs above (never a replacement for them — both stay). [derived]
   Every node position and connector length below was traced from the two
   source images at their native 790×790 canvas (flood-filled bounding boxes
   per node, sampled fill colours), then expressed as percentages of the
   square `.tdiagram` box so the whole thing scales fluidly with no raster
   asset involved. Curved/bezier connector routing in the source art is
   simplified to straight orthogonal lines — flagged here since it's an
   approximation, not a trace.
   -------------------------------------------------------------------------- */

/* Hidden for now — kept in the markup in case it's re-enabled later. */
.tdiagram-section {
  display: none;
  width: 100%;
  max-width: calc(var(--content-w) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-block-start: var(--sp-8);
  gap: var(--sp-24);
}

.tdiagram-section__label {
  margin: 0;
  color: var(--color-text-muted);
}

.tdiagram-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(24px, calc(5.208 * var(--vw)), 100px);
}

.tdiagram-figure {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-16);
}

.tdiagram-figure figcaption {
  margin: 0;
  text-align: center;
  font-size: var(--fs-caps);
  line-height: var(--lh-caps);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.tdiagram {
  position: relative;
  aspect-ratio: 1;
  width: 100%;
  border-radius: 12px;
  overflow: hidden;
  container-type: inline-size;
  background: var(--diagram-bg);

  /* Local palette — traced from the reference PNGs, not the site tokens.
     This is one data-viz moment, not a reusable brand colour. */
  --diagram-bg: #0e296b;
  --diagram-primary: #2973bc;
  --diagram-accent: #3bbbea;
  --diagram-muted: #afc3d2;
  --diagram-ink: #16203a;
  --diagram-line: rgb(255 255 255 / 0.9);
  --diagram-line-accent: #ff2f00;
}

/* `data-reveal` only gets `.tdiagram` onto the site's existing scroll
   observer so it earns an `is-in` class — the container itself skips the
   generic fade/rise (it would double up with the nodes fading in on top of
   it) and appears instantly. The real animation is the child cascade
   below, keyed off `.tdiagram.is-in`. */
.tdiagram[data-reveal] {
  opacity: 1;
  transform: none;
  transition: none;
}

.tdiagram__node,
.tdiagram__pill {
  position: absolute;
  left: calc(var(--x) * 1%);
  top: calc(var(--y) * 1%);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-family: var(--font-body);
  font-weight: 700;
  line-height: 1.15;

  /* Reveal: a soft scale-and-fade pop, staggered top-to-bottom straight off
     the element's own --y — the same number already used to place it, so
     the cascade always matches the chart's real geometry with no JS. */
  --reveal-scale: 0.85;
  opacity: 0;
  transform: translate(-50%, -50%) scale(var(--reveal-scale));
  transition: opacity var(--dur-base) var(--ease-out)
      var(--reveal-delay, calc(var(--y) * 6ms)),
    transform var(--dur-base) var(--ease-spring)
      var(--reveal-delay, calc(var(--y) * 6ms));
}

.tdiagram.is-in .tdiagram__node,
.tdiagram.is-in .tdiagram__pill {
  --reveal-scale: 1;
  opacity: 1;
}

.tdiagram__node {
  width: var(--d);
  height: var(--d);
  border-radius: 50%;
  padding: 8%;
  font-size: clamp(7px, 3cqi, 12px);
}

.tdiagram__node--hub {
  font-weight: 800;
  font-size: clamp(9px, 3.8cqi, 15px);
}

.tdiagram__node--primary {
  background: var(--diagram-primary);
  color: #fff;
}

.tdiagram__node--accent {
  background: var(--diagram-accent);
  color: var(--diagram-ink);
}

.tdiagram__node--accent.tdiagram__node--hub {
  color: #fff;
}

.tdiagram__node--muted {
  background: var(--diagram-muted);
  color: var(--diagram-ink);
}

.tdiagram__pill {
  width: var(--w);
  height: var(--h);
  border-radius: 999px;
  padding-inline: 4%;
  font-size: clamp(6px, 1.6cqi, 11px);
  line-height: 1.05;
}

.tdiagram__pill--primary {
  background: var(--diagram-primary);
  color: #fff;
}

.tdiagram__pill--accent {
  background: var(--diagram-accent);
  color: var(--diagram-ink);
}

.tdiagram__pill--muted {
  background: var(--diagram-muted);
  color: var(--diagram-ink);
}

.tdiagram__line {
  position: absolute;
  left: calc(var(--x) * 1%);
  top: calc(var(--y) * 1%);

  /* Connectors just fade in — a plain opacity change reads as "quiet"
     next to the nodes' scale-pop, which is the point. Same --y stagger. */
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out)
    var(--reveal-delay, calc(var(--y) * 6ms));
}

.tdiagram.is-in .tdiagram__line {
  opacity: 1;
}

.tdiagram__line--h {
  width: var(--len);
  height: 2px;
  background: var(--diagram-line);
}

.tdiagram__line--v {
  width: 2px;
  height: var(--len);
  background: var(--diagram-line);
}

.tdiagram__line--dashed {
  background: none;
}

.tdiagram__line--dashed.tdiagram__line--h {
  height: 0;
  border-top: 2px dashed var(--diagram-line);
}

.tdiagram__line--dashed.tdiagram__line--v {
  width: 0;
  border-left: 2px dashed var(--diagram-line);
}

.tdiagram__line--red {
  --diagram-line: var(--diagram-line-accent);
}

.tdiagram__arrow {
  position: absolute;
  left: calc(var(--x) * 1%);
  top: calc(var(--y) * 1%);
  width: 0;
  height: 0;
  transform: translate(-50%, -50%);
  border-style: solid;
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out)
    var(--reveal-delay, calc(var(--y) * 6ms));
}

.tdiagram.is-in .tdiagram__arrow {
  opacity: 1;
}

.tdiagram__arrow--up {
  border-width: 0 5px 8px 5px;
  border-color: transparent transparent var(--diagram-line) transparent;
}

.tdiagram__arrow--down {
  border-width: 8px 5px 0 5px;
  border-color: var(--diagram-line) transparent transparent transparent;
}

.tdiagram__arrow--left {
  border-width: 5px 8px 5px 0;
  border-color: transparent var(--diagram-line) transparent transparent;
}

.tdiagram__arrow--right {
  border-width: 5px 0 5px 8px;
  border-color: transparent transparent transparent var(--diagram-line);
}

/* --------------------------------------------------------------------------
   section CREDITS  (Figma 2001:9710)
   stat row: 1px rule, 69 above / 27 below a 160px number
   -------------------------------------------------------------------------- */

.credits {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: var(--sp-200);
  width: 100%;
  max-width: calc(var(--content-w) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.credits__stats {
  margin: 0;
}

.credits__stat {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-24);
  padding-top: 68px; /* 69 less the 1px rule, which Figma counts inside */
  padding-bottom: 27px;
  border-top: 1px solid var(--color-rule-dark);
}

.credits__stat:last-child {
  padding-bottom: 26px;
  border-bottom: 1px solid var(--color-rule-dark);
}

.credits__number {
  font-family: var(--font-display);
  font-weight: 200;
  font-size: var(--fs-display-lg);
  line-height: var(--lh-display-lg);
  letter-spacing: var(--ls-display);
  color: var(--color-ink);
  white-space: nowrap;
}

.credits__label {
  margin: 0;
  font-weight: 500;
  font-size: var(--fs-h4);
  line-height: 1.5833; /* 38/24 */
  text-align: right;
  color: var(--color-ink);
}

.credits__copy {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  letter-spacing: 0.5px;
  color: var(--color-ink);
}

.credits__copy p + p {
  margin-top: var(--lh-h2-blank, 64px);
}

/* --------------------------------------------------------------------------
   Full-bleed image  (Figma 2001:9725 — 1920×1080)
   -------------------------------------------------------------------------- */

.pbleed {
  margin: 0;
  aspect-ratio: 16 / 9;
}

.case-inova .pbleed,
.case-lucid-motors .pbleed {
  aspect-ratio: 1920 / 1020;
}

.pbleed img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* --------------------------------------------------------------------------
   section NAV PROJECTS  (Figma 2001:9726 — 1920×1080)
   The title marquee runs in white; a 332×500 wavy "curtain" sits centred and
   reveals the next project image on hover. A second, identical marquee is
   masked to that shape in cream, so the words change colour over the panel.
   -------------------------------------------------------------------------- */

.navnext {
  position: relative;
  aspect-ratio: 16 / 9;
  border-top: 1px solid var(--color-rule);
  border-bottom: 1px solid var(--color-rule);
  overflow: hidden;
  /* Matches both the link and footer so a composited image edge can never
     expose the page underneath as a light seam. */
  background-color: var(--color-bg);
}

.navnext__ticker {
  position: absolute;
  top: 3.7%; /* 40/1080 */
  left: 0;
  right: 0;
  z-index: 1;
  font-weight: 700;
  font-size: var(--fs-h4);
  line-height: var(--lh-h4);
  color: var(--color-text);
}

.navnext__link {
  display: block;
  height: 100%;
  background-color: var(--color-bg);
}

/* [derived] Full-bleed preview of the next project, behind everything else
   in the section. The link rests on the site background color, then the image
   and its readability scrim wipe downward on hover/focus. */
.navnext__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}

.navnext__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background-color: #000;
  opacity: 0;
  clip-path: inset(0 0 100%);
  transition:
    clip-path var(--dur-slow) var(--ease-out),
    opacity var(--dur-slow) var(--ease-out);
}

.navnext__link:hover .navnext__bg::after,
.navnext__link:focus-visible .navnext__bg::after {
  opacity: 0.4;
  clip-path: inset(0);
}

.navnext__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  clip-path: inset(0 0 100%);
  transform: scale(1.1);
  transition:
    clip-path var(--dur-slow) var(--ease-out),
    transform var(--dur-slow) var(--ease-out);
  will-change: clip-path, transform;
}

.navnext__link:hover .navnext__bg img,
.navnext__link:focus-visible .navnext__bg img {
  clip-path: inset(0);
  transform: scale(1);
}

/* content frame 290…790 of the 1080 section */
.navnext__stage {
  position: absolute;
  top: 26.85%;
  bottom: 26.85%;
  left: 0;
  right: 0;
  z-index: 1;
}

.navnext__title {
  position: absolute;
  top: 34.8%; /* text box 174 of the 500 stage */
  left: 0;
  right: 0;
  font-family: var(--font-display);
  font-weight: 200;
  font-size: var(--fs-display-lg);
  line-height: var(--lh-display-lg);
  letter-spacing: var(--ls-display);
  color: var(--color-text);
}

.navnext__title--masked {
  --curtain: url("../assets/img/nav-curtain.svg") center / 100% 100% no-repeat;
}

/* Same marquee, clipped to the curtain silhouette, so the type reads cream
   inside that shape. The mask is all that remains of the curtain panel. */
.navnext__title--masked {
  color: var(--color-cream);
  -webkit-mask: var(--curtain);
  mask: var(--curtain);
  -webkit-mask-position: center calc(50% - 34.8% + 0px);
  mask-size: clamp(160px, calc(17.292 * var(--vw)), 332px) auto;
  -webkit-mask-size: clamp(160px, calc(17.292 * var(--vw)), 332px) auto;
}

/* In light mode the preview rests on paper, then reveals a dark image.
   Start with dark type and switch both title layers to white with the image. */
:root[data-theme="light"] .navnext__title,
:root[data-theme="light"] .navnext__title--masked {
  color: var(--color-text);
  transition: color var(--dur-slow) var(--ease-out);
}

:root[data-theme="light"] .navnext__link:hover .navnext__title,
:root[data-theme="light"] .navnext__link:focus-visible .navnext__title,
:root[data-theme="light"] .navnext__link:hover .navnext__title--masked,
:root[data-theme="light"] .navnext__link:focus-visible .navnext__title--masked {
  color: var(--color-white);
}

.navnext__disc {
  position: absolute;
  left: 50%;
  top: 78.52%; /* 848/1080 */
  z-index: 1;
  width: clamp(110px, calc(8.542 * var(--vw)), 164px);
  height: clamp(110px, calc(8.542 * var(--vw)), 164px);
  transform: translate3d(-50%, 0, 0);
}

/* The generic reveal uses transform for its fade-up. Preserve the disc's
   -50% horizontal translation in both states so `.is-in` cannot shift its
   left edge onto the section's centerline. */
.navnext__disc[data-reveal] {
  transform: translate3d(-50%, 32px, 0);
}

.navnext__disc[data-reveal].is-in {
  transform: translate3d(-50%, 0, 0);
}

.navnext__disc img {
  width: 100%;
  height: 100%;
  animation: spin 24s linear infinite;
}

/* The source artwork is white. Color it dark while the light-theme preview
   is closed, then restore its native white as the dark image reveals. */
:root[data-theme="light"] .navnext__disc img {
  filter: brightness(0) saturate(100%);
  transition: filter var(--dur-slow) var(--ease-out);
}

:root[data-theme="light"] .navnext__link:hover .navnext__disc img,
:root[data-theme="light"] .navnext__link:focus-visible .navnext__disc img {
  filter: none;
}

/* Slow-down on hover is driven by js/main.js via the animation's
   playbackRate, not animation-duration: swapping duration mid-spin remaps
   progress (e.g. 12s elapsed is 50% of 24s but 150% of 8s) and the disc
   visibly snaps to a new angle. playbackRate keeps the current angle and
   only changes how fast time advances from there — same trick as the
   marquee hover slow-down. */

@media (prefers-reduced-motion: reduce) {
  .navnext__bg img,
  .navnext__bg::after {
    transition: none;
  }

  .navnext__disc img {
    animation: none;
  }
}

@media (hover: none), (pointer: coarse) {
  /* Keep the full-page concept independently scrollable on touch devices.
     `overflow: hidden` here previously overrode the base scroller and made
     the panel inert on phones/tablets. Native momentum handles the inner
     viewport; `overscroll-behavior-y: auto` lets a continued swipe return
     to the page once the artwork reaches either edge. */
  .pfigure__scroll {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    touch-action: pan-y;
    overscroll-behavior-y: auto;
  }

  .navnext__bg img {
    /* Safari can anti-alias even an inset(0) clip into a 1px edge. Touch
       devices show the preview permanently, so no clip is needed here. */
    -webkit-clip-path: none;
    clip-path: none;
    transform: scale(1.1);
  }

  .navnext__bg::after {
    opacity: 0.55;
    -webkit-clip-path: none;
    clip-path: none;
  }

  /* Touch has no hover and the dark preview is always open. Keep its text
     and disc white rather than leaving the desktop resting colors active. */
  :root[data-theme="light"] .navnext__title,
  :root[data-theme="light"] .navnext__title--masked {
    color: var(--color-white);
  }

  :root[data-theme="light"] .navnext__disc img {
    filter: none;
  }

}

/* --------------------------------------------------------------------------
   Derived responsive behaviour — no mobile frame exists in Figma.
   -------------------------------------------------------------------------- */

@media (max-width: 1024px) {
  /* Give the long Inova page concept a true portrait viewport on tablet
     and below, revealing substantially more of the artwork at once. */
  .pfigure--portrait {
    --pfigure-ratio: 4 / 5;
  }

  .tblock__inner {
    flex-direction: column;
    gap: var(--sp-32);
  }

  .tblock__body {
    max-width: none;
  }

  /* [derived] The desktop grid keeps two fixed clamp() columns so their x
     positions match Figma exactly, then hands the remainder to a 1fr third
     column — correct at the 1186px desktop width the design was built for.
     At tablet widths the two fixed columns hit their floor and the 1fr
     column inherits far more space than its content (the Links button)
     uses, so the row reads as squeezed left with dead air on the right
     instead of full width. Equal fr columns use the whole measured width. */
  .pmeta {
    grid-template-columns: repeat(3, 1fr);
  }

  .pfeature,
  .credits {
    grid-template-columns: 1fr;
    gap: var(--sp-64);
  }

  .pfeature__title {
    margin-bottom: var(--sp-32);
  }

  .pfigure__feature-inner {
    gap: var(--sp-40);
  }

  /* The disc keeps a fixed 110px floor while the section height still
     follows the 16/9 aspect-ratio down to this breakpoint. Below ~911px
     the fixed offset (78.52% of section height) plus the fixed disc
     height overruns the aspect-ratio height, and `.navnext`'s
     overflow: hidden clips the bottom of the disc. Pin a flat height
     that keeps ~23px of clearance across the whole 701–1024px band; the
     700px query below (later in source, so it wins at that width) takes
     over for narrower viewports with its own taller formula. */
  .navnext {
    aspect-ratio: auto;
    height: 620px;
  }
}

@media (max-width: 700px) {
  /* [derived] The 80px desktop gap and t-h4 (20–24px) copy read heavy once
     the three columns stack into one on phones — tighten both. */
  .pmeta {
    grid-template-columns: 1fr;
    gap: var(--sp-40);
    margin-top: var(--sp-40);
  }

  .pmeta__heading,
  .pmeta__list {
    font-size: 15px;
    line-height: 1.4;
  }

  .pmeta__list {
    margin-top: 20px;
  }

  .pmeta__list li + li {
    margin-top: 16px;
  }

  .pmeta__col--link .btn-circle {
    margin-top: 20px;
  }

  /* [derived] No mobile frame exists for the Pediatrics feature. Stack the
     semantic copy above the square photo rather than shrinking both columns. */
  .pfigure__feature-inner {
    grid-template-columns: 1fr;
    gap: var(--sp-20);
    aspect-ratio: auto;
  }

  .pfigure__feature-copy {
    gap: var(--sp-64);
    padding: var(--sp-40) var(--sp-20);
  }

  .pfigure__feature-media {
    aspect-ratio: 1;
  }

  .credits__stat {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-8);
    padding-top: var(--sp-32);
    padding-bottom: var(--sp-32);
  }

  .credits__label {
    text-align: left;
  }

  /* The disc keeps a fixed 110px while everything else is proportional, so
     the section needs to be tall enough for the design's 78.52% offset to
     still leave it inside — 3/4 clipped it against the bottom edge. */
  /* The disc keeps a fixed 110px while everything else is proportional, so
     the section needs enough height for the design's 78.52% offset to leave
     it inside. A 2:3 box does that above ~373px; below it the 560px floor
     takes over. Height rather than aspect-ratio + min-height, because that
     pair resolves backwards and widens the section past the viewport. */
  .navnext {
    aspect-ratio: auto;
    height: max(560px, calc(150 * var(--vw)));
    border-bottom: 0;
  }
}
