/* ============================================================
   TOKENS
   ============================================================ */
:root {
  --dark:   #1a1a18;
  --light:  #8a8a85;
  --mid:    #555552;
  --rule:   #d8d5cc;
  --bg:     #f4f1ea;
  --surface:#ffffff;
  --accent: #1a1a18;
  --link-blue: #2d4e7a;
  --link-blue-tint: rgba(45, 78, 122, 0.07);
  --serif:  'Lora', Georgia, serif;
  --sans:   'Poppins', sans-serif;
  --transition-theme: background 0.35s ease, color 0.35s ease, border-color 0.35s ease, filter 0.35s ease;
  --pad-x: clamp(20px, 4vw, 64px);
  --gap: clamp(16px, 2vw, 32px);
  --max-w: 2100px;
}

html { font-size: 16px; scroll-behavior: smooth; }
html.fs-small { font-size: 14px; }
html.fs-large { font-size: 19px; }

body.dark {
  --dark:   #efece5;
  --light:  #7a7a75;
  --mid:    #a8a8a2;
  --rule:   #2e2e2b;
  --bg:     #121210;
  --surface:#1c1c19;
  --accent: #efece5;
  --link-blue: #7fa6dd;
  --link-blue-tint: rgba(127, 166, 221, 0.10);
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

body, nav, .page, footer, .project-cell, .banner, .fact, .cv-row {
  transition: var(--transition-theme);
}

body {
  font-family: var(--sans);
  font-weight: 300;
  background: var(--bg);
  color: var(--dark);
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  line-height: 1.65;
}

a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; height: auto; }

/* ============================================================
   NAV
   ============================================================ */
nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 100;
  background: color-mix(in srgb, var(--bg) 55%, transparent);
  backdrop-filter: blur(22px) saturate(1.4);
  -webkit-backdrop-filter: blur(22px) saturate(1.4);
  border-bottom: 0.5px solid color-mix(in srgb, var(--rule) 70%, transparent);
  height: 60px;
  display: flex; align-items: center;
  padding: 0 var(--pad-x);
  justify-content: space-between;
  gap: 16px;
}

.nav-brand {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  height: 38px;
}
.nav-logo {
  height: 36px;
  width: auto;
  display: block;
  filter: brightness(0);
  transition: filter 0.35s ease, opacity 0.2s ease;
}
body.dark .nav-logo { filter: brightness(0) invert(1); }
.nav-brand:hover .nav-logo { opacity: 0.7; }

.nav-links {
  display: flex; gap: clamp(16px, 2.2vw, 32px);
  list-style: none;
}
.nav-links a {
  font-size: 0.625rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--light);
  cursor: pointer;
  transition: color 0.2s;
  font-weight: 400;
}
.nav-links a:hover,
.nav-links a.active { color: var(--dark); }

.nav-modes { display: flex; align-items: center; gap: 6px; }

/* ---- Hamburger button (mobile only) ----
   Lives OUTSIDE <nav> so its z-index is in the top-level stacking context.
   On mobile it's visible; always sits above the mobile menu overlay so the
   user can tap it to close. */
.nav-hamburger {
  display: none;                /* desktop: hidden; mobile media query flips this to flex */
  position: fixed;
  top: 8px;
  right: 12px;
  width: 40px;
  height: 40px;
  padding: 0;
  margin: 0;
  background: transparent;
  border: none;
  cursor: pointer;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 6px;
  z-index: 200;
}
.nav-hamburger span {
  display: block;
  width: 22px;
  height: 1.5px;
  background: var(--dark);
  transition: transform 0.3s ease, opacity 0.3s ease;
  transform-origin: center;
}
/* Hamburger bars use var(--dark) which auto-inverts in dark mode
   (--dark is redefined to the off-white foreground in body.dark). */
.nav-hamburger.open span:nth-child(1) { transform: translateY(3.75px) rotate(45deg); }
.nav-hamburger.open span:nth-child(2) { transform: translateY(-3.75px) rotate(-45deg); }

/* ---- Full-screen mobile menu ----
   Uses display: none/flex for bulletproof toggling (no opacity/visibility
   transition quirks). Sits ABOVE the nav (z-index 100) so it truly covers
   the whole screen. Hamburger at z-index 200 stays above to close. */
.mobile-menu {
  display: none;
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  z-index: 150;
  /* Solid-ish warm fallback color in case backdrop-filter isn't supported */
  background: color-mix(in srgb, var(--bg) 96%, transparent);
  align-items: center;
  justify-content: center;
}
@supports (backdrop-filter: blur(20px)) or (-webkit-backdrop-filter: blur(20px)) {
  .mobile-menu {
    background: color-mix(in srgb, var(--bg) 65%, transparent);
    backdrop-filter: blur(22px) saturate(1.4);
    -webkit-backdrop-filter: blur(22px) saturate(1.4);
  }
}
.mobile-menu.open { display: flex; }
.mobile-menu-links {
  list-style: none;
  padding: 0;
  margin: 0;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: clamp(20px, 5vw, 36px);
}
.mobile-menu-links a {
  font-family: var(--serif);
  font-size: clamp(28px, 8vw, 48px);
  font-weight: 400;
  color: var(--dark);
  letter-spacing: -0.01em;
  cursor: pointer;
  text-decoration: none;
  transition: opacity 0.2s ease;
  display: inline-block;
  padding: 4px 8px;
}
.mobile-menu-links a:hover,
.mobile-menu-links a.active {
  opacity: 0.55;
}

