@charset "utf-8";

/* ============================================================
   MODERN PORTFOLIO DESIGN SYSTEM
   Dark-first, minimal, with gradient accents
   ============================================================ */

/* Metric-matched fallback for Inter: local Arial scaled so text occupies the
   same width as Inter before the web font loads. Prevents layout shift (e.g.
   the "Projects" nav item reflowing) when Inter swaps in. */
@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial'), local('Helvetica Neue'), local('Helvetica'), local('Roboto');
  ascent-override: 90.44%;
  descent-override: 22.52%;
  line-gap-override: 0%;
  size-adjust: 106.17%;
}

/* --- CSS Custom Properties --- */
:root {
  --color-bg: #0d0d0d;
  --color-bg-alt: #131313;
  --color-surface: #191919;
  --color-surface-hover: #212121;
  --color-border: rgba(255, 255, 255, 0.10);
  --color-text: #e0e0e0;
  --color-text-muted: #a3a3a3;
  --color-text-subtle: #8a8a8a;
  --color-heading: #f5f5f5;
  /* Near-monochrome (true neutral, Option A): accent is plain white; color comes from the work. */
  --color-accent: #ffffff;
  --color-accent-hover: #ffffff;
  --color-accent-subtle: rgba(255, 255, 255, 0.08);
  --color-accent-glow: rgba(255, 255, 255, 0.14);
  --gradient-accent: var(--color-accent); /* de-template: flat accent (was a 3-stop indigo->pink gradient) */
  --gradient-surface: linear-gradient(135deg, rgba(129, 140, 248, 0.08) 0%, rgba(192, 132, 252, 0.04) 100%);
  --navbar-bg: rgba(10, 10, 15, 0.8);
  --media-icon-filter: brightness(0) invert(1);
  --hero-glow-1: transparent; /* de-template: hero ambient glow removed */
  --hero-glow-2: transparent;
  --btn-glow: transparent; /* de-template: button glow removed */
  --btn-glow-hover: transparent;
  --font-sans: 'Inter', 'Inter Fallback', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-display: 'Bricolage Grotesque', var(--font-sans); /* distinctive display face; Inter for body */
  /* Type scale — snap every font-size onto these steps for one consistent system.
     xs badges/labels · sm captions/sublines/secondary · base body · md h3/lead ·
     lg h2 · xl h1/page-title · 2xl hero heading. */
  --fs-xs: 0.8rem;
  --fs-sm: 0.9rem;
  --fs-base: 1rem;
  --fs-md: 1.2rem;
  --fs-lg: 1.5rem;
  --fs-xl: 2rem;
  --fs-2xl: 2.2rem;
  --radius: 12px;
  --radius-lg: 20px;
  --radius-sm: 8px;
  --radius-image: 8px; /* image corners; not overridden by the CMS theme */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.5);
  --shadow-glow: none; /* de-template: card hover glow removed */
  --transition-fast: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  --transition-smooth: 0.4s cubic-bezier(0.4, 0, 0.2, 1);
  --transition-spring: 0.3s cubic-bezier(0.4, 0, 0.2, 1); /* de-template: flat ease-out (was bounce) */
}

/* --- Light Mode --- */
[data-theme="light"] {
  --color-bg: #efefef;
  --color-bg-alt: #e8e8e8;
  --color-surface: #f7f7f7;
  --color-surface-hover: #ffffff;
  --color-border: rgba(0, 0, 0, 0.12);
  --color-text: #1a1a1a;
  --color-text-muted: #595959;
  --color-text-subtle: #767676;
  --color-heading: #0a0a0a;
  /* Near-monochrome (true neutral, Option A): accent is near-black; color comes from the work. */
  --color-accent: #0a0a0a;
  --color-accent-hover: #0a0a0a;
  --color-accent-subtle: rgba(0, 0, 0, 0.05);
  --color-accent-glow: rgba(0, 0, 0, 0.12);
  --gradient-accent: var(--color-accent); /* de-template: flat accent */
  --gradient-surface: linear-gradient(135deg, rgba(99, 102, 241, 0.05) 0%, rgba(168, 85, 247, 0.03) 100%);
  --navbar-bg: rgba(248, 249, 252, 0.85);
  --media-icon-filter: brightness(0) invert(0);
  --hero-glow-1: transparent;
  --hero-glow-2: transparent;
  --btn-glow: transparent;
  --btn-glow-hover: transparent;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.08);
  --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.1);
  --shadow-glow: none;
}

/* --- Global Reset & Base --- */
html {
  scroll-behavior: smooth;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Always reserve the scrollbar gutter so the nav doesn't shift between short
     pages (e.g. the private gallery) and long ones, or when a tall dropdown
     opens. Fallback to overflow-y: scroll for browsers without gutter support. */
  overflow-y: scroll;
  scrollbar-gutter: stable;
}

body {
  font-family: var(--font-sans) !important;
  background-color: var(--color-bg) !important;
  color: var(--color-text) !important;
  line-height: 1.7;
  letter-spacing: -0.01em;
  /* clip (not hidden) prevents horizontal scroll without making <body> a scroll
     container — which would break position: sticky (e.g. the project TOC). */
  overflow-x: clip;
}

::selection {
  background: rgba(255, 255, 255, 0.22);
  color: #fff;
}

/* White text is used to emphasize copy in dark mode. In light mode pure white
   would be invisible, so flip it to the dark heading color (preserving the
   emphasis). Covers the <font color> markup the editor produces plus span/rgb
   variants. */
[data-theme="light"] font[color="#ffffff" i],
[data-theme="light"] font[color="#fff" i],
[data-theme="light"] font[color="white" i],
[data-theme="light"] [style*="rgb(255, 255, 255)"],
[data-theme="light"] [style*="#ffffff"],
[data-theme="light"] [style*="#fff;"],
[data-theme="light"] [style*="#fff "] {
  color: var(--color-heading) !important;
}

/* --- Typography --- */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display) !important;
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--color-heading);
  line-height: 1.15;
}

h1 { font-size: clamp(3rem, 7vw, 5.5rem); font-weight: 500; }
h2 { font-size: var(--fs-2xl); }
h3 { font-size: var(--fs-lg); }
h4 { font-size: var(--fs-md); font-weight: 500; }
h5 { font-size: var(--fs-base); font-weight: 600; }
h6 {
  font-family: var(--font-sans) !important;
  font-size: var(--fs-sm);
  font-weight: 400;
  color: var(--color-text-muted);
  letter-spacing: 0;
}

p {
  color: var(--color-text);
  font-size: var(--fs-md);
  line-height: 1.75;
}

a {
  color: var(--color-accent);
  text-decoration: none;
  transition: color var(--transition-fast);
}

a:hover {
  color: var(--color-accent-hover);
  text-decoration: none;
}

/* Accent policy (near-mono): content links are full-contrast text with a 1px
   underline, not a colored link. Hover deepens the underline, never the hue.
   Scoped to prose so nav links and buttons are unaffected. */
.projectdesc a,
.projectdesc-list a,
.case-content p a,
.figure-caption a {
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--color-text-subtle);
}
.projectdesc a:hover,
.projectdesc-list a:hover,
.case-content p a:hover,
.figure-caption a:hover {
  text-decoration-color: currentColor;
}

strong {
  color: var(--color-heading);
  font-weight: 600;
}

/* --- Navbar --- */
.navbar {
  background: var(--navbar-bg) !important;
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border-bottom: 1px solid var(--color-border);
  padding: 0.9rem 1.5rem !important;
  transition: all var(--transition-smooth);
  position: sticky;
  top: 0;
  z-index: 1030;
}

.navbar-brand {
  font-family: var(--font-display) !important;
  font-weight: 500 !important;
  font-size: var(--fs-base) !important;
  letter-spacing: -0.01em;
  color: var(--color-text-muted) !important;
  transition: color var(--transition-fast);
}

.navbar-brand:hover {
  color: var(--color-text) !important;
}

.nav-link {
  font-size: var(--fs-sm) !important;
  font-weight: 400;
  color: var(--color-text-muted) !important;
  padding: 0.5rem 1rem !important;
  transition: color var(--transition-fast);
  letter-spacing: 0;
}

.nav-link:hover,
.nav-link:focus {
  color: var(--color-heading) !important;
}

.navbar-dark .navbar-toggler {
  border-color: var(--color-border);
}

.dropdown-menu {
  background: var(--color-surface) !important;
  border: 1px solid var(--color-border) !important;
  border-radius: var(--radius) !important;
  padding: 0.5rem !important;
  box-shadow: var(--shadow-lg);
  backdrop-filter: blur(20px);
  margin-top: 0.5rem !important;
}

.dropdown-item {
  color: var(--color-text-muted) !important;
  font-size: var(--fs-sm);
  padding: 0.5rem 1rem !important;
  border-radius: var(--radius-sm);
  transition: all var(--transition-fast);
}

.dropdown-item:hover,
.dropdown-item:focus {
  background: var(--color-accent-subtle) !important;
  color: var(--color-heading) !important;
}

/* Lock icon beside a private project's name in the projects dropdown. */
.nav-lock-icon {
  vertical-align: -0.12em;
  margin-right: 0.4em;
  opacity: 0.7;
}

.dropdown-divider {
  border-top-color: var(--color-border) !important;
  margin: 0.3rem 0.5rem !important;
}

/* --- Hero / Jumbotron --- */
.jumbotron {
  background: var(--color-bg) !important;
  border-radius: 0 !important;
  padding: 4rem 2rem 4.5rem !important;   /* landing hero keeps generous breathing room */
  margin-bottom: 0 !important;
  position: relative;
  overflow: hidden;
}

.jumbotron::before {
  content: '';
  position: absolute;
  top: -50%;
  left: -25%;
  width: 150%;
  height: 150%;
  background: radial-gradient(ellipse at 30% 50%, var(--hero-glow-1) 0%, transparent 60%),
              radial-gradient(ellipse at 70% 50%, var(--hero-glow-2) 0%, transparent 60%);
  pointer-events: none;
  animation: heroGlow 8s ease-in-out infinite alternate;
}

@keyframes heroGlow {
  0% { opacity: 0.6; transform: scale(1); }
  100% { opacity: 1; transform: scale(1.05); }
}

.jumbotron h1 {
  font-size: var(--fs-xl);   /* same header size as every other page */
  font-weight: 600;
  color: var(--color-heading);
  margin-bottom: 1.2rem;
  position: relative;
  z-index: 1;
}

