/* Joshua Baller - Personal brand system v01
   Sand / Ink / Stone / Slate / Signal Orange. PP Neue Montreal + JetBrains Mono.
   8px base unit, 1px hairlines, radius 0 everywhere. Light theme locked per brand ratio. */

@font-face {
  font-family: 'PP Neue Montreal';
  src: url('fonts/PPNeueMontreal-Book.otf') format('opentype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'PP Neue Montreal';
  src: url('fonts/PPNeueMontreal-Italic.otf') format('opentype');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: 'PP Neue Montreal';
  src: url('fonts/PPNeueMontreal-Medium.otf') format('opentype');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'PP Neue Montreal';
  src: url('fonts/PPNeueMontreal-Bold.otf') format('opentype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: url('fonts/JetBrainsMono-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  --ink: #17140F;
  --sand: #F4F2EE;
  --stone: #E9E5DC;
  --slate: #6B665C;
  --signal: #E24B01;
  /* Darker/brighter signal variants: --signal itself fails WCAG AA (4.5:1) as
     text or under white button labels. These pass in their respective roles. */
  --signal-text: #B33701;
  --signal-strong: #B33701;
  /* The hero photo + page-bg are position:fixed behind the ENTIRE page (not
     just the hero), and the vignette scrim fades out well before the fold.
     Any section below the hero sits directly on that photo, so its text must
     stay in the light "on-photo" palette regardless of the light/dark theme
     toggle -- these three tokens are intentionally NOT flipped in [data-theme]. */
  --on-photo-text: #F4F2EE;
  --on-photo-muted: rgba(244, 242, 238, 0.6);
  --on-photo-accent: #FF5A1A;
  --on-photo-hairline: rgba(244, 242, 238, 0.16);
  /* Semantic theme tokens (flip under [data-theme="dark"]) */
  --bg: #F4F2EE;
  --bg-rgb: 244, 242, 238;
  --text: #17140F;
  --text-rgb: 23, 20, 15;
  --surface: #E9E5DC;
  --surface-2: #DAD5C8;
  --muted: #6B665C;
  --elev: #17140F;
  --hairline: rgba(var(--text-rgb), 0.14);
  /* 4-column master grid: 20px outer margin, 20px gutter */
  --margin: 20px;
  --gutter: 20px;
  --max: 1400px;
  /* Soft corner system: 4px interactive, 6px media/cards, 16px large panels */
  --r-sm: 4px;
  --r: 6px;
  --r-lg: 16px;
  --sans: 'PP Neue Montreal', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, 'Cascadia Mono', monospace;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
}

[data-theme="dark"] {
  --bg: #17140F;
  --bg-rgb: 23, 20, 15;
  --text: #F4F2EE;
  --text-rgb: 244, 242, 238;
  --surface: #211C15;
  --surface-2: #36312B;
  --muted: #A8A296;
  --elev: #241F18;
  --signal-text: #FF5A1A;
}

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

html { scroll-behavior: smooth; scroll-padding-top: 72px; overflow-x: clip; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reel { height: auto; }
  .reel-sticky { position: static; height: auto; overflow: visible; }
  /* position: relative (not static) so .scroll-cue, which is absolutely
     positioned against its own slide, keeps that slide as its containing
     block instead of escaping to .reel and collapsing every cue onto one
     spot near the bottom of the whole stacked page. */
  .reel-slide { position: relative; opacity: 1; pointer-events: auto; min-height: 100dvh; }
  .caps-slide, .about-slide, .faq-slide { min-height: auto; overflow-y: visible; }
}

/* Ambient WebGL particle layer: fixed behind the whole page, above the photo, below the scrim */
#fx-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -2;
  pointer-events: none;
}

#about { position: relative; }

body {
  font-family: var(--sans);
  font-weight: 400;
  color: var(--text);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
  padding-bottom: 90px; /* clears the fixed footer's height, plus margin */
}

/* Shared page backdrop: the hero photo, fixed behind every section */
.page-bg {
  position: fixed;
  inset: 0;
  z-index: -3;
  background-color: var(--ink);
  background-image: url('assets/hero-portrait.jpg');
  background-size: cover;
  background-position: 55% 30%;
}
@media (max-width: 900px) {
  .page-bg { background-position: 46% 20%; }
}

::selection { background: var(--signal); color: var(--sand); }

img { max-width: 100%; display: block; }

h1, h2, h3 { font-weight: 500; line-height: 1.05; }

a { color: inherit; }

/* ---------- Nav ---------- */

/* Floating nav: no divider line; scrolled state is a translucent blur */
.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 30;
  background: transparent;
}

/* Reversed mark throughout: the fixed photo backdrop runs behind the entire
   page, not just the hero, so the nav stays in its over-photo styling site-wide */
.nav .logo-solid { display: none; }

