/* ============================================
   HERBIG GROUP — Design Tokens
   Values verified against the Figma liquid-glass
   system (hero + contact nodes) for exact fidelity.
   ============================================ */
:root {
  --color-bg: #e3e5e8;
  --color-bg-soft: #eceef0;
  --color-bg-blue-gray: #eef2f5;
  --color-white: #ffffff;
  --color-ink: #21262d;
  --color-border: #e2e5e8;
  --color-border-soft: #e6eaf0;
  /* A11Y-01/03: one brand green cannot serve as decoration, as a fill behind
     white text, and as small text on light surfaces - those three roles need
     different luminance to reach 4.5:1. The hue family is unchanged; only the
     role is split. --color-green stays the decorative accent (bars, blobs,
     icon strokes, borders, large display text) and is still what the brand
     reads as. */
  --color-green: #3ca169;
  --color-green-rgb: 60, 161, 105;
  --color-green-tint: #e8f5ee;
  --color-green-action: #2f7f52;        /* fill behind white action labels */
  --color-green-action-hover: #2a6f47;  /* its hover, darker still */
  --color-green-ink: #2b7049;           /* green text on white / pale tint */
  --color-green-on-dark: #6ecb97;       /* small green text on dark surfaces */
  --color-amber-ink: #8a6715;           /* badge text on its pale fill */

  /* A11Y-04/05: form controls need their own boundary and placeholder
     values. Decorative glass borders stay subtle; these do not, because the
     edge of an editable field is information. Both reach 3:1 (border) and
     4.5:1 (placeholder) against the composited surfaces of BOTH form cards,
     the darker joint-venture card included. */
  --form-control-border: rgba(255, 255, 255, 0.36);
  --form-placeholder: rgba(255, 255, 255, 0.55);
  --color-green-dark-tint: #1a3d2a;
  --color-dark: #21262d;
  --color-dark-panel: #2e3540;
  --color-dark-footer: #0e2416;

  --glass-fill: rgba(255, 255, 255, 0.07);
  --glass-fill-soft: rgba(255, 255, 255, 0.06);
  --glass-border: rgba(255, 255, 255, 0.14);
  --glass-border-soft: rgba(255, 255, 255, 0.09);

  --font-display: "Raleway", "Georgia", serif;
  --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-accent: "Ramsina", "Raleway", serif;

  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-xl: 24px;
  --radius-2xl: 32px;

  --shadow-btn: 0 4px 6px rgba(33, 38, 45, 0.05);
  --shadow-card: 0 16px 16px rgba(33, 38, 45, 0.05);
  --shadow-card-hover: 0 22px 28px rgba(33, 38, 45, 0.10);
  --shadow-deep: 0 24px 56px rgba(0, 0, 0, 0.16);

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-snap: cubic-bezier(0.34, 1.56, 0.64, 1); /* overshoots then settles back, like a released rubber band */
  --ease-smooth: ease-in-out; /* for colour fades — --ease-snap front-loads the change too fast to read as a smooth fade */
  --dur: 0.5s;

  /* Motion system tokens. --ease is already the site's "premium" curve
     (a slow, confident settle with no overshoot) and --dur its default
     duration — these aliases exist so new motion work has named, coherent
     steps to reach for instead of inventing one-off values. Keep reaching
     for these (or --ease/--dur directly) rather than hand-writing new
     durations/easings on individual rules. */
  --motion-ease-premium: var(--ease);
  --motion-duration-fast: 0.35s;
  --motion-duration-medium: 0.65s;
  --motion-duration-slow: 0.9s;
  --motion-distance-small: 12px;
  --motion-distance-medium: 14px;
  --motion-distance-large: 28px;

  /* Institutional motion system (semantic layer over the tokens above -
     same tuned values wherever one already existed, so nothing doubles
     up; only genuinely new curves/scales get a new value). Three eases
     with distinct jobs instead of one curve for everything: enter is
     the site's existing confident settle, exit is quicker and doesn't
     linger, interface is for things a click/hover needs to answer
     immediately. Five duration tiers by what's moving (a UI nudge vs. a
     photograph settling into place are not the same kind of motion),
     four distances by how much visual weight the element itself has. */
  --motion-ease-enter: var(--ease);
  --motion-ease-exit: cubic-bezier(0.4, 0, 0.2, 1);
  --motion-ease-interface: cubic-bezier(0.2, 0.8, 0.2, 1);
  --motion-micro: 180ms;
  --motion-interface: 260ms;
  --motion-content: var(--motion-duration-medium);
  --motion-image: 920ms;
  --motion-distance-xs: 6px;
  --motion-distance-sm: var(--motion-distance-small);
  --motion-distance-md: var(--motion-distance-medium);
  --motion-distance-lg: var(--motion-distance-large);

  /* ---------- Global motion grammar (reveal architecture) ----------
   * One small, named set of timing/distance tokens for the site's three
   * entrance families (see EDITORIAL/SURFACE/GRID reveal rules below) -
   * deliberately reused everywhere rather than each section inventing
   * its own duration or distance. --motion-fast is UI/interface speed
   * for small internal resolves (form fields); the rest are entrance
   * speeds by how much visual weight is moving: --motion-copy for body/
   * supporting text, --motion-editorial for headlines, --motion-surface
   * for panels/forms/split imagery, --motion-image for photography. */
  --motion-fast: 240ms;
  --motion-copy: 560ms;
  --motion-editorial: 640ms;
  --motion-surface: 780ms;
  --ease-premium: var(--ease);

  --container-pad: 20px;
  --nav-height: 76px;
}

@media (min-width: 900px) {
  :root { --container-pad: 80px; --nav-height: 92px; }
}

/* ============================================
   RESET
   ============================================ */
* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
/* Both html and body are set here (not just body) so the overscroll/
   rubber-band area above a fixed-position navbar — visible on a trackpad
   bounce past the top of the page — renders dark instead of flashing
   white; every page opens on a dark hero/header, so this reads as a
   seamless continuation of it rather than a gap. */
html { background: var(--color-dark); }
/* scroll-behavior: smooth is intentionally NOT set globally. It used to
   live here for in-page anchor links, but it also silently hijacked the
   PJAX router's own window.scrollTo(0,0) (transitions.js) - every internal
   navigation would land on the new page still scrolled to the old page's
   position and visibly animate up to the top. Anchor links get their smooth
   scroll explicitly (scrollIntoView({behavior:"smooth"}) in transitions.js);
   the top-of-page reset on navigation stays instant, always. */
body {
  font-family: var(--font-body);
  color: var(--color-ink);
  background: var(--color-dark);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
img { display: block; max-width: 100%; }
/* HG-P4-02: photographs are wrapped in <picture> for AVIF/WebP candidates.
   The wrapper must not become a box of its own, or every existing
   ".container img { width: 100%; height: 100% }" rule would resolve against
   an auto-sized inline element instead of the real container. display:
   contents removes the wrapper from layout entirely, so the <img> keeps
   sizing, positioning and object-fit exactly as before. */
picture { display: contents; }
a { text-decoration: none; color: inherit; }
button { font: inherit; cursor: pointer; }
h1, h2, h3 { font-family: var(--font-display); line-height: 1.2; }
a, button, .btn, .nav-link { touch-action: manipulation; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; transition-delay: 0s !important; scroll-behavior: auto !important; }
}

/* A11Y-02: the ring colour is a token so it can follow the surface it lands
   on. The brand green only reaches 2.78:1 on the soft grey page background,
   below the 3:1 this project requires of a focus indicator, so light
   surfaces use the darker ink green and dark surfaces the lighter one. */
:focus-visible {
  outline: 2px solid var(--focus-ring, var(--color-green-ink));
  outline-offset: 3px;
  border-radius: 4px;
}

/* A11Y-06: skip link. It is the first focusable element in the document on
   every route and lives outside #route-content, so a client-side navigation
   never rebuilds or loses it. Off-screen by transform rather than
   display/visibility so it stays focusable, and it is revealed above the
   fixed header - not behind it - when focus reaches it. */
.skip-link {
  position: fixed;
  top: 12px;
  left: 50%;
  z-index: 200;
  transform: translate(-50%, calc(-100% - 24px));
  display: inline-flex;
  align-items: center;
  padding: 13px 24px;
  border-radius: 999px;
  background: var(--color-dark);
  color: var(--color-white);
  border: 1px solid var(--color-green-on-dark);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.4px;
  text-decoration: none;
  white-space: nowrap;
  box-shadow: 0 14px 30px rgba(0, 0, 0, 0.3);
  transition: transform 0.22s var(--ease);
}
.skip-link:focus,
.skip-link:focus-visible {
  transform: translate(-50%, 0);
  --focus-ring: var(--color-green-on-dark);
}
/* The skip destination is a programmatic focus target only; a ring drawn
   around the entire page would be noise, and :focus-visible would not
   paint one here anyway. */
#main-content:focus { outline: none; }

/* ============================================
   AMBIENT DECORATION — liquid-glass blobs
   Faithful to Figma: soft green radial fields,
   drifting slowly, plus specular highlight rings
   and diamond "notch" corner cutouts.
   ============================================ */
.canvas-glow { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }

/* MOT-06: the drift/shimmer loops below used to run unconditionally,
   including on every mobile viewport and on every one of these elements
   regardless of whether it was ever scrolled into view - unnecessary
   motion and an unnecessary, always-on animation workload on mobile in
   particular. Below 900px they're frozen outright (see the max-width
   block after .blob--soft) at their own resting opacity/position rather
   than a keyframe's mid-cycle value. On desktop they still loop, but
   initAmbientMotion() in script.js pauses (animation-play-state) each
   one while it's offscreen or the document is hidden, and resumes it
   when it's actually visible again. */
.blob {
  position: absolute;
  border-radius: 50%;
  background: rgba(var(--color-green-rgb), 0.07);
  filter: blur(32px);
  pointer-events: none;
  will-change: transform;
  animation: drift 22s ease-in-out infinite;
}
.blob--left   { left: -10%;  top: -8%;  width: 30vw; max-width: 440px; aspect-ratio: 1; animation-delay: 0s; }
.blob--right  { right: -8%;  top: 12%;  width: 26vw; max-width: 380px; aspect-ratio: 1; animation-delay: -7s; animation-direction: reverse; }
.blob--top    { left: 25%;   top: -14%; width: 50vw; max-width: 720px; aspect-ratio: 1; opacity: 0.7; animation-delay: -14s; }
.blob--soft   { opacity: 0.65; }
@media (max-width: 899px) {
  .blob { animation: none; }
}

@keyframes drift {
  0%   { transform: translate(0, 0) scale(1); }
  33%  { transform: translate(3%, 4%) scale(1.05); }
  66%  { transform: translate(-2%, 2%) scale(0.97); }
  100% { transform: translate(0, 0) scale(1); }
}

.specular {
  position: absolute;
  width: 160px;
  height: 160px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.4), rgba(var(--color-green-rgb), 0.14) 45%, transparent 72%);
  filter: blur(1px);
  opacity: 0.55;
  pointer-events: none;
  animation: shimmer 6s ease-in-out infinite;
}
.specular--subtle { opacity: 0.32; }
.specular--tr { top: -8%; right: 8%; }
.specular--bl { bottom: -6%; left: 4%; animation-delay: -3s; }
@media (max-width: 899px) {
  .specular { animation: none; }
}
@media (min-width: 900px) {
  .specular { width: 220px; height: 220px; }
}
@keyframes shimmer {
  0%, 100% { opacity: 0.4; transform: scale(1); }
  50% { opacity: 0.65; transform: scale(1.08); }
}

/* Diamond "notch" corner cutout — a rotated square peeking out of a
   glass panel corner, matching the Figma headline/form card corners. */
.notch {
  position: absolute;
  width: 56px;
  height: 56px;
  background: rgba(var(--color-green-rgb), 0.09);
  transform: rotate(45deg);
  pointer-events: none;
  z-index: 0;
}
.notch--tl { top: -28px; left: -28px; }
.notch--br { width: 48px; height: 48px; bottom: -24px; right: -24px; background: rgba(var(--color-green-rgb), 0.08); }
.notch--on-dark { background: #2e3540; }
.notch--on-glass { background: rgba(60, 161, 105, 0.13); }

/* ============================================
   SHARED
   ============================================ */
.section { padding: 48px var(--container-pad); position: relative; overflow: hidden; }
@media (min-width: 900px) { .section { padding: 120px var(--container-pad); } }

/* HG-P3-06: #core/#team/#contact are the landing targets for the
   Core/Leadership/Contact same-page anchor links (navbar CTA, mobile
   menu, and any cross-page "index.html#..." link) - scrollIntoView on a
   bare element aligns its top flush with the viewport top, which tucks
   it straight behind the fixed navbar. scroll-margin-top offsets that
   for every scroll-into-view path (native fragment navigation on a fresh
   load/refresh included, not just the JS-driven scrollIntoView calls in
   transitions.js) without needing extra JS math anywhere. */
#core, #team, #contact { scroll-margin-top: calc(var(--nav-height) + 16px); }

.tag {
  display: inline-flex;
  padding: 6px 16px;
  border-radius: 100px;
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--color-green);
  position: relative;
  z-index: 1;
}
.tag--light { background: var(--color-green-tint); border: 1px solid rgba(var(--color-green-rgb), 0.19); color: var(--color-green-ink); }
.tag--dark { background: var(--color-green-dark-tint); }
.tag--pillars { background: var(--color-green-action); border: 1px solid var(--glass-border); color: var(--color-white); }
.roots .tag { letter-spacing: 2px; }

.section-heading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  text-align: center;
  margin-bottom: 32px;
  position: relative;
  z-index: 1;
}
@media (min-width: 900px) { .section-heading { gap: 16px; margin-bottom: 56px; } }

.section-title { font-weight: 800; font-size: 26px; line-height: 1.2; color: var(--color-ink); }
.section-title--light { color: var(--color-white); }
@media (min-width: 900px) { .section-title { font-size: 44px; } }
.roots .section-title { line-height: 1.15; }

.section-sub { font-size: 16px; line-height: 1.6; color: var(--color-ink); opacity: 0.8; max-width: 640px; }
.pillars .section-sub { color: var(--color-white); opacity: 0.7; }
.core .section-sub { opacity: 0.5; }
/* Declared AFTER .section-sub on purpose: both are single-class selectors,
   so source order decides. Placed above, the base rule's dark ink won and
   this supporting line rendered near-invisible on its dark section. */
.section-sub--light { color: rgba(255, 255, 255, 0.78); opacity: 1; }

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 14px 24px;
  border-radius: var(--radius-sm);
  font-weight: 700;
  font-size: 14px;
  box-shadow: var(--shadow-btn);
  /* MOT-05: padding here only ever changes via .navbar.is-compact
     .btn--nav - was 0.5s (legacy), now aligned to the navbar's own
     180-260ms appearance-transition timing so the two don't visibly
     drift apart during a compact-state change. */
  transition: transform 0.25s var(--ease), box-shadow 0.25s var(--ease), background 0.25s var(--ease), border-color 0.25s var(--ease), padding 0.2s var(--motion-ease-premium);
  will-change: transform;
  width: 100%;
  border: none;
  position: relative;
  overflow: hidden;
}
@media (min-width: 900px) { .btn { width: auto; padding: 16px 32px; font-size: 15px; } }

.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, transparent 30%, rgba(255, 255, 255, 0.25) 50%, transparent 70%);
  transform: translateX(-120%);
  transition: transform 0.6s var(--ease);
}
.btn:hover::before { transform: translateX(120%); }

.btn--primary { background: var(--color-green-action); color: var(--color-white); box-shadow: 0 8px 12px rgba(var(--color-green-rgb), 0.31); }
.btn--primary:hover { background: var(--color-green-action-hover); transform: translateY(-2px); box-shadow: 0 10px 18px rgba(var(--color-green-rgb), 0.28); }
.btn--primary:active { transform: translateY(0); }

/* HG-P3-04: a CTA-styled placeholder for content that doesn't exist yet.
   Marked up as a <span>, not an <a href="#">, so it carries no link
   semantics (unreachable by keyboard, does nothing on click/Enter) instead
   of a fake destination that just jumps to the top of the page. */
.btn--disabled { opacity: 0.55; cursor: default; pointer-events: none; }
.btn--disabled::before { display: none; }

.btn--outline { background: var(--color-white); color: var(--color-green-ink); border: 1.5px solid var(--color-green-ink); }
.btn--outline:hover { background: var(--color-green-tint); transform: translateY(-2px); box-shadow: 0 10px 18px rgba(var(--color-green-rgb), 0.15); }

.btn--ghost { background: var(--glass-fill-soft); color: var(--color-white); border: 1.5px solid var(--glass-border); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.13); }
.btn--ghost:hover { background: rgba(255, 255, 255, 0.14); transform: translateY(-2px); }

.btn--nav { display: none; }
/* HG-VR-05: the row nav (brand + 4 links + CTA) needs ~1180px of room
   at this type scale/spacing before it stops wrapping onto a second
   line - see the matching 1200px threshold on .nav-links, .menu-toggle
   and .mobile-menu below. Switching all four together at a wider,
   shared breakpoint (instead of 900px) keeps the already-correct
   mobile/hamburger layout in place through tablet-landscape widths
   rather than showing a cramped, wrapping row nav there. */
@media (min-width: 1200px) { .btn--nav { display: inline-flex; width: auto; } }
@media (min-width: 1200px) { .navbar.is-compact .btn--nav { padding: 13px 32px; } }
.btn--full { width: 100%; }

.btn__arrow { transition: transform 0.25s var(--ease); flex-shrink: 0; }
.btn:hover .btn__arrow { transform: translateX(3px); }