.fs-group {
  display: flex; align-items: center;
  border: 0.5px solid var(--rule);
  border-radius: 999px;
  padding: 2px;
  margin-right: 6px;
}
.mode-btn {
  background: none; border: none;
  color: var(--light); cursor: pointer;
  font-family: var(--sans);
  font-weight: 400;
  height: 28px; min-width: 28px; padding: 0 6px;
  border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  transition: color 0.2s, background 0.2s, transform 0.2s;
  line-height: 1;
}
.fs-btn { font-size: 11px; letter-spacing: 0; }
.fs-btn.fs-1 { font-size: 9px; }
.fs-btn.fs-2 { font-size: 11px; }
.fs-btn.fs-3 { font-size: 13px; }

.mode-btn:hover { color: var(--dark); }
.mode-btn.active-mode {
  background: var(--dark);
  color: var(--bg);
}

.theme-btn {
  width: 34px; height: 34px;
  border: 0.5px solid var(--rule);
  border-radius: 50%;
  font-size: 14px;
  margin-left: 4px;
}
.theme-btn:hover { border-color: var(--dark); color: var(--dark); transform: scale(1.08); }

/* ============================================================
   PAGE SYSTEM
   ============================================================ */
.page {
  display: none;
  padding-top: 60px;
  min-height: 100vh;
  max-width: var(--max-w);
  margin: 0 auto;
}
.page.active { display: block; animation: fadeUp 0.6s cubic-bezier(0.16,1,0.3,1) both; }

@keyframes fadeUp {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ============================================================
   HOME
   ============================================================ */
.home-hero {
  padding: clamp(28px, 4vw, 56px) var(--pad-x) clamp(40px, 4.5vw, 80px);
  border-bottom: 0.5px solid var(--rule);
  position: relative;
}
.home-rule {
  position: absolute; top: 60px; left: 0; right: 0;
  height: 2px; background: var(--dark);
}
.home-eyebrow {
  font-size: 0.6875rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--light);
  margin-bottom: clamp(20px, 3vw, 40px);
}
.home-title {
  font-family: var(--serif);
  font-size: clamp(48px, 7.5vw, 136px);
  font-weight: 400;
  line-height: 0.95;
  letter-spacing: -0.015em;
  color: var(--dark);
  margin-bottom: clamp(14px, 1.6vw, 22px);
}
.home-wordmark {
  display: flex;
  align-items: center;
  gap: clamp(20px, 2.5vw, 48px);
  margin-bottom: clamp(24px, 3vw, 56px);
}
.home-mark {
  flex-shrink: 0;
  height: clamp(104px, 13.5vw, 208px);
  width: auto;
  display: block;
  transition: filter 0.35s ease;
}
body.dark .home-mark { filter: invert(1); }
/* Pre-hide the inline-SVG paths so there's no flash before JS primes them.
   drawHomeMark() sets per-path stroke-dasharray/stroke-dashoffset inline,
   then animates dashoffset -> 0 for a stroke-draw reveal. Each O and A
   path draws from its own "bottom" starting point simultaneously. */
.home-mark.draw-logo path {
  stroke-dasharray: 2000;
  stroke-dashoffset: 2000;
}
@media (prefers-reduced-motion: reduce) {
  .home-mark.draw-logo path {
    stroke-dasharray: none;
    stroke-dashoffset: 0;
  }
}
/* Tablet: give the logo + wordmark some breathing room */
@media (max-width: 900px) {
  .home-wordmark { gap: clamp(16px, 3vw, 36px); }
}
/* Mobile: stack the logo above the wordmark, logo centered */
@media (max-width: 640px) {
  .home-wordmark {
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: clamp(20px, 5vw, 32px);
  }
  .home-mark {
    height: clamp(72px, 22vw, 120px);
    margin: 0 auto;
  }
  .home-title {
    text-align: center;
  }
  .home-sub {
    margin-left: auto;
    margin-right: auto;
    text-align: center;
  }
  .home-locus {
    justify-content: center;
  }
}
.home-sub {
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(16px, 1.7vw, 22px);
  color: var(--mid);
  max-width: 640px;
  line-height: 1.45;
}
.home-locus {
  margin-top: clamp(24px, 3vw, 40px);
  display: flex; flex-wrap: wrap; gap: 24px;
  font-size: 0.6875rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--light);
}
.home-locus span::before { content: "— "; opacity: 0.45; }
.home-locus span:first-child::before { content: ""; }

/* ============================================================
   PROJECT GRID
   ============================================================ */
.grid-label {
  padding: clamp(40px, 5vw, 72px) var(--pad-x) clamp(16px, 2vw, 24px);
  font-size: 0.625rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--light);
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 16px; flex-wrap: wrap;
}
.grid-label .count {
  font-family: var(--serif);
  font-style: italic;
  color: var(--mid);
  letter-spacing: 0.05em;
  text-transform: none;
  font-size: 0.875rem;
}

.project-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border-top: 0.5px solid var(--rule);
  border-bottom: 0.5px solid var(--rule);
}
.project-cell {
  position: relative;
  aspect-ratio: 3 / 2;
  border-right: 0.5px solid var(--rule);
  border-bottom: 0.5px solid var(--rule);
  padding: 0;
  cursor: pointer;
  overflow: hidden;
  background: var(--bg);
}
.project-cell:nth-child(2n) { border-right: none; }

.cell-media {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  padding: clamp(24px, 4vw, 64px);
}
.cell-outline {
  width: 100%; height: 100%;
  object-fit: contain;
  filter: brightness(0);
  transition: opacity 0.55s cubic-bezier(0.16,1,0.3,1), filter 0.35s ease;
  opacity: 1;
}
body.dark .cell-outline {
  filter: brightness(0) invert(1);
}
.cell-photo {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity 0.55s cubic-bezier(0.16,1,0.3,1);
  mix-blend-mode: multiply;
}
body.dark .cell-photo {
  filter: invert(1);
  mix-blend-mode: screen;
}
.project-cell:hover .cell-outline { opacity: 0; }
.project-cell:hover .cell-photo { opacity: 1; }