.nav-inner {
  max-width: var(--max);
  margin: 0 auto;
  padding: 0 var(--margin);
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.nav-left {
  position: relative;
  display: flex;
  align-items: center;
  gap: 20px;
}
.nav-panel-left {
  left: 0;
  right: auto;
}
.nav-brand {
  display: flex;
  align-items: center;
  text-decoration: none;
}

.nav-chips {
  display: flex;
  align-items: center;
  gap: 6px;
}
.nav-right {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Chips: mono labels as small controls (Oimachi-style, sharp corners per brand) */
.chip {
  display: inline-flex;
  align-items: center;
  padding: 6px 12px;
  border-radius: var(--r-sm);
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  border: 0;
  cursor: pointer;
  color: var(--sand);
  background: rgba(244, 242, 238, 0.14);
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
.chip:hover { background: rgba(244, 242, 238, 0.28); }
.chip.active {
  background: var(--sand);
  color: var(--ink);
}

/* Theme toggle: quiet icon-only chip, half-circle contrast glyph in currentColor */
.chip-theme {
  background: transparent;
  padding: 6px 8px;
}
.chip-theme:hover { background: rgba(244, 242, 238, 0.14); }
.theme-icon {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  background: linear-gradient(90deg, currentColor 50%, transparent 50%);
  transition: transform 0.4s var(--ease);
}
[data-theme="dark"] .theme-icon { transform: rotate(180deg); }

.chip-contact {
  background: var(--signal-strong);
  color: #FFF8F3;
}
.chip-contact:hover,
.chip-contact[aria-expanded="true"] {
  background: var(--sand);
  color: var(--ink);
}

/* Contact dropdown: stacked ink cards below the chip */
.nav-panel {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  width: 320px;
  display: grid;
  gap: 6px;
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity 0.25s var(--ease), transform 0.25s var(--ease);
  pointer-events: none;
}
.nav-panel.open {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
.nav-panel[hidden] { display: none; }

.panel-card {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 14px 16px;
  background: var(--elev);
  border-radius: var(--r);
  text-decoration: none;
  transition: background 0.2s var(--ease);
}
a.panel-card:hover { background: var(--signal-strong); }
.panel-label {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(244, 242, 238, 0.55);
}
a.panel-card:hover .panel-label { color: rgba(244, 242, 238, 0.9); }
.panel-value {
  font-size: 15px;
  font-weight: 500;
  color: var(--sand);
  letter-spacing: -0.01em;
}

/* Page dim behind the open panel */
.nav-overlay {
  position: fixed;
  inset: 0;
  z-index: 9;
  background: rgba(23, 20, 15, 0.35);
  opacity: 0;
  transition: opacity 0.25s var(--ease);
  pointer-events: none;
}
.nav-overlay.open {
  opacity: 1;
  pointer-events: auto;
}
.nav-overlay[hidden] { display: none; }

/* ---------- Modals (full-screen, follow the site theme) ---------- */

body.no-scroll { overflow: hidden; }

/* Hidden-but-functional scrollbars */
.no-scrollbar { scrollbar-width: none; -ms-overflow-style: none; }
.no-scrollbar::-webkit-scrollbar { display: none; }

.modal {
  position: fixed;
  inset: 0;
  z-index: 20;
}
.modal[hidden] { display: none; }

/* Transparent, blurred backdrop: the page shows through, softened, not hidden */
.modal-scrim {
  position: absolute;
  inset: 0;
  background: rgba(var(--text-rgb), 0.35);
  backdrop-filter: blur(20px) saturate(140%);
  -webkit-backdrop-filter: blur(20px) saturate(140%);
}

/* modal-box is an invisible layout wrapper only; its children are the floating cards.
   Left/right match the nav container's content edges (same max-width + margin
   math as .nav-inner) so modals line up with the logo and Contact chip. */
.modal-box {
  position: absolute;
  top: 90px;
  bottom: 90px;
  left: max(var(--margin), calc((100% - var(--max)) / 2 + var(--margin)));
  right: max(var(--margin), calc((100% - var(--max)) / 2 + var(--margin)));
  display: flex;
  flex-direction: column;
  gap: 16px;
  color: var(--text);
  overflow: visible;
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.modal.open .modal-box { opacity: 1; transform: none; }

/* Small centered variant for single-purpose dialogs (the resume gate) rather
   than the full-height, multi-card bento layout the Work/About/Notes modals use. */
.modal-box--compact {
  top: 50%;
  bottom: auto;
  left: 0;
  right: 0;
  margin: 0 auto;
  width: min(420px, calc(100% - 40px));
  transform: translateY(calc(-50% + 16px));
}
.modal.open .modal-box--compact { transform: translateY(-50%); }

/* Unified floating card: wraps the head strip + scrollable content as ONE panel.
   Used directly by .modal-card (About/Posts) and .mw-preview-card (Work). */
.modal-card,
.mw-preview-card {
  display: flex;
  flex-direction: column;
  min-height: 0;
  height: 100%;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  box-shadow: 0 16px 40px rgba(var(--text-rgb), 0.2);
  overflow: hidden;
}

/* ---------- Resume gate ---------- */

.resume-gate {
  position: relative;
  height: auto;
  padding: 40px 36px 32px;
}
.resume-gate-close { position: absolute; top: 12px; right: 12px; }

.resume-gate .section-label { display: block; margin-bottom: 10px; color: var(--muted); }
.resume-gate h2 {
  font-size: clamp(20px, 2.4vw, 24px);
  letter-spacing: -0.02em;
  max-width: 26ch;
}
.resume-gate-sub {
  margin-top: 10px;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.5;
}

.resume-file-chip {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 20px;
  padding: 12px 14px;
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
}
.resume-file-icon {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-sm);
  background: rgba(var(--text-rgb), 0.06);
  color: var(--signal-text);
}
.resume-file-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.resume-file-name {
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.resume-file-meta {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.resume-file-chip--ready { justify-content: space-between; }
.resume-file-chip--ready .resume-file-meta { color: var(--signal-text); }

.resume-gate-form {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.resume-gate-row { display: flex; gap: 12px; }
.resume-gate-row .field { flex: 1; min-width: 0; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field-label {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.field-optional { text-transform: none; letter-spacing: 0; opacity: 0.75; }
.field-input {
  height: 44px;
  padding: 0 14px;
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  font-family: var(--sans);
  font-size: 15px;
  color: var(--text);
  transition: border-color 0.2s var(--ease), background 0.2s var(--ease);
}
.field-input::placeholder { color: var(--muted); }
.field-input:focus-visible {
  outline: none;
  border-color: var(--signal);
  background: var(--surface);
}
.field.field-invalid .field-input { border-color: var(--signal-text); }

.resume-gate-error {
  margin-top: -6px;
  font-size: 13px;
  color: var(--signal-text);
}
.resume-gate-submit { margin-top: 4px; width: 100%; }
.resume-gate-submit[aria-busy="true"] { opacity: 0.6; pointer-events: none; }

.mw-access {
  margin-top: 20px;
  padding: 18px 20px;
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  border-radius: var(--r);
}
.mw-access-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gutter);
}
.mw-access-hint { font-size: 13px; color: var(--muted); max-width: 32ch; }
.mw-access-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 18px 0 14px;
}
.mw-access-divider::before,
.mw-access-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--hairline);
}
.mw-access-divider span {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.takeout-code-form { margin: 0; }
.takeout-code-row { display: flex; gap: 8px; }
.takeout-code-row .field-input { flex: 1; }
.takeout-code-submit { flex-shrink: 0; }
.takeout-code-error {
  margin-top: 10px;
  font-size: 13px;
  color: var(--signal-text);
}

.resume-gate-privacy {
  margin-top: 16px;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.02em;
  color: var(--muted);
  line-height: 1.5;
}

.resume-gate-view--success .resume-gate-sub { margin-bottom: 22px; }

.modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--gutter);
  padding: 20px var(--margin);
  border-bottom: 1px solid var(--hairline);
  flex: 0 0 auto;
}
.modal-head .section-label { display: block; margin-bottom: 6px; color: var(--muted); }
.modal-close {
  padding: 6px 16px;
  font-size: 18px;
  line-height: 1;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}
.modal-head h2 {
  font-size: clamp(19px, 2vw, 24px);
  letter-spacing: -0.02em;
}

/* Chips on the dark modal surface */
.modal .chip {
  color: var(--text);
  background: rgba(var(--text-rgb), 0.12);
}
.modal .chip:hover { background: rgba(var(--text-rgb), 0.24); }

.modal-body {
  flex: 1;
  min-height: 0;
  font-size: 14px;
}
.modal-body-scroll {
  overflow-y: auto;
  padding: 32px var(--margin) 64px;
}
.modal-body-scroll > * { max-width: 1080px; margin-left: auto; margin-right: auto; }
.modal-body > p,
.modal-body-scroll > p { max-width: 60ch; color: rgba(var(--text-rgb), 0.65); }
.modal-posts-follow { margin-top: 16px; }
.modal-posts-follow a { color: var(--signal-text); }

/* Journal: a full post rendered inline, editorial rag matching the rest of the modal type */
.post-entry { margin-top: 32px; padding-top: 32px; border-top: 1px solid var(--hairline); }
.post-entry:first-of-type { margin-top: 0; padding-top: 0; border-top: 0; }
.post-title { margin-top: 0; font-size: clamp(19px, 2.2vw, 24px); letter-spacing: -0.02em; color: var(--text); }
.post-meta {
  margin-top: 6px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.post-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  list-style: none;
  margin-top: 14px;
}
.post-tags li {
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--signal-strong);
  color: #FFF8F3;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.post-body { margin: 24px auto 0; max-width: 68ch; }
.post-body p { margin-top: 14px; color: rgba(var(--text-rgb), 0.7); }
.post-body p:first-child { margin-top: 0; }
.post-body h4 { margin-top: 28px; font-size: 15px; letter-spacing: -0.01em; color: var(--text); }
.post-body h4 + p { margin-top: 10px; }
.post-body ul,
.post-body ol { margin-top: 14px; padding-left: 20px; color: rgba(var(--text-rgb), 0.7); }
.post-body li { margin-top: 8px; }
.post-body a { color: var(--signal-text); }
.post-body hr { border: 0; border-top: 1px solid var(--hairline); margin: 32px 0; }
.post-body .post-sources { font-size: 12.5px; }
.post-body .post-sources li { margin-top: 10px; }

/* Work modal: project list is a transparent scroll lane of separate item-cards;
   the preview is a single unified card (head + live image) on the right, gap between the two. */
.modal-work-body {
  display: grid;
  grid-template-columns: 340px 1fr;
  gap: 16px;
  height: 100%;
}
.mw-list {
  overflow-y: auto;
  padding: 2px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.mw-item {
  display: flex;
  flex-direction: column;
  gap: 3px;
  text-align: left;
  padding: 14px 16px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r);
  box-shadow: 0 8px 20px rgba(var(--text-rgb), 0.14);
  cursor: pointer;
  font-family: var(--sans);
  color: var(--text);
  flex: 0 0 auto;
  transition: background 0.2s var(--ease), border-color 0.2s var(--ease), transform 0.2s var(--ease);
}
/* Solid opaque tones (not translucent) so the selected card never blends into the blurred backdrop */
.mw-item:hover { background: var(--surface-2); transform: translateY(-1px); }
.mw-item.active {
  background: var(--surface-2);
  border-color: rgba(var(--text-rgb), 0.35);
}
.mw-item-head {
  display: flex;
  align-items: center;
  gap: 12px;
}
.mw-item-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
/* Unsupplied projects fall back to a dashed placeholder tile (same
   treatment as .project-logomark on the Work reel); supplied logos render
   bare, no chip container, since each asset already carries its own
   monochrome treatment (see okrun-logo-mono.svg, anikita-dark-logomark.svg,
   takeout-studio-logo.svg). */
span.mw-logo,
img.mw-logo {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
}
span.mw-logo {
  border-radius: var(--r-sm);
  border: 1px dashed rgba(var(--text-rgb), 0.25);
  background: rgba(var(--text-rgb), 0.06);
}
img.mw-logo {
  object-fit: contain;
}
/* White backdrop, requested as-is: the leaf shape (rgb(242,242,242)) reads
   at very low contrast against it, so only the eye/text glyph (default
   black fill) stays clearly legible here. */
img.mw-logo--chip-dark {
  padding: 6px;
  border-radius: var(--r-sm);
  background: #FFFFFF;
}
/* Phở Saigon's mark is flattened to its Papaya Whip cream tone (see
   pho-saigon-logomark-mono.svg), which is nearly as light as the white
   chip above -- so it gets its own fixed mid-dark grey backdrop instead,
   independent of the site theme, same reasoning as Emerald Eye Care's
   original chip. */
img.mw-logo--chip-grey {
  padding: 6px;
  border-radius: var(--r-sm);
  background: #4A453D;
}
.mw-cat {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.mw-item-locked .mw-cat { color: var(--signal-text); }
/* Case study is the only category badge styled as a pill, outlined rather
   than filled so it stays legible against both the item card and the
   selected/hover surface tones. */
.mw-cat-pill {
  display: inline-block;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--signal-strong);
  font-weight: 500;
  color: var(--signal-text);
}
.mw-name { font-size: 14px; font-weight: 500; letter-spacing: -0.01em; }
.mw-tag-inline {
  padding: 2px 8px;
  margin-left: 4px;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  vertical-align: middle;
}
.mw-desc { font-size: 12px; color: var(--muted); }
.mw-date {
  display: block;
  margin-top: 6px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.mw-preview {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 20px var(--margin) 64px 20px;
  outline: none;
}
.mw-pane { display: none; }
.mw-pane.active { display: block; }

.mw-hero {
  position: relative;
  overflow: hidden;
  border-radius: var(--r);
  background: rgba(var(--text-rgb), 0.06);
  aspect-ratio: 16 / 9;
}
.mw-hero img, .mw-hero video, .mw-grid img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.85) contrast(1.02);
}
.mw-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gutter);
  margin-top: 20px;
}
.mw-grid figure {
  overflow: hidden;
  border-radius: var(--r);
  background: rgba(var(--text-rgb), 0.06);
  aspect-ratio: 3 / 2;
}
/* Full-length reference sheets (Phở Saigon's compact guide, Emerald Eye
   Care's brand asset strip) shown at their own size -- no card frame,
   no cropping, just the full image. */