/* ============================================
   NAVBAR
   ============================================ */
.navbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  background: var(--glass-fill-soft);
  border-bottom: 1px solid var(--glass-border);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  transition: background 0.2s var(--ease), backdrop-filter 0.2s var(--ease), border-color 0.2s var(--ease) 0.04s, box-shadow 0.2s var(--ease) 0.04s;
}
.navbar.is-scrolled { background: var(--color-white); border-bottom: 1px solid var(--color-border); box-shadow: 0 4px 20px rgba(33, 38, 45, 0.06); backdrop-filter: none; -webkit-backdrop-filter: none; }
/* MOT-05: padding transition was 0.5s (legacy), now aligned to the
   navbar's own 180-260ms appearance-transition timing (its background/
   border/shadow swap already uses 0.2s) so a compact-state change
   doesn't visibly run two different durations at once. */
.navbar__inner { display: flex; align-items: center; justify-content: space-between; padding: 16px var(--container-pad); max-width: 1600px; margin: 0 auto; transition: padding 0.2s var(--motion-ease-premium); }
@media (min-width: 900px) { .navbar__inner { padding: 18px var(--container-pad); } }
/* Compact scroll state: ~25% off vertical padding on mobile, ~22% on
   desktop - noticeable without feeling squeezed. */
.navbar.is-compact .navbar__inner { padding: 12px var(--container-pad); }
@media (min-width: 900px) { .navbar.is-compact .navbar__inner { padding: 14px var(--container-pad); } }

.brand { display: inline-flex; align-items: center; gap: 10px; }
.brand__icon { width: 36px; height: 36px; }
@media (min-width: 900px) { .brand__icon { width: 52px; height: 52px; } }
/* MOT-05: width/height transition was 0.5s (legacy) - same alignment to
   180-260ms as .navbar__inner's padding above. */
.brand__icon-wrap { position: relative; width: 36px; height: 36px; flex-shrink: 0; transition: width 0.2s var(--motion-ease-premium), height 0.2s var(--motion-ease-premium); }
@media (min-width: 900px) { .brand__icon-wrap { width: 52px; height: 52px; } }
/* Logo shrinks modestly on scroll (~11-13%), not in lockstep with the
   header - it should stay the visually dominant brand mark. */
.navbar.is-compact .brand__icon-wrap { width: 32px; height: 32px; }
@media (min-width: 900px) { .navbar.is-compact .brand__icon-wrap { width: 45px; height: 45px; } }
.brand__icon-wrap .brand__icon { position: absolute; inset: 0; width: 100%; height: 100%; transition: opacity var(--motion-micro) var(--ease) 0.07s; }
.brand__icon-wrap .brand__icon--color { opacity: 0; }
.brand__icon-wrap .brand__icon--white { opacity: 1; }
.navbar.is-scrolled .brand__icon-wrap .brand__icon--color { opacity: 1; }
.navbar.is-scrolled .brand__icon-wrap .brand__icon--white { opacity: 0; }
.footer .brand { gap: 16px; }
@media (min-width: 900px) { .footer .brand__icon { width: 62px; height: 62px; } .footer .brand__name { font-size: 20px; } }
.brand__text { display: flex; flex-direction: column; gap: 2px; text-transform: uppercase; }
.brand__name { font-family: var(--font-display); font-weight: 800; font-size: 14px; color: var(--color-white); transition: color var(--motion-micro) var(--ease) 0.07s; }
.brand__tagline { font-weight: 600; font-size: 7px; letter-spacing: 0.7px; opacity: 0.8; color: var(--color-white); transition: color var(--motion-micro) var(--ease) 0.07s; }
@media (min-width: 900px) { .brand__name { font-size: 19px; } .brand__tagline { font-size: 9px; letter-spacing: 1.08px; } }
.navbar.is-scrolled .brand__name, .navbar.is-scrolled .brand__tagline { color: var(--color-ink); }
.footer .brand__name, .footer .brand__tagline { color: var(--color-white); }

.nav-links { display: none; align-items: center; gap: 36px; }
@media (min-width: 1200px) { .nav-links { display: flex; } }
/* HG-P3-09: desktop pointers don't need a 44px touch target, but the same
   invisible padding/negative-margin technique used on the mobile menu adds
   some comfortable extra hit height here too, at no cost to the navbar's
   own height (36px of gap between links leaves easy room for it). Only
   padding-top/margin-top are touched, leaving the existing padding-bottom
   (reserved for the ::after underline below) and its offset untouched. */
.nav-link { position: relative; font-weight: 600; font-size: 15px; color: var(--color-white); padding-top: 8px; padding-bottom: 4px; margin-top: -8px; opacity: 0.9; transition: color var(--motion-micro) var(--ease) 0.07s; }
.navbar.is-scrolled .nav-link { color: var(--color-ink); }
.nav-link::after { content: ""; position: absolute; left: 0; bottom: 0; width: 0; height: 2px; background: var(--color-green); transition: width 0.25s var(--ease); }
.nav-link:hover::after { width: 100%; }
.nav-link.is-active { opacity: 1; }
.nav-link.is-active::after, .nav-link.is-active:hover::after { width: 16px; height: 3px; border-radius: 99px; }

.menu-toggle { display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 4px; width: 44px; height: 44px; margin: -10px -10px -10px 0; background: none; border: none; padding: 0; }
@media (min-width: 1200px) { .menu-toggle { display: none; } }
.menu-toggle span { display: block; height: 2px; width: 20px; background: var(--color-white); border-radius: 2px; transition: transform 0.3s var(--ease), opacity 0.3s var(--ease), background var(--motion-micro) var(--ease) 0.07s; }
.navbar.is-scrolled .menu-toggle span { background: var(--color-ink); }
.menu-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.menu-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.menu-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* When the mobile menu is open, the navbar itself eases into the same
   dark theme as the panel beneath it, on the SAME 0.5s duration as the
   panel's own height transition below so both finish together. Colour
   still uses --ease-smooth (plain ease-in-out) rather than the panel's
   bouncy --ease-snap, so it reads as a gradual fade instead of snapping
   early — just synced to end at the same moment. It wins over
   .is-scrolled (white header) since these rules come after it, so the
   header always settles to dark for the menu, even starting from white. */
.navbar.menu-open {
  background: var(--color-dark);
  border-bottom-color: rgba(255, 255, 255, 0.1);
  box-shadow: none;
  backdrop-filter: none; -webkit-backdrop-filter: none;
  transition: background 0.5s var(--ease-smooth), border-color 0.5s var(--ease-smooth), box-shadow 0.5s var(--ease-smooth), backdrop-filter 0.5s var(--ease-smooth);
}
.navbar.menu-open .brand__icon-wrap .brand__icon { transition: opacity 0.5s var(--ease-smooth); }
.navbar.menu-open .brand__icon-wrap .brand__icon--color { opacity: 0; }
.navbar.menu-open .brand__icon-wrap .brand__icon--white { opacity: 1; }
.navbar.menu-open .brand__name, .navbar.menu-open .brand__tagline { color: var(--color-white); transition: color 0.5s var(--ease-smooth); }
.navbar.menu-open .menu-toggle span { background: var(--color-white); transition: transform 0.3s var(--ease), opacity 0.3s var(--ease), background 0.5s var(--ease-smooth); }

/* ============================================
   MOBILE MENU
   Full-screen overlay (< 900px) opened by .menu-toggle.
   Lives outside #route-content alongside the navbar, so it
   persists untouched across PJAX page swaps (see transitions.js).
   ============================================ */
/* MOT-04: was height:0 -> 100vh over 500ms on --ease-snap (cubic-bezier
   (0.34,1.56,0.64,1) - an overshoot/bounce curve, a different motion
   family from the rest of the site, and animating height is a layout
   property, triggering reflow every frame it moves). The panel is now a
   fixed, always-100vh surface (still extends up behind the navbar for
   the same rounding/gap-proofing reason as before) that enters/exits on
   opacity alone - fixed geometry, no bounce, nothing to lay out. Entry
   uses the site's entrance curve, exit its dedicated exit curve, at the
   spec's 240-300ms open / 180-220ms close. */
.mobile-menu {
  display: flex;
  position: fixed;
  top: 0;
  left: 0; right: 0;
  height: 100vh;
  z-index: 95;
  flex-direction: column;
  background: var(--color-dark);
  overflow: hidden;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s var(--motion-ease-exit), visibility 0s linear 0.2s;
}
.mobile-menu.is-open {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
  transition: opacity 0.26s var(--ease), visibility 0s linear 0s;
}
@media (min-width: 1200px) { .mobile-menu { display: none !important; } }
.no-scroll { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .mobile-menu { transition: visibility 0s linear; }
  .mobile-menu.is-open { transition: visibility 0s linear; }
  .navbar.menu-open { transition: none; }
  .navbar.menu-open .brand__icon-wrap .brand__icon,
  .navbar.menu-open .brand__name,
  .navbar.menu-open .brand__tagline { transition: none; }
}

.mobile-menu__glow { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }
.mobile-menu__blob { position: absolute; border-radius: 50%; pointer-events: none; filter: blur(24px); background: rgba(var(--color-green-rgb), 0.08); }
.mobile-menu__blob--left { left: -60px; top: 80px; width: 220px; height: 220px; }
.mobile-menu__blob--right { right: -40px; top: 46%; width: 200px; height: 200px; background: rgba(var(--color-green-rgb), 0.04); filter: blur(20px); }
.mobile-menu__specular { position: absolute; right: -30px; top: -30px; width: 160px; height: 160px; border-radius: 50%; background: radial-gradient(circle, rgba(255, 255, 255, 0.35), rgba(var(--color-green-rgb), 0.12) 45%, transparent 72%); filter: blur(1px); opacity: 0.5; }

.mobile-menu__content { position: relative; z-index: 1; flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; display: flex; flex-direction: column; gap: 16px; padding: calc(var(--nav-height) + 24px) 24px 24px; }

.mobile-menu__list { display: flex; flex-direction: column; gap: 8px; }
.mobile-menu__link { display: flex; align-items: center; gap: 10px; padding: 12px; border-radius: var(--radius-lg); background: rgba(255, 255, 255, 0.03); border: 1px solid var(--glass-border); transition: background 0.2s var(--ease), border-color 0.2s var(--ease); }
.mobile-menu__link:active { background: rgba(255, 255, 255, 0.06); }
.mobile-menu__index { flex-shrink: 0; display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 4px; background: rgba(255, 255, 255, 0.12); color: var(--color-green-on-dark); font-family: var(--font-body); font-weight: 700; font-size: 10px; letter-spacing: 1px; }
.mobile-menu__label { flex: 1 1 auto; min-width: 0; font-family: var(--font-display); font-weight: 700; font-size: 16px; color: var(--color-white); }
.mobile-menu__chevron { display: flex; align-items: center; justify-content: center; flex-shrink: 0; width: 24px; height: 24px; }
.mobile-menu__link.is-active { border-color: rgba(var(--color-green-rgb), 0.3); background: rgba(var(--color-green-rgb), 0.08); }
.mobile-menu__link.is-active .mobile-menu__index { background: var(--color-green-action); color: var(--color-white); }
.mobile-menu__link.is-active .mobile-menu__label { color: var(--color-green-on-dark); }

.mobile-menu__card { display: flex; flex-direction: column; gap: 12px; padding: 16px; border-radius: var(--radius-xl); background: rgba(255, 255, 255, 0.03); border: 1px solid var(--glass-border); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); }
/* A11Y-03: 12px uppercase over the menu's blurred dark glass measured
   4.31:1 with the decorative green - the on-dark green clears 4.5:1 with
   room to spare for whatever the backdrop filter samples behind it. */
.mobile-menu__card-title { font-family: var(--font-display); font-weight: 800; font-size: 12px; letter-spacing: 1px; text-transform: uppercase; color: var(--color-green-on-dark); }
.mobile-menu__card-sub { font-size: 12px; line-height: 1.5; color: var(--color-white); opacity: 0.65; margin-top: 2px; }
/* HG-P3-09: gap widened from 8px to 12px so the two contact rows' expanded
   hit areas below (6px top + 6px bottom each = 12px combined at the seam)
   meet edge-to-edge instead of overlapping. */
.mobile-menu__card-contacts { display: flex; flex-direction: column; gap: 12px; }
/* Visual row is unchanged (~32px, icon-driven) - padding-block expands the
   actual clickable box to 44px and margin-block of the same size pulls the
   element back to its original position, so neither the row's own
   footprint nor the surrounding layout grows. This is the standard
   invisible hit-area expansion technique for meeting a minimum touch
   target without changing visual density. */
.mobile-menu__contact { display: flex; align-items: center; gap: 10px; font-family: var(--font-body); font-size: 12px; font-weight: 500; color: var(--color-white); padding-block: 6px; margin-block: -6px; }
.mobile-menu__contact-icon { display: flex; align-items: center; justify-content: center; flex-shrink: 0; width: 32px; height: 32px; border-radius: var(--radius-lg); background: rgba(var(--color-green-rgb), 0.12); }
.mobile-menu__contact-icon img { width: 16px; height: 16px; }
/* Same technique: ~35px visible button grows to >=44px of hit area via
   padding, offset by an equal negative margin. The 6px it adds each side
   sits well inside the 12px gap above (.mobile-menu__card's own gap, next
   to the contacts block) and the 16px card padding below, so it can't
   collide with a neighboring hit area. */
.mobile-menu__cta.btn.btn--primary { padding: 10px 16px; font-size: 12px; border-radius: var(--radius-md); box-shadow: 0 4px 6px rgba(var(--color-green-rgb), 0.19); padding-block: 16px; margin-block: -6px; }

/* ============================================
   HERO
   ============================================ */
.hero { position: relative; overflow: hidden; display: flex; flex-direction: column; gap: 24px; padding: calc(var(--nav-height) + 32px) 20px 40px; background: var(--color-dark); isolation: isolate; }
.hero__content { display: flex; flex-direction: column; gap: 24px; position: relative; z-index: 1; }

.badge { display: inline-flex; align-self: flex-start; padding: 8px 16px; border-radius: 100px; background: rgba(var(--color-green-rgb), 0.28); border: 1px solid var(--glass-border); color: var(--color-white); font-weight: 700; font-size: 10px; letter-spacing: 1px; text-transform: uppercase; }
/* Hero entrance: one continuous handoff, not several disconnected pops
   (see initHeroZoom's .is-loaded, added next frame on load). Sequence -
   hero image settles (0-1000ms) -> glass panel slides in (80-860ms,
   overlapping) -> headline starts while the panel is still moving
   (220-860ms) -> supporting copy follows ~80ms later (300-860ms) ->
   action buttons trail once the panel has mostly landed (500ms+). The
   eyebrow badge is light/fast and leads everything. Every stage
   overlaps the next rather than waiting for it to finish, which is what
   reads as one handoff instead of a checklist of separate events. */
.hero .badge { opacity: 0; transform: translateY(var(--motion-distance-xs)); transition: opacity var(--motion-interface) var(--motion-ease-enter), transform var(--motion-interface) var(--motion-ease-enter); }
.hero.is-loaded .badge { opacity: 1; transform: translateY(0); }
.hero .hero__actions { opacity: 0; transform: translateY(var(--motion-distance-xs)); transition: opacity var(--motion-interface) var(--motion-ease-interface) 0.5s, transform var(--motion-interface) var(--motion-ease-interface) 0.5s; }
.hero.is-loaded .hero__actions { opacity: 1; transform: translateY(0); }

/* backdrop-filter blurs whatever is actually painted behind an element -
   on a slow connection the hero photo hadn't decoded yet by the time
   this first painted, so the "frosted glass" had nothing to frost and
   read as a flat, cheap-looking translucent panel until the photo
   caught up (a visible pop once it did). Fixed at the source too - the
   hero photo is now preloaded with fetchpriority="high" (see index.html)
   - but this fallback covers it either way: a solid, deliberately-tinted
   panel with NO blur is the resting state, and only once the photo is
   actually decoded (hero.is-photo-ready - see initHeroZoom in script.js,
   which checks img.complete / listens for load) does it frost. That way
   there's never a moment of "glass with nothing behind it" to notice -
   the panel looks intentional either way, just upgrades once it can. */
.headline-card {
  position: relative;
  display: flex; flex-direction: column; gap: 20px;
  background: rgba(22, 26, 31, 0.82);
  border: 1px solid var(--glass-border);
  backdrop-filter: none; -webkit-backdrop-filter: none;
  border-radius: var(--radius-2xl);
  padding: 28px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08), 0 24px 56px rgba(0, 0, 0, 0.15);
  overflow: hidden;
  /* Entrance (opacity/transform) is the section's signature move: an
     architectural surface sliding into frame, not a drawer or a card -
     kept to a modest travel distance and the site's slowest, most
     confident ease so it reads as weight settling into place rather than
     something mechanically sliding. Deliberately gated on .is-loaded
     (next-frame, not network-dependent) rather than .is-photo-ready: on
     any normal connection the preloaded photo (see index.html) is
     already ready well inside this 800ms window, so the panel arrives
     already frosted; on a slow one it still arrives on schedule showing
     the solid fallback below, which already reads as intentional. */
  transition: background var(--motion-content) var(--motion-ease-enter), backdrop-filter var(--motion-content) var(--motion-ease-enter), opacity var(--motion-surface) var(--motion-ease-enter) 0.08s, transform var(--motion-surface) var(--motion-ease-enter) 0.08s;
  opacity: 0.45;
  transform: translateX(-28px);
}
.hero.is-loaded .headline-card { opacity: 1; transform: translateX(0); }
.hero.is-photo-ready .headline-card {
  background: var(--glass-fill-soft);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
}
@media (min-width: 900px) { .headline-card { padding: 44px; gap: 24px; } }
@media (min-width: 900px) { .headline-card { transform: translateX(-64px); } }
/* Typography begins its own entrance while the panel is still mid-motion
   (panel: --motion-surface, 780ms) rather than waiting for it to settle
   or simply riding along with the panel's own opacity - headline leads,
   supporting copy follows ~80ms later, both handed off before the panel
   itself has finished so the whole thing reads as one continuous
   sequence, not three disconnected pops. */