/* Hover behavior (all cells):
   - Idle: SVG sits centered at natural size (viewBox whitespace gives
     comfortable padding). Photo hidden.
   - Hover: photo fades in full-bleed (object-fit: cover); SVG scales
     up and fades out. SVG fades faster than photo so linework briefly
     overlays the emerging photo. */
.project-cell .cell-photo {
  object-fit: cover;
  mix-blend-mode: normal;
  transition: opacity 1.02s cubic-bezier(0.16, 1, 0.3, 1);
}
body.dark .project-cell .cell-photo {
  filter: none;
  mix-blend-mode: normal;
}
.project-cell .cell-outline {
  transform: scale(1);
  transform-origin: center center;
  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1),
              opacity 0.35s ease,
              filter 0.35s ease;
}
.project-cell:hover .cell-outline {
  transform: scale(1.2);
  filter: brightness(0) blur(6px);
}
body.dark .project-cell:hover .cell-outline {
  filter: brightness(0) invert(1) blur(6px);
}
/* Board Room outline: 20% smaller so cell text doesn't overlap the drawing.
   Hover scale reduced proportionally (0.8 × 1.2 = 0.96) to preserve the
   same relative zoom feel as other cells. */
.project-cell .cell-outline[data-project="boardroom"] {
  transform: scale(0.8);
}
.project-cell:hover .cell-outline[data-project="boardroom"] {
  transform: scale(0.96);
}

/* Hover text — blue (#5395E6) across all cells */
.project-cell:hover .cell-overlay { color: #5395E6; }
.project-cell:hover .cell-num { color: rgba(83, 149, 230, 0.8); }
.project-cell:hover .cell-meta { color: rgba(83, 149, 230, 0.9); }

/* Touch devices: mirror hover state when cell is in viewport.
   Gated on (hover: none) so desktop hover isn't disturbed. */
@media (hover: none) {
  .project-cell.in-view .cell-outline {
    opacity: 0;
    transform: scale(1.2);
    filter: brightness(0) blur(6px);
  }
  body.dark .project-cell.in-view .cell-outline {
    filter: brightness(0) invert(1) blur(6px);
  }
  .project-cell.in-view .cell-photo { opacity: 1; }
  .project-cell.in-view .cell-overlay { color: #5395E6; }
  .project-cell.in-view .cell-num { color: rgba(83, 149, 230, 0.8); }
  .project-cell.in-view .cell-meta { color: rgba(83, 149, 230, 0.9); }
  /* Dark wash behind the text for legibility. Applied directly to
     the overlay (so it's above the photo, below the text) and the
     overlay is expanded upward via padding-top so the gradient
     extends from the bottom of the frame to well above the text. */
  .project-cell.in-view .cell-overlay {
    padding-top: clamp(110px, 26vw, 200px);
    background: linear-gradient(to top,
                  rgba(0,0,0,0.82) 0%,
                  rgba(0,0,0,0.55) 45%,
                  rgba(0,0,0,0)    100%);
  }
  /* Other-work grid: text already sits in its own block below the image,
     so skip the wash + don't expand padding. */
  .other-work-grid .project-cell.in-view .cell-overlay {
    padding-top: clamp(16px, 2vw, 28px);
    background: none;
  }
}

.cell-overlay {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: clamp(16px, 2vw, 28px) clamp(20px, 3vw, 36px);
  color: var(--dark);
  pointer-events: none;
  display: flex; justify-content: space-between; align-items: flex-end;
  gap: 16px;
}
.project-cell:hover .cell-overlay::before {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; top: -60%;
  background: linear-gradient(to top, rgba(0,0,0,0.55), rgba(0,0,0,0));
  z-index: -1;
  pointer-events: none;
}
/* Other-work cells: the overlay is positioned BELOW the image (not on top),
   so the gradient has no photo to sit against and creates a weird dark bar
   between image and text. Kill it for both desktop hover and touch/in-view. */
.other-work-grid .project-cell:hover .cell-overlay::before,
.other-work-grid .project-cell.in-view .cell-overlay::before {
  display: none;
}
/* In place of the gradient, apply a subtle 5% dark wash across the WHOLE
   cell on hover (light mode). This now applies to every .project-cell —
   both the home grid and the about-page other-work grid — so both pages
   share the same darkened-bg hover feedback. */
.project-cell::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.05);
  opacity: 0;
  transition: opacity 0.28s ease;
  pointer-events: none;
  z-index: 3;
}
.project-cell:hover::after { opacity: 1; }
@media (hover: none) {
  .project-cell.in-view::after { opacity: 1; }
}
/* Dark mode: the 5% tint reads as nothing against the dark background.
   Replace with a 25% wash so the hover state has real weight. */
body.dark .project-cell::after {
  background: rgba(0, 0, 0, 0.25);
}
.cell-overlay > * { position: relative; z-index: 1; }
.cell-num {
  font-size: 0.625rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--light);
  margin-bottom: 6px;
  transition: color 0.25s;
}
.cell-title {
  font-family: var(--serif);
  font-size: clamp(22px, 2.4vw, 34px);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.005em;
}
.cell-meta {
  font-size: 0.625rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--mid);
  text-align: right;
  line-height: 1.7;
  transition: color 0.25s;
}
.cell-arrow {
  display: inline-block;
  margin-left: 8px;
  transform: translateX(0);
  transition: transform 0.4s cubic-bezier(0.16,1,0.3,1);
  opacity: 0;
}
.project-cell:hover .cell-arrow { transform: translateX(6px); opacity: 1; }

/* ============================================================
   PROJECT DETAIL
   ============================================================ */
.proj-header {
  padding: clamp(48px, 6vw, 96px) var(--pad-x) clamp(24px, 3vw, 48px);
  border-bottom: 0.5px solid var(--rule);
}
.proj-back {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 0.625rem; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--light);
  margin-bottom: clamp(20px, 3vw, 36px);
  cursor: pointer;
}
.proj-back:hover { color: var(--dark); }
.proj-back .arr { transition: transform 0.3s; display: inline-block; }
.proj-back:hover .arr { transform: translateX(-4px); }