.mw-guide {
  margin-top: 20px;
}
.mw-guide img {
  display: block;
  width: 100%;
  height: auto;
}
.mw-locked-cover img { filter: grayscale(1) contrast(0.95) blur(2px); opacity: 0.55; transition: filter 0.4s ease, opacity 0.4s ease; }
.mw-locked-cover:hover img { filter: grayscale(0) contrast(1.02) blur(0); opacity: 1; }
.mw-lock-badge {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.18);
  backdrop-filter: blur(6px);
  color: #F4F2EE;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  pointer-events: none;
  transition: opacity 0.4s ease;
}
.mw-locked-cover:hover .mw-lock-badge { opacity: 0; }

.mw-info {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--gutter);
  margin-top: 20px;
}
.mw-info h3 { font-size: 17px; letter-spacing: -0.01em; }
.mw-info .project-meta { color: var(--muted); }
.mw-pane > p {
  margin-top: 10px;
  max-width: 60ch;
  font-size: 14px;
  color: rgba(var(--text-rgb), 0.65);
}

/* Buttons on the dark surface */
.modal .btn-solid { background: var(--text); color: var(--bg); }
.modal .btn-solid:hover { background: var(--signal-strong); color: #FFF8F3; }
.modal .btn-line { border-color: rgba(var(--text-rgb), 0.35); color: var(--text); }
.modal .btn-line:hover { border-color: var(--text); }
.btn.btn-sm {
  height: 36px;
  padding: 0 16px;
  font-size: 14px;
}

/* About modal: bento grid of separate floating cards.
   3 cols x 3 rows, zero empty cells: bio(2x2) + exp(1x2) fill rows 1-2,
   cap + tools + edu (1x1 each) fill row 3. */
.ma-bento {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: 1.5fr 1.5fr 1.1fr;
  gap: 16px;
  overflow: hidden;
  padding: 2px;
  height: 100%;
}
.ma-card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-radius: var(--r-lg);
  box-shadow: 0 12px 30px rgba(var(--text-rgb), 0.16);
  overflow: hidden;
}
/* Explicit line placement (not implicit span + auto-flow) so bio's taller
   min-height stretches exp to match instead of fragmenting the grid */