.hero__headline { opacity: 0; transform: translateY(var(--motion-distance-md)); transition: opacity var(--motion-editorial) var(--motion-ease-enter) 0.22s, transform var(--motion-editorial) var(--motion-ease-enter) 0.22s; }
.hero.is-loaded .hero__headline { opacity: 1; transform: translateY(0); }
.hero__sub { opacity: 0; transform: translateY(var(--motion-distance-xs)); transition: opacity var(--motion-copy) var(--motion-ease-enter) 0.3s, transform var(--motion-copy) var(--motion-ease-enter) 0.3s; }
.hero.is-loaded .hero__sub { opacity: 0.78; transform: translateY(0); }

/* Short floating accent segment (not a full-height card border) — matches
   the Figma headline-card's independent 4x120 accent-bar node. */
.accent-bar { position: absolute; left: 0; top: 28px; width: 4px; height: 90px; border-radius: 2px; background: var(--color-green); box-shadow: 0 0 10px rgba(60, 161, 105, 0.25); z-index: 1; }
@media (min-width: 900px) { .accent-bar { top: 44px; height: 120px; } }
.pathway-card--reverse .pathway-card__panel .accent-bar { left: auto; right: 0; }

.hero__headline { font-family: var(--font-display); font-weight: 900; font-size: 30px; line-height: 1.2; color: var(--color-white); position: relative; z-index: 1; }
.hero__headline em { font-family: var(--font-display); font-weight: 300; font-style: italic; color: var(--color-green); }
.accent { color: var(--color-green); font-weight: 900; }
/* "Institutional-Grade" uses the Ramsina display face, which renders
   noticeably smaller than the bold Raleway around it at the same
   declared font-size (different type has different cap-height/x-height
   for the same size), so it needs its own larger size to actually look
   consistent rather than smaller — on both mobile and desktop now, not
   just desktop. */
.accent--display { font-family: var(--font-accent); font-weight: 400; font-style: normal; line-height: 0.72; display: inline-block; font-size: 1.63em; }
@media (min-width: 900px) {
  .hero__headline { font-size: 54px; line-height: 1.15; }
}

.hero__sub { font-size: 15px; line-height: 1.65; color: var(--color-white); position: relative; z-index: 1; }
@media (min-width: 900px) { .hero__sub { font-size: 18px; } }

.hero__actions { display: flex; flex-direction: column; gap: 12px; position: relative; z-index: 1; }

.hero__media { position: relative; width: 100%; height: 240px; border-radius: 20px; overflow: hidden; z-index: 1; }
/* One-time arrival settle (step 1 of loader -> hero image -> glass panel
   -> typography), not a continued ambient drift - once the image has
   settled to scale(1) it is at rest, same as everything else on the
   site once .is-visible/.is-loaded has landed. */
.hero__media img { width: 100%; height: 100%; object-fit: cover; transform: scale(1.018); transition: transform 1000ms var(--ease); }
.hero.is-loaded .hero__media img { transform: scale(1); }
.hero__media-veil { position: absolute; inset: 0; }

@media (min-width: 900px) {
  /* Content overlaps the image edge (not a hard side-by-side split) so the
     headline-card's backdrop-filter actually has photo detail to frost —
     this is what makes the glassmorphism visible instead of a flat tint. */
  .hero { position: relative; display: block; min-height: 860px; padding: 0; }
  .hero__content { position: relative; z-index: 2; padding: calc(var(--nav-height) + 56px) 0 64px 80px; justify-content: center; gap: 32px; max-width: 700px; }
  .badge { padding: 8px 16px; font-size: 11px; letter-spacing: 1.5px; }
  .hero__actions { flex-direction: row; gap: 16px; }
  /* bottom: var(--container-pad) (80px at this width), not bottom:0 - the
     image previously ran flush to .hero's own bottom edge, leaving no
     breathing room before the next section. --container-pad is already
     this exact component's own gutter unit (it's the "80px" in
     .hero__content's left padding above), so using it here gives the
     image the same inset from the bottom that the content already gets
     from the left, revealing .hero's own dark background as an
     intentional band beneath the image. .hero__media is absolutely
     positioned, so this is purely visual - it doesn't change .hero's
     height or push any other element. */
  .hero__media { position: absolute; top: 0; right: 0; bottom: var(--container-pad); width: 56%; height: auto; border-radius: 0 0 0 72px; margin: 0; z-index: 0; }
}

/* ============================================
   CORE EQUATION
   ============================================ */
.core { padding: 0; background: var(--color-bg-soft); }
.core__folder { position: relative; width: 100%; padding: 56px 20px; display: flex; flex-direction: column; align-items: center; }
@media (min-width: 900px) { .core__folder { padding: 120px 80px; } }

.equation-grid { display: flex; flex-direction: column; gap: 20px; width: 100%; position: relative; z-index: 1; }
@media (min-width: 900px) { .equation-grid { flex-direction: row; align-items: center; gap: 24px; } }

.eq-card { position: relative; z-index: 1; background: var(--color-white); border: 1px solid var(--color-border-soft); border-radius: var(--radius-2xl); padding: 28px 24px; display: flex; flex-direction: column; gap: 16px; overflow: hidden; box-shadow: 0 16px 32px rgba(33, 38, 45, 0.04); transition: transform 0.35s var(--ease), box-shadow 0.35s var(--ease); will-change: transform; }
.eq-card:hover { transform: translateY(-6px); box-shadow: var(--shadow-card-hover); }
.eq-card__stripe { position: absolute; top: 0; left: 0; right: 0; height: 6px; background: var(--color-green); }
.eq-card__number { display: inline-flex; align-self: flex-start; padding: 4px 12px; border-radius: 6px; background: var(--color-green-tint); color: var(--color-green-ink); font-family: var(--font-display); font-weight: 700; font-size: 10px; letter-spacing: 1.5px; }
.eq-card__number--solid { background: var(--color-green-action); color: var(--color-white); }
.eq-card h3 { font-weight: 900; font-size: 26px; line-height: 1.15; color: var(--color-ink); }
.eq-card__formula { font-family: var(--font-display); font-weight: 700; font-size: 11px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--color-green); margin-top: -10px; }
.eq-card__image { border-radius: var(--radius-lg); overflow: hidden; height: 150px; }
.eq-card__image img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.45s var(--motion-ease-premium); }
.eq-card:hover .eq-card__image img { transform: scale(1.03); }
.eq-card__body { font-size: 14px; line-height: 1.75; color: var(--color-ink); opacity: 0.7; }
/* No .reveal-stagger children-fade here on purpose - at >=900px this
   section has its own bespoke, continuous, scroll-position-driven
   choreography (initEquationReveal() in script.js: Purpose settles as
   the anchor, People/Profit finish aligning around it) that IS this
   site's signature motion moment. Stacking a second, independent
   one-shot reveal on top would fight it rather than strengthen it.
   Below 900px .equation-grid stacks to a single column and
   initEquationReveal() deliberately does nothing there instead - the
   full horizontal scrub choreography doesn't belong on mobile (see the
   "mobile is not a scaled-down desktop system" rule) - so .eq-reveal-mobile
   below gives it one plain, one-shot settle-and-stop instead. */
@media (max-width: 899px) {
  .eq-card.eq-reveal-mobile {
    transition: transform var(--motion-copy) var(--ease), box-shadow 0.35s var(--ease), opacity var(--motion-copy) var(--ease);
    opacity: 0.7;
    transform: translateY(8px);
  }
  .eq-card--dark.eq-reveal-mobile { opacity: 0.8; transform: translateY(10px); }
  .eq-card.eq-reveal-mobile.is-visible { opacity: 1; transform: translateY(0); }
  /* Purpose (DOM order 2nd) settles first, People/Profit follow by a
     short, capped beat - never a long incrementing chain. */
  .eq-reveal-mobile:nth-child(1) { transition-delay: 0.05s; }
  .eq-reveal-mobile:nth-child(2) { transition-delay: 0s; }
  .eq-reveal-mobile:nth-child(3) { transition-delay: 0.05s; }
}

.eq-card--dark { z-index: 2; background: var(--color-dark); border-color: #e6eaf0; border-radius: 36px; box-shadow: 0 32px 48px rgba(33, 38, 45, 0.15); }
.eq-card--dark h3, .eq-card--dark .eq-card__body { color: var(--color-white); }
.eq-card--dark .eq-card__body { opacity: 0.75; }
.eq-card--dark .eq-card__image { height: 190px; }

@media (min-width: 900px) {
  .eq-card { flex: 1; padding: 40px; gap: 28px; }
  .eq-card--dark { flex: 1.15; padding: 44px; align-self: stretch; justify-content: flex-start; }
  .eq-card h3 { font-size: 32px; }
  .eq-card__body { font-size: 15px; }
  .eq-card--dark .eq-card__body { font-size: 18px; }
  .eq-card__image { height: 160px; }
  .eq-card--dark .eq-card__image { height: 232px; }
}

/* ============================================
   PILLARS (dual pathway cards)
   ============================================ */
.pillars { background: var(--color-dark); display: flex; flex-direction: column; gap: 24px; }
@media (min-width: 900px) { .pillars { gap: 20px; } }
/* .canvas-glow already clips its own decorative blobs, so this section
   doesn't need a section-level overflow clip either (same reasoning as
   .team above). */
.pillars { overflow: visible; }

.pathway-card { position: relative; z-index: 1; display: flex; flex-direction: column; border-radius: var(--radius-2xl); overflow: hidden; background: var(--color-dark); border: 1px solid rgba(255, 255, 255, 0.08); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.07), 0 8px 20px rgba(0, 0, 0, 0.09), 0 28px 60px rgba(0, 0, 0, 0.26); }
.pathway-card__image { height: 220px; overflow: hidden; }
/* One-time arrival only - no continued viewport-edge breakout and no
   large scroll-linked scaling. Once .is-visible lands, the image is at
   rest; only the existing hover zoom (below) still moves it. Direction
   follows which side the image sits on: default card = image left
   (arrives from the left), --reverse card = image right (arrives from
   the right) - complementary to the panel/text below, not opposing. */
.pathway-card__image.reveal-surface { opacity: 0.6; transform: translateX(-24px) scale(1.015); }
.pathway-card--reverse .pathway-card__image.reveal-surface { transform: translateX(24px) scale(1.015); }
.pathway-card__image.reveal-surface.is-visible { opacity: 1; transform: translateX(0) scale(1); }
.pathway-card__image img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.8s var(--ease); }
.pathway-card:hover .pathway-card__image img { transform: scale(1.04); }
/* Text panel: plain EDITORIAL reveal (.reveal, in the markup), trailing
   the image by a small fixed beat so it reads as image-leads/text-
   follows rather than two things arriving at once. Primary = image
   surface entrance, supporting = this. */
.pathway-card__panel.reveal { transition-delay: 0.08s; }
.pathway-card__panel { position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 16px; padding: 28px 24px 32px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.06), 0 18px 48px rgba(0, 0, 0, 0.12); overflow: hidden; }
.pathway-card__panel h3 { font-weight: 800; font-size: 24px; line-height: 1.2; color: var(--color-white); position: relative; z-index: 1; }
.pathway-card__panel p { font-size: 15px; line-height: 1.65; color: var(--color-white); opacity: 0.75; position: relative; z-index: 1; }
.pathway-card__chip { display: inline-flex; padding: 6px 12px; border-radius: 999px; background: var(--color-green-action); color: var(--color-white); font-weight: 700; font-size: 11px; letter-spacing: 1px; text-transform: uppercase; position: relative; z-index: 1; }

@media (min-width: 900px) {
  /* Panel overlaps the image edge by ~10% so the frosted glass actually
     reveals photo detail underneath, matching the Figma composition.
     .pathway-card stays overflow:visible (a harmless holdover from the
     old edge-breakout layout — nothing overflows it now that the image
     only does a one-time settle-in), which is why the image's own outer
     corner and the panel's own outer corner each carry their own
     border-radius rather than relying on a parent clip. */
  .pathway-card { display: block; height: 440px; overflow: visible; }
  .pathway-card__image { position: absolute; top: 0; left: 0; bottom: 0; width: 41%; height: 100%; z-index: 0; border-radius: var(--radius-2xl) 0 0 var(--radius-2xl); }
  .pathway-card__panel { position: relative; z-index: 1; margin-left: 31%; width: 69%; height: 100%; justify-content: center; padding: 48px; gap: 24px; border-radius: 0 var(--radius-2xl) var(--radius-2xl) 0; }
  .pathway-card--reverse .pathway-card__image { left: auto; right: 0; border-radius: 0 var(--radius-2xl) var(--radius-2xl) 0; }
  .pathway-card--reverse .pathway-card__panel { margin-left: 0; border-radius: var(--radius-2xl) 0 0 var(--radius-2xl); }
  .pathway-card__panel h3 { font-size: 36px; }
  .pathway-card__panel p { font-size: 16px; }
}

/* ============================================
   ROOTS
   ============================================ */
.roots { background: var(--color-bg-soft); }
.timeline-row { display: flex; flex-direction: column; gap: 16px; position: relative; z-index: 1; }
/* GRID composition reveal (same family/treatment as .holdings-grid) -
   the row establishes as one unit; its cards then settle by ROLE, not
   position: the centre/anchor card (.timeline-item--dark, "Transition")
   settles first and travels a touch further (it's the visual anchor),
   the flanking cards follow it by one small fixed beat and travel less
   - calm, structural grouping, not three cards spreading dramatically
   outward. transition-duration is overridden per role below rather than
   the whole `transition` shorthand, so the card's own hover lift
   (transform, 0.3s) is untouched - only opacity keeps its longer,
   role-specific entrance speed. */
.timeline-row.reveal-grid .timeline-item { opacity: 0.55; transform: translateY(10px); transition-delay: 0.07s; }
.timeline-row.reveal-grid .timeline-item--dark { opacity: 0.75; transform: translateY(16px); transition-delay: 0s; }
.timeline-row.reveal-grid.is-visible .timeline-item { opacity: 1; transform: translateY(0); }
.timeline-row.reveal-grid.is-visible .timeline-item:not(.timeline-item--dark) { transition-duration: 0.3s, 0.3s, 620ms; }
.timeline-row.reveal-grid.is-visible .timeline-item--dark { transition-duration: 0.3s, 0.3s, 700ms; }
@media (min-width: 900px) { .timeline-row { flex-direction: row; align-items: stretch; gap: 16px; } }

.timeline-item { position: relative; background: var(--color-bg-soft); border: 1px solid var(--color-border-soft); border-radius: var(--radius-xl); box-shadow: 0 2px 4px rgba(33, 38, 45, 0.03), 0 8px 12px rgba(33, 38, 45, 0.05); padding: 24px; display: flex; flex-direction: column; gap: 14px; transition-property: transform, box-shadow, opacity; transition-duration: 0.3s, 0.3s, var(--motion-content); transition-timing-function: var(--ease), var(--ease), var(--ease); overflow: hidden; }
.timeline-item::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: var(--color-green); }
.timeline-item:hover { transform: translateY(-4px); box-shadow: 0 16px 20px rgba(33, 38, 45, 0.08); }
.timeline-item--dark { background: var(--color-dark); border-radius: 28px; box-shadow: 0 6px 8px rgba(33, 38, 45, 0.06), 0 16px 16px rgba(33, 38, 45, 0.12); }
@media (min-width: 900px) { .timeline-item { padding: 32px; gap: 20px; flex: 1; } .timeline-item--dark { flex: 1.15; padding: 36px; } }

.timeline-item__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.timeline-item__head h3 { font-weight: 700; font-size: 18px; color: var(--color-ink); }
.timeline-item--dark .timeline-item__head h3 { color: var(--color-white); }
@media (min-width: 900px) { .timeline-item__head h3 { font-size: 24px; } }

.pill { background: var(--color-green-tint); color: var(--color-green-ink); font-weight: 700; font-size: 9px; padding: 4px 10px; border-radius: 100px; text-transform: uppercase; white-space: nowrap; border: 1px solid rgba(var(--color-green-rgb), 0.19); }
@media (min-width: 900px) { .pill { font-size: 10px; padding: 5px 12px; } }

.timeline-item p { font-size: 13px; line-height: 1.5; opacity: 0.8; color: var(--color-ink); }
.timeline-item--dark p { color: var(--color-white); opacity: 0.8; }
@media (min-width: 900px) { .timeline-item p { font-size: 15px; line-height: 1.65; } }

.timeline-bridge { display: none; }
@media (min-width: 900px) { .timeline-bridge { display: block; align-self: center; width: 40px; height: 12px; border-radius: 100px; background: var(--color-green); opacity: 0.15; flex-shrink: 0; } }

/* ============================================
   TEAM
   ============================================ */
.team { background: var(--color-bg-soft); padding-left: 0; padding-right: 0; overflow: visible; }
/* overflow:hidden on .section breaks position:sticky for descendants
   (it becomes the sticky containing block instead of the viewport) —
   .canvas-glow already clips the decorative blobs on its own, so this
   section doesn’t need the section-level clip. */