.proj-attribution {
  font-size: 0.6875rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--mid);
  margin-bottom: clamp(20px, 2.5vw, 32px);
  padding-bottom: 16px;
  border-bottom: 0.5px solid var(--rule);
}
.proj-attribution strong { font-weight: 500; color: var(--dark); letter-spacing: 0.22em; }

.proj-title {
  font-family: var(--serif);
  font-size: clamp(44px, 7vw, 108px);
  font-weight: 400;
  line-height: 0.95;
  letter-spacing: -0.02em;
  color: var(--dark);
  margin-bottom: clamp(24px, 3vw, 40px);
}

.proj-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: clamp(20px, 2vw, 32px);
  margin-top: clamp(20px, 3vw, 36px);
}
.proj-meta .fact label {
  display: block;
  font-size: 0.625rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--light);
  margin-bottom: 8px;
}
.proj-meta .fact span {
  font-family: var(--serif);
  font-size: 0.95rem;
  color: var(--dark);
  line-height: 1.5;
}

/* Hero image at top of project page */
.proj-hero {
  width: 100%;
  height: clamp(300px, 65vh, 760px);
  border-bottom: 0.5px solid var(--rule);
  overflow: hidden;
  background: var(--bg);
}
.proj-hero img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
@media (max-width: 700px) {
  .proj-hero { height: clamp(220px, 45vh, 480px); }
}

/* Description */
.proj-body {
  padding: clamp(40px, 5vw, 80px) var(--pad-x);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: clamp(32px, 4vw, 80px);
  border-bottom: 0.5px solid var(--rule);
}
.proj-body .col-left {
  font-size: 0.625rem; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--light);
}
.proj-body .col-right {
  font-family: var(--serif);
  font-size: clamp(16px, 1.4vw, 20px);
  line-height: 1.65;
  color: var(--dark);
}
.proj-body .col-right p + p { margin-top: 1.4em; }
.placeholder-tag {
  display: inline-block;
  margin-top: 1.4em;
  padding: 4px 10px;
  font-family: var(--sans);
  font-size: 0.6875rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--light);
  border: 0.5px dashed var(--rule);
}

/* Gallery — CSS Grid with priority-driven size classes.

   Priority → size mapping:
     priority1 → .size-large  (full-width hero)
     priority2 → .size-medium (half-width)
     priority3 → .size-small  (quarter-width)  — future

   DOM order is interleaved for a varied "assortment" look on desktop.
   On mobile, flex-direction: column + CSS `order` re-sorts items by size
   class so they display in priority order (large → medium → small).

   Column counts: 1 (mobile) → 2 (tablet) → 4 (laptop) → 6 (wide). */
.proj-gallery {
  padding: clamp(32px, 4vw, 64px) var(--pad-x) clamp(48px, 6vw, 96px);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-flow: dense;
  gap: var(--gap);
  align-items: start;
}
/* legacy class hooks — no-ops so old markup still works */
.proj-gallery.cols-1,
.proj-gallery.cols-2 { /* size classes below handle layout */ }

.proj-gallery figure {
  margin: 0;
  position: relative;
  overflow: hidden;
  /* default span (behaves like size-small if no size class applied) */
  grid-column: span 1;
}
/* Aspect ratios chosen so that M and S row heights always match —
   a medium is twice as wide as a small, so its aspect ratio must be
   twice as wide (2× small aspect) for them to tile without gaps.
     small:  3/4  → row height =  (4/3) × col_w
     medium: 3/2  → row height =  (2/3) × (2·col_w) = (4/3) × col_w  ✓
     large:  3/2  → row height = (2/3) × (4·col_w) = (8/3) × col_w (alone on row)
   Images cropped with object-fit: cover. */
.proj-gallery figure.size-large  { grid-column: span 4; aspect-ratio: 3 / 2; }
.proj-gallery figure.size-medium { grid-column: span 2; aspect-ratio: 3 / 2; }
/* Small's aspect is overridden at runtime by equalizeGalleryHeights() in
   main.js so its rendered height matches size-medium exactly, accounting for
   the gap between the 2 spanned columns of a medium. CSS fallback 3/4 is
   used before JS runs or if JS is disabled. */
.proj-gallery figure.size-small  { grid-column: span 1; aspect-ratio: var(--small-ar, 3 / 4); }

/* .wide kept as alias for legacy markup */
.proj-gallery figure.wide { grid-column: span 4; aspect-ratio: 3 / 2; }

.proj-gallery img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  object-position: center;
  background: var(--surface);
  border: 0.5px solid var(--rule);
}

/* Responsive column counts.
   Size ratios stay consistent across breakpoints:
     large  = full width
     medium = 1/2 width
     small  = 1/4 width
   Wide breakpoint uses 8 cols so small remains a true quarter (span 2 of 8). */
@media (min-width: 1700px) {
  .proj-gallery { grid-template-columns: repeat(8, 1fr); }
  .proj-gallery figure.size-large  { grid-column: span 8; }
  .proj-gallery figure.size-medium { grid-column: span 4; }
  /* Small spans 2 of 8 cols at wide, still 1 col-unit worth of "small",
     and its aspect is still the --small-ar variable set by JS so medium
     (span 4 = 2 col-units + 1 gap) matches its height. */
  .proj-gallery figure.size-small  { grid-column: span 2; }
  .proj-gallery figure.wide        { grid-column: span 8; }
}
/* Mobile — revert to priority order via flex + `order`, and let
   images show at their natural aspect ratio (no cropping on narrow screens). */