.ma-card-bio {
  grid-column: 1 / 3;
  grid-row: 1 / 3;
  position: relative;
  background: var(--ink);
}
.ma-card-exp { grid-column: 3 / 4; grid-row: 1 / 3; padding: 20px var(--margin); }
.ma-card-cap { grid-column: 1 / 2; grid-row: 3 / 4; padding: 18px var(--margin); }
.ma-card-tools { grid-column: 2 / 3; grid-row: 3 / 4; padding: 18px var(--margin); }
.ma-card-edu { grid-column: 3 / 4; grid-row: 3 / 4; padding: 18px var(--margin); }

.ma-card-body { padding: 20px var(--margin) 32px; }

/* Profile photo is the card's own background, full-bleed behind everything.
   A dark scrim sits between the photo and the copy so the header and body
   text stay legible no matter what's behind them in the image. */
.ma-photo {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background: rgba(var(--text-rgb), 0.06);
}
.ma-photo img { width: 100%; height: 100%; object-fit: cover; object-position: 55% 22%; }
.ma-card-bio::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(180deg, rgba(23, 20, 15, 0.72) 0%, rgba(23, 20, 15, 0.4) 30%, rgba(23, 20, 15, 0.55) 60%, rgba(23, 20, 15, 0.86) 100%);
  pointer-events: none;
}
.ma-card-bio .modal-head {
  position: relative;
  z-index: 2;
  padding: 20px var(--margin) 12px;
  border-bottom: 0;
}
.ma-card-bio .modal-head .section-label { color: rgba(244, 242, 238, 0.75); }
.ma-card-bio .modal-head h2 { color: var(--sand); }
.ma-card-bio .ma-card-body { position: relative; z-index: 2; margin-top: auto; }
.modal .about-lede { color: var(--text); font-size: 17px; }
.ma-bio p + p { margin-top: 16px; color: rgba(var(--text-rgb), 0.65); }
.ma-bio .btn { margin-top: 28px; }
.ma-card-bio .ma-bio .about-lede,
.ma-card-bio .ma-bio p { color: var(--sand); }
.ma-card-bio .ma-bio p + p { color: rgba(244, 242, 238, 0.65); }
.ma-card-bio .ma-bio .btn-solid { background: var(--sand); color: var(--ink); }
.ma-card-bio .ma-bio .btn-solid:hover { background: var(--signal-strong); color: #FFF8F3; }
.ma-card h3 {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}
.ma-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}
.ma-card-head h3 { margin-bottom: 0; }
.modal .exp { border-top: 1px solid rgba(var(--text-rgb), 0.14); padding-top: 12px; margin-top: 12px; }
.modal .exp:first-of-type { border-top: 0; padding-top: 0; margin-top: 0; }
.ma-card .exp-head h4 { font-size: 15px; font-weight: 500; letter-spacing: -0.01em; }
.modal .exp-dates { color: var(--muted); }
.modal .exp p { color: rgba(var(--text-rgb), 0.65); margin-top: 4px; font-size: 13px; }
.ma-list { list-style: none; }
.ma-list li {
  padding: 6px 0;
  border-top: 1px solid rgba(var(--text-rgb), 0.14);
  font-size: 13px;
  line-height: 1.4;
}
.ma-list li:first-child { border-top: 0; padding-top: 0; }
.ma-tools { color: rgba(var(--text-rgb), 0.65); font-size: 14px; }
.ma-card-edu > p { font-size: 14px; color: rgba(var(--text-rgb), 0.65); }

