/* ============================================================
   Phu Le - AI & Data Engineering Portfolio
   Hand-built design system. Dark-only. One flat file, no preprocessor.
   ------------------------------------------------------------
   TABLE OF CONTENTS  (grep a banner to jump to its section)
     Type         @font-face: Hanken Grotesk (display+body, primary) + Inter (subset fallback) + metrics-matched fallback
     Tokens       CSS CUSTOM PROPERTIES (Design Tokens)
     Reset        RESET & BASE STYLES
     Typography   TYPOGRAPHY
     Layout       LAYOUT UTILITIES
     Helpers      HELPERS (token-based utilities)
     Components   COMPONENTS  (.btn, .card, .tag, .notebook-content)
     A11y         ACCESSIBILITY  (reduced-motion, reveal, focus, skip-link)
     Responsive   RESPONSIVE BREAKPOINTS
     Pages        PAGE-SPECIFIC COMPONENTS  (header, footer, project, work, metrics, about)
     Mermaid      MERMAID DIAGRAMS
     Images       IMAGE COMPONENTS  (avatar, thumbnail)
     Hero         HOME HERO (typographic) + TELEMETRY SIGNATURE
     ProjectMeta  PROJECT META (detail pages)
     Craft        CRAFT LAYER  (selection, grain, scrollbar, underlines)
     ViewTrans    VIEW TRANSITIONS  (direction-aware page reveal)
     Cover        PROJECT COVER + SCROLL CUE
     Motion       MOTION CRAFT  (heading reveals)
     Print        PRINT  (ink-on-white override)
   ------------------------------------------------------------
   Conventions: BEM-ish component classes (.block__element) plus small
   token-based utilities (.flex, .gap-2) for layout only. See CLAUDE.md.
   ============================================================ */

/* ============================================
   SELF-HOSTED TYPE: Hanken Grotesk (display+body, primary, variable) + Inter (subset fallback for arrows/≥)
   Hanken Grotesk is the single typeface (Europa-cousin, even heights); Inter is a subset fallback for the few glyphs outside Hanken's range (← → ≥); mono carries code.
   ============================================ */
@font-face {
  font-family: "InterVariable";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/fonts/InterVariable-subset.woff2") format("woff2");
  unicode-range: U+2190, U+2192, U+2265;   /* the only glyphs Hanken lacks */
}
/* Metrics-matched fallback → identical metrics → zero CLS while Inter loads */
@font-face {
  font-family: "Inter Fallback";
  src: local("Arial");
  ascent-override: 90%;
  descent-override: 22.43%;
  line-gap-override: 0%;
  size-adjust: 107.4%;
}

/* Self-hosted Hanken Grotesk (variable, latin subset) - the display face. Chosen over
   Jost for its even, uniform letter heights (Europa's calm evenness, not Jost's quirky
   Futura forms). Inter is fallback-only (subset for ← → ≥); mono carries code. Fallback chain runs to
   the loaded Inter (not a system font) so a missing glyph or a failed load degrades
   gracefully; the latin unicode-range matches the self-hosted subset. */
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/fonts/HankenGrotesk-Variable.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ============================================
   CSS CUSTOM PROPERTIES (Design Tokens)
   ============================================ */
:root {
  color-scheme: dark;               /* single dark theme, declared so browsers / Chrome auto-dark don't guess */
  /* Colors - dark is the only theme (see CLAUDE.md: do not add prefers-color-scheme branches) */
  /* Accent ramp (a system, not one blue) */
  --color-accent: #0A84FF;          /* large figures / chrome */
  --color-accent-text: #4DA3FF;     /* small text/links on black - ≥4.5:1 */
  --color-accent-subtle: #0E2438;   /* deep tint: panel fill, active nav, focus halo */
  --color-accent-deep: #0063CC;     /* pressed */
  --color-primary: var(--color-accent);
  --color-primary-hover: #409CFF;

  /* Project category color - a curated triad keyed to the KIND of work (not per-project),
     so a new project just sets `category:` and inherits a hue. Tuned for the navy dark theme;
     brand blue stays the global UI accent (buttons/focus/transitions). See CLAUDE.md.
     --proj-accent/--proj-wash default to brand blue and cascade from a [data-category] wrapper. */
  --proj-data: #3DD6C0;  --proj-data-wash: rgba(61, 214, 192, 0.14);   /* Data Engineering */
  --proj-ml:   #A78BFA;  --proj-ml-wash:   rgba(167, 139, 250, 0.14);  /* Machine Learning */
  --proj-bi:   #F5B544;  --proj-bi-wash:   rgba(245, 181, 68, 0.14);   /* Analytics / BI */
  --proj-accent: var(--color-accent);
  --proj-wash: transparent;

  --color-text: #F5F5F7;
  --color-text-secondary: #A1A1A6;
  /* Cool-neutral elevation ladder */
  --color-bg: #0A0A0C;
  --color-surface: #161617;
  --color-surface-alt: #1C1C1E;
  --color-surface-raised: #242426;  /* surfaces genuinely lighten */
  --color-background: var(--color-bg);
  --color-border: #2E2E30;

  /* Typography - Hanken Grotesk is the single display+body face; Inter is a subset fallback; mono is the code voice */
  --font-display: "Hanken Grotesk", "InterVariable", "Inter Fallback", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;   /* Hanken Grotesk = display face (Europa-cousin, even heights); Inter is fallback only */
  --font-body:    "Hanken Grotesk", "InterVariable", "Inter Fallback", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;   /* unified: Hanken Grotesk is body too (one font, Europa-style); Inter is fallback only */
  --font-system:  var(--font-body); /* back-compat alias for existing usages */
  --font-mono: "SF Mono", "Monaco", "Inconsolata", "Fira Code", "Droid Sans Mono", "Source Code Pro", monospace;  /* code only (code/pre/mermaid) - labels + body use Hanken Grotesk now */
  --font-feature-figures: "tnum" 1, "zero" 1;
  --tracking-label: 0.12em;   /* one tracking value for uppercase mono eyebrows / labels */
  
  /* Type Scale */
  --text-xs: 0.75rem;    /* 12px */
  --text-sm: 0.875rem;   /* 14px */
  --text-base: 1rem;     /* 16px */
  --text-lg: 1.125rem;   /* 18px */
  --text-xl: 1.25rem;    /* 20px */
  --text-2xl: 1.5rem;    /* 24px */
  --text-3xl: 1.875rem;  /* 30px */
  --text-4xl: 2.25rem;   /* 36px */
  --text-5xl: 3rem;      /* 48px */
  
  /* Spacing - 8px Grid System */
  --space-1: 0.5rem;   /* 8px */
  --space-2: 1rem;     /* 16px */
  --space-3: 1.5rem;   /* 24px */
  --space-4: 2rem;     /* 32px */
  --space-5: 2.5rem;   /* 40px */
  --space-6: 3rem;     /* 48px */
  --space-7: 3.5rem;   /* 56px */
  --space-8: 4rem;     /* 64px */
  --space-12: 6rem;    /* 96px */

  /* Layout */
  --max-width: 1200px;
  --max-width-prose: 760px;
  --header-h: 6rem;      /* fixed-header height; covers cancel main's top padding with this */
  --border-radius: 12px;
  --border-radius-sm: 8px;
  --border-radius-xs: 4px;
  --border-radius-lg: 16px;
  
  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0,0,0,.4), 0 2px 6px rgba(0,0,0,.5);
  --shadow-md: 0 2px 4px rgba(0,0,0,.45), 0 8px 24px rgba(0,0,0,.55);
  --shadow-lg: 0 4px 8px rgba(0,0,0,.5), 0 18px 40px rgba(0,0,0,.6);
  --shadow-card: 0 1px 2px rgba(0,0,0,.4), 0 1px 1px rgba(0,0,0,.3);
  
  /* Transitions */
  --transition-fast: 150ms ease-out;
  --transition-base: 200ms ease-out;
  --transition-slow: 300ms ease-out;
  --ease-out-expo: cubic-bezier(0.2, 0.7, 0.2, 1);

  /* Vertical rhythm between major sections */
  --space-section: clamp(5.5rem, 10vw, 10rem);
}

/* Category -> hue. Set data-category on a project wrapper; --proj-accent/--proj-wash cascade
   to the inline cover/thumbnail SVG (currentColor) and the chrome. Unknown/missing -> brand blue. */
[data-category="data"] { --proj-accent: var(--proj-data); --proj-wash: var(--proj-data-wash); }
[data-category="ml"]   { --proj-accent: var(--proj-ml);   --proj-wash: var(--proj-ml-wash); }
[data-category="bi"]   { --proj-accent: var(--proj-bi);   --proj-wash: var(--proj-bi-wash); }

/* ============================================
   RESET & BASE STYLES
   ============================================ */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  font-family: var(--font-system);
  font-size: var(--text-base);
  line-height: 1.6;
  color: var(--color-text);
  background-color: var(--color-background);
  transition: background-color var(--transition-base), color var(--transition-base);
}

/* ============================================
   TYPOGRAPHY
   ============================================ */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-weight: 600;                  /* michellegore inversion: display-medium, not 700 */
  line-height: 1.13;
  margin-bottom: var(--space-2);
  color: var(--color-text);
  text-wrap: balance;
}