@media (max-width: 640px) {
  .proj-gallery {
    display: flex;
    flex-direction: column;
    gap: var(--gap);
  }
  .proj-gallery figure {
    width: 100%;
    grid-column: auto;
    aspect-ratio: auto;
  }
  .proj-gallery img {
    height: auto;
    object-fit: initial;
  }
  .proj-gallery figure.size-large  { order: 1; }
  .proj-gallery figure.size-medium { order: 2; }
  .proj-gallery figure.size-small  { order: 3; }
  .proj-gallery figure.wide        { order: 1; }
  .proj-gallery .priority-divider  { display: none; }
}

/* Image filename labels — hidden by default, shown only when dev mode is on
   (footer toggle). Used during image review/culling. */
.gallery-label { display: none; }
body.dev-mode .gallery-label {
  display: block;
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 2;
  background: rgba(0, 0, 0, 0.72);
  color: #ff3b3b;
  font-family: 'SFMono-Regular', Menlo, Consolas, monospace;
  font-size: 11px;
  letter-spacing: 0.04em;
  padding: 4px 7px;
  border-radius: 2px;
  pointer-events: none;
  max-width: calc(100% - 16px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Footer "dev mode" toggle — intentionally tiny, unobtrusive. */
.dev-toggle {
  position: absolute;
  right: var(--pad-x);
  bottom: 8px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--sans);
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--light);
  opacity: 0.45;
  cursor: pointer;
  user-select: none;
  transition: opacity 0.2s ease;
}
.dev-toggle:hover { opacity: 1; }
.dev-toggle input {
  width: 10px;
  height: 10px;
  margin: 0;
  cursor: pointer;
  accent-color: var(--dark);
}

/* Priority section dividers removed — priority is communicated via image size now. */
.proj-gallery .priority-divider { display: none; }

/* ============================================================
   ABOUT
   ============================================================ */
.about-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
  border-bottom: 0.5px solid var(--rule);
}
.about-left {
  padding: clamp(48px, 6vw, 96px) var(--pad-x);
  border-right: 0.5px solid var(--rule);
}
.about-left .rule { height: 2px; background: var(--dark); width: 48px; margin-bottom: 40px; }
.about-eyebrow {
  font-size: 0.625rem; letter-spacing: 0.28em;
  text-transform: uppercase; color: var(--light);
  margin-bottom: 20px;
}
.about-name {
  font-family: var(--serif);
  font-size: clamp(40px, 5vw, 80px);
  font-weight: 400;
  line-height: 0.95;
  letter-spacing: -0.015em;
  margin-bottom: clamp(32px, 4vw, 56px);
}
.about-facts { display: grid; gap: 24px; }
.about-facts .fact {
  padding-bottom: 20px;
  border-bottom: 0.5px solid var(--rule);
}
.about-facts .fact:last-child { border-bottom: none; }
.about-facts label {
  display: block;
  font-size: 0.625rem; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--light);
  margin-bottom: 8px;
}
.about-facts span {
  font-family: var(--serif);
  font-size: 0.95rem;
  color: var(--dark);
  line-height: 1.55;
}

.about-right { padding: clamp(48px, 6vw, 96px) var(--pad-x); }
.about-bio p {
  font-family: var(--serif);
  font-size: clamp(17px, 1.5vw, 22px);
  color: var(--dark);
  line-height: 1.6;
}
.about-bio p + p { margin-top: 1.5em; }

/* Other work */
/* Full-bleed section: the grid goes edge-to-edge like the home grid,
   while the heading and footnote below get their own horizontal padding. */
.other-work-section {
  padding: clamp(56px, 6vw, 96px) 0 clamp(40px, 5vw, 72px);
  border-top: 0.5px solid var(--rule);
}
.other-work-heading {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: clamp(24px, 3vw, 40px);
  gap: 16px; flex-wrap: wrap;
  padding: 0 var(--pad-x);
}
.other-work-footnote {
  padding: clamp(20px, 3vw, 36px) var(--pad-x) 0;
  font-family: var(--serif);
  font-style: italic;
  color: var(--mid);
  font-size: 0.95rem;
  max-width: 720px;
  line-height: 1.5;
}
.other-work-heading h2 {
  font-family: var(--serif);
  font-size: clamp(28px, 3vw, 44px);
  font-weight: 400;
  letter-spacing: -0.01em;
}
.other-work-heading .sub {
  font-family: var(--serif);
  font-style: italic;
  color: var(--mid);
  font-size: 0.95rem;
  max-width: 480px;
  line-height: 1.5;
}

/* === Other-work grid: reuses home .project-cell markup & styling ===
   Same SVG-outline-to-photo crossfade as the home grid.
   Layout difference from home: the home grid locks each cell to a 3:2
   aspect-ratio with the overlay absolute-positioned at the bottom. That
   works when all titles are a single line, but Morgan State's two-line
   title squeezes the media area. Here we flip that: the MEDIA gets a
   fixed 3:2 ratio and the overlay flows naturally below it, so long
   titles push the whole cell taller instead of shrinking the drawing.
   Hover photo uses `contain` to avoid cropping landscape photos.
   Supports 4-up at wide viewports.
   (Added 2026-04-17.) */
.other-work-grid {
  border-bottom: 0.5px solid var(--rule);
}
/* Cell becomes a flex column: media block on top, overlay flows below. */
.other-work-grid .project-cell {
  aspect-ratio: auto;
  display: flex;
  flex-direction: column;
}
/* Media block holds its own 3:2 ratio independent of text height.
   overflow: hidden clamps oversized SVG viewBoxes (Morgan in particular
   renders taller than the 3:2 frame otherwise). */