.jumbotron p {
  font-size: var(--fs-md);
  max-width: 640px;
  margin-left: auto;
  margin-right: auto;
  color: var(--color-text-muted);
  position: relative;
  z-index: 1;
}

/* "Previously at" wordmark bar under the hero role line. Monochrome and
   theme-driven: the names use --color-heading (white in dark, ink in light);
   the "Previously" label and dots stay quiet. Wordmarks are plain text (no
   brand-asset files); to swap in an official monochrome logo later, replace a
   .prev-bar__mark's text with an inline <svg fill="currentColor"> at the same
   slot and it inherits the color/flip for free. */
/* Full-width affiliations strip beneath the hero grid: a quiet "worked with"
   band that transitions into the work below. Label sits inline with the marks,
   left-aligned to the hero; wraps to its own line only when space runs out. */
.prev-bar {
  display: flex;
  flex-direction: row;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.9rem 1.8rem;
  margin-top: 3.25rem;
  position: relative;
  z-index: 1;
}
.prev-bar__label {
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  font-weight: 400;
  letter-spacing: 0;
  color: var(--color-text-subtle);
}
.prev-bar__marks {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 2.6rem;
}
.prev-bar__mark {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: var(--fs-sm);
  letter-spacing: 0;
  color: var(--color-text-muted);
  line-height: 1;
  display: inline-flex;
  align-items: center;
}
/* Sourced brand logos (Netflix, Meta, Immersion) are supplied brand assets in
   assorted native colors (red PNG, white SVGs). Normalize them all to one
   neutral, theme-aware tone: brightness(0) invert(1) flattens any source color
   to white for dark mode; brightness(0) flattens it to near-black for light.
   Muted opacity keeps them quiet against the row. Heights are tuned per-logo so
   each reads at a similar optical weight despite different lockup proportions. */
.prev-bar__logo {
  display: block;
  width: auto;
  height: 1.5rem;
  opacity: 0.55;
  filter: brightness(0) invert(1);
  border-radius: 0; /* wordmarks: cancel the global img corner-rounding */
}
[data-theme="light"] .prev-bar__logo {
  filter: brightness(0);
}
/* All three SVGs are cropped to their artwork bounds, so a shared height renders
   them at the same optical size. Netflix and Meta fill their box with letters;
   Immersion's tall flame mark makes its wordmark read smaller, so it gets a
   small upward nudge to balance. */
.prev-bar__logo--immersion { height: 1.7rem; }

/* --- Resume header --- */
/* Shared 860px measure for the resume column (was a repeated inline style). */
.container.resume-container { max-width: 860px; }
.resume-hero { position: relative; padding-top: 2.5rem; padding-bottom: 1rem; }
.resume-name { font-size: var(--fs-xl); font-weight: 600; margin-bottom: 0.25rem; }
.resume-title { color: var(--color-text-muted); font-size: var(--fs-base); margin-bottom: 0; }
.resume-links { font-size: var(--fs-sm); line-height: 2; margin-top: 0.75rem; }

/* Download PDF: on wide viewports pin it to the right of the 860px column and
   keep it in view on scroll (secondary style); inline in the header below. */
@media (min-width: 1360px) {
  .resume-hero .resume-download {
    position: fixed;
    top: 6.5rem;
    left: calc(50% + 430px + 1.75rem);
    margin: 0 !important;
    white-space: nowrap;
  }
  .resume-links { margin-top: 0; }
}


/* Hero "split" layout: left identity column (name, role, Previously, CTA)
   beside a right column carrying the statement + intro. Folds two stacked
   centered sections into one row, cutting the homepage's vertical height. */
.jumbotron--split { text-align: left; }
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.62fr) minmax(0, 1fr);
  gap: 4.5rem;
  align-items: center;   /* tagline centers vertically against the greeting block */
}
.hero-identity {
  padding-right: 0;
}
.hero-identity h4 {
  font-family: var(--font-sans) !important;   /* Inter — the byline is the quiet line; the tagline carries the display face (overrides the h1-h6 display rule) */
  font-size: var(--fs-base);
  font-weight: 400;
  color: var(--color-text-muted);
  margin-top: 0.4rem;
}
/* Left-align the Previously bar + CTA buttons inside the split hero (the
   base .prev-bar stays centered for any centered layout). */
.jumbotron--split .prev-bar,
.jumbotron--split .home-cta { justify-content: flex-start; }
/* Compact hero CTAs: content-width, side by side, left-aligned. The short
   labels ("Selected work" / "Private work") fit on one row without wrapping. */
/* Supporting line, not a second headline: "Hi, I'm Bill" leads; this sits under
   it as a smaller, lighter positioning line. */
.hero-statement h2 {
  font-size: var(--fs-md);
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--color-text);
  line-height: 1.4;
  margin: 0 0 0.9rem;
}
/* Hero tagline: the positioning thesis, set larger and in full-strength text so
   it reads as a statement, not a caption. The detailed bio now lives in the
   closing band at the foot of the page. */
.hero-statement p {
  max-width: 34ch;
  margin: 0;
  font-family: var(--font-display);   /* Bricolage Grotesque — the tagline is the emphasized hero statement */
  font-size: clamp(1.35rem, 1.9vw, 1.65rem);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: -0.01em;
  color: var(--color-heading);
}
/* The boxed hero-stat is a <p>, so the reset above also zeroes its margin —
   restore clear separation from the intro with higher specificity. */
.hero-statement .hero-stat {
  margin-top: 2rem;
}
/* Hero proof stat: an impact metric (e.g. "8h → 3h") + short label, sized to
   read as a number in the hero. Theme-aware via tokens; wraps on narrow widths.
   Inert until the markup (.hero-stat / -value / -label) is added by Content. */
/* De-chromed (Phase 5): a plain line of type, not a bordered surface card. */
.hero-stat {
  display: block;
  margin-top: 2rem;
  max-width: 100%;
}
.hero-stat-value { grid-column: 2; }
.hero-stat-label { grid-column: 2; }
/* Proof stat as a quiet inline credential (de-emphasized from the big number). */
.hero-stat {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-top: 1.75rem;
}
.hero-stat-value {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-md);
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--color-heading);
  white-space: nowrap;
}
.hero-stat-label {
  font-size: var(--fs-sm);
  line-height: 1.3;
  color: var(--color-text-muted);
}
/* De-chromed (Phase 5): the decorative purple-gradient controller icon is removed. */
.hero-stat::before { content: none; }

/* Compact featured strip: a 3-up row of the featured cards placed directly
   under the hero so real work lands above the fold. Cards reuse the grid
   badge + year pill; smaller than the main grid so the row fits at 1280x800.
   Contract (CMS renders into the index.php placeholder Content adds):
     <div class="hero-featured">
       <figure class="hero-featured__card">
         <a href="..." class="grid-thumb-link">BADGE + <span class="card-year">…</span> + <img src="publicThumb"></a>
         <figcaption>Title</figcaption>
       </figure>  (x3)
     </div>  */
.featured-strip {
  padding-top: 0.5rem;
  scroll-margin-top: 100px; /* clear the 70px sticky nav when jumping to #work */
}
.hero-featured {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.25rem;
}
.hero-featured__card {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.hero-featured__card .grid-thumb-link {
  position: relative;
  display: block;
  border-radius: var(--radius);
  overflow: hidden;
}
.hero-featured__card img {
  display: block;
  width: 100%;
  border-radius: 0;
}
.hero-featured__card figcaption {
  font-family: var(--font-display);
  font-weight: 500;
  /* Match the "More work" grid card titles (.col-md-3 h4 = 1.1rem) so a
     project name is the same size in both sections. */
  font-size: var(--fs-md);
  color: var(--color-heading);
}
/* Secondary line under the featured card title (matches the gallery
   thumb-label-sub role: smaller, muted, body font). */
.hero-featured__sub {
  display: block;
  margin-top: 0.3rem;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: var(--fs-sm);
  line-height: 1.4;
  color: var(--color-text-muted);
}
/* Primary section heading above the featured strip — sits above the
   de-emphasized "Additional work" heading (1.6rem/500) in the hierarchy. */
.featured-heading {
  font-size: var(--fs-2xl);
  font-weight: 600;
  color: var(--color-heading);
  text-align: center;
  margin: 0 0 1.5rem;
}
@media (max-width: 767px) {
  .hero-featured { gap: 0.6rem; }
  .hero-featured__card figcaption { font-size: var(--fs-xs); }
}
@media (max-width: 767px) {
  .hero-grid { grid-template-columns: 1fr; gap: 1.6rem; }
  .hero-identity {
    border-right: 0;
    padding-right: 0;
    padding-bottom: 1.6rem;
    border-bottom: 1px solid var(--color-border);
  }
}

/* Three principle boxes laid across (was stacked full-width rows with a
   decorative hover). Shorter section, no hover. */
/* Closing "how I work" band: the principles moved below the work on the
   homepage (rendered home-only into the footer.html placeholder). Sets it off
   from the gallery above with a top divider. */
.principles-band {
  border-top: 1px solid var(--color-border);
  padding: 3rem 0;
}

/* Previous/Next project navigation on project pages (replaces the footer
   mega-grid). Prev on the left, Next on the right; either link is omitted at
   the ends (first/last project), and the remaining one keeps its side. */
.prev-next-nav {
  display: flex;
  gap: 1.5rem;
  max-width: 1100px;
  margin: 2.5rem auto 0;
  padding-top: 2rem;
}
.prev-next-nav__prev,
.prev-next-nav__next {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  max-width: 46%;
  color: var(--color-text-muted);
  transition: color var(--transition-fast);
}
.prev-next-nav__next {
  margin-left: auto;
  text-align: right;
}
.prev-next-nav__dir {
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--color-text-subtle);
}
.prev-next-nav__title {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: 500;
  color: var(--color-heading);
  transition: color var(--transition-fast);
}
.prev-next-nav__prev:hover .prev-next-nav__title,
.prev-next-nav__next:hover .prev-next-nav__title {
  color: var(--color-accent);
}
@media (max-width: 575.98px) {
  .prev-next-nav__title { font-size: var(--fs-sm); }
  /* Stack prev/next as a tidy left-aligned column: a long title fits on one line
     (a ~46% column wrapped "Netflix Games Workspace"), and the two entries read as
     a list instead of sitting diagonally offset (prev top-left / next bottom-right,
     which looked worst when the titles were short). The eyebrows carry the
     direction, so drop next's right-alignment. */
  .prev-next-nav { flex-direction: column; gap: 1.1rem; }
  .prev-next-nav__prev,
  .prev-next-nav__next { max-width: 100%; margin-left: 0; text-align: left; }
}
.principles-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2rem;
}
.principle__num {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 600;
  line-height: 1;
  opacity: 0.35;
  color: var(--color-heading);
}
.principle h5 { margin: 0.6rem 0 0.4rem; }
.principle p { margin: 0; font-size: var(--fs-sm); line-height: 1.6; }
@media (max-width: 767px) {
  .principles-grid { grid-template-columns: 1fr; gap: 1.4rem; }
}