h1 { font-size: var(--text-5xl); letter-spacing: -0.04em; }
h2 { font-size: var(--text-4xl); letter-spacing: -0.02em; }
h3 { font-size: var(--text-3xl); letter-spacing: -0.01em; }
h4 { font-size: var(--text-2xl); letter-spacing: -0.005em; }
h5 { font-size: var(--text-xl); letter-spacing: -0.005em; }
h6 { font-size: var(--text-lg); letter-spacing: -0.005em; }

/* Tabular + slashed-zero figures for any numeric UI */
.tnum, .metric, .metric-figure, .metric-label, .tl-figure, .tl-unit {
  font-feature-settings: var(--font-feature-figures);
  font-variant-numeric: tabular-nums slashed-zero;
}

p {
  margin-bottom: var(--space-2);
  color: var(--color-text);
}

a {
  color: var(--color-accent-text);
  text-decoration: none;
  transition: color var(--transition-fast);
}

a:hover {
  color: var(--color-primary-hover);
}

code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background-color: var(--color-surface);
  padding: 0.2em 0.4em;
  border-radius: 4px;
}

pre {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  background-color: var(--color-surface);
  padding: var(--space-2);
  border-radius: var(--border-radius-sm);
  overflow-x: auto;
  margin-bottom: var(--space-2);
}

pre code {
  background: none;
  padding: 0;
}

/* ============================================
   LAYOUT UTILITIES
   ============================================ */
.container {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 clamp(1.25rem, 5vw, 1.5rem);   /* mobile gutter: 20-24px scaling; >=640px bumps to 32px (space-4) below */
}

.section {
  padding: var(--space-8) 0;
}

.section-sm {
  padding: clamp(3rem, 6vw, 5.5rem) 0;
}

/* Flexbox Utilities */
.flex {
  display: flex;
}

.items-center {
  align-items: center;
}

.justify-between {
  justify-content: space-between;
}

.gap-2 {
  gap: var(--space-2);
}

.gap-4 {
  gap: var(--space-4);
}

/* ============================================
   HELPERS (token-based utilities)
   ============================================ */
.text-center { text-align: center; }
.prose { max-width: var(--max-width-prose, 760px); margin-inline: auto; line-height: 1.7; text-wrap: pretty; }
.project-content p, .notebook-content p { line-height: 1.7; max-width: 70ch; }
.lede { font-size: var(--text-lg); color: var(--color-text-secondary); max-width: 60ch; }
.meta-text { color: var(--color-text-secondary); font-size: var(--text-sm); }

/* Connect-CTA heading + lead (used inside .about-connect on the About page). */
.closing__title { font-size: clamp(2rem, 1.4rem + 2.6vw, 3.4rem); letter-spacing: -0.03em; line-height: 1.06; margin-bottom: var(--space-3); text-wrap: balance; }
.closing__lead { color: var(--color-text-secondary); font-size: var(--text-lg); max-width: 42ch; margin: 0 auto var(--space-5); text-wrap: pretty; }
.page-description { color: var(--color-text-secondary); font-size: var(--text-lg); max-width: 60ch; }
.back-link { display: inline-block; color: var(--color-accent-text);
  font-size: var(--text-sm); margin-bottom: var(--space-4); }
.project-meta { color: var(--color-text-secondary); font-size: var(--text-sm); margin-bottom: var(--space-4); }

/* Detail-page layout helpers (project / notebook / page) */
.page-header--spaced { margin-bottom: var(--space-6); }
.page-description--headed { margin-top: var(--space-2); }
.detail-header--spaced { margin-bottom: var(--space-6); }
.detail-back-row { margin-bottom: var(--space-2); }
.detail-description { font-size: var(--text-xl); color: var(--color-text-secondary); margin: var(--space-2) 0; }
.detail-meta-row { margin-top: var(--space-3); flex-wrap: wrap; }
.detail-nav { margin-top: var(--space-8); padding-top: var(--space-4); border-top: 1px solid var(--color-border); }
.flex-center { justify-content: center; flex-wrap: wrap; }
.not-found { padding: var(--space-12) 0; }
.not-found-code { font-size: var(--text-5xl); margin-bottom: var(--space-4); }
.not-found-title { font-size: var(--text-2xl); color: var(--color-text-secondary); margin-bottom: var(--space-6); }
.not-found-lede { margin-bottom: var(--space-8); max-width: var(--max-width-prose); margin-left: auto; margin-right: auto; }
.opt-out-box { margin: var(--space-4) 0; padding: var(--space-4); background-color: var(--color-surface); border-radius: var(--border-radius); }
.opt-out-status { margin: var(--space-2) 0; }

/* ============================================
   COMPONENTS
   ============================================ */

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.8rem 1.6rem;
  font-size: var(--text-base);
  font-weight: 600;
  text-align: center;
  border-radius: var(--border-radius-sm);
  transition: all var(--transition-base) var(--ease-out-expo);
  cursor: pointer;
  border: none;
  min-height: 46px; /* Touch target size */
}

/* Primary = solid accent with a soft glow; secondary = ghost outline */
.btn-primary {
  background-color: var(--color-accent);
  color: #fff;
  border: 1px solid transparent;
  box-shadow: 0 1px 2px rgba(0,0,0,.12); /* fallback for browsers without color-mix() */
  box-shadow: 0 1px 2px rgba(0,0,0,.12), 0 8px 20px color-mix(in srgb, var(--color-accent) 30%, transparent);
}
.btn-primary:hover { background-color: var(--color-accent-deep); transform: translateY(-1px); }

.btn-secondary {
  background-color: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  box-shadow: 0 1px 2px rgba(0,0,0,.06); /* fallback for browsers without color-mix() */
  box-shadow: 0 1px 2px rgba(0,0,0,.06), 0 2px 6px color-mix(in srgb, var(--color-text) 7%, transparent);
}
.btn-secondary:hover {
  border-color: color-mix(in srgb, var(--color-accent) 45%, var(--color-border));
  color: var(--color-accent-text);
  transform: translateY(-1px); /* match the primary's lift for consistent motion */
  box-shadow: 0 2px 4px rgba(0,0,0,.08), 0 6px 16px color-mix(in srgb, var(--color-accent) 18%, transparent);
}

/* Button press feedback (token-based → reduced-motion-guarded) */
.btn-primary:active { transform: translateY(0) scale(0.98); }
.btn-secondary:active { transform: scale(0.98); }

/* Cards */
.card {
  background-color: var(--color-surface);
  border-radius: var(--border-radius);
  padding: var(--space-4);
  box-shadow: var(--shadow-card);
  border: 1px solid transparent;
  transition: all var(--transition-base);
}

.card:hover {
  box-shadow: var(--shadow-md);
  border-color: var(--color-border);
}

.project-card:hover {
  transform: translateY(-4px);
}

.card-title {
  font-size: var(--text-xl);
  font-weight: 600;
  margin-bottom: var(--space-2);
}
.project-card .card-title { font-size: var(--text-2xl); font-weight: 600; }

.card-description {
  color: var(--color-text-secondary);
  max-width: 60ch;
  margin-bottom: var(--space-2);
}

/* Tags / chips - uniform outlined pill; fills brand-blue on hover */
.tag {
  display: inline-block;
  padding: 0.3rem 0.7rem;
  font-size: var(--text-sm);
  background-color: var(--color-surface);
  color: var(--color-text-secondary);
  font-weight: 600;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  transition: background-color var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}

.tag:hover {
  background-color: var(--color-accent-text);
  color: #fff;
  border-color: var(--color-accent-text);
}

/* Notebook */
.notebook-content {
  font-family: var(--font-system);
  max-width: 1000px;
  margin: 0 auto;
}

.notebook-content pre {
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius-sm);
  padding: var(--space-2);
  overflow-x: auto;
}

.notebook-content code {
  font-family: var(--font-mono);
  font-size: 0.9em;
}

.notebook-content img {
  max-width: 100%;
  height: auto;
  border-radius: var(--border-radius-sm);
  margin: var(--space-4) 0;
}

.notebook-content table {
  width: 100%;
  border-collapse: collapse;
  margin: var(--space-4) 0;
}

.notebook-content th,
.notebook-content td {
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  text-align: left;
}

.notebook-content th {
  background-color: var(--color-surface);
  font-weight: 600;
}

/* ============================================
   ACCESSIBILITY
   ============================================ */

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

/* Scroll-reveal - pure CSS scroll-driven (no JS, no IntersectionObserver).
   Content is VISIBLE BY DEFAULT; the rise is a function of the element's view()
   timeline, so nothing is ever hidden waiting on JS. That's the whole point: a
   cross-document View Transition snapshots the incoming page before any module
   runs, so a JS-revealed element was captured blank on Safari = the flash. With
   scroll-driven CSS the in-view state is computed from scroll position, so the
   snapshot is always solid (flash structurally impossible on every engine).
   Same pattern as .section-title below. Degrades to fully visible (no motion)
   when animation-timeline is unsupported or under reduced-motion. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    [data-reveal] {
      animation: reveal-rise both linear;
      animation-timeline: view();
      animation-range: entry 0% cover 18%;
    }
    @keyframes reveal-rise {
      from { opacity: 0; transform: translateY(16px); }
      to   { opacity: 1; transform: none; }
    }
  }
}

/* Focus Visible - ring-with-gap (forced-colors keeps an outline fallback) */
:focus-visible {
  outline: none;
  border-radius: var(--border-radius-sm);
  box-shadow: 0 0 0 2px var(--color-background), 0 0 0 4px var(--color-primary);
}
@media (forced-colors: active) {
  :focus-visible { outline: 2px solid CanvasText; box-shadow: none; }
}