.other-work-grid .cell-media {
  position: relative;
  inset: auto;
  width: 100%;
  aspect-ratio: 3 / 2;
  padding: clamp(20px, 3vw, 40px);
  flex: 0 0 auto;
  overflow: hidden;
}
/* Force both the outline SVG and the hover photo to fit inside the 3:2
   media box regardless of their own intrinsic aspect ratios. Without
   these explicit rules, some SVGs (e.g. Morgan) were scaling beyond the
   cell's height and making that cell visibly taller than the others. */
.other-work-grid .cell-outline,
.other-work-grid .cell-photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  padding: 0;
}
/* Outline drawings use contain so the full linework is always visible
   (SVGs don't actually use object-fit, but this is a safety net). */
.other-work-grid .cell-outline {
  object-fit: contain;
  /* 80% scale (20% smaller) + vertically centered. */
  transform: scale(0.8);
  transform-origin: center center;
}
/* Hover photos use cover so they bleed edge-to-edge like Morgan —
   any aspect mismatch is cropped rather than letterboxed. */
.other-work-grid .cell-photo {
  object-fit: cover;
  object-position: center center;
}
/* Other-work cells have no photo, so the outline IS the content —
   keep it visible on hover (override the base fade/blur) and just
   let it bump proportionally (0.8 * 1.2 = 0.96). */
.other-work-grid .project-cell:hover .cell-outline {
  opacity: 1;
  transform: scale(0.96);
  filter: brightness(0);
}
body.dark .other-work-grid .project-cell:hover .cell-outline {
  filter: brightness(0) invert(1);
}
/* Mirror for touch (in-view) state — same override logic. */
@media (hover: none) {
  .other-work-grid .project-cell.in-view .cell-outline {
    opacity: 1;
    transform: scale(0.96);
    filter: brightness(0);
  }
  body.dark .other-work-grid .project-cell.in-view .cell-outline {
    filter: brightness(0) invert(1);
  }
}
/* Overlay flows naturally below media — text length dictates cell height.
   Stack contents vertically (title above, location below) left-aligned.
   This overrides the home grid's space-between flex layout where the
   location floats to the right of the title. */
.other-work-grid .cell-overlay {
  position: relative;
  left: auto; right: auto; bottom: auto;
  padding: clamp(16px, 2vw, 28px) clamp(20px, 3vw, 36px);
  flex: 1 1 auto;
  display: block;
}
.other-work-grid .cell-overlay .cell-meta {
  margin-top: 10px;
  text-align: left;
}

/* 4-up at wide viewports (use compound selector to beat .project-grid
   @media rules that try 3-col at >=1600px). */
@media (min-width: 1000px) {
  .project-grid.other-work-grid {
    grid-template-columns: repeat(4, 1fr);
  }
  /* Reset home 2n border rule and set 4n for the 4-col layout. */
  .project-grid.other-work-grid .project-cell {
    border-right: 0.5px solid var(--rule);
  }
  .project-grid.other-work-grid .project-cell:nth-child(4n) {
    border-right: none;
  }
}

/* ============================================================
   CV
   ============================================================ */
.cv-head {
  padding: clamp(48px, 6vw, 96px) var(--pad-x) clamp(24px, 3vw, 48px);
  border-bottom: 0.5px solid var(--rule);
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: clamp(24px, 3vw, 48px);
  align-items: end;
}
.cv-title {
  font-family: var(--serif);
  font-size: clamp(40px, 6vw, 88px);
  font-weight: 400;
  line-height: 0.95;
  letter-spacing: -0.015em;
}
.cv-title em { font-style: italic; color: var(--mid); font-weight: 400; }
.cv-credentials {
  font-size: 0.625rem; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--light);
  margin-top: 16px;
  line-height: 1.9;
}
.cv-download {
  justify-self: end;
  display: inline-flex; align-items: center; gap: 10px;
  padding: 12px 20px;
  border: 0.5px solid var(--dark);
  font-size: 0.625rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--dark);
  transition: background 0.2s, color 0.2s;
  white-space: nowrap;
}
.cv-download:hover { background: var(--dark); color: var(--bg); }

.cv-section {
  padding: clamp(32px, 4vw, 64px) var(--pad-x);
  border-bottom: 0.5px solid var(--rule);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 3fr);
  gap: clamp(24px, 4vw, 64px);
}
.cv-section h2 {
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(22px, 2.4vw, 32px);
  font-weight: 400;
  color: var(--mid);
}
.cv-row {
  display: grid;
  grid-template-columns: 120px minmax(0, 1fr);
  gap: 24px;
  padding: 14px 0;
  border-top: 0.5px solid var(--rule);
}
.cv-row:first-child { border-top: none; }
.cv-row.cv-row-joined { border-top: none; padding-top: 0; }
.cv-entry .collab-note {
  font-family: var(--serif);
  font-style: italic;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--mid);
  margin-top: 0;
  line-height: 1.4;
}
.cv-year {
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  color: var(--light);
  font-variant-numeric: tabular-nums;
}
.cv-entry {
  font-family: var(--serif);
  font-size: 0.95rem;
  color: var(--dark);
  line-height: 1.55;
}
.cv-entry .role {
  font-family: var(--sans);
  font-size: 0.625rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--mid);
  margin-top: 4px;
}
.cv-entry ul { list-style: none; margin-top: 10px; }
.cv-entry li {
  font-family: var(--sans);
  font-size: 0.8125rem;
  color: var(--mid);
  padding: 2px 0;
}
.cv-entry .awards {
  margin-top: 10px;
  padding-left: 16px;
  border-left: 2px solid var(--rule);
}
.cv-entry .awards .awlabel {
  font-family: var(--sans);
  font-size: 0.625rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--light);
  display: block;
  margin-bottom: 4px;
}

/* ============================================================
   CONTACT
   ============================================================ */