/* --- Buttons --- */
/* System: 12px rounded rectangles (not pills). Standard height 40px; the compact
   variant (.btn-compact) is 24px. Height is fixed and content is flex-centered,
   so horizontal padding sets the width. */
.btn-primary {
  background: var(--color-heading) !important; /* near-mono: solid light fill, dark text */
  border: none !important;
  border-radius: 12px !important;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 1.75rem !important;
  font-family: var(--font-sans) !important;
  font-weight: 500 !important;
  font-size: var(--fs-sm) !important;
  line-height: 1;
  letter-spacing: 0;
  color: var(--color-bg) !important;
  transition: all var(--transition-smooth) !important;
  box-shadow: 0 4px 20px var(--btn-glow);
  position: relative;
  z-index: 1;
}

.btn-primary:hover {
  box-shadow: 0 6px 30px var(--btn-glow-hover) !important;
}

.btn-outline-dark {
  border-color: var(--color-border) !important;
  color: var(--color-text-muted) !important;
  border-radius: 12px !important;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 1.4rem !important;
  font-size: var(--fs-sm) !important;
  font-weight: 500;
  line-height: 1;
  transition: all var(--transition-fast) !important;
  background: transparent !important;
}

.btn-outline-dark:hover {
  background: var(--color-surface) !important;
  border-color: var(--color-accent) !important;
  color: var(--color-accent) !important;
}

/* Secondary action button — a slight translucent fill (no stroke) with heading-
   color text; legible in both themes via the tokens. */
.btn-outline-light {
  background: var(--color-accent-subtle) !important;
  border: none !important;
  border-radius: 12px !important;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 1.75rem !important;
  font-family: var(--font-sans) !important;
  font-weight: 500 !important;
  font-size: var(--fs-sm) !important;
  line-height: 1;
  color: var(--color-heading) !important;
  transition: all var(--transition-smooth) !important;
}

/* Compact button variant: 24px tall. Radius is 8px (not the standard 12px): at
   24px tall a 12px radius equals half the height and renders as a pill, so the
   compact size needs a smaller corner to stay a rectangle. */
.btn.btn-compact {
  height: 24px;
  padding: 0.11rem 0.85rem 0 !important; /* top nudge = optical centering at 0.8rem */
  font-size: var(--fs-xs) !important;
  border-radius: 8px !important;
}

.btn-outline-light:hover {
  background: rgba(255, 255, 255, 0.14) !important;
  color: var(--color-heading) !important;
}
[data-theme="light"] .btn-outline-light:hover {
  background: rgba(0, 0, 0, 0.09) !important;
}

/* Shorter hero CTA buttons. */
.home-cta .btn {
  padding-top: 0.5rem !important;
  padding-bottom: 0.5rem !important;
}

/* Homepage call-to-action buttons: center them with an even gap (instead of the
   inline margin), and stack to equal full-width buttons on small screens. */
.home-cta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1.5rem;
}
.home-cta .btn { margin-left: 0 !important; }
@media (max-width: 575.98px) {
  .home-cta { flex-direction: column; }
  .home-cta .btn {
    width: 100%;
    max-width: 340px;
  }
}
/* Scroll-to-work CTA is mobile-only: redundant on desktop where the featured
   strip is already on screen; useful on mobile where work is below the fold. */
@media (min-width: 768px) {
  .home-cta__scroll { display: none; }
}

/* Hero CTA hierarchy: Request access = tertiary ghost (no border, muted, faint
   hover). Latest work = the prominent action — filled primary on desktop where
   the scroll CTA is hidden, and it stays outline on mobile so it doesn't
   compete with the filled scroll "Selected work" primary there. */
.home-cta__request {
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  color: var(--color-text-muted) !important;
  padding: 0.75rem 1rem !important;
}
.home-cta__request:hover {
  background: var(--color-surface) !important;
  color: var(--color-text) !important;
  transform: none !important;
}
@media (min-width: 768px) {
  .home-cta__private {
    background: var(--color-heading) !important; /* near-mono: light fill, dark text */
    border-color: transparent !important;
    color: var(--color-bg) !important;
    box-shadow: 0 4px 20px var(--btn-glow) !important;
  }
  .home-cta__private:hover {
    background: var(--color-accent) !important;
    color: var(--color-bg) !important;
    box-shadow: 0 6px 30px var(--btn-glow-hover) !important;
  }
}

/* --- Sections --- */
section {
  padding: 1rem 0;
}

/* Intro tagline section */
section .container > .row > .col-12.text-center > h1 {
  font-size: clamp(1.8rem, 3.5vw, 2.6rem);
  font-weight: 600;
  color: var(--color-heading);
  margin-bottom: 0.8rem;
}

/* --- Feature Cards (Design Thinker, etc.) --- */
.media {
  padding: 1.8rem;
  background: var(--color-surface);
  border-radius: var(--radius);
  border: 1px solid var(--color-border);
  transition: all var(--transition-smooth);
  height: 100%;
}

.media:hover {
  border-color: var(--color-accent-glow);
  background: var(--color-surface-hover);
  transform: translateY(-4px);
  box-shadow: var(--shadow-glow);
}

.media img {
  filter: var(--media-icon-filter);
  opacity: 0.7;
}

/* Project sidebar: remove card container from metadata */
.bg-light .media {
  background: transparent;
  border: none;
  padding: 0;
}
.bg-light .media:hover {
  transform: none;
  box-shadow: none;
  background: transparent;
  border-color: transparent;
}

.media .media-body {
  color: var(--color-text-muted);
  font-size: var(--fs-sm);
  line-height: 1.7;
}

.media .media-body h5 {
  color: var(--color-heading);
  font-family: var(--font-display) !important;
  margin-bottom: 0.75rem !important;
}

.list-unstyled {
  margin-bottom: 0;
}

/* Topic tags are hidden site-wide — the subtitle carries the role/context. Kept
   the markup + styling below so they can be re-enabled by dropping this one line. */
.project-tags {
  display: none;
  flex-wrap: wrap;
  gap: 0.3rem;
  padding-top: 0.25rem;
}

.project-tag {
  display: inline-block;
  background: var(--color-accent-subtle, rgba(99,102,241,0.1));
  color: var(--color-accent);
  font-size: var(--fs-xs);
  font-weight: 500;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  letter-spacing: 0.02em;
}

/* --- Project Thumbnails / Gallery --- */
a img {
  transition: all var(--transition-smooth) !important;
  border-radius: var(--radius);
  opacity: 1;
}

a img:hover {
  opacity: 0.85;
  transform: scale(1.03);
  box-shadow: var(--shadow-lg);
}

/* "Selected Work" / "More work" heading (now id="more-work"; #work is the
   featured-strip anchor and must stay a normal full-width block). */
#more-work {
  font-size: var(--fs-sm);   /* match the de-emphasized resume section labels */
  font-weight: 400;
  letter-spacing: 0.02em;
  color: var(--color-text-muted);
  position: relative;
  display: inline-block;
}

/* Project cards in footer gallery */
.col-md-6 > a > img,
.col-md-4 > a > img,
.col-md-3 > a > img {
  border-radius: var(--radius);
  transition: all var(--transition-smooth);
}

/* Featured gallery thumbnails fill their column so every card is the same size,
   even when a source image is lower resolution than the others. */
.col-md-6 > a > img,
.col-md-4 > a > img {
  width: 100%;
}

/* Thumbnail hover overlay */
.thumb-overlay {
  position: relative;
  display: block;
  border-radius: var(--radius);
  overflow: hidden;
}

/* "Private" badge (lock icon on the left) shown on the thumbnail of a private
   project surfaced on the public grid. The featured (large) cards wrap their
   thumb in .grid-thumb-link so the absolute badge has a positioning context.
   Block (not inline-block) so a full-width thumbnail/placeholder fills the
   column exactly like a non-private featured card, instead of shrink-wrapping. */
.grid-thumb-link {
  position: relative;
  display: block;
}

.grid-private-badge {
  position: absolute;
  top: 0.7rem;
  left: 0.7rem;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 0.36rem;
  padding: 0.3rem 0.66rem 0.3rem 0.56rem;
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.02em;
  /* Inverted + neutralized (was gold #f0c651); theme-independent light pill with
     fixed dark text/lock — sits on a thumbnail, must not follow --color-bg. */
  color: #111111;
  background: rgba(245, 245, 245, 0.95);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  border-radius: 999px;
  pointer-events: none;
}

.grid-private-badge svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
}

.thumb-overlay img {
  display: block;
  width: 100%;
  transition: transform var(--transition-smooth), opacity var(--transition-smooth) !important;
}

.thumb-overlay .thumb-label {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0.75rem;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: #fff;
  background: rgba(0, 0, 0, 0.65);
  backdrop-filter: blur(4px);
  opacity: 0;
  transition: opacity var(--transition-smooth);
  pointer-events: none;
  border-radius: var(--radius);
}

/* Subtitle shown beneath the name when hovering a grid (non-featured) thumb. */
.thumb-overlay .thumb-label-sub {
  margin-top: 0.4rem;
  max-width: 92%;
  font-size: var(--fs-xs);
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.3;
  color: rgba(255, 255, 255, 0.82);
}

.thumb-overlay:hover .thumb-label {
  opacity: 1;
}

.thumb-overlay:hover img {
  transform: scale(1.05);
  opacity: 1 !important;
}

/* Solid-color placeholder for projects without a thumbnail image.
   Capped to the standard 400px thumbnail size (and centered) so it never
   renders larger than the real thumbnails on the main gallery. */
.thumb-placeholder {
  width: 100%;
  max-width: 400px;
  margin-left: auto;
  margin-right: auto;
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 1.25rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  overflow: hidden;
  transition: border-color var(--transition-smooth);
}

