/* ============================================================================
   SITE CHROME — shared styles
   ============================================================================
   Single source of truth for everything that looks and behaves the same on
   every page: fonts, design tokens, the base reset, the sticky header, the
   full-screen menu overlay, the bottom-sheet/drawer system, pill buttons,
   the footer, the scroll-reveal animation, and the cursor-trail canvas.

   Every page (index.html, case-study-template.html, and every
   generated case study) links this file instead of inlining these rules —
   edit it once here and every page picks up the change on next reload, no
   per-file propagation needed.

   What does NOT live here: anything page-type-specific (.home-hero,
   .programs-list, .story, .augments-card, .impact-card, etc.) and the
   responsive @media overrides, which stay in each page's own <style> block
   since they're tightly interleaved with that page's specific layout and
   aren't worth the risk of splitting across two files. ---------------------------------------------------------------- */

/* ---------- Fonts ----------
   PP Neue Montreal (Pangram Pangram), self-hosted from a personal-use
   license, converted from the OTFs provided to WOFF2 (roughly a third the
   file size) and living in ./fonts next to each page (../fonts from here).
   The family doesn't ship a 500/Medium cut; any font-weight:500 anywhere on
   the site falls back to Regular (400) automatically per the browser's
   normal font-matching behavior, so nothing extra is needed for that. */