/* Skip to Content */
.skip-to-content {
  position: absolute;
  top: 0;
  left: 0;
  transform: translateY(-200%);   /* fully off-screen until focused (no blue sliver above the logo) */
  background: var(--color-primary);
  color: white;
  padding: var(--space-1) var(--space-2);
  z-index: 1000;
  transition: transform var(--transition-fast);
}

.skip-to-content:focus-visible {
  transform: translateY(0);
}

/* ============================================
   RESPONSIVE BREAKPOINTS
   ============================================ */
/* Mobile: < 640px (default) */
/* Tablet: 640px - 1024px */
/* Desktop: > 1024px */

@media (min-width: 640px) {
  html {
    font-size: 17px;
  }
  
  .container {
    padding: 0 var(--space-4);
  }
}

@media (min-width: 1024px) {
  html {
    font-size: 18px;
  }

  .section {
    padding: var(--space-12) 0;
  }
}

/* ============================================
   PAGE-SPECIFIC COMPONENTS
   ============================================ */

/* Site Header - sits at the top of the page and scrolls away with the content (NOT pinned/fixed):
   michellegore.com's nav that only appears at the top. Fully transparent on every page (home, project,
   about) - no background, border, blur, or shadow (no "stripe"), just the wordmark + nav in white over
   whatever is behind it. position: absolute (not fixed) so it overlaps the cover and scrolls off-screen;
   covers bleed up behind it (negative margin on .hero-home/.project-cover cancels main's padding). */
.site-header {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
}

/* Fixed header floats over content, so push non-cover page content clear of it. Cover pages cancel this
   with a negative margin on .hero-home/.project-cover so the cover bleeds up behind the header. */
main { padding-top: var(--header-h); }

/* Transparent over dark covers/content -> light chrome. Tagline hidden for a minimal wordmark + nav. */
.site-header .site-tagline { display: none; }
.site-header .nav-link { color: rgba(255,255,255,0.85); }
.site-header .nav-link:hover { color: #fff; background-color: transparent; }

/* On project detail pages the nav blends with the project's category theme:
   data-category on <body> cascades --proj-accent, so hover text, the underline,
   and the active item pick up the project hue. Non-project pages stay white/blue. */
body[data-category] .site-header .nav-link:hover { color: var(--proj-accent); }
body[data-category] .nav-link::after { background: var(--proj-accent); }
body[data-category] .nav-link.active { color: var(--proj-accent); }

.site-nav {
  padding: var(--space-3) 0;
}

.site-logo .site-name {
  display: block;
  font-size: var(--text-lg);   /* smaller, minimalist wordmark */
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--color-text);
  margin: 0;
}

.nav-menu {
  list-style: none;
  margin: 0;
}

.nav-link {
  position: relative;
  font-size: var(--text-sm);   /* smaller, minimalist nav */
  font-weight: 500;
  color: var(--color-text-secondary);
  transition: color var(--transition-fast);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--border-radius-sm);
  min-height: 44px;
  display: flex;
  align-items: center;
}

.nav-link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -4px;
  height: 2px;
  width: 0;
  background: var(--color-primary);
  transition: width var(--transition-base);
}

.nav-link:hover::after,
.nav-link:focus-visible::after { width: 100%; }

.nav-link:hover {
  color: var(--color-accent-text);
  background-color: var(--color-surface);
}

@media (max-width: 640px) {
  .nav-menu {
    gap: var(--space-2);
  }
  
  .nav-link {
    font-size: var(--text-sm);
    padding: var(--space-1);
  }
}

/* Site Footer - minimal one-line sign-off: a warm message (left), social icons (middle),
   © (right), all on a single bar. Email stays obfuscated (nav.js mailto), so the mail icon
   carries no address in static HTML. */
.site-footer {
  background-color: var(--color-surface);
  margin-top: var(--space-12);
}
.footer-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-5);
  padding: var(--space-5) 0;
  border-top: 1px solid var(--color-border);
}
/* The warm line - readable but quiet, sits left of the icons. */
.footer-message { font-size: var(--text-sm); color: var(--color-text); }
.footer-icons { display: flex; align-items: center; gap: var(--space-3); }
.footer-icon-link { display: inline-flex; align-items: center; justify-content: center; color: var(--color-text-secondary); transition: color var(--transition-fast); cursor: pointer; }
.footer-icon-link:hover { color: var(--color-text); }
.footer-icon-link svg { width: 16px; height: 16px; }
.footer-copy { font-size: var(--text-xs); color: var(--color-text-secondary); letter-spacing: var(--tracking-label); }

/* CTA button row (shared by hero + 404) */
.cta-buttons {
  justify-content: center;
  flex-wrap: wrap;
}

/* Section Title - left-aligned editorial */
.section-title {
  text-align: left;
  max-width: 60rem;
  margin-bottom: var(--space-6);
  font-size: clamp(1.9rem, 1.4rem + 1.8vw, 2.85rem);
  letter-spacing: -0.03em;
  line-height: 1.05;
  text-wrap: balance;
}

/* Project Cards - editorial, single focusable unit */
.project-card {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  text-align: left;
  height: 100%;
  position: relative;
  padding: 0;
  overflow: hidden;
  background: var(--color-surface-raised);
  transition: transform var(--transition-base) var(--ease-out-expo), box-shadow var(--transition-base) var(--ease-out-expo);
}
.card-title__link { color: var(--color-text); }
/* On hover the title tracks the project's category hue (not global a:hover blue),
   so it stays consistent with the art + "View project" CTA. Specificity beats a:hover. */
.project-card:hover .card-title__link,
.project-card:focus-within .card-title__link { color: var(--proj-accent); }
.card-title__link::after { content: ""; position: absolute; inset: 0; } /* whole card is the click target */
.card-title__text { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } /* clamp the TEXT, not the link box (3 lines fits the longest project title) */
.project-card:focus-within { box-shadow: 0 0 0 2px var(--color-bg), 0 0 0 4px var(--color-accent); }
.card-title__link:focus-visible { outline: none; box-shadow: none; }

.project-card__noimg {
  aspect-ratio: 16 / 9; display: grid; place-items: center; margin-bottom: var(--space-4);
  background: var(--color-surface-alt); color: var(--color-text-secondary);
  font-size: var(--text-sm); border-radius: var(--border-radius-sm);
}

/* Work listing (Projects page) - eyebrow header + 2-up grid on desktop */
/* Eyebrow - michellegore.com style: uppercase, bold tracked kicker in the display face (Hanken
   Grotesk), not mono (mono read thin/airy at this size). Uses the shared --tracking-label token
   (one label voice); weight 700 is her solid-label treatment (the inversion: light display, bold labels). */
.eyebrow { font-size: var(--text-sm); font-weight: 700; letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--color-accent-text); margin-bottom: var(--space-3); }

/* Project card body - image sits flush to the top, content padded below */
.project-card__body { padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-2); }

/* Chrome-less editorial project sections (home + /work): no card box, large image + text columns,
   sides alternating per row. michellegore.com homepage-section style. overflow-x:clip contains the
   horizontal slide-in below so it never spills a page scrollbar. */
.work-rows { display: flex; flex-direction: column; gap: clamp(3rem, 7vw, 6rem); overflow-x: clip; }
.work-rows .project-card { height: auto; background: transparent; overflow: visible; }
.work-rows .project-card:hover { transform: none; }   /* no lift - the slide + image zoom carry it */
.work-rows .project-card:focus-within { box-shadow: none; }   /* the link outline rule below is the sole indicator */
.work-rows .project-card .image-thumbnail { border-radius: var(--border-radius); }
/* Show the full project name - no line clamp (rows grow to fit the title) */
.work-rows .card-title__text { display: block; -webkit-line-clamp: none; overflow: visible; }
.work-rows .project-card__kicker { color: var(--color-text-secondary); margin-bottom: var(--space-2); }
/* Restore a visible focus ring on the link now that the card box (+ its focus-within ring) is gone */
.work-rows .card-title__link:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 4px; }
@media (min-width: 760px) {
  .work-rows .project-card { display: grid; grid-template-columns: 1.05fr 0.95fr; align-items: center; gap: var(--space-8); }
  .work-rows .project-card .image-thumbnail { aspect-ratio: 16 / 9; height: auto; margin: 0; }
  .work-rows .project-card:nth-child(even) .image-thumbnail { order: 2; }
  .work-rows .project-card__body { padding: 0; justify-content: center; }
  .work-rows .project-card .card-title { font-size: clamp(1.6rem, 1.2rem + 1.4vw, 2.4rem); }
}

/* Category chip (michellegore's tinted discipline pill): category-hue text on the same hue
   at low opacity. An element of the category color system, not a decorative ornament -
   approved 2026-07-01, superseding the earlier "kicker stays muted" reach (the kicker is
   now date-only so the chip doesn't duplicate the first tag). */
.project-card__tag {
  display: inline-block; align-self: flex-start;   /* hug the text - the card body is a flex column */
  margin: var(--space-2) 0 0; padding: 0.3em 0.75em;
  font-size: var(--text-xs); font-weight: 700; letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--proj-accent);
  background: color-mix(in srgb, var(--proj-accent) 10%, transparent);
  border-radius: 4px;
}

/* "View project ->" CTA: muted, accents + tracks open + nudges the arrow on hover
   (her full VIEW PROJECT signature: color, +0.5px letter-spacing, arrow slide). */