.thumb-placeholder span {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(0.9rem, 2.2vw, 1.3rem);
  line-height: 1.25;
  color: var(--color-heading);
}

.thumb-overlay .thumb-placeholder {
  border-radius: var(--radius);
}

a:hover > .thumb-placeholder,
.thumb-overlay:hover .thumb-placeholder {
  border-color: var(--color-accent);
}

/* Project titles under thumbnails */
.col-md-6 h4,
.col-md-4 h4,
.col-md-3 h4 {
  font-size: var(--fs-md);
  font-weight: 500;
  margin-top: 1rem;
  color: var(--color-heading);
}

.col-md-6 h6,
.col-md-4 h6,
.col-md-3 h6 {
  font-size: var(--fs-sm);
  line-height: 1.4;
  color: var(--color-text-muted);
  font-weight: 400;
  margin-top: 0.3rem;
  margin-bottom: 1.5rem;
}

/* Year label on every project card. Always visible (no hover needed) so the
   grid reads as dated at a skim. Sits top-right of the thumbnail; the private
   lock badge is top-left, so the two never collide. Matches the badge's pill
   treatment for a consistent overlay language. Positioning context is the
   card's anchor (.grid-thumb-link on featured, .thumb-overlay on grid cards). */
.card-year {
  position: absolute;
  top: 0.7rem;
  right: 0.7rem;
  z-index: 2;
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  /* Inverted, theme-independent (sits on a thumbnail, not the page): a light
     pill with fixed dark text — must not follow --color-bg, which flips light. */
  color: #111111;
  background: rgba(245, 245, 245, 0.95);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  padding: 0.3rem 0.6rem;
  border-radius: 999px;
  pointer-events: none;
}

/* Tablet + mobile: the Private badge (top-left) and year pill (top-right)
   collide on narrow cards (the featured strip's 3-up cards, and 4-up col-md-3 /
   2-up col-6 grid cards). On any card carrying both, drop the year pill to the
   bottom-right so the two never share the top row. Applied up to 991px because
   the 4-up grid cards are still narrow enough to collide at the 768px tablet
   width; desktop (>=992px) is unchanged. */
@media (max-width: 991px) {
  a:has(.grid-private-badge) .card-year {
    top: auto;
    bottom: 0.6rem;
  }
}
/* Phones: also tighten both badges a touch, where cards are the smallest. */
@media (max-width: 767px) {
  .grid-private-badge,
  .card-year {
    font-size: var(--fs-xs);
    padding: 0.26rem 0.52rem;
  }
  .grid-private-badge {
    gap: 0.28rem;
  }
  .grid-private-badge svg {
    width: 12px;
    height: 12px;
  }
}

/* Hover-reveal cards: a flat drawn mark at rest, the real screenshot fading in
   on hover. Public second-tier cards only — featured cards are password-gated
   and must never reveal a screenshot. The square mark <img> sets the box; the
   screenshot is layered over it and cross-fades in. */
.thumb-reveal .thumb-mark {
  display: block;
  width: 100%;
}
.thumb-reveal .thumb-shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
}
.thumb-reveal:hover .thumb-shot { opacity: 1; }
/* Override the generic `.thumb-overlay:hover img { opacity:1 !important }` so
   the mark actually fades out under the revealed screenshot. */
.thumb-reveal:hover .thumb-mark { opacity: 0 !important; }

/* Second-tier grid cards now show the real screenshot at rest (no icon reveal),
   framed in a uniform 4:3 box so the grid stays even regardless of the source
   thumbnail's native aspect. Hover zoom comes from `.thumb-overlay:hover img`. */
.thumb-overlay .thumb-shot {
  aspect-ratio: 4 / 3;
  width: 100%;
  height: auto;
  object-fit: cover;
}

/* Collapsed "Archive" of earlier work: text links, no thumbnails, closed by
   default. Keeps the visible grid to the curated 6-8 without dropping the
   older projects entirely. */
.archive {
  margin: 2.5rem 0 0;
  border-top: 1px solid var(--color-border);
  padding-top: 1.25rem;
}
.archive > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-family: var(--font-display);
  font-size: var(--fs-base);
  font-weight: 600;
  color: var(--color-text-muted);
  transition: color var(--transition-fast);
  user-select: none;
}
.archive > summary::-webkit-details-marker { display: none; }
/* Disclosure chevron drawn from a rotated square border (no icon dependency). */
.archive > summary::before {
  content: "";
  width: 0.45rem;
  height: 0.45rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--transition-fast);
}
.archive[open] > summary::before { transform: rotate(45deg); }
.archive > summary:hover { color: var(--color-text); }
.archive-count {
  color: var(--color-text-subtle);
  font-weight: 400;
}
.archive-list {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 0 1.5rem;
}
.archive-list a {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--color-border);
  color: var(--color-text-muted);
  transition: color var(--transition-fast);
}
.archive-list a:hover { color: var(--color-accent); }
.archive-title { font-size: var(--fs-sm); }
.archive-year {
  font-size: var(--fs-xs);
  color: var(--color-text-subtle);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

/* Grid dividers */
hr {
  border-top: 1px solid var(--color-border) !important;
  margin: 2rem 0;
}

/* --- Project Detail Pages --- */

/* Project title */
section .container > .row > .col-12 > h1.text-left {
  font-size: var(--fs-xl);   /* same as the patents page title — one header size across all pages */
  font-weight: 600;
  color: var(--color-heading);
  padding-top: 1.5rem !important;   /* + the section's 16px = 2.5rem text gap, matching resume/summary/patents (overrides pt-3) */
}

/* Sidebar */
.bg-light {
  background: var(--color-surface) !important;
  border-radius: var(--radius) !important;
  border: 1px solid var(--color-border);
  padding: 1.5rem !important;
}

.bg-light .small,
.bg-light li {
  color: var(--color-text-muted) !important;
  font-size: var(--fs-sm);
  line-height: 1.8;
}

.sidedesc {
  background: var(--color-surface) !important;
  border-radius: var(--radius);
  border: 1px solid var(--color-border);
  padding: 1.5rem;
}

/* Project content */
/* Project/case-study body copy on the SAME restrained scale as the summary pages
   (1rem, not the old 1.125rem), so the two page types read consistently. */
.projectdesc,
p.projectdesc {
  color: var(--color-text);
  font-size: var(--fs-base);
  line-height: 1.75;
}

/* Section headers, dialed down to pair with the 1rem body (was 1.8 / 1.4rem). */
h2.projectdesc {
  color: var(--color-heading);
  font-size: var(--fs-lg);
  margin-top: 2.5rem;
  margin-bottom: 0.75rem;
}

h3.projectdesc {
  color: var(--color-heading);
  font-size: var(--fs-md);
  margin-top: 2.5rem;
  margin-bottom: 0.75rem;
}

/* Bulleted lists in project content */
ul.projectdesc-list {
  color: var(--color-text);
  font-size: var(--fs-base);
  line-height: 1.75;
  margin: 0.5rem 0 1.5rem;
  padding-left: 1.4rem;
  list-style: none;
}

ul.projectdesc-list li {
  position: relative;
  padding-left: 0.5rem;
  margin-bottom: 0.5rem;
}

ul.projectdesc-list li::before {
  content: '';
  position: absolute;
  left: -0.6rem;
  top: 0.7em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-accent);
}

/* Block quote: optional all-caps title, shaded background, left color strip */
.project-quote {
  margin: 2rem 0;
  padding: 1.25rem 1.5rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}

/* Small label / eyebrow — ONE shared quiet-label treatment, identical to the
   resume/patents section titles (.resume-section-title) and "Earlier work"
   (#more-work): display face, 0.9rem, weight 400, 0.02em tracking, muted,
   sentence case. Every standalone "section label" on the site reads the same;
   only the in-document case-study headings (h2/h3.projectdesc) stay larger.
   Each rule keeps its own margins — only the type is shared. */
.project-quote-title {
  margin: 0 0 0.5rem;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0.02em;
  color: var(--color-text-muted);
}

.project-quote-body,
.project-quote-body p {
  color: var(--color-text);
  font-size: var(--fs-md);
  line-height: 1.7;
  margin-bottom: 0;
}

.project-quote-body p + p {
  margin-top: 0.85rem;
}

/* Section eyebrow that sits tight above a title — shares the small-label type
   above (sentence case, semibold, muted). */
.project-subhead {
  margin: 2.5rem 0 0;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0.02em;
  color: var(--color-text-muted);
}
/* A Phase eyebrow is authored as `projectdesc project-subhead`, and p.projectdesc
   (specificity 0,1,1) would otherwise override the label's size + color with body
   copy. Re-assert the label type at 0,2,0 so it matches the other subheads. */
.projectdesc.project-subhead {
  font-size: var(--fs-sm);
  line-height: 1.4;
  color: var(--color-text-muted);
}

/* "At a glance" label at the top of the case-study summary box — an
   understated section label, same treatment as .project-quote-title but muted
   so it names the box without competing with it. */
.case-summary-label {
  margin: 0 0 0.75rem;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0.02em;
  color: var(--color-text-muted);
}

/* Case-study "At a glance" summary box — reusable class replacing the per-page
   inline style (identical box look), plus two refinements: the field labels
   (b) are muted + de-emphasized so the value reads as the brighter of the two,
   and text-wrap: pretty kills orphaned last words on the field lines. */
.case-summary {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 1.25rem 1.5rem;
  margin: 0 0 1.5rem;
  font-size: var(--fs-sm);
  line-height: 1.7;
}
.case-summary b {
  font-weight: 500;
  color: var(--color-text-muted);
}
.case-summary div {
  text-wrap: pretty;
}
/* Separate the field rows (Role / Timeframe / What changed / Result) so they
   read as distinct lines, not one run-on paragraph. Skips the label→first-field
   gap (the label keeps its own 0.75rem bottom margin above). */
.case-summary > div:not(.case-summary-label) + div {
  margin-top: 0.5rem;
}

/* Full-width variant: the box lifts out of the content column to span the full
   container above the row (Content adds `case-summary--wide` and moves the
   markup). Above lg the fields lay out as a 2-column grid so the extra width
   becomes structure + white space instead of over-long lines; the label spans
   both columns. Below lg it falls back to the stacked single-column box. */