.contact-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  min-height: calc(100vh - 60px);
}
.contact-left {
  padding: clamp(48px, 8vw, 120px) var(--pad-x);
  border-right: 0.5px solid var(--rule);
  display: flex; flex-direction: column; justify-content: space-between;
  gap: 48px;
}
.contact-heading {
  font-family: var(--serif);
  font-size: clamp(56px, 10vw, 160px);
  font-weight: 400;
  line-height: 0.9;
  letter-spacing: -0.02em;
}
.contact-sub {
  font-family: var(--serif);
  font-style: italic;
  color: var(--mid);
  font-size: clamp(16px, 1.6vw, 22px);
  margin-top: 20px;
  max-width: 520px;
  line-height: 1.5;
}
.contact-links { display: grid; }
.contact-link {
  display: flex; justify-content: space-between; align-items: center;
  padding: 20px 16px;
  margin: 0 -16px;
  border-top: 0.5px solid var(--rule);
  cursor: pointer;
  transition: padding 0.25s, background-color 0.2s ease, color 0.2s ease;
}
.contact-link:last-of-type { border-bottom: 0.5px solid var(--rule); }
.contact-link:hover {
  padding-left: 24px;
  background-color: var(--link-blue-tint);
  color: var(--link-blue);
}
.contact-link-label {
  font-size: 0.625rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--light);
  margin-bottom: 6px;
  transition: color 0.2s ease;
}
.contact-link .val {
  font-family: var(--serif);
  font-size: 1.05rem;
  color: var(--dark);
  transition: color 0.2s ease;
}
.contact-link .arr {
  color: var(--light);
  transition: transform 0.3s, color 0.2s;
}
.contact-link:hover .contact-link-label,
.contact-link:hover .val,
.contact-link:hover .arr { color: var(--link-blue); }
.contact-link:hover .arr { transform: translateX(6px); }

.contact-right {
  padding: clamp(24px, 3vw, 48px) var(--pad-x);
  display: flex; align-items: center; justify-content: center;
  background: var(--bg);
  min-height: calc(100vh - 60px);
}
.contact-svg-wrap {
  width: 100%; max-width: min(820px, 92%);
  opacity: 0.55;
  position: relative;
  /* Reserve a consistent canvas so stacked items don't collapse layout. */
  aspect-ratio: 4 / 3;
  /* Paths use stroke="currentColor". --dark is redefined in dark mode
     to the off-white foreground (#efece5), so this single rule covers
     both modes — no body.dark override needed. */
  color: var(--dark);
}

.contact-cycle-item {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  opacity: 0;
  transition: opacity 0.45s ease;
  pointer-events: none;
}
.contact-cycle-item.is-active { opacity: 1; }
.contact-cycle-item svg {
  width: 100%; height: 100%;
  max-height: 100%;
}
.contact-cycle-item svg path {
  stroke: currentColor;
  fill: none;
}

/* ============================================================
   FOOTER — outline watermark
   ============================================================ */
footer {
  padding: clamp(40px, 4vw, 72px) var(--pad-x) clamp(24px, 3vw, 40px);
  border-top: 0.5px solid var(--rule);
  position: relative;
  overflow: hidden;
}
.footer-watermark {
  position: absolute;
  right: -4%; bottom: -25%;
  width: min(560px, 60%);
  opacity: 0.05;
  pointer-events: none;
  filter: brightness(0);
}
body.dark .footer-watermark { filter: brightness(0) invert(1); opacity: 0.08; }
.footer-row {
  position: relative; z-index: 1;
  display: flex; justify-content: space-between; align-items: flex-end;
  gap: 24px; flex-wrap: wrap;
}
.footer-col .footer-label {
  font-size: 0.625rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--light);
  margin-bottom: 8px;
}
.footer-col .footer-val {
  font-family: var(--serif);
  font-size: 0.95rem;
  color: var(--dark);
  line-height: 1.5;
}
.footer-credit {
  font-size: 0.6875rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--light);
}

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width: 900px) {
  .nav-links { gap: 14px; }
  .fs-group { margin-right: 2px; }
  .project-grid { grid-template-columns: 1fr; }
  .project-cell { border-right: none !important; }
  .about-layout { grid-template-columns: 1fr; }
  .about-left { border-right: none; border-bottom: 0.5px solid var(--rule); }
  .cv-head { grid-template-columns: 1fr; }
  .cv-download { justify-self: start; }
  .cv-section { grid-template-columns: 1fr; gap: 16px; }
  .cv-row { grid-template-columns: 80px 1fr; gap: 16px; }
  .proj-body { grid-template-columns: 1fr; }
  /* .proj-gallery column-count handled by dedicated media queries above */
  .contact-layout { grid-template-columns: 1fr; min-height: auto; }
  .contact-left { border-right: none; border-bottom: 0.5px solid var(--rule); }
}

/* Mobile-nav breakpoint bumped from 600px to 900px so the hamburger
   appears on tablets and mid-size phones in landscape (2026-04-17). */
@media (max-width: 900px) {
  nav {
    height: 56px;
    /* Reserve right-side space for the fixed hamburger button (40px wide + 12px inset) */
    padding: 0 60px 0 16px;
    gap: 8px;
    /* 3-column layout: brand left, modes centered, empty right (hamburger is fixed outside) */
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    justify-content: unset;
  }
  .nav-brand { justify-self: start; }
  .nav-modes { justify-self: center; }
  .nav-hamburger { display: flex !important; }
  .page { padding-top: 56px; }
  .home-rule { top: 56px; }
  .nav-logo { height: 28px; }
  .nav-links { display: none !important; }
  .fs-btn { height: 26px; min-width: 22px; padding: 0 4px; }
  .theme-btn { width: 30px; height: 30px; font-size: 12px; }
  .proj-meta { grid-template-columns: 1fr 1fr; }
}

@media (min-width: 1600px) {
  .project-grid { grid-template-columns: repeat(3, 1fr); }
  .project-cell:nth-child(2n) { border-right: 0.5px solid var(--rule); }
  .project-cell:nth-child(3n) { border-right: none; }
}
@media (min-width: 2200px) {
  :root { --pad-x: clamp(40px, 4vw, 96px); }
}

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