.team .section-heading { padding: 0 var(--container-pad); }

/* Default (mobile / reduced-motion) layout: the existing drag + wheel +
   scroll-snap horizontal strip. On wide viewports with motion allowed,
   script.js adds .is-pinned to .cinematic-scroll and instead pins the
   track in the viewport while translating it horizontally as the page
   scrolls vertically — see the rules below. */
.cinematic-scroll { position: relative; }

.cinematic-track-outer { overflow-x: auto; overflow-y: hidden; padding: 4px var(--container-pad) 20px; scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch; position: relative; z-index: 1; cursor: grab; }
.cinematic-track-outer:active { cursor: grabbing; }
.cinematic-track-outer::-webkit-scrollbar { height: 6px; }
.cinematic-track-outer::-webkit-scrollbar-thumb { background: var(--color-border); border-radius: 100px; }

.cinematic-track { display: flex; gap: 20px; width: max-content; }

.cinematic-card { position: relative; width: 280px; height: 420px; border-radius: 20px; overflow: hidden; border: 1px solid rgba(255, 255, 255, 0.08); background: var(--color-dark); scroll-snap-align: start; flex-shrink: 0; }
.cinematic-card img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: transform 0.6s var(--ease); }
.cinematic-card:hover img { transform: scale(1.05); }
.cinematic-card::after { content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, rgba(22, 25, 30, 0) 0%, rgba(22, 25, 30, 0) 50%, rgba(22, 25, 30, 0.439) 75%, rgba(22, 25, 30, 0.902) 100%), rgba(33, 38, 45, 0.2); }
.cinematic-card__index { position: absolute; top: 15px; right: 15px; z-index: 2; background: rgba(0, 0, 0, 0.4); border: 1px solid var(--glass-border); color: var(--color-white); font-weight: 700; font-size: 11px; padding: 6px 10px; border-radius: 100px; }
.cinematic-card__bio { position: absolute; left: 14px; right: 14px; bottom: 14px; z-index: 2; display: flex; flex-direction: column; gap: 8px; padding: 18px; border-radius: 20px; background: rgba(11, 15, 20, 0.55); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid var(--glass-border); box-shadow: 0 0 18px rgba(60, 161, 105, 0.1), 0 10px 24px -8px rgba(0, 0, 0, 0.15); transition: transform 0.35s var(--ease); }
.cinematic-card:hover .cinematic-card__bio { transform: translateY(-4px); }
.cinematic-card__bio h3 { font-weight: 800; font-size: 18px; line-height: 1.2; color: var(--color-white); }
.cinematic-card__role { font-weight: 700; font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--color-green); }
.cinematic-card__desc { font-size: 12px; line-height: 1.55; color: rgba(255, 255, 255, 0.8); }
.cinematic-card--placeholder { background: var(--color-dark-panel); }
.cinematic-card__avatar-fallback { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -60%); width: 84px; height: 84px; border-radius: 50%; background: var(--color-green); color: var(--color-white); font-family: var(--font-display); font-weight: 800; font-size: 26px; display: flex; align-items: center; justify-content: center; z-index: 1; }

@media (min-width: 900px) {
  .cinematic-card { width: 380px; height: 620px; }
  .cinematic-card__bio { left: 23px; right: auto; bottom: 15px; width: 332px; padding: 24px; }
  .cinematic-card__bio h3 { font-size: 22px; }
  .cinematic-card__role { font-size: 11px; letter-spacing: 1.2px; }
  .cinematic-card__desc { font-size: 13px; line-height: 1.6; }
}
/* .section already supplies the bottom gap before the next section;
   no extra padding needed here (previously stacked to 216px). */

/* Pinned scroll animation (desktop, motion allowed — script.js decides
   eligibility and toggles this class; everything above is the fallback). */
.cinematic-scroll.is-pinned .cinematic-scroll__sticky {
  position: sticky;
  top: var(--nav-height);
  height: calc(100vh - var(--nav-height));
  display: flex;
  align-items: center;
  overflow: hidden;
}
.cinematic-scroll.is-pinned .cinematic-track-outer {
  overflow: visible;
  cursor: default;
  padding-top: 0;
  padding-bottom: 0;
  width: 100%;
}
.cinematic-scroll.is-pinned .cinematic-track {
  will-change: transform;
}

/* ============================================
   CONTACT
   ============================================ */
.contact { background: var(--color-bg-blue-gray); }
.contact-stage { display: flex; flex-direction: column; gap: 24px; position: relative; z-index: 1; }
@media (min-width: 900px) { .contact-stage { flex-direction: row; gap: 40px; align-items: flex-start; } }

.contact-context { display: flex; flex-direction: column; gap: 32px; }
@media (min-width: 900px) { .contact-context { width: 380px; flex-shrink: 0; } }
.contact-context h3 { font-weight: 800; font-size: 22px; line-height: 1.25; color: var(--color-ink); margin-bottom: -16px; }
@media (min-width: 900px) { .contact-context h3 { font-size: 30px; } }
.contact-context > p { font-size: 15px; line-height: 1.65; opacity: 0.8; }
@media (min-width: 900px) { .contact-context > p { font-size: 16px; } }

.contact-links { display: flex; flex-direction: column; gap: 12px; }
/* Tight 60ms relationship between the two link cards - a small
   supporting beat riding on .contact-context's own reveal (both start
   at the same instant .is-visible lands), not an independent entrance. */
.contact-link { display: flex; align-items: center; gap: 12px; padding: 18px; background: var(--color-white); border: 1px solid var(--color-border); border-radius: 10px; font-weight: 600; font-size: 15px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.03), 0 6px 16px rgba(33, 38, 45, 0.06); opacity: 0; transition: transform 0.25s var(--ease), border-color 0.25s var(--ease), opacity var(--motion-fast) var(--ease); }
.contact-context.reveal.is-visible .contact-link { opacity: 1; }
.contact-context.reveal.is-visible .contact-link:nth-child(2) { transition-delay: 0.06s; }
.contact-link:hover { transform: translateY(-2px); border-color: var(--color-green); }

.inquiry-form { position: relative; flex: 1; min-width: 0; width: 100%; display: flex; flex-direction: column; gap: 16px; background: var(--color-dark); border: 1px solid var(--glass-border-soft); border-radius: var(--radius-xl); padding: 28px 24px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.07), 0 6px 16px rgba(33, 38, 45, 0.08), 0 28px 60px rgba(33, 38, 45, 0.21); overflow: hidden; }
.inquiry-form__stripe { position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--color-green); }
/* Heading gets one small resolve of its own (translateY 8px, opacity),
   a beat after the panel starts settling - everything else inside the
   panel (sub-copy, fields) simply rides along with it rather than each
   getting an independent entrance. */
.inquiry-form h3 { font-weight: 800; font-size: 24px; line-height: 1.25; color: var(--color-white); position: relative; z-index: 1; opacity: 0; transform: translateY(8px); transition: opacity var(--motion-fast) var(--ease) 0.1s, transform var(--motion-fast) var(--ease) 0.1s; }
.inquiry-form.is-visible h3 { opacity: 1; transform: translateY(0); }
.inquiry-form__sub { font-size: 14px; line-height: 1.6; color: var(--color-white); opacity: 0.65; margin-top: -8px; margin-bottom: 8px; position: relative; z-index: 1; }
@media (min-width: 900px) {
  .inquiry-form { padding: 40px; gap: 24px; }
  .inquiry-form h3 { font-size: 32px; }
  .inquiry-form__sub { font-size: 15px; }
  .form-field { gap: 16px; }
  .inquiry-form .btn--full { padding-block: 18px; }
}

/* Fields resolve together as one small supporting group - opacity only,
   no independent per-field travel or incrementing stagger chain. The
   panel itself is the primary (surface) entrance; this is one quiet
   beat once it's mostly settled, not N separate animations. */