.case-summary--wide {
  max-width: none;
  padding: 1.4rem 1.6rem;
}
@media (min-width: 992px) {
  .case-summary--wide {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 2.5rem;
    row-gap: 0.7rem;
    align-items: start;
  }
  .case-summary--wide > .case-summary-label {
    grid-column: 1 / -1;
    margin-bottom: 0.4rem;
  }
  /* Grid row-gap owns the rhythm here, so drop the stacked field top-margin. */
  .case-summary--wide > div:not(.case-summary-label) + div {
    margin-top: 0;
  }
}
.project-subhead:first-child {
  margin-top: 0;
}
/* No gap between a subhead and the heading directly beneath it */
.project-subhead + h1,
.project-subhead + h2,
.project-subhead + h3 {
  margin-top: 0 !important;
}

/* --- Condensed project summary (public teaser that links to the gated full
   case study). Image-forward, ~300 words, key metrics + role, then the gate.
   Content column caps at 900px on purpose (narrower than the 1100px full
   case-study pages, wider than the resume's 860px measure). --- */
.project-summary { padding-top: 2.5rem; padding-bottom: 6rem; }
.project-summary .container { max-width: 900px; }
/* Restrained title on the homepage's scale, not the base h1 (3–5.5rem). */
.project-summary h1 {
  font-size: var(--fs-xl);
  font-weight: 600;
  margin-bottom: 0.5rem;
}
.project-summary__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.5rem 2.5rem;
  flex-wrap: wrap;
  margin-bottom: 0;
}
.project-summary__intro { flex: 1 1 24rem; min-width: 0; }
/* Top description — a short summary of the project and role. Smaller than a lede,
   with generous space around it. */
.project-summary__desc {
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  font-weight: 400;
  line-height: 1.75;
  color: var(--color-text);
  margin: 3.5rem 0 4.5rem;
}
/* The desc is a container of one or more paragraphs; the block keeps its framing
   margins above, the paragraphs get normal inter-paragraph spacing and inherit the
   container's type (font: inherit beats the generic project `p` size). */
.project-summary__desc p { margin: 0; font: inherit; color: inherit; }
.project-summary__desc p + p { margin-top: 1.1rem; }
/* Body copy at the homepage's restrained size, not the 1.125rem project scale. */
.project-summary .projectdesc {
  font-size: var(--fs-base);
  line-height: 1.75;
  max-width: 60ch;
}
/* Key visuals span the full content width (real captures are wide; the square
   placeholders just render large). Figures are display:block (Bootstrap makes
   .figure inline-block). */
.project-summary__hero,
.project-summary__shot {
  display: block;
  width: 100%;
  margin: 0 0 4rem;
}
.project-summary__shot { margin: 4rem 0; }
/* Stacked device frames need much more air above them than a flat image, so two
   laptops in a row don't crowd (metrics -> first-frame gap is 4.5rem; this is
   ~2.25x that). Frameless inset images share the same top spacing. */
.project-summary__shot:has(.device-showcase) { margin-top: 10.125rem; }
/* A device showcase dropped into a NON-summary page (its figure isn't inside a
   .project-summary section) gets symmetric breathing room — so an isolated frame
   (text above and below) reads balanced. When one frame is immediately followed by
   another, the second takes a larger top gap so stacked frames don't crowd (the
   summary template spaces its own showcases via stacking + section padding). */
figure:has(> .device-showcase):not(:is(.project-summary *)) { margin: 4rem 0; }
figure:has(> .device-showcase):not(:is(.project-summary *)) + figure:has(> .device-showcase) { margin-top: 6rem; }
/* Frameless single-image inset to the laptop-frame footprint (720px), centered.
   Its top gap tracks the framed showcase IN THE SAME CONTEXT: a summary section
   keeps the tall stacked gap (matches .project-summary__shot:has(.device-showcase)),
   but on a non-summary page it takes the framed shot's symmetric 4rem (matches the
   figure:has(> .device-showcase) rule) so a small inset doesn't tower over its text.
   Compound/descendant selectors (0,2,0) beat the base .project-summary__shot and the
   global figure margin reset. */
.project-summary__shot.project-summary__shot--inset { margin: 4rem auto; max-width: 720px; }
.project-summary .project-summary__shot--inset { margin-top: 10.125rem; }
/* When a device showcase is the FIRST block in a case-study content column, drop
   the 4rem top margin the showcase rules add so its top edge lines up with the TOC
   rail. Scoped to .case-content, so summary pages keep their own hero spacing. */
.case-content > figure.project-summary__hero:first-child,
.case-content > figure.project-summary__shot:first-child {
  margin-top: 0;
}
.project-summary__hero img,
.project-summary__shot img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-image);
}

/* --- Device showcase: a reusable device frame holding phased screens, with an
   auto-advancing timer line, phase buttons, play/pause, and a frame-less
   lightbox. Swap .device-frame__art + the four --screen-* values to use another
   frame (a laptop PNG green-screen, a phone, etc). --- */
.device-showcase { width: 100%; max-width: 720px; margin: 0 auto; }
.device-frame { position: relative; width: 100%; line-height: 0; }
.device-frame__art { display: block; width: 100%; height: auto; border-radius: 0; pointer-events: none; }
.device-frame__screen {
  position: absolute;
  /* Green screen region of images/laptop.png, measured from the asset
     (left 11.586% / top 5.892% / width 76.778% / height 82.408%), nudged out a
     hair on each side so no green shows at the edges. Recalibrate if the frame
     art changes. */
  top: 5.7%;
  left: 11.4%;
  width: 77.2%;
  height: 82.8%;
  overflow: hidden;
  border-radius: 3px;
  /* Always dark, NOT var(--color-bg): the frame holds dark app screenshots, so a
     light-mode (#efefef) screen showed through the crossfade as a jarring white
     flash (and a white hairline above the image). A fixed near-black sits invisibly
     behind the dark shots during the opacity dip. */
  background: #0d0d0d;
}
.device-slide {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease, transform 0.5s ease;
}
.device-slide.is-active { opacity: 1; pointer-events: auto; }

/* Transition variants — set data-transition on the .device-showcase (a CMS
   option). Default (fade) is opacity-only. "slide": the incoming screen enters
   from the right and the outgoing one exits to the left (right-to-left). */
.device-showcase[data-transition="slide"] .device-slide { transform: translateX(100%); }
/* Settled slide uses `none`, not translateX(0): a resting transform forces the
   image onto a GPU-composited layer where downscaling turns to mush/jaggies.
   translateX(100%) -> none still animates (none interpolates as the identity). */
.device-showcase[data-transition="slide"] .device-slide.is-active { transform: none; }
.device-showcase[data-transition="slide"] .device-slide.is-leaving { transform: translateX(-100%); opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .device-slide { transition: opacity 0.3s ease; }
  .device-showcase[data-transition="slide"] .device-slide { transform: none; }
}
.device-frame img { border-radius: 0; }
.device-slide img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Frameless multi-image showcase (device: none, >1 image): no laptop art, so the
   screen defines its own 16:10 box instead of being inset into the frame art. */
.device-showcase--frameless .device-frame__screen {
  position: relative;
  top: auto; left: auto; width: 100%; height: auto;
  aspect-ratio: 16 / 10;
  border-radius: var(--radius);
}

/* Fit mode (data-fit): the image is contained within the frame box (scaled by
   width or height, never cropped) instead of bleeding to cover it. The box shows
   the current theme background behind the letterboxed image. A frameless fit box
   gets a light stroke around it; the laptop frame keeps its own bezel. */
.device-showcase--fit .device-slide img { object-fit: contain; }
.device-showcase--fit .device-frame__screen { background: var(--color-bg); }
.device-showcase--frameless.device-showcase--fit .device-frame__screen {
  border: 1px solid var(--color-border);
}

.device-controls { margin-top: 1.1rem; line-height: 1.4; }
.device-controls__row { display: flex; align-items: center; gap: 0.85rem; flex-wrap: wrap; }
.device-playpause {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: transparent;
  color: var(--color-text);
  cursor: pointer;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}
.device-playpause:hover { border-color: var(--color-text-subtle); color: var(--color-heading); }
.device-ic--play { display: none; }
.is-paused .device-ic--pause { display: none; }
.is-paused .device-ic--play { display: block; }
.device-tabs { display: flex; gap: 0.4rem; }
.device-tab {
  position: relative;
  overflow: hidden;
  z-index: 0;
  min-width: 26px;
  height: 26px;
  padding: 0 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: transparent;
  color: var(--color-text-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  font-weight: 500;
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}
/* Timer fill grows left-to-right inside the active phase button (replaces the
   separate progress bar). --p (0..1) is driven by the script. */
.device-tab::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(255, 255, 255, 0.16);
  transform: scaleX(var(--p, 0));
  transform-origin: left center;
}
:root[data-theme="light"] .device-tab::before { background: rgba(0, 0, 0, 0.1); }
.is-paused .device-tab::before { background: rgba(255, 255, 255, 0.09); }
:root[data-theme="light"] .is-paused .device-tab::before { background: rgba(0, 0, 0, 0.06); }
.device-tab:hover { color: var(--color-heading); border-color: var(--color-text-subtle); }
.device-tab.is-active { border-color: var(--color-text-subtle); color: var(--color-heading); }
/* One caption style for every frame: its own line at the showcase's left edge
   (not tucked into the controls row, where indent varied with control count). */
.device-caption { margin: 0; font-family: var(--font-sans); font-size: var(--fs-sm); line-height: 1.4; color: var(--color-text-muted); }
.device-caption + .device-controls__row { margin-top: 0.7rem; }
/* Frameless/inset figures on the summary page share the exact same caption look. */
.project-summary .figure-caption,
.project-summary figcaption.figure-caption {
  color: var(--color-text-muted) !important;
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  line-height: 1.4;
  padding: 0.6rem 0 0;
}

/* Frame-less lightbox — shows only the raw screen image. */
.device-lightbox {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 5vmin;
  background: rgba(0, 0, 0, 0.86);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  opacity: 0;
  visibility: hidden;
  cursor: zoom-out;
  transition: opacity 0.25s ease, visibility 0.25s ease;
}
.device-lightbox.is-open { opacity: 1; visibility: visible; }
.device-lightbox__figure {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;   /* caption aligns to the image's left edge */
  gap: 0.9rem;
  max-width: 100%;
  max-height: 100%;
}
/* flex:0 1 auto + min-height:0 lets the image shrink so the caption always fits.
   Square corners: the raw screen shows without the frame's rounding. */