/* ---------- Buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 46px;
  padding: 0 24px;
  border-radius: var(--r-sm);
  font-family: var(--sans);
  font-size: 16px;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.2s var(--ease), color 0.2s var(--ease), border-color 0.2s var(--ease), transform 0.15s var(--ease);
}
.btn:active { transform: translateY(1px); }

.btn-solid {
  background: var(--text);
  color: var(--bg);
}
.btn-solid:hover { background: var(--signal-strong); color: #FFF8F3; }

.btn-line {
  border: 1px solid var(--hairline);
  color: var(--text);
}
.btn-line:hover { border-color: var(--text); }

/* ---------- Layout shell ---------- */

.section {
  max-width: var(--max);
  margin: 0 auto;
  padding: 120px var(--margin) 0;
}

.section-head {
  display: grid;
  grid-template-columns: 1fr 3fr;
  gap: var(--gutter);
  align-items: baseline;
  padding-bottom: 40px;
  border-bottom: 1px solid var(--on-photo-hairline);
  margin-bottom: 56px;
}
.section-label {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--on-photo-muted);
}
.section-head h2 {
  font-size: clamp(24px, 2.8vw, 36px);
  letter-spacing: -0.02em;
  color: var(--on-photo-text);
}
.section-head h2 .slots { color: var(--signal-text); }

/* ---------- Hero (full-bleed) ---------- */

.hero {
  position: relative;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  overflow: hidden;
}

/* Vignette: fixed behind every section, above the particles, below the content */
.page-scrim {
  position: fixed;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(90deg, rgba(23, 20, 15, 0.72) 0%, rgba(23, 20, 15, 0.38) 45%, rgba(23, 20, 15, 0) 72%),
    linear-gradient(0deg, rgba(23, 20, 15, 0.45) 0%, rgba(23, 20, 15, 0) 30%),
    linear-gradient(180deg, rgba(23, 20, 15, 0.4) 0%, rgba(23, 20, 15, 0) 18%);
}

.hero-inner {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--max);
  margin: 0 auto;
  padding: 116px var(--margin) 48px;
}

.hero-copy {
  max-width: 720px;
  color: var(--sand);
}

.hero h1 {
  font-size: clamp(34px, 4.4vw, 56px);
  letter-spacing: -0.03em;
  line-height: 1.06;
}
/* Clip masks the rise-in; the padding/margin pair reserves descender room so "g" is not cropped */
.hero h1 .line {
  display: block;
  overflow: hidden;
  padding-bottom: 0.08em;
  margin-bottom: -0.08em;
}
.hero h1 .line > span {
  display: block;
}

.hero-sub {
  margin-top: 28px;
  max-width: 44ch;
  font-size: 17px;
  color: rgba(244, 242, 238, 0.82);
}

/* Pre-hide the hero entrance's targets in plain CSS so first paint doesn't
   show the finished headline for a beat before the deferred script hides it
   again to animate in -- that flash-then-snap is what reads as a stray box.
   Opacity only (no transform/filter): GSAP's yPercent tracks its own
   internal transform cache separately from CSS percentage transforms, so
   pre-setting transform here fights the animation instead of matching it.
   Invisible text needs no position/blur match, since it can't be seen either
   way. Skipped under reduced motion, matching main.js's own guard. */
@media (prefers-reduced-motion: no-preference) {
  .hero h1 .line > span,
  .hero-sub {
    opacity: 0;
  }
}

/* ---------- Reel (Hero -> Work-intro -> projects -> Stats -> About ->
   Capabilities -> FAQ, one continuous desktop scroll-jack sequence, 11 slides) ---------- */

.reel {
  position: relative;
  height: calc(11 * 100dvh);
}
/* Invisible scrollspy target: see the HTML comment above #work-spy.
   Spans slides 2-5 (work-intro through the last project); the hero (slide 1)
   is deliberately left out of this range. */
.spy-marker {
  position: absolute;
  top: 100vh;
  left: 0;
  width: 1px;
  height: calc(4 * 100vh);
  pointer-events: none;
}
.reel-sticky {
  position: sticky;
  top: 0;
  height: 100dvh;
  overflow: hidden;
}
.reel-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
}
.reel-slide.is-active {
  opacity: 1;
  pointer-events: auto;
}

.scroll-cue {
  position: absolute;
  left: 50%;
  /* --footer-h is measured live in main.js: the fixed footer bar wraps to a
     different height at different widths, so a flat bottom offset either
     crops the cue under the footer or leaves a mismatched gap. */
  bottom: calc(var(--footer-h, 64px) + 28px);
  transform: translateX(-50%);
  color: var(--sand);
  opacity: 0;
  filter: drop-shadow(0 0 6px rgba(var(--bg-rgb), 0.5)) drop-shadow(0 0 14px var(--signal-text));
  transition: opacity 0.5s ease, transform 0.5s ease;
  pointer-events: none;
  z-index: 2;
}
.scroll-cue.is-visible {
  opacity: 0.85;
}
.scroll-cue-chevron {
  transform-origin: center;
  animation: scroll-cue-bounce 1.6s ease-in-out infinite;
}
.scroll-cue-chevron--2 {
  animation-delay: 0.15s;
}
@keyframes scroll-cue-bounce {
  0%, 100% { transform: translateY(0); opacity: 1; }
  50% { transform: translateY(5px); opacity: 0.5; }
}
@media (prefers-reduced-motion: reduce) {
  .scroll-cue-chevron { animation: none; }
}

.work-intro {
  display: flex;
  align-items: center;
}
.work-intro-inner {
  max-width: var(--max);
  margin: 0 auto;
  padding: 0 var(--margin);
  width: 100%;
}
.work-intro h2 {
  max-width: 20ch;
  font-size: clamp(32px, 5vw, 60px);
  letter-spacing: -0.02em;
  line-height: 1.08;
  color: var(--sand);
}

.project-scene {
  display: flex;
  align-items: center;
}
.project-scene-inner {
  max-width: var(--max);
  margin: 0 auto;
  padding: 0 var(--margin);
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 48px;
}
.project-scene-copy {
  flex: 0 0 auto;
  max-width: 460px;
  color: var(--sand);
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 16px;
}
/* Logomark sits in its own column so the title, meta, and body copy below
   all share the same left edge instead of trailing the logo. */