.project-card__cta {
  display: inline-flex; align-items: center; gap: 0.4em; margin-top: var(--space-3);
  font-size: var(--text-sm); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--color-text-secondary);
  transition: color var(--transition-base), letter-spacing var(--transition-base);
}
.project-card__cta .cta-arrow { transition: transform var(--transition-base); }
.work-rows .project-card:hover .project-card__cta,
.work-rows .project-card:focus-within .project-card__cta { color: var(--proj-accent); }
@media (prefers-reduced-motion: no-preference) {
  .work-rows .project-card:hover .project-card__cta,
  .work-rows .project-card:focus-within .project-card__cta { letter-spacing: calc(var(--tracking-label) + 0.5px); }
  .work-rows .project-card:hover .project-card__cta .cta-arrow,
  .work-rows .project-card:focus-within .project-card__cta .cta-arrow { transform: translateX(4px); }
}

/* Work-rows entrance = a bespoke converging slide (our design - her work reveals are plain opacity
   fades, no slide; the slide-to-middle is our embellishment, not a michellegore lift). Pure
   scroll-driven CSS (animation-timeline: view()): image + text CONVERGE to
   the middle from opposite sides as a row enters (scroll down) and part back to the sides when it
   scrolls back out (scroll up). Image leads (~100px), text trails (~60px); the grid already
   alternates the image side per row, so each row's image comes from its outer edge and the text
   from the opposite edge. Pure translateX (no opacity fade) so the slide reads in BOTH scroll
   directions; circ-out easing (cubic-bezier(0,0.55,0.45,1) - the house / VT page-rise curve)
   and a range widened into cover (entry 10% -> cover 45%) for a slower, smoother settle.
   Visible-by-default + fill:both, so the VT snapshot is always solid (no
   Safari flash - no JS in the motion path). Plays as a row enters FROM BELOW (home work section
   under the hero; lower /projects/ rows); a card already on-screen at the top of /projects/ has
   nothing to scroll into (positional - give the listing headroom so rows arrive from below).
   overflow-x:clip on .work-rows contains the x-slide. Desktop + no-reduced-motion + support-gated;
   else cards just show. */
@media (min-width: 760px) and (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .work-rows .project-card[data-reveal] { animation: none; }
    .work-rows .project-card[data-reveal] .image-thumbnail,
    .work-rows .project-card[data-reveal] .project-card__body {
      animation: both cubic-bezier(0, 0.55, 0.45, 1);
      animation-timeline: view();
      animation-range: entry 10% cover 45%;
    }
    /* converge to the middle: image from one side, text from the OTHER; image leads (~100px), text trails (~60px) */
    .work-rows .project-card[data-reveal] .image-thumbnail   { animation-name: wr-slide-img-l; }
    .work-rows .project-card[data-reveal] .project-card__body { animation-name: wr-slide-body-r; }
    .work-rows .project-card[data-reveal]:nth-child(even) .image-thumbnail   { animation-name: wr-slide-img-r; }
    .work-rows .project-card[data-reveal]:nth-child(even) .project-card__body { animation-name: wr-slide-body-l; }
    @keyframes wr-slide-img-l  { from { transform: translateX(-100px); } to { transform: none; } }
    @keyframes wr-slide-img-r  { from { transform: translateX(100px);  } to { transform: none; } }
    @keyframes wr-slide-body-l { from { transform: translateX(-60px);  } to { transform: none; } }
    @keyframes wr-slide-body-r { from { transform: translateX(60px);   } to { transform: none; } }
  }
}

/* ============================================
   MERMAID DIAGRAMS
   ============================================ */
.mermaid {
  font-family: var(--font-system);
  background: var(--color-surface);
  border-radius: var(--border-radius);
  padding: var(--space-4);
  margin: var(--space-4) 0;
  overflow-x: auto;
}

.mermaid text {
  fill: var(--color-text);
}

.mermaid .node rect,
.mermaid .node circle,
.mermaid .node ellipse,
.mermaid .node polygon,
.mermaid .node path {
  fill: var(--color-surface);
  stroke: var(--color-primary);
  stroke-width: 2px;
}

.mermaid .edgePath .path {
  stroke: var(--color-text-secondary);
  stroke-width: 1.5px;
}

.mermaid .edgeLabel {
  background-color: var(--color-surface);
  color: var(--color-text);
}

.mermaid .cluster rect {
  fill: var(--color-surface);
  stroke: var(--color-border);
  stroke-width: 1px;
}

/* Metric figures use the brand accent (--color-accent == --color-primary); keep weight 700 for emphasis */
.metric { color: var(--color-accent); font-variant-numeric: tabular-nums; font-weight: 700;
  font-size: max(1em, 0.875rem); }

@media (max-width: 768px) {
  .mermaid {
    font-size: 0.875rem;
    padding: var(--space-2);
  }
}

/* ============================================
   IMAGE COMPONENTS
   ============================================ */

/* Profile Avatar */
.profile-avatar {
  display: flex;
  justify-content: center;
  margin-bottom: var(--space-4);
}

.avatar-image {
  width: 200px;
  height: 200px;
  border-radius: 50%;
  object-fit: cover;
  object-position: 50% 25%;
  border: 4px solid var(--color-border);
  box-shadow: var(--shadow-md);
  transition: transform var(--transition-base), box-shadow var(--transition-base);
}

.avatar-image:hover {
  transform: scale(1.05);
  box-shadow: var(--shadow-lg);
}

/* Project Thumbnail */
.image-thumbnail {
  margin: 0;
  border-radius: var(--border-radius);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform var(--transition-base), box-shadow var(--transition-base);
}

.image-thumbnail:hover {
  box-shadow: var(--shadow-lg);
}

/* Card-scoped image zoom - image zooms INSIDE the lifting card, not independently */
.project-card .image-thumbnail img {
  transition: transform var(--transition-slow);
}
.project-card:hover .image-thumbnail img {
  transform: scale(1.04);
}

.image-thumbnail img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.project-card .image-thumbnail { margin-bottom: 0; aspect-ratio: 16 / 9; border-radius: 0; box-shadow: none; position: relative; }
.project-card .image-thumbnail img { height: 100%; }
.project-card .image-thumbnail::after { content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg, transparent 58%, color-mix(in srgb, var(--color-bg) 26%, transparent)); }

/* Inline category SVG art: the figure carries the project hue (currentColor); mirror the <img>
   sizing + card-zoom. A thin accent hairline rides in on hover (restrained category cue). */
.image-thumbnail { color: var(--proj-accent); }
.image-thumbnail svg { width: 100%; height: 100%; display: block; }
.project-card .image-thumbnail svg { transition: transform var(--transition-slow); }
.project-card:hover .image-thumbnail svg { transform: scale(1.04); }
.work-rows .project-card:hover .image-thumbnail,
.work-rows .project-card:focus-within .image-thumbnail {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--proj-accent) 55%, transparent), var(--shadow-lg);
}
@media (prefers-reduced-motion: reduce) { .project-card:hover .image-thumbnail svg { transform: none; } }

/* ==========================================================================
   Mobile nav
   ========================================================================== */
.nav-toggle { display: none; flex-direction: column; justify-content: space-between;
  width: 28px; height: 22px; padding: 0; background: none; border: none; cursor: pointer; }
.nav-toggle-bar { display: block; width: 100%; height: 2px; background: var(--color-text);
  transition: transform var(--transition-base), opacity var(--transition-base); }

@media (max-width: 767px) {
  /* 44px touch-target floor; hamburger centered, uncrowded */
  .nav-toggle { display: flex; min-height: 44px; min-width: 44px;
    width: 44px; height: 44px; padding: 11px 8px; }
  .nav-menu { position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; gap: 0; padding: var(--space-2) var(--space-4) var(--space-4);
    background: var(--color-background); border-bottom: 1px solid var(--color-border);
    display: none; }
  .nav-menu.open { display: flex; }
  .nav-menu li { width: 100%; }
  .nav-menu .nav-link { display: block; padding: var(--space-3) 0; }
  /* Staggered item entrance on open (michellegore's 0.08s overlay-menu stagger).
     The display:none→flex toggle restarts the animation on every open. Three items,
     so nth-child delays beat --i plumbing. backwards fill hides delayed items pre-start. */
  @media (prefers-reduced-motion: no-preference) {
    .nav-menu.open .nav-link { animation: nav-item-in 0.35s var(--ease-out-expo) backwards; }
    .nav-menu.open li:nth-child(2) .nav-link { animation-delay: 0.08s; }
    .nav-menu.open li:nth-child(3) .nav-link { animation-delay: 0.16s; }
    @keyframes nav-item-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
  }
  /* Lock background scroll while the mobile drawer is open. */
  body[data-nav-open] { overflow: hidden; }
}
/* progressive enhancement: if JS is disabled the links remain reachable via the open state */

.nav-link.active { color: var(--color-accent-text); font-weight: 600; }
.nav-link.active::after { width: 100%; }

/* ===== Header site tagline ===== */
.site-tagline { display: block; font-size: var(--text-xs); color: var(--color-text-secondary);
  letter-spacing: 0.05em; font-weight: 500; margin-top: 2px; }
@media (max-width: 767px) { .site-tagline { display: none; } }