.form-field {
  display: flex; flex-direction: column; gap: 8px; position: relative; z-index: 1;
  opacity: 0;
  transition: opacity var(--motion-fast) var(--ease) 0.14s;
}
.inquiry-form.is-visible .form-field { opacity: 1; }
.form-field__label { font-weight: 700; font-size: 11px; letter-spacing: 1px; text-transform: uppercase; color: var(--color-green); }
.form-field input, .form-field textarea { width: 100%; background: rgba(255, 255, 255, 0.06); border: 1px solid var(--form-control-border); border-radius: 10px; padding: 16px 18px; color: var(--color-white); font-family: var(--font-body); font-size: 15px; resize: vertical; transition: border-color 0.25s var(--ease), background 0.25s var(--ease); }
.form-field textarea { min-height: 140px; }
.form-field input::placeholder, .form-field textarea::placeholder { color: var(--form-placeholder); }
.form-field input:focus, .form-field textarea:focus { outline: none; border-color: var(--color-green); background: rgba(255, 255, 255, 0.09); }
.form-field input:user-invalid, .form-field textarea:user-invalid { border-color: #d9695f; }
/* HG-P3-08: JS-driven validation state (initContactForm), independent of
   the browser's own :user-invalid so it stays in sync with our custom
   error text/focus handling rather than relying on native UI alone. */
.form-field input.is-invalid, .form-field textarea.is-invalid { border-color: #d9695f; }
.form-field__error { font-size: 12px; font-weight: 600; color: #ef8b83; min-height: 15px; }
/* Phase 5: the contact form now delivers through Netlify Forms, so it needs
   the same visually-hidden honeypot the joint-venture form uses, and a
   pending state for the moment between submit and confirmation. */
.form-field--hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
/* A11Y-07: the optional marker is the cue that carries the "unless marked
   optional" instruction, so it has to be readable rather than a faded
   version of the label - an alpha fade over the dark field surface put it
   under 4.5:1. Explicit colour, no opacity. */
.form-field__optional { font-weight: 600; text-transform: none; letter-spacing: 0; opacity: 1; color: var(--form-placeholder); }

/* A11Y-07: required/optional instruction, stated before the first field on
   both forms. */
.form-required-note {
  font-size: 12px;
  line-height: 1.5;
  font-weight: 600;
  color: var(--form-placeholder);
  position: relative;
  z-index: 1;
}
.inquiry-form__note.is-pending { color: rgba(255, 255, 255, 0.72); }
.inquiry-form .btn--full:disabled { opacity: 0.7; cursor: not-allowed; }

.inquiry-form .btn--full { border-radius: 10px; box-shadow: 0 2px 6px rgba(33, 38, 45, 0.13), 0 10px 28px rgba(60, 161, 105, 0.31); }
.inquiry-form__note { font-size: 13px; color: var(--color-green); min-height: 18px; font-weight: 600; position: relative; z-index: 1; }
.inquiry-form__note.is-error { color: #ef8b83; }
.inquiry-form__note.is-success { color: var(--color-green); }

/* ============================================
   FOOTER
   ============================================ */
/* Same charcoal (--color-dark) as desktop at every width - the footer
   previously used a separate green-tinted --color-dark-footer below
   900px, which read as a visibly different (greener) color from the
   desktop footer instead of one consistent brand background. */
.footer { background: var(--color-dark); color: var(--color-white); padding: 48px 20px 24px; display: flex; flex-direction: column; gap: 40px; position: relative; overflow: hidden; box-shadow: 0 24px 72px rgba(0, 0, 0, 0.14); }
@media (min-width: 900px) { .footer { padding: 80px 80px 40px; gap: 64px; } }

.footer__header { display: flex; flex-direction: column; gap: 24px; position: relative; z-index: 1; }
@media (min-width: 900px) { .footer__header { flex-direction: row; align-items: center; justify-content: space-between; } }

.footer__meta { display: flex; flex-direction: column; gap: 8px; }
@media (min-width: 900px) { .footer__meta { align-items: flex-end; text-align: right; gap: 4px; } }
.footer__meta-title { font-weight: 700; font-size: 12px; letter-spacing: 2px; text-transform: uppercase; }
@media (min-width: 900px) { .footer__meta-title { font-size: 14px; } }
.footer__meta-desc { font-size: 13px; opacity: 0.8; }
@media (min-width: 900px) { .footer__meta-desc { font-size: 14px; } }

.footer__bottom { border-top: 1px solid rgba(255, 255, 255, 0.13); padding-top: 24px; display: flex; flex-direction: column; gap: 16px; font-size: 12px; position: relative; z-index: 1; }
@media (min-width: 900px) { .footer__bottom { flex-direction: row; align-items: center; justify-content: space-between; padding-top: 32px; font-size: 13px; } }
.footer__bottom p { opacity: 0.6; }
/* P7-07: social links join this row once their profile URLs exist (see
   SOCIAL_PROFILES in stamp.js). Five items do not fit on one 320px line, so
   the row wraps rather than pushing the footer into horizontal overflow -
   set now, while the row is still short, so adding them cannot regress the
   320px reflow gate. */
.footer__links { display: flex; flex-wrap: wrap; gap: 8px; margin: -12px -12px -12px 0; }
.footer__links a { opacity: 0.6; transition: opacity 0.2s var(--ease); padding: 12px; min-height: 44px; display: inline-flex; align-items: center; }
/* min-height rather than auto: at 13px/1.4 the desktop padding left these
   links 23px tall, one pixel under WCAG 2.2's 24x24 target minimum, and they
   are a standalone row rather than links inline in a sentence, so the inline
   exception does not cover them. Nothing moves visually - the row simply
   stops one pixel short of a failure. */
@media (min-width: 900px) { .footer__links { margin: 0; gap: 24px; } .footer__links a { padding: 4px 8px; min-height: 24px; } }
.footer__links a:hover { opacity: 1; text-decoration: underline; }
/* HG-P3-04: Privacy Policy / Terms of Engagement have no real page yet.
   A plain, non-interactive <span> (not an <a href="#">) so it isn't in the
   tab order and does nothing on click - no fake destination to jump to. */
.footer__link-disabled { opacity: 0.35; padding: 12px; min-height: 44px; display: inline-flex; align-items: center; cursor: default; }
@media (min-width: 900px) { .footer__link-disabled { padding: 4px 8px; min-height: 24px; } }

/* ============================================
   SCROLL REVEAL — global motion grammar
   ============================================
   Three entrance families, used sitewide instead of one-off per-section
   motion. Every one is a plain CSS-transition, one-shot reveal (a single
   IntersectionObserver in script.js adds .is-visible once, then
   unobserves - see initScrollReveal()). The rule everywhere: a section
   may move while it arrives, then it is at rest - nothing here keeps
   moving once .is-visible has landed and the transition has finished.

   1. EDITORIAL  (.reveal, .reveal-stagger) - text, headings, copy,
      metrics labels. translateY(14px) -> 0, opacity 0 -> 1, ~650ms.
   2. SURFACE    (.reveal-surface) - glass panels, forms, dark CTA
      blocks, large split-layout panels. translateX(±32px) -> 0 (or
      translateY for centered surfaces via [data-direction="up"]),
      opacity .5 -> 1, scale .99 -> 1, ~780ms.
   3. GRID       (.reveal-grid) - card grids and photo compositions.
      The grid container settles as ONE unit (translateY(14px) + opacity
      .4 -> 1, already partly present rather than starting from nothing -
      a composition "resolves into focus" rather than pops in); children
      get a small capped stagger, never independent directional
      entrances. See .photo-grid further below for the photography
      variant of this same family. */
.reveal, .reveal-stagger {
  opacity: 0;
  transform: translateY(var(--motion-distance-medium));
  transition: opacity var(--motion-content) var(--ease), transform var(--motion-content) var(--ease);
}
.reveal.is-visible, .reveal-stagger.is-visible { opacity: 1; transform: translateY(0); }
/* Capped stagger: 0/50/100/150ms, then it stops increasing - a 5th+
   item shares the 4th's delay rather than the chain growing indefinitely. */
.reveal-stagger:nth-child(1) { transition-delay: 0s; }
.reveal-stagger:nth-child(2) { transition-delay: 0.05s; }
.reveal-stagger:nth-child(3) { transition-delay: 0.1s; }
.reveal-stagger:nth-child(n+4) { transition-delay: 0.15s; }

.reveal-surface {
  opacity: 0.5;
  transform: translateX(0) translateY(0) scale(0.99);
  transition: opacity var(--motion-surface) var(--ease), transform var(--motion-surface) var(--ease);
}
.reveal-surface[data-direction="left"] { transform: translateX(-32px) scale(0.99); }
.reveal-surface[data-direction="right"] { transform: translateX(32px) scale(0.99); }
.reveal-surface[data-direction="up"] { transform: translateY(16px) scale(0.99); }
.reveal-surface.is-visible { opacity: 1; transform: translateX(0) translateY(0) scale(1); }
/* Two-surface layouts (a form panel handing off from its left column) -
   a small fixed delay stands in for the old scroll-linked "handoff",
   letting the panel read as following the left column by ~80ms without
   needing per-section JS choreography. */
.reveal-surface--delayed { transition-delay: 0.08s; }

.reveal-grid {
  opacity: 0.4;
  transform: translateY(var(--motion-distance-medium));
  transition: opacity var(--motion-content) var(--ease), transform var(--motion-content) var(--ease);
}
.reveal-grid.is-visible { opacity: 1; transform: translateY(0); }

/* Scroll velocity awareness (html.is-fast-scroll, toggled from
   script.js's updateNavbar): a fast scroll still gets every reveal, just
   without the stagger/duration that only reads as choreography at a
   normal pace - without this, a quick scroll would leave a visible
   trail of content still catching up behind the user. */
html.is-fast-scroll .reveal-stagger,
html.is-fast-scroll .metric-card:nth-child(1)::before,
html.is-fast-scroll .metric-card:nth-child(1) .metric-card__value,
html.is-fast-scroll .metric-card:nth-child(1) .metric-card__label,
html.is-fast-scroll .metric-card:nth-child(2)::before,
html.is-fast-scroll .metric-card:nth-child(2) .metric-card__value,
html.is-fast-scroll .metric-card:nth-child(2) .metric-card__label,
html.is-fast-scroll .metric-card:nth-child(3)::before,
html.is-fast-scroll .metric-card:nth-child(3) .metric-card__value,
html.is-fast-scroll .metric-card:nth-child(3) .metric-card__label,
html.is-fast-scroll .reveal-grid .holding-card,
html.is-fast-scroll .reveal-grid .timeline-item,
html.is-fast-scroll .feature-cards.reveal-grid .feature-card,
html.is-fast-scroll .inquiry-form.is-visible .form-field,
html.is-fast-scroll .jv-form-card.is-visible .jv-field,
html.is-fast-scroll .photo-grid .gallery-main img,
html.is-fast-scroll .photo-grid .gallery-col .gallery-card img,
html.is-fast-scroll .photo-grid.gallery-row--triple .gallery-card img {
  /* Matches (or beats) the base rules' own specificity - .gallery-col/
     .gallery-row--triple are folded in here for that reason, not because
     the selectors need to be this specific to be correct. */
  transition-delay: 0s;
}

/* Beyond zeroing stagger delay above, a fast scroll also shortens
   whatever transition duration is still running - a quick flick
   shouldn't leave any reveal-family element visibly catching up once
   it's already back on screen; it should just arrive, faster. */
html.is-fast-scroll .reveal,
html.is-fast-scroll .reveal-stagger,
html.is-fast-scroll .reveal-surface,
html.is-fast-scroll .reveal-grid,
html.is-fast-scroll .form-field,
html.is-fast-scroll .jv-field {
  transition-duration: 0.22s;
  transition-delay: 0s !important;
}

/* ============================================
   MOBILE MOTION OVERRIDES
   Mobile is not a scaled-down desktop system. Two changes below 900px:
   (1) every image/text split and both forms that slide in from the side
   on desktop settle vertically instead - a horizontal slide reads as
   sideways drift once the layout is a single stacked column, so the
   "direction" half of the surface family is dropped and only the
   opacity+scale settle remains, travelling a shorter vertical distance;
   (2) the hero's typography handoff delays are compressed, since a
   narrower viewport is scrolled through faster and a long staggered
   chain reads as lag rather than choreography on a phone.
   ============================================ */
@media (max-width: 899px) {
  .reveal-surface[data-direction="left"],
  .reveal-surface[data-direction="right"] {
    transform: translateY(16px) scale(0.99);
  }

  .pathway-card__image.reveal-surface,
  .pathway-card--reverse .pathway-card__image.reveal-surface {
    transform: translateY(16px) scale(1.015);
  }
  .pathway-card__image.reveal-surface.is-visible { transform: translateY(0) scale(1); }

  /* Forms: vertical-only settle, no horizontal slide, at every width. */
  .inquiry-form.reveal-surface,
  .jv-form-card.reveal-surface {
    transform: translateY(16px) scale(0.99);
  }

  /* Hero handoff: same five-stage sequence, compressed delays so the
     headline/sub/actions land sooner relative to the image+panel. */
  .hero__headline { transition-delay: 0.14s; }
  .hero__sub { transition-delay: 0.2s; }
  .hero .hero__actions { transition-delay: 0.34s; }

  /* Grid/composition stagger tightened - a phone-width column reads a
     4-item cascade as slower than the same stagger does in a wide grid. */
  .reveal-stagger:nth-child(2) { transition-delay: 0.04s; }
  .reveal-stagger:nth-child(3) { transition-delay: 0.07s; }
  .reveal-stagger:nth-child(n+4) { transition-delay: 0.1s; }

  /* MOT-01: mobile entrance target is 250-400ms, not desktop's 650ms
     var(--motion-content) - explicit overrides rather than redefining
     --motion-content itself, which many unrelated components also read
     (navbar, feature cards, photo grids...) and shouldn't shrink here.
     (.stage-card's own mobile override lives next to its base rule
     further down - same specificity, and its base rule sits later in
     the file, so a copy placed here would lose to it.) */
  .reveal, .reveal-stagger { transition-duration: 0.35s, 0.35s; }
}

/* ============================================
   COMING SOON (stub pages)
   ============================================ */
.coming-soon { position: relative; min-height: 70vh; display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: 20px; background: var(--color-dark); padding: calc(var(--nav-height) + 60px) var(--container-pad) 80px; overflow: hidden; }
.coming-soon h1 { font-size: 34px; color: var(--color-white); font-weight: 900; max-width: 760px; position: relative; z-index: 1; }
@media (min-width: 900px) { .coming-soon h1 { font-size: 48px; } }
.coming-soon p { font-size: 16px; color: var(--color-white); opacity: 0.75; max-width: 560px; line-height: 1.6; position: relative; z-index: 1; }
.coming-soon .tag { position: relative; z-index: 1; }
.coming-soon .btn { position: relative; z-index: 1; }

/* ============================================
   SUBPAGE: PAGE HEADER
   ============================================ */
/* No box-shadow here - .page-header sits flush against the white section
   that follows it on every interior page, not floating above a surface,
   so a downward-cast shadow just reads as a soft grey gap/seam between
   the two rather than a real drop shadow. The 1px border-bottom is the
   only divider. */
.page-header { position: relative; display: flex; flex-direction: column; gap: 24px; padding: calc(var(--nav-height) + 32px) var(--container-pad) 40px; margin-bottom: 0; overflow: hidden; border-bottom: 1px solid var(--glass-border); color: var(--color-white); background: var(--color-dark); }
/* Vertical overscan, not inset:0 - initPhotographyDepth() (script.js)
   translates this whole layer (image + ::after overlay together) up to
   +-14px on desktop as the page scrolls. Sized to inset:0 exactly, that
   translate would walk the layer's leading/trailing edge past .page-header's
   own (overflow:hidden) boundary, exposing a sliver of .page-header's flat
   background colour past whichever edge it retreated from - a gap that
   reads as "the photo without its dark tint" since both would otherwise sit
   over a similarly dark background. 16px of overscan on each edge (the
   14px max throw plus a small margin) keeps the layer larger than the box
   it fills at every point in the animation, so there's never an edge to
   expose. Clipped by .page-header's own overflow:hidden either way, so
   this never affects layout height or causes horizontal scroll. */
.page-header__bg { position: absolute; left: 0; right: 0; top: -16px; bottom: -16px; z-index: 0; }

/* ---------- Internal-page hero -> first section handoff ----------
 * Every interior page's first body section (.holdings-showcase,
 * .housing-highlights, .operating-pillars, .jv-section, .case-deep-dive)
 * carries its own generic top padding, meant for the breathing room
 * BETWEEN sections further down the page - on desktop a couple of those
 * grow to 80px top padding for that reason. Stacked directly under the
 * hero, that reads as an excessive, disconnected gap instead. This rule
 * targets only the section immediately after the hero (main's direct
 * .page-header/.case-hero sibling) and overrides just its top padding,
 * not the class's own side/bottom padding or any later section's
 * spacing - so the first eyebrow/label sits a consistent ~40px below
 * the hero boundary on every interior page, and the hero visually
 * connects to the content that follows it. */
main > .page-header + section,
main > .case-hero + section {
  padding-top: 40px;
}
/* Premium entrance (not the generic .reveal): initHeroEntrance() in
   script.js adds .is-loaded to .page-header on content init - it does
   NOT wait on this image, or on anything else, to load. The image just
   settles from a slight scale while everything else stays visible from
   frame 0; nothing here can block or depend on network timing. */
.page-header__bg img { width: 100%; height: 100%; object-fit: cover; transform: scale(1.025); transition: transform 1.15s var(--motion-ease-premium) 0.1s; }
.page-header.is-loaded .page-header__bg img { transform: scale(1); }
/* MOT-08: the 1.15s/0.1s-delay image settle above is a deliberate desktop
   flourish, but on mobile it was the last piece of a ~1.1-1.25s chain
   (heading, then copy, then image) standing between arrival and being able
   to read the page - too long for a mobile visitor deciding whether to
   keep reading. Below 900px the image settles fast and barely scales at
   all, so it never delays the primary message. Placed after the base rule
   above so it wins the specificity tie (same lesson as every other
   reveal-transition bug this phase). */
@media (max-width: 899px) {
  .page-header__bg img { transform: scale(1.01); transition: transform 0.4s var(--motion-ease-premium); }
}
.page-header__bg::after { content: ""; position: absolute; inset: 0; background: rgba(15, 23, 42, 0.68); }
.page-header--tint-62 .page-header__bg::after { background: rgba(15, 23, 42, 0.62); }
.page-header--tint-66 .page-header__bg::after { background: rgba(15, 23, 42, 0.66); }
.page-header__copy { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: flex-start; gap: 20px; max-width: 760px; }
/* Motion hierarchy for the header entrance: eyebrow tag, then the masked
   headline, then the supporting line - staggered rather than moving
   together, so it reads as one composed arrival, not three things
   animating at once. */
.page-header__copy .tag { opacity: 0; transform: translateY(var(--motion-distance-small)); transition: opacity 0.6s var(--motion-ease-premium) 0.15s, transform 0.6s var(--motion-ease-premium) 0.15s; }
.page-header.is-loaded .page-header__copy .tag { opacity: 1; transform: translateY(0); }
.page-header__copy h1 { overflow: hidden; }
.page-header__copy h1 > span { display: inline-block; transform: translateY(28px); transition: transform 0.8s var(--motion-ease-premium) 0.28s; }
.page-header.is-loaded .page-header__copy h1 > span { transform: translateY(0); }
.page-header__sub { opacity: 0; transform: translateY(var(--motion-distance-small)); transition: opacity 0.65s var(--motion-ease-premium) 0.45s, transform 0.65s var(--motion-ease-premium) 0.45s; }
.page-header.is-loaded .page-header__sub { opacity: 1; transform: translateY(0); }
/* MOT-08: heading (800ms+280ms) + copy (650ms+450ms) together used to
   settle ~1.1s after the image had even started, before a mobile visitor
   could read the primary message. Below 900px the whole text group reads
   fast - short fades, a much smaller lift, and a stagger that stays inside
   the "total <=80ms" mobile budget - so the message is the first thing to
   land, not the last. Desktop keeps its original masked-reveal timing.
   Placed after the base rules above for the same specificity-order reason
   as the image override. */
@media (max-width: 899px) {
  .page-header__copy .tag {
    transform: translateY(6px);
    transition: opacity 0.25s var(--motion-ease-premium), transform 0.25s var(--motion-ease-premium);
  }
  .page-header__copy h1 > span {
    transform: translateY(10px);
    transition: transform 0.3s var(--motion-ease-premium) 0.04s;
  }
  .page-header__sub {
    transform: translateY(6px);
    transition: opacity 0.35s var(--motion-ease-premium) 0.08s, transform 0.35s var(--motion-ease-premium) 0.08s;
  }
}
.page-header h1 { font-family: var(--font-display); font-weight: 900; font-size: 30px; line-height: 1.15; }
@media (min-width: 900px) { .page-header h1 { font-size: 48px; } }
.page-header__sub { font-size: 15px; line-height: 1.65; opacity: 0.75; }
@media (min-width: 900px) { .page-header__sub { font-size: 18px; } }
@media (min-width: 900px) {
  .page-header { flex-direction: row; align-items: center; gap: 40px; padding-top: calc(var(--nav-height) + 32px); padding-bottom: 20px; }
  .page-header__copy { gap: 24px; }
}

/* ============================================
   SUBPAGE: GRID TITLE (left-aligned section heading)
   ============================================ */
.grid-title { display: flex; flex-direction: column; align-items: flex-start; gap: 16px; text-align: left; }
.grid-title .section-title { text-align: left; }
.grid-title .section-sub { text-align: left; max-width: 720px; }
.title-rule { display: block; width: 40px; height: 4px; background: var(--color-green); }

/* ============================================
   SUBPAGE: PIPELINE / STAGE GRID
   ============================================ */
.operating-pillars { position: relative; background: var(--color-bg-soft); padding: 48px var(--container-pad); display: flex; flex-direction: column; gap: 40px; box-shadow: 0 16px 48px rgba(0, 0, 0, 0.06); overflow: hidden; }
@media (min-width: 900px) { .operating-pillars { padding: 80px; gap: 56px; } }
.pipeline-grid { display: flex; flex-direction: column; gap: 24px; }
@media (min-width: 900px) { .pipeline-grid { flex-direction: row; } }
/* MOT-01: transform/box-shadow here share a selector (one class) with
   .reveal-stagger's own opacity/transform transition, and the shorthand
   below used to replace that transition outright rather than add to it
   (same specificity, later in the file wins) - opacity had no
   transition at all, so the reveal-stagger entrance snapped straight
   from 0 to 1 instead of fading. Listed longhand (transition-property/
   -duration/-timing-function, same pattern as .timeline-item) so the
   card's own hover transform/box-shadow keep their quick 0.35s while
   opacity gets the shared entrance duration back. */
.stage-card { position: relative; flex: 1; background: var(--color-white); border: 1px solid var(--color-border-soft); border-radius: var(--radius-2xl); padding: 32px; display: flex; flex-direction: column; gap: 20px; overflow: hidden; box-shadow: 0 16px 32px rgba(33, 38, 45, 0.04); transition-property: transform, box-shadow, opacity; transition-duration: 0.35s, 0.35s, var(--motion-content); transition-timing-function: var(--ease), var(--ease), var(--ease); }
/* MOT-01: mobile entrance target is 250-400ms - placed here, after the
   base rule above, so it actually wins (same selector/specificity as
   the base rule; a copy in the shared mobile block earlier in the file
   would lose the tie to this later rule). */
@media (max-width: 899px) { .stage-card { transition-duration: 0.35s, 0.35s, 0.35s; } }
/* MOT-03: the shared .reveal-stagger:nth-child() capped-stagger rules
   (further up) set a single transition-delay value, which - like
   .holding-card above - applies to every one of .stage-card's own
   transitioning properties, not just opacity, so its hover lift kept
   inheriting the entrance stagger indefinitely. Same specificity as
   those shared rules, so this has to sit after them (it does, being
   this far down the file) to actually win; scoped 3-value lists (same
   transform/box-shadow/opacity order as .stage-card's own transition
   above) zero the delay for hover's transform/box-shadow and keep it
   only on the entrance opacity fade. */
.stage-card:nth-child(1) { transition-delay: 0s, 0s, 0s; }
.stage-card:nth-child(2) { transition-delay: 0s, 0s, 0.05s; }
.stage-card:nth-child(3) { transition-delay: 0s, 0s, 0.1s; }
.stage-card:nth-child(n+4) { transition-delay: 0s, 0s, 0.15s; }
@media (max-width: 899px) {
  .stage-card:nth-child(2) { transition-delay: 0s, 0s, 0.04s; }
  .stage-card:nth-child(3) { transition-delay: 0s, 0s, 0.07s; }
  .stage-card:nth-child(n+4) { transition-delay: 0s, 0s, 0.1s; }
}
.stage-card:hover { transform: translateY(-6px); box-shadow: var(--shadow-card-hover); }
.stage-card__number { font-family: var(--font-display); font-weight: 900; font-size: 34px; color: var(--color-green); opacity: 0.4; line-height: 1; }
@media (min-width: 900px) { .stage-card__number { font-size: 40px; } }
.stage-card h3 { font-family: var(--font-display); font-weight: 800; font-size: 20px; color: var(--color-ink); }
.stage-card p { font-size: 14px; line-height: 1.6; color: var(--color-ink); opacity: 0.75; }

/* ============================================
   SUBPAGE: PHILOSOPHY FOLD
   ============================================ */
.philosophy-fold { position: relative; overflow: hidden; background: #2f343a; padding: 48px 20px; display: flex; flex-direction: column; gap: 40px; align-items: center; box-shadow: 0 24px 72px rgba(0, 0, 0, 0.14); }
@media (min-width: 900px) { .philosophy-fold { flex-direction: row; padding: 80px; gap: 64px; align-items: center; } }
.philosophy-fold__image { position: relative; width: 100%; max-width: 520px; height: 260px; border-radius: var(--radius-2xl); overflow: hidden; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.07), 0 24px 56px rgba(0, 0, 0, 0.15); flex-shrink: 0; z-index: 1; }
@media (min-width: 900px) { .philosophy-fold__image { height: 400px; width: 520px; } }
.philosophy-fold__image img { width: 100%; height: 100%; object-fit: cover; transform: scale(1.015); transition: transform var(--motion-image) var(--motion-ease-enter); }
.philosophy-fold__image.reveal-surface.is-visible img { transform: scale(1); }
.philosophy-fold__image::after { content: ""; position: absolute; inset: 0; background: rgba(33, 38, 45, 0.22); }
.philosophy-fold__content { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: flex-start; gap: 24px; color: var(--color-white); }
.philosophy-fold__actions { display: flex; flex-wrap: wrap; gap: 12px; }
@media (min-width: 900px) { .philosophy-fold__content { gap: 32px; flex: 1; } }
.philosophy-fold__content h2 { font-family: var(--font-display); font-weight: 800; font-size: 28px; line-height: 1.2; }
@media (min-width: 900px) { .philosophy-fold__content h2 { font-size: 44px; } }
.philosophy-fold__content p { font-size: 15px; line-height: 1.7; opacity: 0.8; }
@media (min-width: 900px) { .philosophy-fold__content p { font-size: 16px; } }

.tag--pillars { padding: 8px 16px; }

/* ============================================
   IMAGE PLACEHOLDER (temporary, pending real asset swap)
   ============================================ */
[data-placeholder="true"] { position: relative; background: linear-gradient(135deg, #3ca169 0%, #21262d 65%, #161a1f 100%); }
[data-placeholder="true"]::before { content: "Photo pending"; position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-family: var(--font-body); font-size: 12px; font-weight: 600; letter-spacing: 0.5px; color: rgba(255, 255, 255, 0.45); text-transform: uppercase; }
.page-header__bg[data-placeholder="true"] { position: absolute; left: 0; right: 0; top: -16px; bottom: -16px; z-index: 0; }

/* ============================================
   SUBPAGE: ACTIVE HOLDINGS GRID
   ============================================
   Reusable card-level hover system. Every state change below is
   transform / opacity / color only (never width, height, margin or
   position offsets), so the grid tracks never resize and neighbouring
   cards never move when one card is hovered. scale(1.008) grows a card a
   fraction of a percent past its own footprint; the 20-28px grid gap
   absorbs that with room to spare.

   Two card treatments share the same shell (.holding-card):
   - image cards (1/2/4/5/6): image -> depth (gradient/scrim) -> supporting
     content, revealed on hover.
   - the dark card (3, .holding-card--dark): typography -> depth (glow /
     shadow) -> CTA nudge. No image, no content-reveal panel.

   :hover is gated behind (hover: hover) and (pointer: fine) so touch
   devices never get a "stuck" hover state; :active covers the tap
   itself and :focus-visible covers keyboard use, both mirroring the
   hover treatment so every input method gets the same feedback. */
.holdings-showcase { position: relative; background: var(--color-bg-soft); padding: 48px var(--container-pad) 80px; }
.holdings-showcase .section-heading { max-width: 720px; margin-left: auto; margin-right: auto; text-align: center; align-items: center; }
.holdings-showcase__cta { display: flex; justify-content: center; margin-top: 40px; }
/* Phase 5: a plain dark section for the future-direction blocks that replaced
   the old placeholder project cards. */
.future-direction { position: relative; background: var(--color-dark); padding-block: 64px; text-align: center; }
.future-direction .section-heading { max-width: 720px; margin-inline: auto; align-items: center; text-align: center; }

.holdings-grid { display: grid; grid-template-columns: 1fr; gap: 20px; width: 100%; }
@media (min-width: 600px) { .holdings-grid { grid-template-columns: repeat(2, 1fr); gap: 24px; } }
@media (min-width: 900px) { .holdings-grid { grid-template-columns: repeat(3, 1fr); gap: 28px; } }
/* Phase 5: the homepage now features a single project. Left in the
   three-column grid it would sit as a lone third; this keeps it centred at
   a feature size instead, at every width. */
.holdings-grid--single { grid-template-columns: 1fr; max-width: 640px; margin-inline: auto; }
@media (min-width: 600px) { .holdings-grid--single { grid-template-columns: 1fr; } }
@media (min-width: 900px) { .holdings-grid--single { grid-template-columns: 1fr; } }
/* GRID composition reveal: the whole holdings area is treated as one
   curated editorial composition, not a card-by-card cascade - the grid
   establishes as one unit (container-level translateY(16px)+opacity,
   ~700ms, own values rather than the shared .reveal-grid defaults),
   then its cards get a small capped stagger by group (0/70/120ms max) -
   never independent per-card directional entrances. */
.holdings-grid.reveal-grid {
  opacity: 0.35;
  transform: translateY(16px);
  transition: opacity 700ms var(--ease), transform 700ms var(--ease);
}
/* Same specificity as the rule above, so this has to be its own explicit
   reset rather than relying on the generic .reveal-grid.is-visible (it
   sits earlier in the file, so on a same-specificity tie it would lose
   to the unconditional resting-state rule above and the grid would get
   stuck at 0.35 opacity forever). */
.holdings-grid.reveal-grid.is-visible { opacity: 1; transform: translateY(0); }
.holdings-grid.reveal-grid .holding-card { opacity: 0; }
.holdings-grid.reveal-grid.is-visible .holding-card { opacity: 1; }
/* MOT-03: .holding-card's own transition lists 4 properties, in this
   order - transform, box-shadow, border-color, opacity (see .holding-card
   below). transition-delay with a single value applies (cyclically) to
   every one of them, so a plain "transition-delay: 0.07s" here delayed
   hover/focus/press feedback by the same amount as the entrance fade,
   even long after the grid had finished revealing. A 4-value list keeps
   the stagger on entrance opacity only and zeroes it for the rest. */
.holdings-grid.reveal-grid.is-visible .holding-card:nth-child(1) { transition-delay: 0s, 0s, 0s, 0s; }
.holdings-grid.reveal-grid.is-visible .holding-card:nth-child(2) { transition-delay: 0s, 0s, 0s, 0.07s; }
.holdings-grid.reveal-grid.is-visible .holding-card:nth-child(n+3) { transition-delay: 0s, 0s, 0s, 0.12s; }

.holding-card {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-xl);
  overflow: hidden;
  border: 1px solid var(--color-border-soft);
  background: var(--color-dark);
  color: inherit;
  text-decoration: none;
  transform: translateY(0) scale(1);
  transition: transform var(--motion-interface) var(--motion-ease-interface), box-shadow var(--motion-interface) var(--motion-ease-interface), border-color var(--motion-interface) var(--motion-ease-interface), opacity var(--motion-content) var(--ease);
  box-shadow: 0 16px 32px rgba(33, 38, 45, 0.04);
}
a.holding-card, .holding-card[tabindex] { cursor: pointer; }

/* Restrained hover: no card lift, no dramatic shadow bump - the image's
   own scale(1.02) zoom (below) and the content panel's reveal already
   carry the interaction, so the card itself only needs a quiet
   border/shadow acknowledgement. */
@media (hover: hover) and (pointer: fine) {
  .holding-card:hover { box-shadow: 0 20px 34px -14px rgba(33, 38, 45, 0.18), 0 0 0 1px rgba(var(--color-green-rgb), 0.14); border-color: rgba(var(--color-green-rgb), 0.35); z-index: 2; }
}
.holding-card:focus-visible { outline: none; box-shadow: 0 20px 34px -14px rgba(33, 38, 45, 0.18), 0 0 0 2px var(--color-green); border-color: rgba(var(--color-green-rgb), 0.35); z-index: 2; }
.holding-card:active { box-shadow: 0 16px 26px -14px rgba(33, 38, 45, 0.16), 0 0 0 1px rgba(var(--color-green-rgb), 0.14); border-color: rgba(var(--color-green-rgb), 0.35); z-index: 2; }

/* ---- Image cards ---- */
.holding-card__media { position: absolute; inset: 0; overflow: hidden; border-radius: inherit; }
/* The image keeps settling slightly after the card itself has landed
   (see .reveal-stagger for the card-level arrival) - a heavier, slower
   photographic settle layered under the card's own quicker entrance,
   same "text stops, photography keeps a little more weight" idea used
   for the pathway/gallery imagery elsewhere on the site. .is-visible is
   the same class the card's own .reveal-stagger reveal adds, so this
   needs no separate trigger. Hover afterward gets its own much faster
   interface-speed override so it doesn't inherit the slow entrance
   transition. */
.holding-card__media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transform: scale(1.018); transition: transform var(--motion-image) var(--motion-ease-enter); }
.holdings-grid.reveal-grid.is-visible .holding-card__media img { transform: scale(1); }
.holdings-grid.reveal-grid.is-visible .holding-card:hover .holding-card__media img,
.holdings-grid.reveal-grid.is-visible .holding-card:focus-visible .holding-card__media img,
.holdings-grid.reveal-grid.is-visible .holding-card:active .holding-card__media img {
  transform: scale(1.02);
  transition: transform var(--motion-interface) var(--motion-ease-interface);
}

.holding-card__scrim { position: absolute; inset: 0; background: rgba(33, 38, 45, 0.18); pointer-events: none; }
.holding-card__gradient { position: absolute; inset: 0; pointer-events: none; background: linear-gradient(180deg, rgba(22, 25, 30, 0) 0%, rgba(22, 25, 30, 0) 50%, rgba(22, 25, 30, 0.44) 75%, rgba(22, 25, 30, 0.9) 100%); }
.holding-card__gradient--hover { opacity: 0; pointer-events: none; transition: opacity var(--motion-interface) var(--motion-ease-interface); background: linear-gradient(180deg, rgba(22, 25, 30, 0) 0%, rgba(22, 25, 30, 0.18) 42%, rgba(22, 25, 30, 0.72) 74%, rgba(22, 25, 30, 0.96) 100%); }
.holding-card:hover .holding-card__gradient--hover, .holding-card:focus-visible .holding-card__gradient--hover, .holding-card:active .holding-card__gradient--hover { opacity: 1; }

.holding-card__index { position: absolute; top: 15px; right: 15px; z-index: 2; display: inline-flex; align-items: center; padding: 6px 10px; border-radius: 100px; background: rgba(0, 0, 0, 0.4); border: 1px solid rgba(255, 255, 255, 0.15); color: var(--color-white); font-weight: 700; font-size: 15px; transition: background var(--motion-interface) var(--motion-ease-interface), border-color var(--motion-interface) var(--motion-ease-interface); }
.holding-card:hover .holding-card__index, .holding-card:focus-visible .holding-card__index, .holding-card:active .holding-card__index { background: rgba(var(--color-green-rgb), 0.65); border-color: rgba(var(--color-green-rgb), 0.7); }

/* HG-VR-07: name/location need to be readable at rest, not just on
   hover/focus - this is the only place a card identifies itself, and
   touch devices have no hover state to reveal it at all. The base
   .holding-card__gradient already darkens the bottom of the image for
   contrast regardless of hover, so content is legible here without
   waiting on .holding-card__gradient--hover's extra darkening. */
.holding-card__content { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; padding: 22px; display: flex; flex-direction: column; align-items: flex-start; gap: 6px; opacity: 1; transform: translateY(0); }
.holding-card__content .pill { background: rgba(255, 255, 255, 0.14); border-color: rgba(255, 255, 255, 0.22); color: var(--color-white); }
.holding-card__title { font-family: var(--font-display); font-weight: 800; font-size: 17px; color: var(--color-white); line-height: 1.25; }
.holding-card__location { font-weight: 600; font-size: 12px; color: rgba(255, 255, 255, 0.75); }

/* ---- Dark / typography card ---- */
.holding-card--dark {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px; padding: 28px; text-align: center;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.031) 0%, rgba(255, 255, 255, 0) 100%),
    linear-gradient(90deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0.04) 100%),
    linear-gradient(90deg, rgba(11, 15, 20, 0.92) 0%, rgba(11, 15, 20, 0.92) 100%);
  border: 1px solid rgba(255, 255, 255, 0.15);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
}
.holding-card--dark:hover, .holding-card--dark:focus-visible, .holding-card--dark:active { box-shadow: 0 0 26px rgba(var(--color-green-rgb), 0.18), 0 20px 36px -14px rgba(0, 0, 0, 0.4); }
.holding-card__top-row { position: absolute; top: 22px; left: 22px; right: 22px; display: flex; align-items: center; justify-content: space-between; z-index: 2; }
.holding-card--dark .holding-card__index { position: static; }
.holding-card__cta { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 100px; background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.18); color: var(--color-white); font-weight: 700; font-size: 12px; opacity: 0.55; transition: transform var(--motion-interface) var(--motion-ease-interface), opacity var(--motion-interface) var(--motion-ease-interface), background var(--motion-interface) var(--motion-ease-interface); }
.holding-card__cta-arrow { display: inline-block; transition: transform var(--motion-interface) var(--motion-ease-interface); }
.holding-card--dark:hover .holding-card__cta, .holding-card--dark:focus-visible .holding-card__cta, .holding-card--dark:active .holding-card__cta { transform: translateX(4px); background: rgba(var(--color-green-rgb), 0.22); opacity: 0.85; }
.holding-card--dark:hover .holding-card__cta-arrow, .holding-card--dark:focus-visible .holding-card__cta-arrow, .holding-card--dark:active .holding-card__cta-arrow { transform: translate(4px, -4px); }
.holding-card__eyebrow { font-family: var(--font-body); font-weight: 700; font-size: 11px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--color-green); }
.holding-card--dark .holding-card__title { font-size: 24px; color: var(--color-white); }
.holding-card--dark .holding-card__body { font-size: 14px; line-height: 1.7; color: rgba(255, 255, 255, 0.8); }