@font-face {
  font-family: "PP Neue Montreal";
  src: url("../fonts/PPNeueMontreal-Light.woff2") format("woff2");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "PP Neue Montreal";
  src: url("../fonts/PPNeueMontreal-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "PP Neue Montreal";
  src: url("../fonts/PPNeueMontreal-Italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "PP Neue Montreal";
  src: url("../fonts/PPNeueMontreal-Semibold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "PP Neue Montreal";
  src: url("../fonts/PPNeueMontreal-Extrabold.woff2") format("woff2");
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

/* ---------- Design tokens ---------- */
:root {
  --bg: #f8f8f7;
  --ink: #23212C;
  --muted: #5e5855;
  --muted-2: #8a8481;
  --white: #ffffff;
  --line: rgba(35, 33, 44, 0.1);
  --line-soft: rgba(35, 33, 44, 0.06);
  /* Stable references that never get locally overridden — used by the
     dark-zone / permanently-dark-footer math so those swaps don't
     self-reference the same --bg/--ink they're redefining. */
  --ink-solid: #23212C;
  --bg-solid: #f8f8f7;
  --radius: 16px;
  --radius-lg: 24px;
  /* --font-serif points at the same PP Neue Montreal family as --font-sans
     (it used to be Fraunces, a real serif) — every rule that reads
     --font-serif also sets font-weight: 800 (Extrabold, the one bold cut
     registered above), so "serif" now means "Neue Montreal at Extra Bold"
     everywhere on the site. */
  --font-serif: "PP Neue Montreal", "Inter", sans-serif;
  --font-sans: "PP Neue Montreal", "Inter", sans-serif;
  /* Fluid side padding that floors at 16px and only starts growing once
     the viewport exceeds a 1244px content cap. Raised to a 30px floor at
     desktop widths (>900px, same breakpoint the page-level @media rules
     use for their mobile layouts) -- below 1244px wide the calc() side
     goes negative and max() was falling all the way back to the mobile
     16px floor even on a desktop monitor with a merely-average browser
     window, which read as too tight. Mobile keeps the original 16px. */
  --gutter: max(1rem, calc((100vw - 1244px) / 2));
  --gutter-width: 1rem; /* column-gap within the 10-col grid */
  --columns: 10;
  --ease: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
}

@media (min-width: 901px) {
  :root { --gutter: max(30px, calc((100vw - 1244px) / 2)); }
}

/* ---------- Base reset ---------- */
* { box-sizing: border-box; }
/* overflow-x: hidden here is what stops the whole page from panning
   sideways on a touch-drag. Nothing on the page is meant to be wider than
   the viewport, but the fixed-width hero-carousel assets (51.25rem, not
   vw-scaled, see .hero-carousel .asset below) and similar fixed-size rows
   can round up a fraction of a pixel wider than the viewport on some
   screens. Without this, that sliver of overflow makes the whole document
   horizontally scrollable, and a touch-drag anywhere pans the entire
   layout instead of just the carousel it landed on. The carousels
   themselves are unaffected: they scroll via their own overflow-x: auto
   on a bounded container, not the page's. */
html { scroll-behavior: smooth; overflow-x: hidden; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
img { max-width: 100%; display: block; }
button { font-family: inherit; }
a { color: inherit; }

/* ---------- Header ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: 1.5rem var(--gutter) 1.25rem;
  background: var(--bg);
  transform: translateY(0);
  transition: transform 320ms var(--ease), background 250ms var(--ease);
}
/* Hides on scroll-down, slides back down on scroll-up — see the
   scroll-direction JS in site-chrome.js. Disabled while the menu is open
   so the close button never scrolls out of reach. */
.site-header.is-hidden { transform: translateY(-100%); }
/* Single inline SVG mark, colored via currentColor — so the same markup
   serves both "logo" and "reverse logo" duties. It just inherits
   .brand-mark's color, which flips to white in menu-open / dark-nav state,
   so there's no need to swap in a second file for dark backgrounds. */
.brand-mark {
  display: flex;
  align-items: center;
  color: var(--ink);
  text-decoration: none;
  transition: color 250ms var(--ease);
}
.brand-mark svg {
  width: 60px;
  height: auto;
  display: block;
  shape-rendering: geometricPrecision;
}
.menu-toggle {
  background: none;
  border: 0;
  cursor: pointer;
  padding: 0 0.5rem 1.5rem 0.5rem;
  display: flex;
  align-items: flex-start;
  justify-content: center;
}
.menu-toggle svg { width: 22px; height: 22px; display: block; }
.menu-toggle svg line {
  stroke: var(--ink);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 350ms var(--ease), stroke 250ms var(--ease);
}
/* Sticky header re-themes to match the dark zone once its background has
   swapped to #23212C beneath it (homepage only: Case Studies → Approach →
   Footer). .site-header lives outside the .dark-zone wrapper on the pages
   that have one, so it can't just inherit the --bg/--ink swap descendants
   get for free — it's toggled separately via a class on <body>, added by
   the same IntersectionObserver, so header and page background cross-fade
   together whichever direction you scroll. On pages with no #darkZone
   element (every case study page) this class is simply never added, so
   these rules are inert there. */
body.is-dark-nav .site-header { background: var(--ink-solid); }
body.is-dark-nav .brand-mark { color: var(--white); }
body.is-dark-nav .menu-toggle svg line { stroke: var(--white); }
/* Hamburger morphs into an X and the header goes transparent (logo/icon
   turn white) while the full-screen menu is open — takes priority over
   the dark-nav state above regardless of scroll position. */
body.menu-open .site-header { background: transparent; }
body.menu-open .brand-mark { color: var(--white); }
body.menu-open .menu-toggle svg line { stroke: var(--white); }
body.menu-open .menu-toggle svg line:first-child { transform: translateY(3.5px) rotate(45deg); }
body.menu-open .menu-toggle svg line:last-child { transform: translateY(-3.5px) rotate(-45deg); }

/* ---------- Full-screen menu overlay ---------- */
.menu-overlay {
  position: fixed;
  inset: 0;
  z-index: 15;
  background: var(--ink);
  color: var(--white);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 11rem var(--gutter) 3rem;
  /* Slides up from the bottom edge instead of down from the top, moving
     fast-in/fast-out rather than a flat 550ms, per
     "drawers: 200-500ms" + asymmetric enter/exit. */
  transform: translateY(100%);
  transition: transform 480ms var(--ease-drawer);
  pointer-events: none;
}
.menu-overlay.is-open { transform: translateY(0); pointer-events: auto; }
/* Nav links + "Work with us" row are the centered block — vertically
   centered on the page via justify-content:center above, still
   left-aligned with the logo since they share the same --gutter inset. */
.menu-links { display: flex; flex-direction: column; margin-bottom: 2.5rem; }
.menu-links a {
  font-family: var(--font-serif);
  font-weight: 800;
  font-size: clamp(2.25rem, 6vw, 4.25rem);
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--white);
  text-decoration: none;
  /* Each link fades + rises into place after the panel arrives — the
     "nested" feel: the container moves, then its contents settle in,
     staggered 40-50ms apart. Closing skips the stagger (fast exit). */
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 420ms var(--ease-out), transform 420ms var(--ease-out), color 200ms var(--ease);
}
.menu-links a:hover { color: #F1FEC8; }
.menu-overlay.is-open .menu-links a { opacity: 1; transform: translateY(0); }
.menu-overlay.is-open .menu-links a:nth-child(1) { transition-delay: 160ms; }
.menu-overlay.is-open .menu-links a:nth-child(2) { transition-delay: 205ms; }
.menu-overlay.is-open .menu-links a:nth-child(3) { transition-delay: 250ms; }
.menu-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1.75rem;
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 420ms var(--ease-out), transform 420ms var(--ease-out);
}
.menu-overlay.is-open .menu-row { opacity: 1; transform: translateY(0); transition-delay: 300ms; }
.menu-row .pill-btn.is-invert { background: var(--white); color: var(--ink); border-color: var(--white); }
.menu-row .pill-btn.is-invert:hover { background: transparent; color: var(--white); }
.menu-row a:not(.pill-btn) { color: var(--white); opacity: 0.7; text-decoration: none; font-size: 0.95rem; }
.menu-row a:not(.pill-btn):hover { opacity: 1; }
/* Pinned to the bottom of the overlay independently of the centered nav
   block above — pulled out of the flex flow so it doesn't get centered
   along with .menu-links/.menu-row. */
.menu-foot {
  position: absolute;
  left: var(--gutter);
  right: var(--gutter);
  bottom: 3rem;
  display: flex;
  justify-content: flex-end;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 2rem;
}
.menu-social { display: flex; gap: 1.5rem; font-size: 0.85rem; }
.menu-social a { color: var(--white); opacity: 0.75; text-decoration: none; }
.menu-social a:hover { opacity: 1; }
@media (max-width: 560px) {
  .menu-overlay { padding: 10rem 1.25rem 2rem; }
  .menu-foot { left: 1.25rem; right: 1.25rem; bottom: 2rem; flex-direction: column; align-items: flex-start; }
}

/* ---------- Pill buttons ---------- */
.pill-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.85rem 1.5rem;
  border-radius: 96px;
  border: 1px solid var(--line);
  background: transparent;
  color: inherit;
  font-size: 0.875rem;
  text-decoration: none;
  cursor: pointer;
  transition: background 200ms var(--ease), color 200ms var(--ease);
}
.pill-btn:hover { background: var(--ink); color: var(--white); }
.pill-btn.is-solid { background: var(--ink); color: var(--white); border-color: var(--ink); }
.pill-btn.is-solid:hover { background: transparent; color: var(--ink); }
.pill-btn.is-invert { background: var(--white); color: var(--ink); border-color: var(--white); }
.pill-btn.is-invert:hover { background: transparent; color: var(--white); }

/* ---------- Bottom sheet / drawer system ----------
   Same component used everywhere a "read the full story" pattern shows up:
   case study teasers, homepage competency drawers, the Approach section.
   Page-specific dialogs (their id, and the copy inside them) live in each
   page's own HTML — only the shared shell styling lives here. */
dialog.sheet {
  position: fixed;
  inset: 20px 0 0;
  width: 100%;
  height: calc(100% - 20px);
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 24px 24px 0 0;
  background: var(--white);
  color: var(--ink);
  overflow-y: auto;
  transform: translateY(100%);
  transition: transform 420ms var(--ease);
}
dialog.sheet::backdrop { background: rgba(35,33,44,0.35); }
dialog.sheet[open] { transform: translateY(0); }
.sheet-close {
  position: sticky;
  top: 1.25rem;
  float: right;
  margin-right: 1.25rem;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  background: var(--bg);
  border: 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  z-index: 5;
  outline: none;
}
.sheet-close:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
/* The close glyph is an inline SVG, not the "✕" character it used to be:
   that character renders via the system's color-emoji font on macOS/iOS
   regardless of any CSS color rule, which is why it was showing up blue
   instead of the page's ink color. currentColor on an SVG stroke doesn't
   have that problem. */
.sheet-close svg { width: 1.15rem; height: 1.15rem; display: block; }

/* Back caret inside the "Case Study" eyebrow on case-study pages. Plain
   history.back() (wired in initBehavior() below) rather than a hardcoded
   href, so it always returns to wherever the visitor actually came from. */
.eyebrow-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
}
.eyebrow-back svg { width: 1.1rem; height: 1.1rem; display: block; }
.eyebrow-back:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 4px; }
.sheet-inner { padding: 0 var(--gutter) 6rem; clear: both; }
.sheet-eyebrow {
  font-family: var(--font-sans);
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 0.75rem;
}
/* Used for the gallery/augments dialog titles on case study pages only —
   harmless unused CSS on the homepage, same as .sheet-eyebrow being
   unused on case study pages above. */
.sheet-title {
  font-family: var(--font-serif);
  font-weight: 800;
  font-size: clamp(2rem, 4vw, 2.75rem);
  margin: 0 0 2.5rem;
  padding-top: 1rem;
}
/* Centers the read column in the drawer rather than flush-left against
   .sheet-inner's own padding. */
.story-sheet-content { max-width: 44rem; margin: 0 auto; }
.story-lead {
  font-family: var(--font-serif);
  font-weight: 800;
  font-size: clamp(1.75rem, 3vw, 2.25rem);
  line-height: 1.05;
  color: var(--ink);
  margin: 0 0 3rem;
}
.story-rest p {
  font-family: var(--font-sans);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: rgba(35, 33, 44, 0.72);
  margin: 0 0 1.5rem;
}
.story-rest p:last-child { margin-bottom: 0; }

/* ---------- Scroll reveal ----------
   Generic fade + rise-in for section headers, rows and cards as they enter
   the viewport. Only animates transform + opacity (GPU-cheap, no layout
   thrash). Stagger an item by giving it style="--i:1" (2), etc. — see the
   JS observer in site-chrome.js, which adds .is-visible once and stops
   watching (no re-trigger on scrolling back up). */
.reveal {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 700ms var(--ease-out), transform 700ms var(--ease-out);
  transition-delay: calc(var(--i, 0) * 70ms);
}
.reveal.is-visible { opacity: 1; transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  /* Keep opacity transitions (aids comprehension), just drop the staggered
     delays and slide/rise distances down to near-nothing — the menu still
     needs its transform to show/hide, so we shrink the motion rather than
     removing transform outright. */
  .menu-links a, .menu-row, .reveal { transform: none !important; }
  .menu-overlay, .menu-links a, .menu-row, .reveal {
    transition-duration: 150ms !important;
    transition-delay: 0ms !important;
  }
}

/* ---------- Footer (permanently dark — same visual family as the
   full-screen menu overlay: big bold nav links + a "Contact Michael" pill,
   just scaled down to footer height instead of full-screen). Hardcodes the
   dark-zone token swap directly on .site-footer (rather than relying on
   the scroll-triggered .dark-zone/.is-active class), so the footer reads
   dark on every page load, independent of scroll position. ---------- */
.site-footer {
  padding: 5rem var(--gutter) 2.5rem;
  --bg: var(--ink-solid);
  --ink: var(--bg-solid);
  --muted: rgba(248, 248, 247, 0.6);
  --muted-2: rgba(248, 248, 247, 0.4);
  --line: rgba(248, 248, 247, 0.16);
  --line-soft: rgba(248, 248, 247, 0.08);
  background: var(--bg);
  color: var(--ink);
  /* Hairline that reads correctly whether the section right above the
     footer is light or dark, since it's a fixed color rather than a
     --line token that would otherwise flip with the footer's own
     dark-zone override. */
  border-top: 1px solid #6C6393;
}
/* Brand mark reused verbatim from the header (same component, same
   currentColor trick) -- .site-footer already redefines --ink to the
   light bg-solid tone for its dark background, so this renders white
   with zero extra color rules, same as the header does in menu-open /
   dark-nav state. Left-aligned by default (flex, no justify-content
   override) at the same 60px size as the header -- not the full-bleed
   scale it reads at in the menu overlay. Margin-bottom matches
   .footer-nav's own (2.5rem) so the logo-to-links gap equals the existing
   links-to-Contact-button gap, per spec, rather than guessing a number. */
.footer-brand { margin-bottom: 2.5rem; }
.footer-nav {
  display: flex;
  flex-direction: column;
  margin-bottom: 2.5rem;
}
.footer-nav a {
  font-family: var(--font-serif);
  font-weight: 800;
  font-size: clamp(1.75rem, 4.4vw, 2.75rem);
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
  transition: opacity 200ms var(--ease);
}
.footer-nav a:hover { color: #F1FEC8; }
.footer-cta { margin-bottom: 3.5rem; }
/* .site-footer redefines --ink to the light bg-solid tone (so body text
   reads light-on-dark), but the pill button's own is-invert rule uses
   --ink for ITS text color too, expecting the ORIGINAL dark ink for
   text-on-white-pill. Pin it back to the real dark ink explicitly so the
   button doesn't render white-on-white. */
.footer-cta .pill-btn.is-invert { color: var(--ink-solid); }
/* The rule above pins the button's resting text color to dark ink so it
   reads correctly on the white pill, but its equal specificity plus later
   source position also overrode .pill-btn.is-invert:hover's color, which
   was supposed to flip to white when the background goes transparent on
   hover, revealing the dark footer behind it. That left dark-on-dark text
   invisible on hover. Restore the intended hover color explicitly here. */
.footer-cta .pill-btn.is-invert:hover { color: var(--white); }
.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 1.5rem;
  padding-top: 1.75rem;
  border-top: 1px solid #6C6393;
  font-size: 0.82rem;
  color: var(--muted);
}
.footer-links, .footer-social { display: flex; gap: 1.25rem; }
.footer-links a, .footer-social a { text-decoration: none; color: var(--muted); transition: color 200ms var(--ease); }
.footer-social a:hover { color: var(--ink); }

/* ---------- Stardust cursor trail ----------
   #stardust-canvas sits above everything, transparent, pointer-events:none.
   mix-blend-mode:difference math-inverts whatever's underneath wherever a
   wisp is painted (text, photos, dark or light sections alike) — a true
   photo-negative, not a flat-color trail. */
#stardust-canvas {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 9999;
  pointer-events: none;
  mix-blend-mode: difference;
}