.device-lightbox img { max-width: 100%; min-height: 0; flex: 0 1 auto; border-radius: 0; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); }
.device-lightbox__caption {
  flex: 0 0 auto;
  margin: 0;
  max-width: 100%;
  text-align: left;
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  line-height: 1.4;
  color: var(--color-text-muted);
}
/* Prev/next through the set — chevrons flanking the image; hidden for a lone image. */
.device-lightbox__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: color 0.15s ease, transform 0.15s ease;
  z-index: 1;
}
.device-lightbox__nav:hover { color: var(--color-heading); }
.device-lightbox__nav--prev { left: 2vmin; }
.device-lightbox__nav--next { right: 2vmin; }
.device-lightbox__nav--prev:hover { transform: translateY(-50%) translateX(-2px); }
.device-lightbox__nav--next:hover { transform: translateY(-50%) translateX(2px); }
.device-lightbox__nav[hidden] { display: none; }

/* Key metrics: 2–3 big numerals bracketed by hairlines — the one loud moment.
   A grid spreads them evenly across the full width (left-aligned within cells). */
.project-metrics {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;   /* center the whole cluster instead of stretching it full-width */
  gap: 1.5rem 3.5rem;
  margin: 0 0 4.5rem;
  padding: 1.4rem 0;
}
.project-metric { text-align: left; }
/* Single-stat pull-stat reads inline with the prose, so keep it left-aligned. */
.project-metrics--solo { justify-content: flex-start; }
/* On a case-study page the metrics sit between the "at a glance" box and the full
   write-up, so balance the space above and below them. The base 4.5rem bottom is
   meant to precede the summary's first showcase and reads lopsided here. */
.case-content .project-metrics { margin: 1rem 0; }
/* The divider that follows the metrics carries a 2rem top margin, which would make
   the space below the cards larger than above; trim it so the gaps match. */
.case-content .project-metrics + hr { margin-top: 1rem; }
/* On phones the cluster wraps to stacked rows; left-align it so the cards share a
   left edge (centering each wrapped row by its own width staggers them). Placed
   after the base rule so it wins at equal specificity. */
@media (max-width: 575.98px) {
  .project-metrics { justify-content: flex-start; }
  /* 3+ metrics: switch to a real 2-column grid so BOTH columns line up exactly (a
     wrapping flex row offsets the 2nd column by the 1st item's width). 1–2 metrics
     keep the flex fallback, so wide "X -> Y" values stack full-width instead of
     being squeezed into a narrow grid column. */
  .project-metrics:has(.project-metric:nth-child(3)) {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1.5rem 2rem;
  }
}
.project-metric__value {
  font-family: var(--font-display);
  font-size: clamp(1.55rem, 2.8vw, 2rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--color-heading);
}
.project-metric__label {
  margin: 0.55rem 0 0;
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  line-height: 1.35;
  color: var(--color-text-muted);
  white-space: nowrap;   /* the secondary line stays intact even if it overflows the column a bit */
}
/* Single-stat usage (one pull-stat inside prose): the label is a sentence-length
   caption, so let it wrap and read at a comfortable width. */
.project-metrics--solo .project-metric__label { white-space: normal; max-width: 46ch; }
/* One outcome statement, set a little larger than body. */
.project-outcome {
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 1.8vw, 1.4rem);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: -0.01em;
  color: var(--color-heading);
  max-width: 40ch;
  margin: 4rem 0;
}

/* Prev/next uses the shared `.prev-next-nav` component (defined above) so the
   summary template matches every other project page — no bespoke pager here. */

/* Access CTA at the top of the summary: "View full case study" opens the
   request-access modal (data-contact-trigger); "I have a login" goes straight
   to the gated /private/ page for viewers who already hold the password. */
/* No paired case study -> the CTA placeholder resolves to nothing; hide the empty
   actions column so the header doesn't reserve space for it. */
.project-summary__actions:not(:has(a)) { display: none; }
.project-summary__actions {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.7rem;
  flex-shrink: 0;
  margin-top: 0.35rem;
  text-align: right;
}
/* Compact secondary CTA — 24px tall; 8px radius so it stays a rectangle (12px
   would be a pill at this height). Tiny top padding nudges the text down off the
   cap line so it reads optically centered at this small size. */
.project-summary__cta.btn {
  height: 24px;
  padding: 0.11rem 0.85rem 0 !important;
  font-size: var(--fs-xs) !important;
  border-radius: 8px !important;
  white-space: nowrap;
}
/* "I have a login" line inside the request-access modal → private gallery. */
.cf-have-login {
  margin: 0 0 1.25rem;
  padding-bottom: 1.1rem;
  border-bottom: 1px solid var(--color-border);
}
.cf-login-link {
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--color-text-subtle);
}
.cf-login-link:hover {
  color: var(--color-heading);
  text-decoration-color: currentColor;
}

/* Project layout: 32px gap between sidebar and content */
.bg-light + [class*="col-"] {
  padding-left: 32px;
}

/* --- Byline layout (preview) ---------------------------------------------
   Meta lifts up under the title as a byline + tag row; the side column slims
   to just a floating (background-less) TOC via `.case-rail`; the content
   column (`.case-content`) widens for a fuller reading experience. Content
   supplies the markup (byline element, class swaps, wider col classes); this
   is the presentation. */
.project-byline {
  color: var(--color-text-muted);
  font-size: var(--fs-base);
  line-height: 1.5;
  margin: 0.35rem 0 0.55rem;
  text-align: left;
}
.project-tags--top {
  text-align: left;
  margin: 0 0 0.5rem;
}

/* Slim, floating TOC column: drop the boxed background, keep the sticky nav.
   Zero the rail's top padding (pt-2) and the TOC's default top margin so the
   "Jump to" label aligns with the top of the content / lead image. */
.case-rail {
  background: transparent !important;
  padding-top: 0 !important;
}
.case-rail .project-toc {
  margin-top: 0;
}
/* Gap between the slim rail and the widened content column. */
.case-rail + .case-content {
  padding-left: 40px;
}
/* Body copy fills the full (widened) content column, matching the figure
   width — Bill's explicit call. This runs ~90ch at the widest, past the
   ~72ch readability sweet spot, but the full-width read is the intent. */

/* Wider content column (xl only). The rail (col-xl-3) + content (col-xl-7)
   filled just 10 of 12 columns, leaving the content narrow with ~2 empty
   columns of dead space on the right. Widen the content so figures and media
   use that space; the running prose keeps a readable measure via max-width so
   text doesn't stretch too wide inside the wider column. */
@media (min-width: 1200px) {
  .bg-light.col-xl-3 + [class*="col-xl-7"] {
    flex: 0 0 70%;
    max-width: 70%;
  }
  .bg-light.col-xl-3 + [class*="col-xl-7"] > p.projectdesc,
  .bg-light.col-xl-3 + [class*="col-xl-7"] > ul.projectdesc-list,
  .bg-light.col-xl-3 + [class*="col-xl-7"] > blockquote.project-quote,
  .bg-light.col-xl-3 + [class*="col-xl-7"] > .case-summary {
    max-width: 42rem;
  }
}

/* Project sidebar table of contents (built by js/toc.js). Sticky so it stays
   pinned near the top of the viewport as the page scrolls. */
.project-toc {
  position: sticky;
  top: 5rem;
  margin-top: 1.75rem;
}
/* "Jump to" — shares the unified small-label type (see .project-subhead); keeps
   its own bottom margin. */
.project-toc-title {
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0.02em;
  color: var(--color-text-muted);
  margin: 0 0 0.6rem;
}
.project-toc ul {
  list-style: none;
  margin: 0;
  padding: 0;
  border-left: 1px solid var(--color-border);
}
.project-toc li { margin: 0; }
.project-toc a {
  display: block;
  padding: 0.28rem 0 0.28rem 0.85rem;
  margin-left: -1px;
  border-left: 2px solid transparent;
  color: var(--color-text-muted);
  font-size: var(--fs-sm);
  line-height: 1.35;
  text-decoration: none;
  transition: color 0.15s, border-color 0.15s;
}
.project-toc a:hover { color: var(--color-heading); }
.project-toc li.active > a {
  color: var(--color-accent);
  border-left-color: var(--color-accent);
  font-weight: 600;
}
/* Indent nested headings */
.project-toc-l2 a { padding-left: 1.7rem; }
.project-toc-l3 a { padding-left: 2.55rem; font-size: var(--fs-xs); }

/* On stacked (mobile) layout the sidebar sits above content — don't pin it. */
@media (max-width: 767px) {
  .project-toc { position: static; }
}

/* Remove top margin from first content element so it aligns with sidebar */
h2.projectdesc:first-child,
h3.projectdesc:first-child,
p.projectdesc:first-child {
  margin-top: 0;
}

/* Figures & Media */
figure,
.figure {
  margin: 2rem 0 0;
  /* No overflow clip: the img/video round their own corners, so figures don't
     impose a different radius (which previously mismatched top vs bottom). */
}

/* Lead image: the opening visual of a case study. Content drops a
   <figure class="figure case-lead"> as the first child of the content column;
   it spans the full (widened) column above the At-a-glance box and carries a
   lift so the page opens on a screenshot instead of a text box. */
.case-lead {
  margin: 0 0 1.75rem;
  max-width: none;
}
.case-lead img,
.case-lead video {
  width: 100%;
  height: auto;
  display: block;
  box-shadow: var(--shadow-lg);
}
/* The ART page's lead is a video (poster + controls). Give it the same rounded
   hero treatment as the image leads and keep it from collapsing before the
   poster/metadata load. */
.case-lead video {
  border-radius: var(--radius);
  aspect-ratio: 16 / 9;
  background: #000;
  object-fit: cover;
}
/* Byline layout: tighten the gap between the lead image and the first heading
   below it (was lead mb 1.75rem + heading mt 2.5rem ≈ 68px) so the hero and the
   Overview read as connected, without cramping. */
.case-content > .case-lead {
  margin-bottom: 1.25rem;
}
.case-content > .case-lead + h2.projectdesc,
.case-content > .case-lead + h3.projectdesc,
.case-content > .case-lead + .project-subhead {
  margin-top: 0.25rem;
}

.figure-caption,
figcaption.figure-caption {
  color: var(--color-text-subtle) !important;
  font-size: var(--fs-sm);
  padding: 0.75rem 0;
  line-height: 1.6;
}