/* ---- Reduced motion: keep a static hover/focus cue (colour + shadow
   change only), drop every transform, zoom and content-slide. ---- */
@media (prefers-reduced-motion: reduce) {
  .holding-card, .holding-card__media img, .holding-card__gradient--hover, .holding-card__index, .holding-card__cta, .holding-card__cta-arrow {
    transition-duration: 0.01ms !important;
  }
  .holding-card:hover, .holding-card:focus-visible, .holding-card:active { transform: none; }
  .holding-card:hover .holding-card__media img, .holding-card:focus-visible .holding-card__media img, .holding-card:active .holding-card__media img { transform: none; }
  .holding-card--dark:hover .holding-card__cta, .holding-card--dark:focus-visible .holding-card__cta, .holding-card--dark:active .holding-card__cta,
  .holding-card--dark:hover .holding-card__cta-arrow, .holding-card--dark:focus-visible .holding-card__cta-arrow, .holding-card--dark:active .holding-card__cta-arrow { transform: none; }
}

/* HG-P3-05: static cards (Herbigives Center, Res Resiliency, the three
   "Coming Soon" cards) have no real destination - they're no longer
   tabindex'd or wrapped in an <a>, so cursor: pointer already reverts on
   its own (see "a.holding-card, .holding-card[tabindex]" above). This
   block also cancels every :hover/:focus-visible/:active "this is
   clickable" cue - shadow/border pop, image zoom, gradient darken, index
   badge recolor, cta pill slide - so the card is inert but still shows its
   photo, status pill and copy exactly as it does at rest. */
.holding-card--static:hover,
.holding-card--static:focus-visible,
.holding-card--static:active {
  box-shadow: 0 16px 32px rgba(33, 38, 45, 0.04);
  border-color: var(--color-border-soft);
  transform: translateY(0) scale(1);
}
.holding-card--static.holding-card--dark:hover,
.holding-card--static.holding-card--dark:focus-visible,
.holding-card--static.holding-card--dark:active {
  box-shadow: 0 16px 32px rgba(33, 38, 45, 0.04);
  border-color: rgba(255, 255, 255, 0.15);
}
.holdings-grid.reveal-grid.is-visible .holding-card--static:hover .holding-card__media img,
.holdings-grid.reveal-grid.is-visible .holding-card--static:focus-visible .holding-card__media img,
.holdings-grid.reveal-grid.is-visible .holding-card--static:active .holding-card__media img { transform: scale(1); }
.holding-card--static:hover .holding-card__gradient--hover,
.holding-card--static:focus-visible .holding-card__gradient--hover,
.holding-card--static:active .holding-card__gradient--hover { opacity: 0; }
.holding-card--static:hover .holding-card__index,
.holding-card--static:focus-visible .holding-card__index,
.holding-card--static:active .holding-card__index { background: rgba(0, 0, 0, 0.4); border-color: rgba(255, 255, 255, 0.15); }
.holding-card--static:hover .holding-card__cta,
.holding-card--static:focus-visible .holding-card__cta,
.holding-card--static:active .holding-card__cta { transform: none; background: rgba(255, 255, 255, 0.08); opacity: 0.55; }

/* ============================================
   HOMEPAGE: ABOUT US STAGE
   ============================================ */
.about { background: var(--color-bg-soft); }

.about-stage { display: flex; flex-direction: column; gap: 24px; position: relative; z-index: 1; }
@media (min-width: 900px) { .about-stage { flex-direction: row; align-items: center; } }

.about-stage__image { position: relative; border-radius: var(--radius-xl); overflow: hidden; aspect-ratio: 520 / 480; box-shadow: var(--shadow-card); }
@media (min-width: 900px) { .about-stage__image { flex: 0 0 44%; max-width: 520px; aspect-ratio: 520 / 620; } }
/* Image+text split: image gets a small SURFACE entrance (own values,
   scoped here rather than the shared .reveal-surface defaults), its own
   <img> settles with a restrained scale independently - text gets its
   own two-stage EDITORIAL reveal (headline, then body a beat later)
   instead of one plain fade on the whole card, so the sequence reads as
   image leads -> headline follows -> body settles, not three things
   arriving at once. Complementary motion, not opposing directions. */
/* MOT-02: mobile-first now, not a desktop-only unconditional rule with a
   mobile override earlier in the file trying (and losing, same
   specificity, later-in-file wins) to redirect it - the horizontal
   entrance is scoped to >=900px directly, so mobile just uses the base
   vertical one with nothing to override. */
.about-stage__image.reveal-surface {
  opacity: 0.6;
  transform: translateY(16px);
  transition: opacity 760ms var(--ease), transform 760ms var(--ease);
}
@media (min-width: 900px) {
  .about-stage__image.reveal-surface { transform: translateX(-28px); }
}
.about-stage__image.reveal-surface.is-visible { opacity: 1; transform: translateX(0) translateY(0); }
.about-stage__image img { width: 100%; height: 100%; object-fit: cover; object-position: 55% 45%; display: block; transform: scale(1.015); transition: transform var(--motion-image) var(--motion-ease-enter); }
.about-stage__image.is-visible img { transform: scale(1); }
/* .about-stage__card keeps the shared .reveal class only so it still
   gets observed on the standard editorial trigger tier - the wrapper
   itself never visibly moves; h3/p below carry the actual sequenced
   motion once .is-visible lands on it. */