/* ===== Metrics strip (mono telemetry readout) ===== */
.metrics-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-5) var(--space-6); }
@media (min-width: 768px) { .metrics-grid { grid-template-columns: repeat(4, 1fr); } }
.metric-cell { display: flex; flex-direction: column; gap: var(--space-1); text-align: left; padding: var(--space-3) 0 0; border-top: 1px solid var(--color-border); transition: border-color var(--transition-fast); }
/* Subtle hairline accent on hover - gives the telemetry readout life on pointer devices only */
@media (hover: hover) { .metric-cell:hover { border-top-color: color-mix(in srgb, var(--color-accent) 45%, var(--color-border)); } }
.metric-figure { font-size: clamp(2.75rem, 1.9rem + 2.8vw, 4rem); font-weight: 700;
  color: var(--color-accent); line-height: 0.95; font-variant-numeric: tabular-nums; letter-spacing: -0.035em; }
.metric-label { font-size: var(--text-xs); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--color-text-secondary); }

/* ===== About: Editorial elements ===== */
.identity-block { margin-block: var(--space-6) 0; }
.identity-block .identity-name { font-size: clamp(2.5rem, 1.5rem + 3vw, 3.5rem); font-weight: 500; line-height: 1.1; margin: 0; letter-spacing: -0.04em; } /* visually matches .hero-home__title (the home hero name) */
.identity-block .identity-title { color: var(--color-accent); font-weight: 600; font-size: clamp(1.3rem, 1rem + 1vw, 1.625rem); margin-top: var(--space-2); }
.identity-block .identity-loc { color: var(--color-text-secondary); font-size: var(--text-lg); margin-top: var(--space-1); }
.cert-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-4); }
.cert-chips .chip { font-size: var(--text-sm); font-weight: 600; padding: 0.3rem 0.7rem; border-radius: 999px; background: var(--color-surface); color: var(--color-text-secondary); border: 1px solid var(--color-border); transition: background-color var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast); }
.cert-chips .chip:hover { background-color: var(--color-accent-text); color: #fff; border-color: var(--color-accent-text); }
.pullquote { font-size: clamp(1.25rem, 1rem + 1vw, 1.6rem); line-height: 1.4; font-weight: 600; color: var(--color-text); border-left: 3px solid var(--color-accent); padding-left: var(--space-4); margin: var(--space-8) 0; max-width: 32ch; text-wrap: balance; }

/* ===== About page: section cards (matches project cards) ===== */
/* About intro carries the cover identity: the same navy + blue-glow surface as Home/Projects,
   bleeding up behind the transparent header, so all three top-of-page moments share one brand
   surface. Content-height (not a 100vh cover) - the about-grid follows right after, no scroll-cue.
   The bottom fades into --color-bg so the band melts into the grid instead of hard-cutting. */
.about-intro {
  position: relative; isolation: isolate; text-align: left; color: #fff;
  margin-top: calc(-1 * var(--header-h));
  padding: calc(var(--header-h) + var(--space-section)) 0 var(--space-section);
  background:
    linear-gradient(to bottom, transparent 70%, var(--color-bg)),
    radial-gradient(120% 90% at 82% 8%, rgba(10,132,255,0.20), transparent 58%),
    linear-gradient(162deg, #0C2747 0%, #081627 52%, #16103a 100%);
}
.about-intro::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  inset: -10% auto auto -12%; width: 60%; height: 95%;
  background: radial-gradient(50% 50% at 35% 25%, color-mix(in srgb, var(--color-accent) 22%, transparent), transparent 72%);
  filter: blur(48px); opacity: 0.55;
}
.about-intro .container { max-width: var(--max-width-prose); }
.about-intro__card { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-4); }
@media (min-width: 768px) { .about-intro__card { flex-direction: row; align-items: center; gap: var(--space-6); } }
.about-intro .profile-avatar { margin-bottom: 0; justify-content: flex-start; flex-shrink: 0; }
.about-intro .cert-chips { justify-content: flex-start; }
.about-body { padding: var(--space-2) 0 var(--space-12); }
.about-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 var(--space-4); }
.about-contact-full { grid-column: 1 / -1; padding-top: var(--space-8); margin-top: var(--space-4); }
@media (max-width: 767px) { .about-grid { grid-template-columns: 1fr; } .about-contact-full { grid-column: 1; } }
.about-section { padding: var(--space-6); }
/* De-box About sections — spacing only, no rules */
.about-section.card { background: none; box-shadow: none; border: none; border-radius: 0; padding: var(--space-6) 0 0; }
.about-section.card.about-col-right { padding-left: var(--space-8); }
@media (max-width: 767px) { .about-section.card.about-col-right { padding-left: 0; } }
.about-section p { line-height: 1.7; }
/* About headings use .section-title for one heading family across pages (see markup). */

/* Vertical timeline */
.xp-timeline { position: relative; padding-left: 1.75rem; margin-top: var(--space-5); }
.xp-timeline::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  bottom: 0;
  width: 1px;
  background: linear-gradient(to bottom, color-mix(in srgb, var(--color-accent) 50%, var(--color-border)) 0%, var(--color-border) 35%, transparent 100%);
}
.xp-entry { position: relative; padding-bottom: var(--space-6); }
.xp-entry:last-child { padding-bottom: 0; }
.xp-entry::before {
  content: "";
  position: absolute;
  left: calc(-1.75rem - 3px);
  top: 0.55em;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-surface-raised);
  border: 1.5px solid var(--color-border);
}
.xp-entry:first-child::before {
  background: var(--color-accent);
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 15%, transparent);
}
.xp-period { display: block; font-size: var(--text-xs); letter-spacing: var(--tracking-label); color: var(--color-text-secondary); margin-bottom: var(--space-1); }
.xp-company { font-size: clamp(1.5rem, 3vw, 2.5rem); font-weight: 600; letter-spacing: -0.025em; line-height: 1.1; margin: 0 0 var(--space-1); color: var(--color-text); }
.xp-company--dim { color: var(--color-text-secondary); font-size: clamp(1.1rem, 2vw, 1.75rem); }
.xp-role { font-size: var(--text-sm); color: var(--color-text-secondary); margin: 0 0 var(--space-1); }
.xp-signal { font-size: var(--text-xs); letter-spacing: var(--tracking-label); color: var(--color-accent-text); display: block; }

/* Credentials - icon links */
.credentials-list { list-style: none; padding: 0; margin: 0; }
.credentials-list li { display: flex; justify-content: space-between; align-items: center; gap: var(--space-3); padding: var(--space-3) 0; border-bottom: 1px solid var(--color-border); }
.credentials-list li:last-child { border-bottom: none; }
.cred-name { font-weight: 600; }
.cred-meta { color: var(--color-text-secondary); font-size: var(--text-sm); }
.cred-link { color: var(--color-text-secondary); display: inline-flex; transition: color var(--transition-fast), transform var(--transition-fast); }
.cred-link:hover { color: var(--color-accent-text); transform: translate(1px, -1px); }
.cred-link svg { width: 16px; height: 16px; }

/* Currently */
.currently-list { list-style: disc; padding-left: var(--space-5); margin: 0; }
.currently-list li { padding: var(--space-1) 0; }
.currently-list li::marker { color: var(--color-accent-text); }
/* Let's Connect — accent-wash CTA card, centered */
.about-connect { background: color-mix(in srgb, var(--color-accent) 4%, transparent); border-radius: var(--border-radius); padding: var(--space-8) !important; border: 1px solid color-mix(in srgb, var(--color-accent) 18%, var(--color-border)) !important; text-align: center; }
.about-connect .closing__lead { margin-left: auto; margin-right: auto; }
.about-connect .cta-buttons { justify-content: center; }

/* Mobile only: box the About intro + each section as inset cards so the 1-column
   stack reads as distinct blocks with breathing room. Desktop (>=768px) keeps the
   borderless 2-column layout untouched - these rules are scoped to max-width: 767px
   and placed after the desktop rules so they win on source order. */
@media (max-width: 767px) {
  .about-intro__card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--border-radius); padding: var(--space-4); }
  .about-intro__card .avatar-image { width: 140px; height: 140px; }
  .about-grid { gap: var(--space-3); }
  .about-contact-full { padding-top: 0; margin-top: 0; }
  .about-section.card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--border-radius); padding: var(--space-4); }
  .about-section.card.about-col-right { padding-left: var(--space-4); }
  .about-connect { padding: var(--space-5) !important; }
}

/* ============================================
   HOME HERO (typographic) + TELEMETRY SIGNATURE
   ============================================ */
/* Full-page hero cover (home + projects listing): static dark navy -> indigo gradient, white
   centered copy, arrow at the bottom. Shared by .hero-home and .projects-hero for one cover
   language. The negative top margin cancels main's --header-h padding so the cover bleeds up
   behind the transparent header (reveals the cover, not the page bg); min-height fills one screen. */
.hero-home, .projects-hero {
  position: relative; isolation: isolate;
  display: flex; align-items: center;
  margin-top: calc(-1 * var(--header-h));
  min-height: 100vh;
  min-height: 100svh;
  padding-block: var(--space-8);
  color: #fff;
  background:
    radial-gradient(120% 90% at 82% 8%, rgba(10,132,255,0.20), transparent 58%),
    linear-gradient(162deg, #0C2747 0%, #081627 52%, #16103a 100%);
}
.hero-home::before, .projects-hero::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  inset: -8% auto auto -15%; width: 72%; height: 82%;
  background: radial-gradient(50% 50% at 35% 25%, color-mix(in srgb, var(--color-accent) 22%, transparent), transparent 72%);
  filter: blur(48px); opacity: 0.55;
}
/* Subtle dot-grid texture - fades from the top, reads as a "data grid" (on-brand for a
   data engineer). Masked so it's a texture, not a pattern; behind all hero content. */