video,
.embed-responsive {
  border-radius: var(--radius);
  background: var(--color-surface);
}

/* ── Video play affordance (js/video-play.js) ─────────────────────────────
   A dark scrim + circular play icon + "Play now" label over each playable
   video (controls, not autoplay) until playback starts, then it fades out. */
.video-wrap {
  position: relative;
  display: inline-block;
  max-width: 100%;
  line-height: 0;
  border-radius: var(--radius);
}
.video-wrap > video {
  display: block;
  max-width: 100%;
}
.video-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: inherit;
  background: rgba(8, 8, 14, 0.5);
  color: #fff;
  cursor: pointer;
  line-height: 1.2;
  transition: opacity 0.3s ease, background 0.25s ease;
  -webkit-appearance: none;
  appearance: none;
}
.video-overlay:hover,
.video-overlay:focus-visible {
  background: rgba(8, 8, 14, 0.38);
  outline: none;
}
.video-overlay__icon {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.16);
  border: 2px solid rgba(255, 255, 255, 0.85);
  transition: transform 0.2s ease, background 0.2s ease;
}
.video-overlay:hover .video-overlay__icon,
.video-overlay:focus-visible .video-overlay__icon {
  transform: scale(1.06);
  background: rgba(255, 255, 255, 0.28);
}
.video-overlay__icon svg {
  width: 30px;
  height: 30px;
  margin-left: 4px; /* optically center the play triangle */
  fill: #fff;
}
.video-overlay__label {
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.02em;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
}
.video-wrap.is-playing .video-overlay {
  opacity: 0;
  pointer-events: none;
}
@media (max-width: 575.98px) {
  .video-overlay__icon { width: 58px; height: 58px; }
  .video-overlay__icon svg { width: 24px; height: 24px; }
  .video-overlay__label { font-size: var(--fs-sm); }
}

img.img-fluid,
img.img-responsive {
  /* !important to beat Bootstrap's .rounded (4px). */
  border-radius: var(--radius) !important;
}

/* Project content images (inside figures) use the smaller image radius.
   More specific than img.img-fluid above, so it wins; gallery thumbnails
   (not in figures) keep the themed --radius. */
figure img.img-fluid,
figure img.img-responsive {
  border-radius: var(--radius-image) !important;
}

/* Zoomable project images + lightbox / shadowbox */
figure img.zoomable {
  cursor: zoom-in;
}

body.lightbox-active {
  overflow: hidden;
}

.lightbox-overlay {
  position: fixed;
  inset: 0;
  z-index: 1100;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 2.5rem;
  background: rgba(0, 0, 0, 0.85);
  backdrop-filter: blur(8px);
}

.lightbox-overlay.open {
  display: flex;
  animation: lightboxFade 0.18s ease-out;
}

@keyframes lightboxFade {
  from { opacity: 0; }
  to { opacity: 1; }
}

.lightbox-figure {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  max-width: 100%;
  max-height: 100%;
  margin: 0;
}

.lightbox-img {
  /* As large as the viewport allows, never upscaled past native resolution */
  max-width: 100%;
  max-height: 100%;
  min-height: 0; /* allow shrinking in the flex column so the caption fits */
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--radius-image);
  box-shadow: var(--shadow-lg);
  cursor: default;
}

.lightbox-caption {
  flex-shrink: 0;
  /* Match the image's width without widening the figure past it: width:0 keeps
     the caption out of the figure's max-content sizing, min-width:100% then
     fills the figure (which the image sized). */
  width: 0;
  min-width: 100%;
  margin-top: 0.9rem;
  color: rgba(255, 255, 255, 0.8);
  font-size: var(--fs-sm);
  line-height: 1.5;
  text-align: left;
}

.lightbox-close {
  position: fixed;
  top: 1rem;
  right: 1.25rem;
  width: 2.75rem;
  height: 2.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
  font-size: var(--fs-xl);
  line-height: 1;
  cursor: pointer;
  transition: background var(--transition-fast), transform var(--transition-fast);
}

.lightbox-close:hover {
  background: rgba(0, 0, 0, 0.8);
  transform: scale(1.05);
}

/* --- Footer --- */
footer,
/* Tail spacer that replaced the footer bio band: empty closing whitespace in the
   page background (no band, no border), roughly the height of the old band. */
.site-footer-spacer { height: 9.5rem; background: var(--color-bg); }

#footbg {
  background: var(--color-bg-alt) !important;
  border-top: 1px solid var(--color-border);
  padding: 2rem 0 !important;
}

/* Closing site footer: a balanced two-column band in the dark alt surface —
   trimmed bio on the left, affiliations on the right. Replaces the old
   copyright line. ID-level specificity so it beats the shared #footbg padding. */
#footbg.site-footer {
  padding-top: 3.25rem !important;
  padding-bottom: 3.25rem !important;
}
.site-footer__inner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.5rem 3rem;
  flex-wrap: wrap;
}
.site-footer__bio {
  max-width: 62ch;
  margin: 0 auto;
  text-align: center;
  font-size: var(--fs-sm) !important;
  line-height: 1.6;
  color: var(--color-text) !important;
}
/* Affiliations sit on the right of the band (via space-between), left-aligned to
   each other so "I've worked with" lines up with the start of the logos. The
   band top-aligns its columns (align-items on the inner), so the label sits
   level with the bio's first line and the logos with its second — a tidy grid
   rather than a stack floating beside the bio. */
.site-footer__side {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.55rem;
  text-align: left;
}

/* Private gallery landing page: keep the footer at the bottom of the viewport
   even when the content is short. */
.private-page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
.private-page > footer {
  margin-top: auto;
}

footer p {
  color: var(--color-text-subtle) !important;
  font-size: var(--fs-sm);
}

/* --- Utility Overrides --- */
.bg-dark {
  background: transparent !important;
}

.mt-2 {
  margin-top: 1.5rem !important;
}

.container {
  max-width: var(--content-width, 1100px);
}

/* --- Scroll Reveal Animation --- */
@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.animate-in {
  animation: fadeInUp 0.6s ease-out forwards;
}

/* Stagger delays for grid items */
.row > [class*="col-"]:nth-child(1) { animation-delay: 0s; }
.row > [class*="col-"]:nth-child(2) { animation-delay: 0.1s; }
.row > [class*="col-"]:nth-child(3) { animation-delay: 0.2s; }
.row > [class*="col-"]:nth-child(4) { animation-delay: 0.3s; }

/* --- Responsive --- */
@media (max-width: 768px) {
  .jumbotron {
    padding: 4rem 1.5rem 3rem !important;
  }

  .jumbotron h1 {
    font-size: var(--fs-xl);
  }

  h1 {
    font-size: var(--fs-2xl);
  }

  .media {
    padding: 1.2rem;
  }

  .navbar {
    padding: 0.75rem 1rem !important;
  }

  section {
    padding: 0.5rem 0;
  }
}

@media (max-width: 480px) {
  .jumbotron h1 {
    font-size: var(--fs-xl);
  }

  .jumbotron p {
    font-size: var(--fs-base);
  }

  .btn-primary {
    padding: 0.65rem 1.5rem !important;
    font-size: var(--fs-sm) !important;
  }
}

/* --- Smooth Scrollbar --- */
::-webkit-scrollbar {
  width: 8px;
}

::-webkit-scrollbar-track {
  background: var(--color-bg);
}

::-webkit-scrollbar-thumb {
  background: var(--color-surface-hover);
  border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--color-text-subtle);
}

/* --- Focus styles for accessibility --- */
:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* --- Prevent FOUC on icon image --- */
.jumbotron .align-items-center img {
  max-width: 280px;
  margin: 2rem auto 0;
  display: block;
  filter: brightness(0.9) contrast(1.1);
  opacity: 0.8;
}

/* ============================================================
   RESUME PAGE
   ============================================================ */

/* De-emphasized section label (Experience / Skills / Education on the resume,
   Sole/Co-inventor on patents): a quiet muted label so the emphasis lands on the
   NAME, COMPANY and SCHOOL below it, not the section header. No accent, no
   underline. Shared by both pages so they align. */
.resume-section-title {
  font-family: var(--font-display) !important;
  font-size: var(--fs-sm);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0.02em;
  color: var(--color-text-muted);
  margin-bottom: 0.7rem;
  padding-bottom: 0;
  border-bottom: none;
}
/* Pull the Skills grid up under its label (overrides the .mt-2 = 1.5rem util). */
section[data-section-label="Skills"] .row.mt-2 { margin-top: 0.6rem !important; }

.resume-entry {
  margin-bottom: 2rem;
}

.resume-company {
  font-family: var(--font-display) !important;
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--color-heading);
  margin-bottom: 0;
}

.resume-role {
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
  margin-bottom: 0.75rem;
  font-weight: 500;
}

.resume-date {
  font-size: var(--fs-sm);
  color: var(--color-text-subtle);
  white-space: nowrap;
}

.resume-list {
  list-style: none;
  padding-left: 0;
  margin-top: 0.5rem;
}

.resume-list li {
  position: relative;
  padding-left: 1.25rem;
  margin-bottom: 0.5rem;
  font-size: var(--fs-sm);
  color: var(--color-text);
  line-height: 1.7;
}

.resume-list li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.65em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--color-accent);
  opacity: 0.6;
}

.resume-skills {
  list-style: none;
  padding-left: 0;
  margin-bottom: 0;
  /* Flow the flat list into a 3-column table-like grid */
  column-count: 3;
  column-gap: 2.5rem;
}

.resume-skills li {
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
  padding: 0.35rem 0;
  transition: color var(--transition-fast);
  break-inside: avoid;
}

.resume-skills li:hover {
  color: var(--color-heading);
}

@media (max-width: 768px) {
  .resume-skills {
    column-count: 2;
  }
}

@media (max-width: 480px) {
  .resume-skills {
    column-count: 1;
  }
}

@media (max-width: 768px) {
  .resume-company {
    font-size: var(--fs-md);
  }

  .resume-date {
    display: block;
    margin-bottom: 0.5rem;
  }
}

/* ============================================================
   THEME TOGGLE
   ============================================================ */

.theme-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background: var(--color-surface);
  color: var(--color-text-muted);
  cursor: pointer;
  transition: all var(--transition-fast);
  margin-left: 0.5rem;
  flex-shrink: 0;
}

.theme-toggle:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
  background: var(--color-accent-subtle);
  transform: scale(1.08);
}

.theme-toggle:active {
  transform: scale(0.95);
}