.project-heading {
  display: contents;
}
span.project-logomark,
img.project-logomark {
  grid-column: 1;
  grid-row: 1;
}
.project-heading-text {
  grid-column: 2;
  grid-row: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.project-scene-copy h3 {
  font-size: 28px;
  letter-spacing: -0.02em;
}
/* Still-unsupplied projects use a dashed <span> placeholder box;
   supplied logos are an <img> constrained to the same visual weight. */
span.project-logomark {
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  border-radius: var(--r-sm);
  border: 1px dashed rgba(244, 242, 238, 0.35);
  background: rgba(244, 242, 238, 0.08);
}
img.project-logomark {
  flex: 0 0 auto;
  height: 56px;
  width: auto;
  max-width: 140px;
  object-fit: contain;
  object-position: left center;
}
.project-scene-copy .project-meta {
  display: block;
  margin-top: 8px;
  color: rgba(244, 242, 238, 0.6);
}
.project-scene-copy p {
  grid-column: 2;
  grid-row: 2;
  margin-top: 20px;
  color: rgba(244, 242, 238, 0.82);
}
.project-scene-images {
  display: flex;
  gap: 20px;
  flex: 1 1 auto;
  max-width: 760px;
}
.project-scene-images .g-item {
  flex: 1 1 0;
}

/* A card can hold several stacked images that crossfade on an interval
   instead of showing separate cards (see .g-slideshow in main.js). */
.g-slideshow {
  position: relative;
}
/* Doubled-up selector to outrank .g-item img's transform-only transition
   shorthand, which would otherwise wipe out the opacity transition below. */
.g-item.g-slideshow img {
  position: absolute;
  inset: 0;
  opacity: 0;
  /* Symmetric ease so the outgoing and incoming images cross fade evenly,
     rather than the snappier ease-out curve used for one-shot UI motion. */
  transition: opacity 0.9s ease-in-out, transform 0.7s var(--ease);
}
.g-slideshow img.is-active {
  opacity: 1;
}

/* ---------- Stats (3 scroll-jacked slides leading the About/Capabilities reel) ---------- */

.stat-slide {
  display: flex;
  align-items: center;
  justify-content: center;
}
.stat-slide-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  text-align: center;
}
.stat-num {
  font-size: clamp(96px, 15vw, 220px);
  font-weight: 500;
  letter-spacing: -0.04em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--on-photo-text);
}
.glyph-inf {
  display: inline-block;
  transform: scale(1.9);
}
.stat-label {
  font-family: var(--mono);
  font-size: 15px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--on-photo-muted);
}

/* ---------- Work: image tile (reused inside project-scene-images) ---------- */

.project-meta {
  display: block;
  margin-top: 8px;
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.g-item {
  overflow: hidden;
  background: var(--surface);
  aspect-ratio: 4 / 5;
  border-radius: var(--r);
}
.g-item img,
.g-item video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.85) contrast(1.02);
  transition: transform 0.7s var(--ease);
}
.g-item:hover img,
.g-item:hover video { transform: scale(1.03); }

.g-item--zoom img {
  transform: scale(1.6);
  object-position: 50% 38%;
}
.g-item--zoom:hover img { transform: scale(1.66); }

.g-item--zoom-130 img {
  transform: scale(1.3);
}
.g-item--zoom-130:hover img { transform: scale(1.33); }

/* ---------- About -> Capabilities (second reel) ---------- */

.caps-slide,
.about-slide,
.faq-slide {
  display: flex;
  align-items: center;
  overflow-y: auto;
}
/* Unlike About/Capabilities, the FAQ cards change height as their answers
   expand. Centering would re-balance the whole block on every hover, so the
   heading appears to drift -- anchoring to the top keeps it fixed and lets
   only the grid grow underneath it. */
.faq-slide { align-items: flex-start; }

.caps-slide-inner,
.about-slide-inner,
.faq-slide-inner {
  max-width: var(--max);
  margin: 0 auto;
  padding: 96px var(--margin) 110px;
  width: 100%;
}

/* The FAQ heading carries no eyebrow label (unlike About/Capabilities), so
   its .section-head collapses to a single centered column instead of the
   label-plus-heading two-column grid those sections use. */
.faq-slide .section-head {
  grid-template-columns: 1fr;
  justify-items: center;
  text-align: center;
}
.faq-slide .section-label { display: none; }

.caps-intro h2 {
  font-size: clamp(26px, 3.2vw, 40px);
  letter-spacing: -0.02em;
  max-width: 18ch;
  white-space: nowrap;
  color: var(--on-photo-text);
}
.caps-intro p {
  margin-top: 20px;
  max-width: 52ch;
  color: var(--on-photo-muted);
  font-size: 17px;
}
.scrub-words .w { display: inline-block; }
.stat-label .w { display: inline-block; }

.caps-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 var(--gutter);
  margin-top: 64px;
}
.cap {
  padding: 28px 0;
  border-top: 1px solid var(--on-photo-hairline);
}
.cap h3 { font-size: 18px; letter-spacing: -0.01em; color: var(--on-photo-text); }
.cap p {
  margin-top: 8px;
  color: var(--on-photo-muted);
  max-width: 52ch;
}

/* ---------- About ---------- */

.about-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gutter);
}

.about-lede {
  font-size: 21px;
  font-weight: 500;
  letter-spacing: -0.015em;
  line-height: 1.35;
  color: var(--on-photo-text);
}
.about-bio p + p {
  margin-top: 24px;
  color: var(--on-photo-muted);
}

.exp {
  padding: 28px 0;
  border-top: 1px solid var(--on-photo-hairline);
}
.exp:first-child { border-top: 0; padding-top: 0; }
.exp-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
}
.exp h3 { font-size: 17px; letter-spacing: -0.01em; color: var(--on-photo-text); }
.exp-dates {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--on-photo-muted);
  white-space: nowrap;
}
.exp p {
  margin-top: 6px;
  color: var(--on-photo-muted);
  font-size: 15px;
}

/* ---------- FAQ ---------- */