.hero-home::after, .projects-hero::after {
  content: ""; position: absolute; z-index: -1; pointer-events: none; inset: 0;
  background-image: radial-gradient(circle, rgba(255,255,255,0.08) 1px, transparent 1.5px);
  background-size: 22px 22px; opacity: 0.7;
  -webkit-mask-image: radial-gradient(ellipse 78% 70% at 50% 28%, #000 18%, transparent 80%);
  mask-image: radial-gradient(ellipse 78% 70% at 50% 28%, #000 18%, transparent 80%);
}
/* Home hero only: the blue identity glow slowly wanders so the cover reads as a living surface,
   not a flat gradient (Projects shares the same glow but keeps it static). Decorative -1 z-index
   pseudo, transform/opacity only -> never blanks the VT snapshot; reduced-motion gets the static glow. */
@media (prefers-reduced-motion: no-preference) {
  .hero-home::before {
    animation: heroAurora 24s ease-in-out infinite alternate;
    will-change: transform, opacity;
  }
  @keyframes heroAurora {
    0%   { transform: translate(0, 0) scale(1);       opacity: 0.5; }
    50%  { transform: translate(7%, 5%) scale(1.14);  opacity: 0.64; }
    100% { transform: translate(-5%, 9%) scale(1.06); opacity: 0.44; }
  }
}

/* Centered hero composition (michellegore.com style): eyebrow -> name -> lead -> CTA -> telemetry,
   stacked and centered in the middle of the cover. */
.hero-home__inner { display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--space-6); max-width: 52rem; }
.hero-home__text { width: 100%; }
.hero-home__aside { min-width: 0; width: 100%; }
.hero-home__aside .hero-telemetry { margin: 0 auto; max-width: 40rem; width: 100%; }
.hero-home .hero-telemetry__figures { justify-content: center; }
/* hero eyebrow typography comes from .eyebrow; .hero-home__eyebrow only carries the stagger delay below. */
/* Soft brand-blue glow hugging the letterforms - michellegore's indigo hero
   text-shadow (0 10px 50px), retuned for the navy surface. The entrance mask that
   would clip it is released after the line-rise (see MOTION CRAFT below). */
.hero-home__title { font-size: clamp(3rem, 1.2rem + 7vw, 6rem); font-weight: 500; letter-spacing: -0.04em; line-height: 1.0; margin: 0 0 var(--space-3); }
.hero-home__lead { font-size: clamp(1.25rem, 1rem + 1vw, 1.6rem); color: var(--color-text-secondary); max-width: 42ch; margin: 0 auto var(--space-4); text-wrap: pretty; }
.hero-home__cta { justify-content: center; margin-top: var(--space-5); }
@media (prefers-reduced-motion: no-preference) {
  .hero-home__text > *, .hero-home .hero-telemetry { animation: heroIn 0.7s var(--ease-out-expo) backwards; }
  .hero-home__eyebrow { animation-delay: 0.05s; }
  .hero-home__title   { animation-delay: 0.12s; }
  .hero-home__lead    { animation-delay: 0.22s; }
  .hero-home .hero-telemetry { animation-delay: 0.32s; }
  .hero-home__cta     { animation-delay: 0.46s; }
  @keyframes heroIn { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
}

/* Cover is always dark (regardless of OS theme), so force copy + instrument to read white.
   Eyebrow is intentionally subtle/blended white; lead is high-but-soft white. */
.hero-home__eyebrow { color: rgba(255,255,255,0.62); }
.hero-home__lead { color: rgba(255,255,255,0.82); }
.hero-home .hero-telemetry {
  background: rgba(255,255,255,0.05);
  border-color: rgba(255,255,255,0.12);
  box-shadow: none;
}
.hero-home .tl-label,
.hero-home .tl-unit,
.hero-home .tl-legend { color: rgba(255,255,255,0.7); }
.hero-home .tl-value { color: #fff; }
.hero-home .tl-nodes circle { fill: rgba(255,255,255,0.85); stroke: rgba(255,255,255,0.25); }
.hero-home .tl-legend--proc { color: var(--color-accent-text); }
/* Ghost button as a white outline on the dark cover (the light default would fight the gradient). */
.hero-home .btn-secondary {
  background: transparent; color: #fff;
  border-color: rgba(255,255,255,0.4); box-shadow: none;
}
.hero-home .btn-secondary:hover {
  background: rgba(255,255,255,0.08); color: #fff; border-color: #fff;
}
/* Pin the arrow to the cover's bottom edge (out of the flex flow) so the content stays centered. */
.hero-home .scroll-cue {
  position: absolute; left: 50%; bottom: var(--space-3);
  transform: translateX(-50%); margin: 0; color: rgba(255,255,255,0.8);
}
.hero-home .scroll-cue:hover, .hero-home .scroll-cue:focus-visible { color: #fff; }

/* Projects listing cover (.projects-hero) shares the .hero-home cover shell above (navy gradient,
   blue glow, dot-grid, bleed-behind-header, min-height for the work-rows slide headroom, white
   text) for one unified cover language - NO tri-color surface. Centered title + lead, with the
   color-coded category legend placed AFTER the heading (the title leads; the taxonomy closes the
   stack - the one change from the upfront-legend layout that shipped on main). */
.projects-hero__inner { display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--space-5); max-width: 52rem; }
.projects-hero__title { font-size: clamp(2.75rem, 1rem + 5.5vw, 5rem); font-weight: 500; letter-spacing: -0.03em; line-height: 1.05; margin: 0; text-wrap: balance; }
.projects-hero__lead { font-size: clamp(1.15rem, 1rem + 0.9vw, 1.5rem); color: rgba(255,255,255,0.82); max-width: 46ch; margin: 0; text-wrap: pretty; }
/* Category legend = the three work types as a color-coded legend (the site's own category system).
   Colored dot + soft-white label, centered, placed below the heading - reads like a data legend,
   on-brand for a data engineer. */
.projects-hero__cats { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-4); margin: var(--space-2) 0 0; padding: 0; list-style: none; }
.projects-hero__cat {
  position: relative; padding-left: 1.15rem;
  font-size: var(--text-xs); font-weight: 700; letter-spacing: var(--tracking-label);
  text-transform: uppercase; line-height: 1; color: rgba(255,255,255,0.72);
}
.projects-hero__cat::before {
  content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--proj-data);
}
.projects-hero__cat--ml::before { background: var(--proj-ml); }
.projects-hero__cat--bi::before { background: var(--proj-bi); }
.projects-hero .scroll-cue {
  position: absolute; left: 50%; bottom: var(--space-3);
  transform: translateX(-50%); margin: 0; color: rgba(255,255,255,0.8);
}
.projects-hero .scroll-cue:hover, .projects-hero .scroll-cue:focus-visible { color: #fff; }
/* Transform-only rise (NO opacity) so the entrance can never blank the VT snapshot - structurally
   flash-safe, same principle as the work-rows fix. Staggered: categories -> title -> lead. */
@media (prefers-reduced-motion: no-preference) {
  .projects-hero__inner > * { animation: projectsCoverRise 0.7s var(--ease-out-expo) backwards; }
  .projects-hero__title { animation-delay: 0.05s; }
  .projects-hero__lead  { animation-delay: 0.14s; }
  .projects-hero__cats  { animation-delay: 0.24s; }
  @keyframes projectsCoverRise { from { transform: translateY(18px); } to { transform: none; } }
}

/* Telemetry instrument panel */
.hero-telemetry { margin-top: var(--space-5); max-width: 44rem; padding: var(--space-4); background: var(--color-accent-subtle); background: linear-gradient(155deg, var(--color-accent-subtle), color-mix(in srgb, var(--color-accent-subtle) 55%, var(--color-bg))); border: 1px solid color-mix(in srgb, var(--color-accent) 20%, var(--color-border)); border-radius: var(--border-radius); box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255,255,255,.06); }
.hero-telemetry__figures { display: flex; gap: var(--space-6); margin: 0 0 var(--space-3); }
.tl-item { display: flex; flex-direction: column; gap: 4px; }
.tl-label { font-size: var(--text-xs); letter-spacing: var(--tracking-label); color: var(--color-text-secondary); }
.tl-value { font-weight: 700; color: var(--color-accent); font-size: clamp(1.5rem, 1rem + 2vw, 2.25rem); line-height: 1; }
.tl-unit { font-size: 0.6em; color: var(--color-text-secondary); }
.hero-telemetry__flow .tl-pipe { width: 100%; height: 24px; display: block; }
.tl-nodes circle { fill: var(--color-surface-raised); stroke: var(--color-border); stroke-width: 1.5; }
.tl-nodes .tl-node--proc { stroke: var(--color-accent); }
.tl-legend { list-style: none; display: flex; justify-content: space-between; margin: 6px 0 0; padding: 0; font-size: var(--text-xs); color: var(--color-text-secondary); }
/* Telemetry captions on the accent-tinted panel (dark theme passes WCAG AA at the secondary color).
   On the cover the white .hero-home .tl-* overrides win. */
.hero-telemetry .tl-label,
.hero-telemetry .tl-unit,
.hero-telemetry .tl-legend { color: var(--color-text-secondary); }
.tl-legend--proc { color: var(--color-accent-text); }
.tl-pulse { opacity: 0; }
@media (prefers-reduced-motion: no-preference) {
  .tl-pulse { offset-path: path("M12 12 H588"); animation: tl-flow 1.8s var(--ease-out-expo) 0.6s 1 both; }
  @keyframes tl-flow { 0% { offset-distance: 0%; opacity: 0; } 8% { opacity: 1; } 92% { opacity: 1; } 100% { offset-distance: 100%; opacity: 0; } }
}
/* Telemetry figures wrap before they can overflow a narrow phone (same class of fix as footer chips) */
@media (max-width: 640px) {
  .hero-telemetry__figures { flex-wrap: wrap; gap: var(--space-4) var(--space-5); }
}

/* ============================================
   PROJECT META (detail pages)
   ============================================ */
.project-meta-block { display: flex; flex-wrap: wrap; gap: var(--space-4); margin: var(--space-3) 0 var(--space-5); padding-bottom: var(--space-3); border-bottom: 1px solid var(--color-border); }
.project-meta-block .pm-item dt { font-size: var(--text-xs); letter-spacing: var(--tracking-label); color: var(--color-text-secondary); text-transform: uppercase; }
.project-meta-block .pm-item dd { margin: 2px 0 0; font-weight: 500; }
.project-content { line-height: 1.75; }
/* Section headings take the category hue (michellegore's accent-colored case-study subheads),
   carrying the cover's color through the write-up. Falls back to brand blue when uncategorized. */
.project-content > h2 { margin-top: var(--space-7); color: var(--proj-accent); }
.project-content figure figcaption { font-size: var(--text-xs); color: var(--color-text-secondary); margin-top: 8px; }
.project-content img:not([src$=".svg"]) { border: 1px solid var(--color-border); border-radius: var(--border-radius-sm); }

/* ============================================
   CRAFT LAYER (selection, scrollbar, prose underlines)
   ============================================ */
::selection { background: var(--color-accent-subtle); color: var(--color-text); }

/* Subtle film grain - tactile depth on flat surfaces (very low opacity, non-interactive) */
body::after {
  content: ""; position: fixed; inset: 0; z-index: 9999; pointer-events: none; opacity: 0.02;
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='180'%20height='180'%3E%3Cfilter%20id='g'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.8'%20numOctaves='2'%20stitchTiles='stitch'/%3E%3CfeColorMatrix%20type='saturate'%20values='0'/%3E%3C/filter%3E%3Crect%20width='100%25'%20height='100%25'%20filter='url(%23g)'/%3E%3C/svg%3E");
}
@media print { body::after { display: none; } }
* { scrollbar-color: var(--color-border) transparent; }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-thumb { background: var(--color-border); border: 3px solid var(--color-bg); border-radius: 8px; }
::-webkit-scrollbar-thumb:hover { background: var(--color-text-secondary); }
.prose a, .about-body a:not(.btn), .project-content a {
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--color-accent-text) 45%, transparent);
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1.5px;
  transition: text-decoration-color var(--transition-fast) var(--ease-out-expo);
}
.prose a:hover, .about-body a:not(.btn):hover, .project-content a:hover { text-decoration-color: var(--color-accent-text); }