.theme-toggle svg {
  width: 18px;
  height: 18px;
  transition: transform var(--transition-spring), opacity var(--transition-fast);
}

.theme-toggle .icon-sun {
  display: none;
}

.theme-toggle .icon-moon {
  display: block;
}

[data-theme="light"] .theme-toggle .icon-sun {
  display: block;
}

[data-theme="light"] .theme-toggle .icon-moon {
  display: none;
}

/* Light mode: override navbar toggler for visibility */
[data-theme="light"] .navbar-dark .navbar-toggler-icon {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
}

[data-theme="light"] .navbar-dark .navbar-toggler {
  border-color: rgba(0, 0, 0, 0.1);
}

/* Light mode: selection color */
[data-theme="light"] ::selection {
  background: rgba(99, 102, 241, 0.2);
  color: #111827;
}

/* Light mode: scrollbar */
[data-theme="light"] ::-webkit-scrollbar-track {
  background: var(--color-bg);
}

[data-theme="light"] ::-webkit-scrollbar-thumb {
  background: #d1d5db;
}

[data-theme="light"] ::-webkit-scrollbar-thumb:hover {
  background: #9ca3af;
}

/* Smooth dissolve on theme change — applied via class to avoid interfering with page load */
html.theme-transitioning,
html.theme-transitioning *,
html.theme-transitioning *::before,
html.theme-transitioning *::after {
  transition: background-color 300ms ease,
              color 300ms ease,
              border-color 300ms ease,
              box-shadow 300ms ease,
              fill 300ms ease,
              stroke 300ms ease,
              opacity 300ms ease !important;
}

/* Bootstrap's modal adds padding-right to <body> to offset a scrollbar it
   believes it hid via body{overflow:hidden}. This site permanently reserves the
   scrollbar gutter on <html> (see the html rule above), so nothing is ever
   reclaimed and that compensation just shoves the page ~8px left on open. Cancel
   it: the gutter is always there, so no offset is needed. */
body.modal-open {
  padding-right: 0 !important;
}

/* --- Contact / request-access modal --- */
/* Wider than the Bootstrap default (500px) — the fields carry placeholder labels
   and Name/Email share a row. */
#contactModal .modal-dialog { max-width: 640px; }
#contactModal .modal-content {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  color: var(--color-text);
  box-shadow: var(--shadow-lg);
}
/* Name + Email side by side; stack on narrow screens. */
.cf-row {
  display: flex;
  gap: 1rem;
}
.cf-row > .form-group {
  flex: 1 1 0;
  min-width: 0;
}
@media (max-width: 479px) {
  .cf-row { flex-direction: column; gap: 0; }
}
/* 24px inset on all outer sides of the modal; balanced spacing between sections. */
#contactModal .modal-header {
  border-bottom: 0;
  padding: 24px 24px 16px;
}
#contactModal .modal-body {
  padding: 16px 24px;
}
/* No divider above the action buttons. */
#contactModal .modal-footer {
  border-top: 0;
  padding: 16px 24px 24px;
}
#contactModal .modal-title {
  font-family: var(--font-display);
  color: var(--color-heading);
  font-weight: 600;
}
#contactModal .modal-header .close {
  color: var(--color-text-muted);
  text-shadow: none;
  opacity: 1;
}
#contactModal .modal-header .close:hover {
  color: var(--color-heading);
}
/* When the modal is taller than the viewport (short phones, landscape, or the taller
   request-access variant) a vertically-centered dialog clips its top AND its footer
   buttons with no way to reach them. On short screens, top-align the dialog so the
   content flows from the top and the whole modal scrolls within its container — the
   title stays put on open and the buttons are reachable by scroll. Tall phones keep
   the centered dialog, where everything fits. */
@media (max-width: 575.98px) and (max-height: 700px) {
  #contactModal .modal-dialog-centered { align-items: flex-start; }
}
#contactModal label {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--color-text-muted);
  margin-bottom: 0.3rem;
  transition: color var(--transition-fast);
}
/* Highlight a field's label in the accent color while that field is focused. */
#contactModal .form-group:focus-within label {
  color: var(--color-accent);
}
#contactModal .form-control {
  background: var(--color-bg-alt);
  /* Very light resting border (<=15% in dark); the field reads from its darker
     fill + roomy padding, and focus adds a clear accent ring. Theme-aware. */
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: var(--radius-sm);
  color: var(--color-text);
  height: auto;
  padding: 0.75rem 0.95rem;
  font-size: var(--fs-sm);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
[data-theme="light"] #contactModal .form-control {
  border-color: rgba(0, 0, 0, 0.22);
}
#contactModal .form-control:focus {
  background: var(--color-bg-alt);
  border-color: var(--color-accent);
  box-shadow: none;
  color: var(--color-text);
  outline: none;
}
#contactModal .form-control::placeholder {
  color: var(--color-text-subtle);
}
#contactModal textarea.form-control {
  height: 11rem;
  min-height: 11rem;
  resize: none;
}
/* Honeypot: hidden from humans, tempting to bots. Not display:none (some bots skip those). */
#contactModal .cf-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
#contactModal .cf-status {
  font-size: var(--fs-sm);
  margin: 0;
}
/* Collapse the status line when there's no message, so it doesn't reserve an
   empty row beneath the message field. */
#contactModal .cf-status:empty {
  display: none;
}
#contactModal .cf-status.is-error {
  color: #f87171;
}
#contactModal .cf-status a {
  color: inherit;
  text-decoration: underline;
  font-weight: 500;
}
#contactModal .cf-status a:hover {
  color: var(--color-accent-hover);
}
#contactModal .cf-status.is-success {
  color: #34d399;
}
#contactModal .cf-submit[disabled] {
  opacity: 0.6;
  cursor: default;
}
/* Tertiary "Copy email" button: an outlined pill with full-contrast text, so it
   reads as a real (subordinate) button beside the solid primary Send. */
#contactModal .cf-copy-email {
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: 12px;
  box-shadow: none;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 1.2rem;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: var(--fs-sm);
  line-height: 1;
  color: var(--color-text);
  transition: border-color var(--transition-fast), background var(--transition-fast), color var(--transition-fast);
}
#contactModal .cf-copy-email:hover,
#contactModal .cf-copy-email:focus {
  border-color: var(--color-text-subtle);
  background: var(--color-accent-subtle);
  color: var(--color-heading);
  outline: none;
}

/* Per-field validation error: red field + label, message beneath the field.
   Placed after the :focus-within rule so an invalid field's label reads red
   (error state) even while focused. */
#contactModal .cf-field-error {
  display: none;
  color: #f87171;
  font-size: var(--fs-xs);
  line-height: 1.35;
  margin: 0.35rem 0 0;
}
#contactModal .form-group.cf-invalid .cf-field-error {
  display: block;
}
#contactModal .form-group.cf-invalid label {
  color: #f87171;
}
#contactModal .form-group.cf-invalid .form-control {
  border-color: #f87171;
}
#contactModal .form-group.cf-invalid .form-control:focus {
  border-color: #f87171;
  box-shadow: 0 0 0 3px rgba(248, 113, 113, 0.18);
}

/* --- Patents page: a reference page of native <details> accordions styled as a
   plain divider-ruled list (table rows, not boxed cards). Existing tokens only. --- */
.patents-hero { padding: 2.5rem 0 0.5rem; }
.patents-title { font-family: var(--font-display); font-size: var(--fs-xl); font-weight: 600; margin: 0 0 1.1rem; }
.patents-note { font-size: var(--fs-base); line-height: 1.7; color: var(--color-text); margin: 0 0 0.85rem; }
.patents-note strong { font-weight: 600; }
.patents-note--sub { color: var(--color-text-muted); font-size: var(--fs-sm); }
.patents-list { margin-top: 1.4rem; border-top: 1px solid var(--color-border); }


/* Each invention is a ruled row: a bottom divider, no box, aligned to the content
   column. A faint row highlight on hover signals it's interactive. */
.patent { border-bottom: 1px solid var(--color-border); }
.patent__head {
  display: flex;
  align-items: baseline;
  gap: 0.9rem;
  padding: 0.9rem 0.2rem;
  cursor: pointer;
  list-style: none;                 /* Firefox: drop the triangle */
  transition: background var(--transition-smooth);
}
.patent__head::-webkit-details-marker { display: none; }  /* Safari/Chrome */
.patent__head:hover { background: var(--color-surface); }
.patent__head:focus-visible { outline: 2px solid var(--color-accent); outline-offset: -2px; }
.patent__title { flex: 1 1 auto; font-size: var(--fs-base); font-weight: 400; color: var(--color-text); line-height: 1.75; }
.patent__meta { flex: 0 0 auto; font-size: var(--fs-sm); color: var(--color-text-muted); white-space: nowrap; }
/* CSS chevron: points down when closed, flips up when open. */
.patent__head::after {
  content: '';
  flex: 0 0 auto;
  align-self: center;
  width: 0.5rem; height: 0.5rem;
  margin-left: 0.1rem;
  border-right: 2px solid var(--color-text-subtle);
  border-bottom: 2px solid var(--color-text-subtle);
  transform: rotate(45deg);
  transition: transform var(--transition-smooth);
}
.patent[open] > .patent__head::after { transform: rotate(-135deg); }

.patent__body { padding: 0 0.2rem 1.1rem; }
.patent__grants { list-style: none; margin: 0; padding: 0; }
.patent__grants li { padding: 0.28rem 0; font-size: var(--fs-sm); line-height: 1.5; }
.patent__grants a { color: var(--color-text); border-bottom: 1px solid var(--color-border); text-decoration: none; }
.patent__grants a:hover { color: var(--color-heading); border-bottom-color: var(--color-accent); }
.patent__date { color: var(--color-text-muted); font-size: var(--fs-xs); margin-left: 0.45rem; }
.patent__intl { margin: 0.7rem 0 0; font-size: var(--fs-sm); line-height: 1.7; color: var(--color-text-muted); }
.patent__intl-label { color: var(--color-text-subtle); margin-right: 0.35rem; }

/* Narrow: stack title over meta and pin the chevron top-right, so a long
   multi-line title never strands the chevron on its own line. */
@media (max-width: 560px) {
  .patent__head {
    position: relative;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.2rem;
    padding-right: 1.8rem;
  }
  .patent__head::after { position: absolute; top: 1.1rem; right: 0.2rem; align-self: auto; }
}