/* ============================================================
   Lightbox — full-size image viewer
   ============================================================ */
.proj-gallery figure { cursor: zoom-in; }

.lightbox {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 300;
  background: rgba(0, 0, 0, 0.88);
  align-items: center;
  justify-content: center;
  padding: 32px;
  cursor: zoom-out;
  opacity: 0;
  transition: opacity 0.18s ease;
}
.lightbox.open {
  display: flex;
  opacity: 1;
}
.lightbox-img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  display: block;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  cursor: default;
  /* block the zoom-out cursor from the overlay when hovering the image */
}
.lightbox-close {
  position: fixed;
  top: 16px;
  right: 20px;
  width: 48px;
  height: 48px;
  border: none;
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font-size: 32px;
  line-height: 1;
  font-family: var(--serif);
  cursor: pointer;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition: background 0.15s ease, transform 0.15s ease;
}
.lightbox-close:hover {
  background: rgba(255, 255, 255, 0.18);
  transform: scale(1.05);
}
.lightbox-close:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

/* Prevent body scroll while lightbox is open */
body.lightbox-open { overflow: hidden; }

@media (max-width: 640px) {
  .lightbox { padding: 16px; }
  .lightbox-close {
    top: 10px;
    right: 10px;
    width: 40px;
    height: 40px;
    font-size: 28px;
  }
}

/* === Generic draw-in + page reveal system :: START ===
   Works with:
     - inline <svg class="draw-in"> — any number on any page. JS sets each
       path's stroke-dasharray/offset and animation-delay, then adds
       `.is-drawing` to trigger the keyframe.
     - elements with [data-reveal="fade"] — fade up into place.
     - elements with [data-reveal="type"] — letters cascade in (JS-driven).
   Orchestration lives in js/main.js `revealPage()` / `revealHome()`.
   (Added 2026-04-17, generalized 2026-04-18.) */

/* SVG draw-in — every <svg class="draw-in"> waits until `.is-drawing` is set.
   Each path starts invisible (opacity 0, dashoffset = full length) and runs
   two independent animations simultaneously:
     - svgDraw: animates stroke-dashoffset → 0 over the per-path draw duration
       (set inline on each path via JS as animationDuration / --draw-dur).
     - svgFade: fades opacity 0 → 1 over a fixed 1200ms regardless of
       draw duration, so every line has a visible sequential fade-in rather
       than popping into existence — even tiny segments or very long draws
       both get the same perceptible fade. */
.draw-in path {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0;
}
.draw-in.is-drawing path {
  /* `animation-duration` (set inline per-path by JS) applies to the FIRST
     animation in the shorthand (svgDraw). The second animation keeps its
     own fixed duration declared below via `animation`. */
  animation-name: svgDraw, svgFade;
  animation-duration: var(--draw-dur, 0.6s), 1200ms;
  animation-timing-function: ease-out, ease-out;
  animation-fill-mode: forwards, forwards;
}
@keyframes svgDraw {
  to { stroke-dashoffset: 0; }
}
@keyframes svgFade {
  to { opacity: 1; }
}

/* Text reveal — default hidden-ish state, JS adds `.revealed` to play.
   Base transition is 0.45s. The home hero's logo wordmark + support text
   override this with a 25%-slower 0.56s (see `.home-hero` rule below). */
[data-reveal="fade"] {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.45s cubic-bezier(0.16,1,0.3,1),
              transform 0.45s cubic-bezier(0.16,1,0.3,1);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}
/* Home hero fades (wordmark, sub, locus) match the logo's 1200ms fade
   so the whole hero arrives as one beat. Pure opacity — no translateY
   rise, so the text reads as simply appearing rather than sliding in. */
.home-hero [data-reveal="fade"] {
  transform: none;
  transition-duration: 1.2s, 1.2s;
}
.home-hero [data-reveal="fade"].revealed {
  transform: none;
}
/* Home secondary text (tagline + principal/credentials line) uses a
   transition-delay so it arrives ~200ms after the main wordmark begins,
   on top of the JS stagger. Gives the hierarchy a beat of breathing room. */
.home-hero .home-sub {
  --reveal-delay: 200ms;
}
.home-hero .home-locus {
  --reveal-delay: 320ms;
}
[data-reveal="fade"].revealed {
  opacity: 1;
  transform: translateY(0);
}

/* Type-in: each letter is wrapped in a <span class="rl"> by JS; they
   cascade in with a small stagger. The underlying text stays selectable. */
.rl {
  display: inline-block;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.32s cubic-bezier(0.16,1,0.3,1),
              transform 0.32s cubic-bezier(0.16,1,0.3,1);
}
.rl.revealed {
  opacity: 1;
  transform: translateY(0);
}

/* Line-by-line reveal: each word in a [data-reveal="lines"] element is
   wrapped in <span class="rwl-word"> by JS. Words are initially hidden;
   JS groups them by offsetTop and fades each visual line as a group.
   Pure opacity transition (no translateY) for a smooth, ink-soaking feel
   rather than a per-line pop. */
[data-reveal="lines"] .rwl-word {
  display: inline-block;
  opacity: 0;
  transition: opacity 0.9s ease-out;
}
[data-reveal="lines"] .rwl-word.revealed {
  opacity: 1;
}

/* Respect users who opt out of motion — skip everything. */
@media (prefers-reduced-motion: reduce) {
  .draw-in.is-drawing path {
    animation: none !important;
    stroke-dashoffset: 0 !important;
    opacity: 1 !important;
  }
  [data-reveal="fade"],
  [data-reveal="lines"] .rwl-word,
  .rl {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}
/* === Generic draw-in + page reveal system :: END === */