/* ============================================
   VIEW TRANSITIONS (card → detail morph)
   ============================================ */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: reduce) { @view-transition { navigation: none; } }
::view-transition-group(*) { animation-timing-function: var(--ease-out-expo); }

/* Persistent header (app-shell feel): lift the wordmark + nav into their own snapshot
   group so they stay pinned while the page content slides underneath them, instead of
   sliding away with the page. Most of the header is identical page-to-page; only the
   active-nav underline differs, so it cross-fades cleanly within this group. */
.site-header { view-transition-name: vt-header; }

/* (Removed the vt-phu name morph: it lifted "Phu Le" out of the page snapshot and
   tweened it separately, which tore a hole in the cohesive pull-down slide below.
   The name now slides with the page as one sheet — the michellegore.com look.) */

/* Page transition — michellegore.com style. Both pages are fully OPAQUE and slide as
   one continuous UPWARD motion: the new page RISES from below while the old page exits
   up and out the top, like scrolling down to the next section. (Matches Semplice's
   forward nav — content rises into place. Browser-back reverses it; see below.)
   No crossfade — you never see the two ghosted over each other, so the new page
   reads as "everything at once" the moment it arrives.
   CRITICAL: old and new MUST share identical duration + easing + (no) delay, or the
   seam between the two sheets tears (a gap or an overlap opens at the join).
   circ.out (0,.55,.45,1) is Semplice's content-slide easing. */
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) {
    animation: vt-page-exit 600ms cubic-bezier(0, 0.55, 0.45, 1) both;
    z-index: 1;
  }
  ::view-transition-new(root) {
    animation: vt-page-enter 600ms cubic-bezier(0, 0.55, 0.45, 1) both;
    z-index: 2;          /* new sits on top so the rising seam is clean */
    /* Leading-edge depth: a black shadow on the rising page's TOP edge so it reads as a
       physical sheet sliding up over the old one. The page-change *highlight* is now the
       top loading bar (.page-progress), not a blue seam glow — see PROJECT COVER section
       below / assets/js/page-progress.js. The shadow sits just above the top edge (negative
       y), so it shows only while sliding and vanishes once settled. */
    box-shadow: 0 -12px 32px rgba(0, 0, 0, 0.50);
  }

  /* Direction-aware (type set by assets/js/transition-direction.js): on browser-back,
     reverse the motion — new DESCENDS from above, old exits down and out the bottom — so
     returning feels like going back up the stack rather than another forward rise.
     Overriding animation-name keeps the shared 600ms / circ.out timing; the shadow flips
     to the bottom (now-leading) edge so the accent still rides the moving seam. */
  :root:active-view-transition-type(back)::view-transition-old(root) {
    animation-name: vt-page-exit-back;
  }
  :root:active-view-transition-type(back)::view-transition-new(root) {
    animation-name: vt-page-enter-back;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.50);   /* shadow flips to the now-leading bottom edge */
  }
}

@keyframes vt-page-exit       { to   { transform: translateY(-100%); } } /* fwd: old exits UP/top */
@keyframes vt-page-enter      { from { transform: translateY(100%); } }  /* fwd: new RISES from below */
@keyframes vt-page-exit-back  { to   { transform: translateY(100%); } }  /* back: old exits down/bottom */
@keyframes vt-page-enter-back { from { transform: translateY(-100%); } } /* back: new descends from top */

/* Top loading bar (michellegore.com style): a thin brand-blue bar pinned to the very top of
   the viewport that sweeps left -> right as a new page settles in, then fades. Driven by
   assets/js/page-progress.js (it adds .is-active to fill, then .is-done to fade on completion);
   it sweeps on every page arrival - cold load, refresh, and after a cross-document View
   Transition. This is the page-change "highlight" that replaced the old blue seam glow.
   Reduced motion hides it entirely (the JS also bails), so it never animates for those users. */
.page-progress {
  position: fixed;
  inset: 0 0 auto 0;
  height: 2px;
  z-index: 1100;                       /* above header (100) + skip-link (1000) */
  background: var(--color-accent);
  transform: scaleX(0);
  transform-origin: left center;
  pointer-events: none;
  view-transition-name: vt-progress;   /* lift out of the root page-rise snapshot - otherwise the fixed bar rises WITH the page AND sweeps live = two bars */
}
/* The bar is invisible (scaleX 0) at rest, so it has nothing to morph during a page transition;
   skip its snapshot animation so it never flickers or duplicates - the live sweep is the only motion. */
::view-transition-group(vt-progress) { animation: none; }
.page-progress.is-active {
  transform: scaleX(1);
  transition: transform 1100ms cubic-bezier(0.33, 1, 0.68, 1);   /* slow, smooth easeOutCubic - a deliberate decelerating glide (michellegore feel), not a snap */
}
.page-progress.is-done {
  opacity: 0;
  transition: opacity 320ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .page-progress { display: none; }
}

/* ============================================
   PROJECT COVER + SCROLL CUE  (michellegore.com-inspired)
   ============================================ */

/* Full-bleed cover: the project art fills the viewport with the eyebrow + title overlaid
   in white and a scroll cue pinned to the bottom edge - the michellegore.com cover treatment
   (image-first, white overlay text, chevron) adapted to our dark navy SVG art.
   svh dodges the mobile URL-bar height jump; the vh line is the older-engine fallback.
   The negative top margin cancels main's --header-h padding so the art bleeds up behind the fixed
   header (so the floating header reveals the art, not the page bg). */