.about-stage__card.reveal { opacity: 1; transform: none; }
.about-stage__card { display: flex; flex-direction: column; gap: 16px; }
@media (min-width: 900px) { .about-stage__card { flex: 1; padding: 40px; } }
.about-stage__card h3 {
  font-family: var(--font-display); font-weight: 900; font-size: 24px; line-height: 1.15; color: var(--color-ink);
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--motion-editorial) var(--ease) 0.09s, transform var(--motion-editorial) var(--ease) 0.09s;
}
.about-stage__card.reveal.is-visible h3 { opacity: 1; transform: translateY(0); }
@media (min-width: 900px) { .about-stage__card h3 { font-size: 32px; } }
.about-stage__card p {
  font-size: 15px; line-height: 1.75; color: var(--color-ink);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity var(--motion-copy) var(--ease) 0.15s, transform var(--motion-copy) var(--ease) 0.15s;
}
.about-stage__card.reveal.is-visible p { opacity: 0.7; transform: translateY(0); }

/* ============================================
   SUBPAGE: JOINT VENTURES SPLIT
   ============================================ */
.jv-section { position: relative; background: var(--color-white); padding: 48px var(--container-pad) 80px; overflow: hidden; }
.jv-split { display: flex; flex-direction: column; gap: 48px; position: relative; z-index: 1; }
@media (min-width: 900px) { .jv-split { flex-direction: row; align-items: flex-start; gap: 32px; } }
.jv-panel { display: flex; flex-direction: column; gap: 24px; }
@media (min-width: 900px) { .jv-panel { gap: 32px; width: 560px; flex-shrink: 0; } }
.jv-panel h2 { font-family: var(--font-display); font-weight: 800; font-size: 26px; color: var(--color-ink); }
@media (min-width: 900px) { .jv-panel h2 { font-size: 32px; } }
.jv-panel > p { font-size: 14px; line-height: 1.65; color: var(--color-ink); opacity: 0.75; }
@media (min-width: 900px) { .jv-panel > p { font-size: 15px; } }
.jv-criteria { display: flex; flex-direction: column; gap: 16px; }
.jv-criteria__title { font-weight: 700; font-size: 14px; color: var(--color-green); }
.jv-criteria__item { display: flex; align-items: center; gap: 12px; }
.jv-criteria__item svg { flex-shrink: 0; color: var(--color-green); }
.jv-criteria__item span { font-size: 14px; color: var(--color-ink); opacity: 0.8; }