/* Three even columns, two rows, filling the full slide measure. The glass
   cards stay legible over the portrait because they blur/tint what's behind
   them rather than hide it -- that's the point of the material here. */
.faq-grid {
  display: flex;
  flex-direction: column;
  gap: 16px;
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
}

.faq-card { cursor: pointer; outline: none; height: 100%; }
.faq-card-inner {
  position: relative;
  z-index: 0;
  isolation: isolate;
  height: 100%;
  background: rgba(244, 242, 238, 0.08);
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  border: 1px solid var(--on-photo-hairline);
  border-radius: var(--r-lg);
  padding: 26px 28px;
  transition: background 0.55s var(--ease), border-color 0.55s var(--ease), transform 0.6s var(--ease);
}
/* Soft radial bloom, not a hard ring -- sits behind the glass so it reads as
   ambient light spilling from the card rather than an outline snapping on. */
.faq-card-inner::before {
  content: '';
  position: absolute;
  inset: -22px;
  z-index: -1;
  border-radius: calc(var(--r-lg) + 18px);
  background: radial-gradient(60% 60% at 50% 100%, rgba(226, 75, 1, 0.55), rgba(226, 75, 1, 0) 72%);
  opacity: 0;
  transform: scale(0.85);
  filter: blur(2px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
  pointer-events: none;
}
.faq-card:hover .faq-card-inner,
.faq-card:focus-visible .faq-card-inner,
.faq-card:focus-within .faq-card-inner {
  background: rgba(244, 242, 238, 0.16);
  border-color: rgba(244, 242, 238, 0.3);
  transform: translateY(-6px) scale(1.012);
}
.faq-card:hover .faq-card-inner::before,
.faq-card:focus-visible .faq-card-inner::before,
.faq-card:focus-within .faq-card-inner::before {
  opacity: 1;
  transform: scale(1);
}
.faq-card:focus-visible {
  outline: 2px solid var(--on-photo-accent);
  outline-offset: 4px;
  border-radius: var(--r-lg);
}

.fc-q {
  font-size: 16px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--on-photo-text);
  transition: color 0.5s var(--ease);
}
.faq-card:hover .fc-q,
.faq-card:focus-visible .fc-q,
.faq-card:focus-within .fc-q {
  color: #FFD9BF;
}
.fc-a {
  margin-top: 0;
  transition: margin-top 0.45s var(--ease);
}
.faq-card:hover .fc-a,
.faq-card:focus-visible .fc-a,
.faq-card:focus-within .fc-a {
  margin-top: 12px;
}
.fc-a-inner {
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  filter: blur(6px);
  transform: translateY(10px);
  transition: max-height 0.55s var(--ease), opacity 0.5s var(--ease) 0.05s, filter 0.5s var(--ease), transform 0.5s var(--ease);
}
.faq-card:hover .fc-a-inner,
.faq-card:focus-visible .fc-a-inner,
.faq-card:focus-within .fc-a-inner {
  max-height: 220px;
  opacity: 1;
  filter: blur(0px);
  transform: none;
}
.fc-a p {
  padding-top: 10px;
  border-top: 1px solid var(--on-photo-hairline);
  font-size: 14px;
  line-height: 1.6;
  color: var(--on-photo-muted);
}


/* ---------- Footer ---------- */

/* Fixed to the bottom of the viewport across the whole site, mirroring the
   fixed nav at the top; no background of its own (transparent over the
   shared page backdrop); body padding below compensates the lost flow space */
.footer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 25;
  border-top: 1px solid var(--on-photo-hairline);
}
.footer-inner {
  max-width: var(--max);
  margin: 0 auto;
  padding: 16px var(--margin);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px var(--gutter);
  font-family: var(--mono);
  font-size: 13px;
  color: var(--on-photo-muted);
}
.footer-tagline { font-family: var(--sans); font-size: 14px; }
.footer-tagline .slots { color: var(--on-photo-accent); }
.footer-meta {
  display: flex;
  align-items: center;
  gap: 16px;
}
.footer-links {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
}
.footer-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--on-photo-muted);
  transition: color 0.2s var(--ease);
}
.footer-icon svg { width: 16px; height: 16px; }
.footer-icon:hover { color: var(--on-photo-accent); }
.footer-sep {
  color: var(--on-photo-hairline);
  font-family: var(--mono);
  font-size: 13px;
}
.footer-clock {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--on-photo-muted);
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
}

/* ---------- Reveal motion (JS adds .in; GSAP handles the rest) ---------- */

@media (prefers-reduced-motion: no-preference) {
  .reveal {
    opacity: 0;
    transform: translateY(24px);
    filter: blur(14px);
    transition: opacity 0.95s var(--ease), transform 0.95s var(--ease), filter 1.1s var(--ease);
    transition-delay: calc(var(--i, 0) * 90ms);
    will-change: opacity, transform, filter;
  }
  .reveal.in {
    opacity: 1;
    transform: none;
    filter: blur(0px);
  }

  /* FAQ heading/cards: a separate baseline from .reveal because they're not
     driven by the generic scroll-position IntersectionObserver above (see
     main.js) -- every .reel-slide sits stacked at the same inset:0 spot, so
     that observer sees them all as "on screen" the moment the reel scrolls
     into view, well before the user actually reaches the FAQ slide. GSAP
     owns the animation directly (replayed on arrival), so no transition
     here -- just the pre-hidden state it animates from.
   */
  .faq-reveal { opacity: 0; }
}

/* ---------- Reel-scoped motion: elements that crossfade with .is-active
   rather than scroll into view (Work reel + About/Capabilities reel) ---------- */

@media (min-width: 901px) and (prefers-reduced-motion: no-preference) {
  .work-intro-inner h2,
  .project-scene-copy,
  .project-scene-images .g-item {
    opacity: 0;
    filter: blur(16px);
    transform: translateY(22px);
    transition: opacity 0.75s var(--ease), filter 0.85s var(--ease), transform 0.75s var(--ease);
  }
  .reel-slide.is-active .work-intro-inner h2,
  .reel-slide.is-active .project-scene-copy {
    opacity: 1;
    filter: blur(0px);
    transform: none;
    transition-delay: 0.12s;
  }
  .reel-slide.is-active .project-scene-images .g-item {
    opacity: 1;
    filter: blur(0px);
    transform: none;
  }
  .reel-slide.is-active .project-scene-images .g-item:nth-child(1) { transition-delay: 0.14s; }
  .reel-slide.is-active .project-scene-images .g-item:nth-child(2) { transition-delay: 0.22s; }
  .reel-slide.is-active .project-scene-images .g-item:nth-child(3) { transition-delay: 0.3s; }
}