.project-cover {
  position: relative;
  display: flex;
  width: 100%;
  margin-top: calc(-1 * var(--header-h));
  min-height: 100vh;
  min-height: 100svh;
  margin-bottom: var(--space-6);
  overflow: hidden;
}
.project-cover__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;          /* scale 16:9 art to fill the tall cover, cropping gracefully */
  object-position: 50% 50%;
  color: var(--proj-accent);  /* inline SVG art reads this via currentColor (raster fallback ignores) */
}
/* Inline cover SVG fills the header; preserveAspectRatio="slice" crops like object-fit:cover. */
.project-cover__img svg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
/* Cover art depth (michellegore /work covers): the art drifts down at a fraction of scroll
   speed while the page scrolls over it (parallax - the constant scale() is crop headroom so
   the drift never exposes an edge), and the inline SVG idles with a slow breathing zoom
   (her 50s coverZoom, retuned 1.24 → 1.045). Parallax is scroll-driven (view(), exit range =
   the cover leaving the first viewport) so there is no JS in the motion path; it degrades to
   the static cover without animation-timeline support. Both are reduced-motion-gated and
   transform-only (VT-snapshot-safe). Raster fallback covers get parallax only. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .project-cover__img {
      animation: cover-parallax linear both;
      animation-timeline: view();
      animation-range: exit 0% exit 100%;
    }
    @keyframes cover-parallax {
      from { transform: scale(1.12) translateY(0); }
      to   { transform: scale(1.12) translateY(6%); }
    }
  }
  .project-cover__img svg { transform-origin: 50% 80%; animation: cover-breathe 45s linear infinite; }
  @keyframes cover-breathe {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.045); }
  }
}
/* Scrim: top gradient for nav legibility + center radial vignette for centered text. */
.project-cover::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(0,0,0,0.35) 0%, rgba(0,0,0,0) 16%),                          /* top: nav legibility */
    linear-gradient(90deg, rgba(0,0,0,0.90) 0%, rgba(0,0,0,0.74) 30%, rgba(0,0,0,0.30) 52%, rgba(0,0,0,0) 76%),  /* left: text-column legibility (art reads on the right) */
    radial-gradient(ellipse 60% 70% at 16% 50%, var(--proj-wash), transparent 72%);           /* hue glow behind the text */
}
.project-cover__inner {
  position: relative;
  z-index: 1;
  align-self: center;          /* vertically centered, michellegore.com cover */
  width: 100%;
  padding-top: calc(var(--header-h) + var(--space-4));
  padding-bottom: var(--space-8);
  text-align: left;
}
/* Meta column sits on the left; the art bleeds through on the right. */
.project-cover__text { max-width: 34rem; margin-top: 0; }
.project-cover__eyebrow { color: var(--proj-accent); margin-bottom: var(--space-2); }
.project-cover__title {
  margin: 0;
  color: #fff;
  font-weight: 500;                 /* light cover title (michellegore inversion) */
  /* Gentle slope: ~28px on a 390px phone (no clip) up to ~60px on desktop. */
  font-size: clamp(1.75rem, 0.9rem + 3.2vw, 3.75rem);
  line-height: 1.05;
  letter-spacing: -0.04em;
  overflow-wrap: break-word;
  text-shadow: 0 2px 24px rgba(0,0,0,0.45);
}
.project-cover__desc {
  margin: var(--space-3) 0 0;
  max-width: 38ch;
  color: rgba(255,255,255,0.74);
  font-size: var(--text-lg);
  line-height: 1.5;
  text-wrap: pretty;
  text-shadow: 0 1px 12px rgba(0,0,0,0.5);
}
/* Credits on the cover - michellegore.com's vertical divided list (label over value, hairline rules). */
.project-cover .project-meta-block {
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  margin: var(--space-5) 0 0;
  max-width: 22rem;
  padding-bottom: 0;
  border-bottom: none;
}
.project-cover .project-meta-block .pm-item {
  padding: var(--space-2) 0;
  border-top: 1px solid rgba(255,255,255,0.16);
}
.project-cover .project-meta-block .pm-item:last-child { border-bottom: 1px solid rgba(255,255,255,0.16); }
.project-cover .project-meta-block .pm-item dt { color: rgba(255,255,255,0.6); }
.project-cover .project-meta-block .pm-item dd { color: #fff; display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
/* Cover credit pills: neutral white, brightened to read on the dark art (no category hue - the art + eyebrow already carry it). */
.project-cover .tag { background-color: rgba(255,255,255,0.06); border-color: rgba(255,255,255,0.22); color: rgba(255,255,255,0.92); }
.project-cover .tag:hover { background-color: #fff; color: #0a0f1a; border-color: #fff; }

/* Scroll cue: centered chevron link, reused by the project cover AND the home hero. */
.scroll-cue {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  margin: var(--space-4) auto 0;
  color: var(--color-text-secondary);
  transition: color var(--transition-base);
}
.scroll-cue:hover, .scroll-cue:focus-visible { color: var(--color-accent-text); }
/* On the cover: pin the chevron to the bottom edge, centered over the art, in white. */
.project-cover .scroll-cue {
  position: absolute;
  left: 50%;
  bottom: var(--space-3);
  transform: translateX(-50%);
  z-index: 2;
  margin: 0;
  color: rgba(255,255,255,0.85);
}
.project-cover .scroll-cue:hover, .project-cover .scroll-cue:focus-visible { color: #fff; }
.scroll-cue__icon { display: block; fill: currentColor; }

/* Restrained "scroll down" bob - off under reduced motion (cue stays static, still usable). */
@media (prefers-reduced-motion: no-preference) {
  .scroll-cue__icon { animation: scroll-cue-bob 2.4s var(--ease-out-expo) infinite; }
}
@keyframes scroll-cue-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(4px); }
}

/* Back-to-top: michellegore's muted bottom-right chevron (the scroll-cue polygon, flipped).
   Pure CSS control (markup in default.html): href="#top" scrolls home natively - smooth via
   the scroll-behavior rule above, instant under reduced motion. The fade-in past the first
   viewport is scroll-driven; without animation-timeline support it is simply always visible
   (subtle enough). The fade is functional show/hide, not decorative motion, so it is NOT
   reduced-motion-gated. */
.back-to-top {
  position: fixed; right: var(--space-4); bottom: var(--space-4); z-index: 90;
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;           /* touch-target floor */
  color: var(--color-text-secondary);
  transition: color var(--transition-base);
}
.back-to-top:hover, .back-to-top:focus-visible { color: var(--color-text); }
.back-to-top__icon { display: block; fill: currentColor; transform: rotate(180deg); }
@supports (animation-timeline: scroll()) {
  .back-to-top {
    animation: back-to-top-fade linear both;
    animation-timeline: scroll();
    animation-range: 0 90vh;
  }
  @keyframes back-to-top-fade {
    from { opacity: 0; visibility: hidden; }
    to   { opacity: 1; visibility: visible; }
  }
}

/* Prev/next band: layout only. The .detail-nav class on the same element supplies
   the top border + spacing. Empty <span> placeholders keep one-sided nav aligned. */
.project-nav { display: flex; justify-content: space-between; gap: var(--space-4); }
.project-nav__link {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  max-width: 22rem;
  color: var(--color-text);
  text-decoration: none;
  transition: color var(--transition-fast);
}
.project-nav__link--prev { margin-right: auto; }
.project-nav__link--next { margin-left: auto; text-align: right; }
.project-nav__kicker {
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-text-secondary);
}
.project-nav__title { font-size: var(--text-lg); font-weight: 600; letter-spacing: -0.01em; }
.project-nav__link:hover .project-nav__title,
.project-nav__link:focus-visible .project-nav__title { color: var(--color-accent-text); }

/* Sticky-header offset so scroll-cue anchor jumps don't tuck the target under the header.
   #project-body = project cover target; #glance = home hero cue target. */
#project-body, #glance { scroll-margin-top: 6rem; }   /* clear the header on cue jumps */

/* Card -> cover morph: keep both snapshots filling their box so a differing aspect ratio
   scales instead of stretching (also smooths the Home <-> About title morph). */
::view-transition-old(*), ::view-transition-new(*) { object-fit: cover; }

/* Smooth in-page scrolling for the cue anchors - disabled under reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

/* ============================================
   MOTION CRAFT - heading reveals (native, reduced-motion + no-support safe)
   ============================================ */
/* Hero title: masked line rise on entrance (single-line name → clip-safe).
   The mask is an ANIMATED clip-path, not overflow:hidden: it holds inset(0) through the
   rise (backwards fill covers the 1.05s delay = 0.14s stagger + 0.9s rise + margin), then
   expands outward so the title's blue glow (text-shadow above) blooms past the box instead
   of staying clipped forever. Reduced motion = no rise, no mask → glow just paints. */
.hero-home__title > span { display: inline-block; }
@media (prefers-reduced-motion: no-preference) {
  .hero-home__title { animation: hero-title-unmask 0.6s var(--ease-out-expo) 1.05s both; }
  .hero-home__title > span { animation: hero-title-rise 0.9s var(--ease-out-expo) 0.14s backwards; }
  @keyframes hero-title-rise { from { transform: translateY(105%); } to { transform: translateY(0); } }
  @keyframes hero-title-unmask { from { clip-path: inset(0); } to { clip-path: inset(-120px); } }
}

/* Section titles: scroll-driven rise + fade. No clip → descenders safe; degrades to fully visible. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .section-title {
      animation: section-title-rise both linear;
      animation-timeline: view();
      animation-range: entry 0% cover 14%;
    }
    @keyframes section-title-rise {
      from { opacity: 0; transform: translateY(0.6em); }
      to   { opacity: 1; transform: translateY(0); }
    }
  }
}

/* ============================================
   PRINT (the live site is the résumé)
   ============================================ */
@media print {
  .site-header, .nav-menu, .nav-toggle, .cta-buttons, .hero-telemetry__flow, .skip-to-content, .site-footer, .back-to-top { display: none !important; }
  /* Reset the dark palette to ink-on-white for print (surfaces/borders too, so they don't bleed dark). */
  :root { --color-text: #000; --color-text-secondary: #333; --color-bg: #fff; --color-background: #fff;
    --color-surface: #fff; --color-surface-alt: #fff; --color-surface-raised: #fff; --color-border: #ccc; }
  body { background: #fff; color: #000; }
  a { color: #000; text-decoration: underline; }
  .hero-home, .section, .section-sm { padding-block: 1rem; }
  .project-card, .card { break-inside: avoid; box-shadow: none; border: 1px solid #ccc; }
}