.jv-form-card { position: relative; flex: 1; min-width: 0; background: #0f172a; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: var(--radius-2xl); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); padding: 28px 24px; display: flex; flex-direction: column; gap: 20px; overflow: hidden; box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2), 0 18px 48px rgba(0, 0, 0, 0.4); }
@media (min-width: 900px) { .jv-form-card { padding: 40px; gap: 24px; } }
.jv-form-card .accent-bar { box-shadow: 0 0 10px rgba(60, 161, 105, 0.4); }
.jv-form-card__heading { display: flex; flex-direction: column; gap: 8px; position: relative; z-index: 1; }
/* Small resolve once the panel (the primary surface entrance) has mostly
   settled - mirrors .inquiry-form h3 exactly. */
.jv-form-card__heading h3 { font-family: var(--font-display); font-weight: 800; font-size: 22px; color: #f8fafc; opacity: 0; transform: translateY(8px); transition: opacity var(--motion-fast) var(--ease) 0.1s, transform var(--motion-fast) var(--ease) 0.1s; }
.jv-form-card.is-visible .jv-form-card__heading h3 { opacity: 1; transform: translateY(0); }
@media (min-width: 900px) { .jv-form-card__heading h3 { font-size: 24px; } }
.jv-form-card__heading p { font-size: 14px; color: #cbd5e1; opacity: 0.9; }
/* Very subtle internal field motion - the panel itself is the primary
   (surface) entrance; fields get one quiet opacity resolve riding along
   with it, not N independently-staggered entrances. Mirrors .form-field. */
.jv-field {
  display: flex; flex-direction: column; gap: 8px; position: relative; z-index: 1;
  opacity: 0;
  transition: opacity var(--motion-fast) var(--ease) 0.14s;
}
.jv-form-card.is-visible .jv-field { opacity: 1; }
.jv-field__label { font-weight: 700; font-size: 11px; letter-spacing: 1px; text-transform: uppercase; color: var(--color-green); }
.jv-field input { width: 100%; background: rgba(255, 255, 255, 0.05); border: 1px solid var(--form-control-border); border-radius: 10px; padding: 16px; color: #f8fafc; font-family: var(--font-body); font-size: 14px; transition: border-color 0.25s var(--ease), background 0.25s var(--ease); }
.jv-field input::placeholder { color: var(--form-placeholder); opacity: 1; }
.jv-field input:focus { outline: none; border-color: var(--color-green); background: rgba(255, 255, 255, 0.08); }
/* HG-P3-02: per-field validation state, set/cleared by JS in initJVForm(). */
.jv-field input.is-invalid { border-color: #d9695f; }
.jv-field__error { font-size: 12px; font-weight: 600; color: #ef8b83; min-height: 15px; }
/* Honeypot field: present for bots/scripts to fill in, hidden from sighted
   and assistive-tech users via the standard visually-hidden pattern (not
   display:none/visibility:hidden, which some bots skip filling in). */
.jv-field--hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
.jv-form-card .btn--primary { width: 100%; justify-content: center; position: relative; z-index: 1; box-shadow: 0 10px 14px rgba(60, 161, 105, 0.31); }
.jv-form-card .btn--primary:disabled { opacity: 0.7; cursor: not-allowed; }
.jv-panel__actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 4px; }
.jv-form-card__note { font-size: 13px; color: var(--color-green); min-height: 18px; font-weight: 600; position: relative; z-index: 1; }
.jv-form-card__note.is-error { color: #ef8b83; }
.jv-form-card__note.is-success { color: var(--color-green); }
.jv-form-card__note.is-pending { color: #cbd5e1; }

/* A11Y-02: keyboard focus must stay recognisable independently of validity.
   The field :focus rules above set outline: none, and the invalid rules paint
   the same red border whether or not the field is focused - so a keyboard
   user in an error state had only an 8%-vs-5% fill change to go on. These are
   declared last, after every one of those rules, so nothing can override
   them, and they use the surface-aware ring token: 6.4:1 against the contact
   field fill and 8:1 against the joint-venture one, well clear of the 3:1
   this project requires, and a different hue from the red error border.
   Fields sit 24-40px inside their card's padding, so the offset ring is
   never clipped by the card's own overflow. */
.form-field input:focus-visible,
.form-field textarea:focus-visible,
.jv-field input:focus-visible {
  outline: 2px solid var(--focus-ring, var(--color-green-on-dark));
  outline-offset: 3px;
}

/* ============================================
   SUBPAGE: HOUSING HIGHLIGHTS (housing-projects)
   ============================================ */
.housing-highlights { position: relative; background: var(--color-white); padding: 48px var(--container-pad) 80px; display: flex; flex-direction: column; gap: 48px; }

.feature-split { display: flex; flex-direction: column; gap: 32px; align-items: center; }
@media (min-width: 900px) { .feature-split { flex-direction: row; gap: 48px; align-items: center; } }
.feature-split__image { position: relative; width: 100%; height: 320px; border-radius: var(--radius-2xl); overflow: hidden; flex-shrink: 0; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.07), 0 24px 56px rgba(0, 0, 0, 0.15); }
/* HG-VR-08: the image was a flat 560px at every desktop width, so at
   900-1024px (tablet landscape) the copy column was squeezed down to
   ~130-260px - a wrapped-to-3-lines heading, a narrow ribbon of body
   copy, and the metrics stacking instead of sitting in a row. Sizing
   both columns off the same 560:672 ratio the design uses at 1440px
   (the site's reference/widest width - see .gallery-main/.gallery-col
   for the same technique) keeps that exact composition at 1440px and
   lets both columns shrink together, proportionally, below it, instead
   of the image staying fixed while the content column absorbs the
   entire loss. aspect-ratio replaces the flat 520px height so the image
   doesn't get cropped taller and taller as its width comes down. */
@media (min-width: 900px) {
  .feature-split__image { width: auto; height: auto; aspect-ratio: 560 / 520; flex: 5 1 0%; flex-shrink: 1; max-width: 560px; min-width: 0; }
}
.feature-split__image img { width: 100%; height: 100%; object-fit: cover; }
.feature-split__image::after { content: ""; position: absolute; inset: 0; background: rgba(33, 38, 45, 0.18); }
.feature-split__content { display: flex; flex-direction: column; gap: 20px; flex: 1; min-width: 0; }
@media (min-width: 900px) { .feature-split__content { gap: 24px; flex: 6 1 0%; } }
.feature-split__content h2 { font-family: var(--font-display); font-weight: 800; font-size: 24px; color: var(--color-ink); }
@media (min-width: 900px) { .feature-split__content h2 { font-size: 32px; } }
.feature-split__content > p { font-size: 14px; line-height: 1.6; color: var(--color-ink); opacity: 0.75; }
@media (min-width: 900px) { .feature-split__content > p { font-size: 15px; } }

.metrics-row { display: flex; flex-wrap: wrap; gap: 16px; }
.metric-card { flex: 1; min-width: 140px; position: relative; overflow: hidden; background: var(--color-white); border: 1px solid var(--color-border-soft); border-radius: 15px; padding: 20px; display: flex; flex-direction: column; gap: 8px; box-shadow: 0 16px 16px rgba(33, 38, 45, 0.04); }
/* Coordinated stat reveal: divider, then number, then label - a tight
   internal sequence per card, plus a slightly longer stagger card-to-card.
   The card itself doesn't fade as a block (that would fight the sequence
   below it); .reveal-stagger only supplies the "has this scrolled into
   view" trigger via .is-visible. */
.metric-card.reveal-stagger, .metric-card.reveal-stagger.is-visible { opacity: 1; transform: none; transition: none; }
.metric-card::before { content: ""; position: absolute; top: 0; left: 20px; right: 20px; height: 2px; background: var(--color-green); transform: scaleX(0); transform-origin: left; transition: transform 0.5s var(--motion-ease-premium); }
.metric-card.is-visible::before { transform: scaleX(1); }
.metric-card__value { font-family: var(--font-display); font-weight: 800; font-size: 22px; color: var(--color-green); opacity: 0; transform: translateY(var(--motion-distance-small)); transition: opacity 0.45s var(--motion-ease-premium), transform 0.45s var(--motion-ease-premium); }
.metric-card.is-visible .metric-card__value { opacity: 1; transform: translateY(0); }
.metric-card__label { font-size: 12px; font-weight: 600; color: var(--color-ink); opacity: 0; transform: translateY(var(--motion-distance-small)); transition: opacity 0.45s var(--motion-ease-premium), transform 0.45s var(--motion-ease-premium); }
.metric-card.is-visible .metric-card__label { opacity: 1; transform: translateY(0); }
/* Structural sequence within each card (rule -> value -> descriptor) and
   a tiny card-to-card stagger, both kept to 50-70ms steps rather than
   compounding into a long cascade - metrics should read as immediately
   present, not as something arriving one line at a time. */
.metric-card:nth-child(1)::before { transition-delay: 0s; }
.metric-card:nth-child(1) .metric-card__value { transition-delay: 0.06s; }
.metric-card:nth-child(1) .metric-card__label { transition-delay: 0.12s; }
.metric-card:nth-child(2)::before { transition-delay: 0.05s; }
.metric-card:nth-child(2) .metric-card__value { transition-delay: 0.11s; }
.metric-card:nth-child(2) .metric-card__label { transition-delay: 0.17s; }
.metric-card:nth-child(3)::before { transition-delay: 0.1s; }
.metric-card:nth-child(3) .metric-card__value { transition-delay: 0.16s; }
.metric-card:nth-child(3) .metric-card__label { transition-delay: 0.22s; }

.grant-block { position: relative; overflow: hidden; background: var(--color-dark); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 36px; padding: 32px 24px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.07), 0 8px 20px rgba(0, 0, 0, 0.09), 0 28px 60px rgba(0, 0, 0, 0.26); }
@media (min-width: 900px) { .grant-block { padding: 40px; } }
.grant-block__row { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 24px; }
@media (min-width: 900px) { .grant-block__row { flex-direction: row; align-items: center; justify-content: space-between; gap: 40px; } }
.grant-block__copy { display: flex; flex-direction: column; gap: 20px; color: var(--color-white); }
@media (min-width: 900px) { .grant-block__copy { gap: 24px; max-width: 620px; } }
.grant-block__copy h3 { font-family: var(--font-display); font-weight: 800; font-size: 22px; }
@media (min-width: 900px) { .grant-block__copy h3 { font-size: 24px; } }
.grant-block__copy p { font-size: 14px; line-height: 1.65; opacity: 0.8; }
@media (min-width: 900px) { .grant-block__copy p { font-size: 15px; } }
.grant-block__cta { display: flex; justify-content: flex-start; }
@media (min-width: 900px) { .grant-block__cta { justify-content: flex-end; flex-shrink: 0; } }

/* ============================================
   SUBPAGE: FORTITUDE ARIZONA CASE STUDY
   ============================================ */
.breadcrumbs { background: #30353b; border-bottom: 1px solid rgba(255, 255, 255, 0.4); padding: calc(var(--nav-height) + 24px) var(--container-pad) 12px; display: flex; gap: 8px; align-items: center; font-size: 13px; }
.breadcrumbs a { color: #cfcfcf; font-weight: 600; text-decoration: none; transition: color 0.25s var(--ease); }
.breadcrumbs a:hover { color: var(--color-white); }
.breadcrumbs .sep { color: #cfcfcf; font-weight: 600; }
.breadcrumbs .current { color: var(--color-green-on-dark); font-weight: 700; }

/* Same reasoning as .page-header above - no downward box-shadow, since
   this sits flush against the white content that follows, not above it. */
.case-hero { position: relative; background: var(--color-dark); padding: 48px var(--container-pad) 56px; margin-bottom: 0; display: flex; flex-direction: column; gap: 32px; overflow: hidden; }
@media (min-width: 900px) { .case-hero { padding: 48px 80px 72px; gap: 48px; } }
.case-hero__intro { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: flex-start; gap: 20px; color: var(--color-white); max-width: 760px; }
@media (min-width: 900px) { .case-hero__intro { gap: 24px; } }
.case-hero__intro h1 { font-family: var(--font-display); font-weight: 900; font-size: 32px; line-height: 1.1; overflow: hidden; }
@media (min-width: 900px) { .case-hero__intro h1 { font-size: 56px; } }
.case-hero__intro h1 > span { display: inline-block; transform: translateY(28px); transition: transform 0.8s var(--motion-ease-premium) 0.28s; }
.case-hero.is-loaded .case-hero__intro h1 > span { transform: translateY(0); }
.case-hero__intro .tag { opacity: 0; transform: translateY(var(--motion-distance-small)); transition: opacity 0.6s var(--motion-ease-premium) 0.15s, transform 0.6s var(--motion-ease-premium) 0.15s; }
.case-hero.is-loaded .case-hero__intro .tag { opacity: 1; transform: translateY(0); }
.case-hero__intro p { font-size: 15px; line-height: 1.65; opacity: 0; transform: translateY(var(--motion-distance-small)); transition: opacity 0.65s var(--motion-ease-premium) 0.45s, transform 0.65s var(--motion-ease-premium) 0.45s; }
.case-hero.is-loaded .case-hero__intro p { opacity: 0.75; transform: translateY(0); }
@media (min-width: 900px) { .case-hero__intro p { font-size: 18px; } }
/* MOT-08: same fix and same reasoning as .page-header's mobile override
   above - shortened text entrance + a fast, barely-there image settle, so
   the case-study headline and summary are readable almost immediately on
   mobile instead of ~1.1-1.25s in. Desktop keeps the original timing.
   Placed after every base rule it overrides, for the same specificity-tie
   reason as elsewhere in this file. */
@media (max-width: 899px) {
  .case-hero__intro h1 > span {
    transform: translateY(10px);
    transition: transform 0.3s var(--motion-ease-premium) 0.04s;
  }
  .case-hero__intro .tag {
    transform: translateY(6px);
    transition: opacity 0.25s var(--motion-ease-premium), transform 0.25s var(--motion-ease-premium);
  }
  .case-hero__intro p {
    transform: translateY(6px);
    transition: opacity 0.35s var(--motion-ease-premium) 0.08s, transform 0.35s var(--motion-ease-premium) 0.08s;
  }
}
.case-hero__media { position: relative; z-index: 1; width: 100%; height: 260px; border-radius: var(--radius-2xl); overflow: hidden; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.07), 0 24px 56px rgba(0, 0, 0, 0.15); }
@media (min-width: 900px) { .case-hero__media { height: 640px; } }
.case-hero__media img { width: 100%; height: 100%; object-fit: cover; transform: scale(1.025); transition: transform 1.15s var(--motion-ease-premium) 0.1s; }
.case-hero.is-loaded .case-hero__media img { transform: scale(1); }
@media (max-width: 899px) {
  .case-hero__media img { transform: scale(1.01); transition: transform 0.4s var(--motion-ease-premium); }
}
.case-hero__media::after { content: ""; position: absolute; inset: 0; background: rgba(33, 38, 45, 0.28); }
.case-meta-bar { position: relative; z-index: 1; display: flex; flex-wrap: wrap; gap: 20px; background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.14); border-radius: var(--radius-2xl); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); padding: 24px; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08), 0 24px 56px rgba(0, 0, 0, 0.15); }
@media (min-width: 900px) { .case-meta-bar { padding: 32px; gap: 32px; flex-wrap: nowrap; } }
.case-meta-col { flex: 1 1 140px; display: flex; flex-direction: column; gap: 8px; }
.case-meta-col__label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; color: #cfcfcf; }
.case-meta-col__value { font-size: 16px; font-weight: 800; color: var(--color-white); font-family: var(--font-body); }
@media (min-width: 900px) { .case-meta-col__value { font-size: 18px; } }
.status-badge { display: inline-flex; align-items: center; align-self: flex-start; padding: 4px 12px; border-radius: 100px; background: #ffffb3; color: var(--color-amber-ink); font-size: 11px; font-weight: 800; }
@media (min-width: 900px) { .status-badge { font-size: 12px; } }

.case-deep-dive { position: relative; background: var(--color-bg-soft); padding: 48px var(--container-pad); display: flex; flex-direction: column; gap: 40px; box-shadow: 0 16px 24px rgba(0, 0, 0, 0.06); }
/* Row layout needs a fixed 560px narrative PLUS a legible feature-cards
   column to coexist - that needs roughly 560 + 56 gap + ~360 minimum
   card width + 160 side padding =~1136px. Switching to the row at the
   generic 900px breakpoint left the row active for ~250px of viewport
   width where there wasn't room for both: .case-narrative's
   flex-shrink:0 never gave up its 560px, so .feature-cards absorbed the
   entire shortfall - past a point that's overflow, not just cramped
   text. Reusing the existing stacked (column) layout up to 1150px,
   same as it already works below 900px, means both children get their
   full natural width instead of fighting over space that doesn't
   exist yet. 1280/1440px (and up) are unaffected - same row layout as
   before, same 560px narrative. */
@media (min-width: 1150px) { .case-deep-dive { flex-direction: row; gap: 56px; padding: 80px; align-items: flex-start; } }
.case-narrative { display: flex; flex-direction: column; gap: 24px; }
@media (min-width: 1150px) { .case-narrative { max-width: 560px; flex-shrink: 0; } }
.case-narrative__eyebrow { display: flex; flex-direction: column; gap: 16px; }
.case-narrative__eyebrow span { font-family: var(--font-display); font-weight: 700; font-size: 12px; letter-spacing: 2px; text-transform: uppercase; color: var(--color-green-ink); }
.case-narrative__eyebrow h2 { font-family: var(--font-display); font-weight: 800; font-size: 26px; line-height: 1.15; color: var(--color-ink); }
@media (min-width: 900px) { .case-narrative__eyebrow h2 { font-size: 40px; line-height: 1.05; } }
.case-narrative p { font-size: 15px; line-height: 1.65; color: var(--color-ink); opacity: 0.82; }
@media (min-width: 900px) { .case-narrative p { font-size: 16px; } }

.feature-cards { display: flex; flex-direction: column; gap: 24px; }
@media (min-width: 900px) { .feature-cards { gap: 36px; } }
/* GRID composition reveal (same family/treatment as .holdings-grid /
   .timeline-row) - the group establishes as one unit, then each card
   gets a small capped stagger, not independently-directional entrances. */
.feature-cards.reveal-grid .feature-card { opacity: 0; }
.feature-cards.reveal-grid.is-visible .feature-card { opacity: 1; }
.feature-cards.reveal-grid.is-visible .feature-card:nth-child(1) { transition-delay: 0s; }
.feature-cards.reveal-grid.is-visible .feature-card:nth-child(2) { transition-delay: 0.05s; }
.feature-cards.reveal-grid.is-visible .feature-card:nth-child(3) { transition-delay: 0.1s; }
.feature-cards.reveal-grid.is-visible .feature-card:nth-child(n+4) { transition-delay: 0.15s; }
.feature-card { position: relative; background: var(--color-white); border: 1px solid var(--color-border-soft); border-radius: var(--radius-2xl); padding: 24px; display: flex; flex-direction: column; gap: 16px; overflow: hidden; box-shadow: 0 16px 32px rgba(33, 38, 45, 0.04); transition: opacity var(--motion-content) var(--ease); }
.feature-card::before { content: ""; position: absolute; left: 0; top: 0; width: 6px; height: 140px; background: var(--color-green); }
.feature-card__index { display: inline-flex; align-items: center; justify-content: center; padding: 4px 12px; border-radius: 6px; background: var(--color-green-tint); color: var(--color-green); font-family: var(--font-display); font-weight: 700; font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; align-self: flex-start; }
.feature-card h3 { font-family: var(--font-display); font-weight: 900; font-size: 24px; line-height: 1.15; color: var(--color-ink); }
@media (min-width: 900px) { .feature-card h3 { font-size: 28px; } }
.feature-card__category { font-family: var(--font-display); font-weight: 700; font-size: 11px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--color-green); }
.feature-card__image { width: 100%; height: 200px; border-radius: var(--radius-lg); overflow: hidden; }
.feature-card__image img { width: 100%; height: 100%; object-fit: cover; }
.feature-card__desc { font-size: 14px; line-height: 1.75; color: var(--color-ink); opacity: 0.7; }
@media (min-width: 900px) { .feature-card__desc { font-size: 15px; } }

.gallery-section { position: relative; background: var(--color-dark); padding: 48px var(--container-pad) 64px; display: flex; flex-direction: column; gap: 32px; box-shadow: 0 24px 72px rgba(0, 0, 0, 0.14); overflow: hidden; }
@media (min-width: 900px) { .gallery-section { padding: 80px; gap: 48px; } }
.gallery-header { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: flex-start; gap: 12px; color: var(--color-white); max-width: 800px; }
.gallery-header h2 { font-family: var(--font-display); font-weight: 800; font-size: 24px; }
@media (min-width: 900px) { .gallery-header h2 { font-size: 32px; } }
.gallery-header p { font-size: 14px; line-height: 1.6; opacity: 0.8; }
@media (min-width: 900px) { .gallery-header p { font-size: 16px; } }

.gallery-row { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 16px; }
@media (min-width: 900px) { .gallery-row { flex-direction: row; gap: 24px; } }
.gallery-main { position: relative; width: 100%; height: 260px; border-radius: var(--radius-2xl); overflow: hidden; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.07), 0 24px 56px rgba(0, 0, 0, 0.15); flex-shrink: 0; }
/* Fluid 1.53:1 split (main:col), not a fixed 760px main - a fixed width
   plus flex-shrink:0 on .gallery-main left .gallery-col (flex:1) to
   absorb 100% of the shortfall as the row narrows, so it hit 0px well
   before tablet widths. 1.53:1 reproduces the original 760/496 split
   exactly at the 1256px-available 1440px reference width (the design
   this was built at), then scales both sides down together below that,
   so the composition's proportions - not just its pixel widths - stay
   intact all the way to 900px instead of one side starving the other.
   max-width keeps .gallery-main from growing past its original 760px
   on wider-than-1440px screens; .gallery-col simply gets the extra. */
@media (min-width: 900px) { .gallery-main { width: auto; height: 420px; flex: 1.53 1 0%; flex-shrink: 1; max-width: 760px; min-width: 0; } }
.gallery-main img { width: 100%; height: 100%; object-fit: cover; }
.gallery-main::after { content: ""; position: absolute; inset: 0; background: rgba(33, 38, 45, 0.28); }
.gallery-col { display: flex; flex-direction: column; gap: 16px; flex: 1; min-width: 0; }
@media (min-width: 900px) { .gallery-col { gap: 24px; } }
.gallery-card { position: relative; width: 100%; height: 190px; border-radius: var(--radius-lg); overflow: hidden; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.03), 0 12px 24px rgba(0, 0, 0, 0.08); }
.gallery-card img { width: 100%; height: 100%; object-fit: cover; }
.gallery-card::after { content: ""; position: absolute; inset: 0; background: rgba(33, 38, 45, 0.22); }
.gallery-row--triple { gap: 16px; }
.gallery-row--triple .gallery-card { width: 100%; height: 260px; }
@media (min-width: 900px) { .gallery-row--triple { gap: 10px; } .gallery-row--triple .gallery-card { flex: 1; height: 420px; } }

/* ---- Photo grid: composition reveal (GRID family) ----
   The row is treated as ONE visual surface, not a photo-1/2/3/4 cascade:
   a thin structural rule draws first (the "dark section" language: a
   line establishing before the imagery does), then the whole row settles
   as a single translateY+opacity unit - initPhotoGrid() in script.js adds
   .is-visible to the .photo-grid row (and its preceding .gallery-rule)
   from one IntersectionObserver hit, and CSS does the rest. Every image
   in the row gets the same restrained scale-settle, with only a very
   small (<=80ms) relational stagger between them - no per-tile directional
   travel, no opposing clip-paths, no continuous parallax. The photography
   itself is the visual interest; motion just helps it arrive. */
.gallery-rule { position: relative; z-index: 1; height: 1px; background: var(--glass-border); transform: scaleX(0); transform-origin: left; transition: transform var(--motion-interface) var(--motion-ease-enter); }
.gallery-rule.is-visible { transform: scaleX(1); }

.photo-grid { opacity: 0.4; transform: translateY(var(--motion-distance-medium)); transition: opacity var(--motion-content) var(--ease), transform var(--motion-content) var(--ease); }
.photo-grid.is-visible { opacity: 1; transform: translateY(0); }

.photo-grid .gallery-main img,
.photo-grid .gallery-card img {
  transform: scale(1.015);
  transition: transform var(--motion-image) var(--motion-ease-enter);
}
.photo-grid.is-visible .gallery-main img,
.photo-grid.is-visible .gallery-card img {
  transform: scale(1);
}
/* Small relational stagger only - a second/third tile settles a beat
   after the first, capped well under 150ms total. */
.photo-grid.is-visible .gallery-col .gallery-card:nth-child(2) img,
.photo-grid.is-visible.gallery-row--triple > .gallery-card:nth-child(2) img {
  transition-delay: 0.05s;
}
.photo-grid.is-visible.gallery-row--triple > .gallery-card:nth-child(3) img {
  transition-delay: 0.08s;
}

/* Restrained desktop hover, once settled: image only, no tile movement,
   no shadow/border/text piling on at the same time. Overriding duration/
   easing here is safe - only transform changes on hover, so this just
   gives that one change interface-speed timing instead of the
   (already-finished) entrance's slower one. */
@media (min-width: 900px) {
  .photo-grid.is-visible .gallery-main img,
  .photo-grid.is-visible .gallery-card img {
    transition-duration: var(--motion-interface);
    transition-timing-function: var(--motion-ease-interface);
    transition-delay: 0s;
  }
  .photo-grid.is-visible .gallery-main:hover img,
  .photo-grid.is-visible .gallery-card:hover img {
    transform: scale(1.015);
  }
}

.case-cta { position: relative; background: var(--color-bg-soft); padding: 48px var(--container-pad); display: flex; flex-direction: column; gap: 20px; align-items: center; text-align: center; box-shadow: 0 16px 48px rgba(0, 0, 0, 0.06); }
@media (min-width: 900px) { .case-cta { padding: 100px 80px; gap: 32px; } }
.case-cta h2 { font-family: var(--font-display); font-weight: 900; font-size: 26px; color: var(--color-ink); }
@media (min-width: 900px) { .case-cta h2 { font-size: 36px; } }
.case-cta > p { font-size: 15px; color: #656d76; max-width: 720px; }
@media (min-width: 900px) { .case-cta > p { font-size: 16px; } }
.case-cta__actions { display: flex; flex-wrap: wrap; gap: 16px; justify-content: center; }
@media (min-width: 900px) { .case-cta__actions { gap: 20px; } }

.btn--outline-dark { background: var(--color-white); color: var(--color-ink); border: 1.5px solid var(--color-ink); box-shadow: 0 8px 10px rgba(0, 0, 0, 0.06); }
.btn--outline-dark:hover { background: var(--color-bg-soft); transform: translateY(-2px); }

/* ============================================================
   PAGE TRANSITION SYSTEM
   Header (#navbar) is never touched by this — it stays a stable,
   persistent frame. #route-content is the region that moves.

   First-load intro (once per browser session, on an actual page load
   or refresh — see transitions.js). #route-content sits fully in its
   final resting position underneath the opaque overlay the whole time
   (only opacity is touched, and only a very small settle distance, not
   the ~100-200px rise this used to do) — because the overlay is fully
   opaque and covers it completely, there is nothing to gain visually
   from also moving the content underneath, and every extra pixel of
   travel is one more thing that can still be moving when the overlay
   clears. The overlay's own fade-out (see transitions.js's runIntro —
   it removes hg-intro-pending and starts the overlay fade in the same
   tick, no added delay between them) is what actually reveals it: brand
   mark → surface clears → hero already there, completing only its own
   small internal settle (image scale, masked headline — see
   .page-header/.case-hero/.hero below). That's what keeps this from
   reading as "loader, THEN a separate second animation."

   Every internal nav link (navigate() in transitions.js) instead plays
   a brief exit conceal (.is-leaving below) then swaps #route-content's
   innerHTML directly — no overlay, no branded moment. See navigate(). */
#route-content {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

/* Deliberately no transition on either of these two rules: while
   hg-intro-pending is present #route-content is fully hidden behind the
   opaque overlay anyway, so there is nothing to gain by fading it in -
   and fading it WOULD cost something, because that fade would run
   concurrently with the overlay's own fade-out and the two would blend
   into exactly the washed-out double-exposure this system used to have.
   Instead this flips instantly, in the same tick runIntro() (see
   transitions.js) removes hg-intro-pending - invisibly, since the
   overlay is still fully opaque at that exact instant - so that when the
   overlay's fade-out (its own, separate transition) reveals what's
   underneath, it reveals an already-crisp, already-settled hero. Any
   perceived "settling" from here on is the hero's own choreography
   (image scale, masked headline - see .hero/.page-header/.case-hero),
   which already started the moment the page's scripts ran and simply
   continues past the reveal, rather than something re-started by it. */
html.hg-intro-pending #route-content {
  opacity: 0;
  transform: translate3d(0, var(--motion-distance-sm), 0);
}

/* Internal-navigation exit conceal: just enough to register that
   something is happening, never long enough to feel like a wait (see
   EXIT_MS in transitions.js). This transition only exists on the way
   out — is-leaving is removed the instant the swap happens, and with no
   transition defined on the base #route-content-without-is-leaving
   state at that moment, the recovery is an instant, unseen snap-back
   (concealed behind the swap itself), not a second visible animation. */
#route-content.is-leaving {
  transition: opacity var(--motion-micro) var(--motion-ease-exit), transform var(--motion-micro) var(--motion-ease-exit);
  opacity: 0.94;
  transform: translate3d(0, -6px, 0);
}

.page-transition-overlay {
  position: fixed;
  inset: 0;
  z-index: 110;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #F4F5F2;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
.page-transition-overlay.is-visible { opacity: 1; pointer-events: all; }

/* Dark surfaces get the lighter ring; everything else inherits the ink one. */
.mobile-menu,
.footer,
.inquiry-form,
.jv-form-card,
.navbar.menu-open,
[data-navbar-theme="dark"] {
  --focus-ring: var(--color-green-on-dark);
}
.page-transition-overlay__logo { width: 364px; max-width: 77vw; aspect-ratio: 1280 / 720; }
@media (min-width: 900px) { .page-transition-overlay__logo { width: 448px; } }
.page-transition-overlay__logo svg { display: block; width: 100%; height: 100%; }

@media (prefers-reduced-motion: reduce) {
  #route-content,
  html.hg-intro-pending #route-content {
    opacity: 1 !important;
    transform: none !important;
  }
  /* .is-leaving still has a real transition to neutralize (it's the one
     internal-navigation animation that isn't already gated by the
     sitewide transition-duration:0.01ms override having something else
     override it back) - collapse it to a plain instant conceal instead. */
  #route-content.is-leaving {
    transition: none !important;
    opacity: 0.94;
    transform: none !important;
  }
  .page-transition-overlay { display: none !important; }

  /* Belt-and-suspenders alongside the sitewide transition-duration:0.01ms
     rule above: the internal-page hero entrance (.page-header/.case-hero)
     starts several children hidden/offset pending a class the JS always
     adds regardless of motion preference - this guarantees they render
     in their final, fully visible position outright rather than relying
     on a near-zero transition to get there. */
  .page-header__bg img,
  .case-hero__media img,
  .page-header__copy h1 > span,
  .case-hero__intro h1 > span {
    transform: none !important;
  }
  .page-header__copy .tag,
  .page-header__sub,
  .case-hero__intro .tag {
    opacity: 1 !important;
    transform: none !important;
  }
  .case-hero__intro p {
    opacity: 0.75 !important;
    transform: none !important;
  }

  /* Same belt-and-suspenders guarantee for the homepage hero's own
     entrance family (badge / glass-panel slide-in / actions) and the
     photo-grid + holding-card entrances below. */
  .hero .badge,
  .hero .hero__actions,
  .headline-card,
  .hero__headline,
  .hero__media img {
    opacity: 1 !important;
    transform: none !important;
  }
  .hero__sub {
    opacity: 0.78 !important;
    transform: none !important;
  }
  .photo-grid .gallery-main img,
  .photo-grid .gallery-card img {
    clip-path: none !important;
    transform: none !important;
    opacity: 1 !important;
  }
  .gallery-rule { transform: scaleX(1) !important; }
  .holding-card__media img { transform: none !important; }
  .gallery-main:hover img, .gallery-card:hover img { transform: none !important; }
}