/* ---------- Mobile ---------- */

@media (max-width: 900px) {
  :root { --margin: 20px; }

  body { font-size: 17px; }

  .nav-left { gap: 12px; }
  .chip { padding: 6px 10px; font-size: 11px; }
  .nav-panel { width: min(320px, calc(100vw - 40px)); }

  .hero { min-height: 100svh; align-items: flex-end; }
  .hero-inner { padding-top: 200px; padding-bottom: 56px; }
  .page-scrim {
    background:
      linear-gradient(0deg, rgba(23, 20, 15, 0.78) 0%, rgba(23, 20, 15, 0.35) 45%, rgba(23, 20, 15, 0.1) 70%),
      linear-gradient(180deg, rgba(23, 20, 15, 0.4) 0%, rgba(23, 20, 15, 0) 18%);
  }
  .hero h1 { font-size: clamp(36px, 10vw, 48px); }
  .hero-sub { margin-top: 24px; font-size: 16px; }

  .stat-num { font-size: clamp(72px, 22vw, 120px); }

  .section { padding-top: 88px; }
  .section-head {
    grid-template-columns: 1fr;
    gap: 12px;
    padding-bottom: 32px;
    margin-bottom: 40px;
  }

  .work-intro-inner h2,
  .work-intro h2 { color: var(--on-photo-text); font-size: clamp(28px, 8vw, 40px); }

  .project-scene-inner {
    flex-direction: column;
    align-items: flex-start;
    gap: 20px;
    padding-top: 88px;
    padding-bottom: 56px;
  }
  .project-scene-copy { max-width: none; color: var(--on-photo-text); }
  .project-scene-copy .project-meta { color: var(--on-photo-muted); }
  .project-scene-copy p { color: var(--on-photo-muted); }
  /* Horizontal scroll-snap row instead of a vertical stack: three stacked
     3:2 images cost far more slide height than the reel can spare once
     it's pinned on a phone screen, so they scroll sideways instead. A
     horizontal drag here doesn't compete with the reel's vertical
     swipe-to-advance gesture. */
  .project-scene-images {
    max-width: none;
    flex-direction: row;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
  }
  .project-scene-images .g-item {
    flex: 0 0 70%;
    scroll-snap-align: start;
  }

  /* When these slides' content is taller than the viewport, centering via
     align-items on the overflowing flex item lets the box overflow above
     the container edge -- clipping its own top padding along with it,
     which is what keeps the navbar clear. Anchor to the top on mobile so
     that padding always renders. */
  .caps-slide, .about-slide { align-items: flex-start; }
  .caps-slide-inner, .about-slide-inner, .faq-slide-inner { padding: 72px var(--margin) 48px; }
  .caps-intro h2 { white-space: normal; }
  .caps-intro p { font-size: 18px; }
  .caps-grid { grid-template-columns: 1fr; gap: 0; margin-top: 28px; }
  .cap { padding: 16px 0; }

  .about-grid { grid-template-columns: 1fr; gap: 28px; }
  .about-lede { font-size: 19px; }
  .about-bio p + p { margin-top: 16px; }
  .exp { padding: 16px 0; }

  /* Same horizontal scroll-snap treatment as the project images above: it
     removes FAQ's vertical overflow entirely, so a vertical swipe on this
     slide always advances the reel, and browsing questions is a sideways
     drag instead of competing with that gesture. */
  .faq-grid {
    flex-direction: row;
    gap: 12px;
    max-width: none;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
  }
  .faq-card { flex: 0 0 85%; scroll-snap-align: start; }

  /* Fixed and bottom-anchored, the footer sits on top of the last ~130px
     of every slide -- on the reel's short mobile viewports that's often
     the only part of a card still on screen, blocking text instead of
     just trimming margin the way it does on desktop's taller viewports.
     Rather than hiding it (it carries real contact info), drop it into
     normal document flow: it renders after the reel and only scrolls into
     view once the user scrolls past the last slide (FAQ), same as
     reaching the end of any normally-scrolling page. */
  .footer { position: static; }
  body { padding-bottom: 0; }

  .modal-box { top: 72px; right: var(--margin); bottom: 16px; left: var(--margin); gap: 12px; }
  .modal-box--compact {
    top: 50%;
    bottom: auto;
    left: 0;
    right: 0;
    margin: 0 auto;
    width: min(420px, calc(100% - 40px));
    transform: translateY(calc(-50% + 16px));
  }
  .modal.open .modal-box--compact { transform: translateY(-50%); }
  .resume-gate { padding: 32px 24px 28px; }
  .resume-gate-row { flex-direction: column; gap: 16px; }
  .modal-card,
  .mw-preview-card,
  .mw-item {
    border-radius: var(--r);
  }

  .modal-work-body {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr;
    gap: 12px;
  }
  .mw-list {
    flex-direction: row;
    overflow-x: auto;
    overflow-y: hidden;
    padding: 12px 20px;
  }
  .mw-item { min-width: 220px; }
  .mw-preview { padding: 20px; }
  .mw-grid { grid-template-columns: 1fr; }
  /* Switched from grid to a plain flex column: a CSS Grid auto-sized track
     containing an `overflow: hidden` item (every `.ma-card`) resolves that
     item's automatic minimum size to 0 per spec, so the row collapsed
     toward a sliver instead of the card's real content height -- each card
     then clipped its own squeezed text, which is what made Profile/
     Experience unreadable. A flex column doesn't have that failure mode:
     each card keeps its natural content height, and the container (bounded
     to the modal's fixed-height box, see height below) scrolls past
     whatever doesn't fit. */
  .ma-bento {
    display: flex;
    flex-direction: column;
    gap: 12px;
    height: 100%;
    overflow-y: auto;
  }
  .ma-card-bio,
  .ma-card-exp,
  .ma-card-cap,
  .ma-card-tools,
  .ma-card-edu { flex: none; }
}
