/* ============================================
   Design Tokens — From Figma
   ============================================ */
:root {
  /* Brand — the easter egg in page-transitions.js overrides all of
     these so the whole site re-skins when a footer dot is clicked. */
  --brand-orange: #F5941E;
  --brand-sunset: #E85814;
  --brand-rgb: 245, 148, 30;          /* R, G, B for use in rgba() */
  --brand-deep-rgb: 160, 90, 30;      /* warm pillow-shadow variant */
  --brand-light: var(--brand-light);             /* button gradient — top */
  --brand-deep:  var(--brand-deep);             /* button gradient — bottom */
  --brand-hover-light: var(--brand-hover-light);       /* button gradient — hover top */
  --brand-hover-deep:  var(--brand-hover-deep);       /* button gradient — hover bottom */

  /* Text */
  --text-primary: #1A1916;
  --text-secondary: #575757;
  --text-tertiary: #888580;
  --text-inverse: #FFFFFF;

  /* Surface */
  --surface-default: #FFFFFF;
  --surface-subtle: #F7F6F4;
  --surface-muted: #CDCDCD;
  --surface-screw: #E1E1E1;
  --surface-screw-detail: #888786;

  /* Decorative */
  --decorative-stripe: #E0E0E0;
  --decorative-ink: #000000;

  /* Border */
  --border-hairline: rgba(0, 0, 0, 0.07);

  /* Footer dots */
  --footer-dot-purple: #6E2D8C;
  --footer-dot-orange: #E85814;
  --footer-dot-teal: #0094A8;
  --footer-dot-blue: #263A94;
  --footer-dot-green: #5AAA32;

  /* Typography */
  --font-serif: 'DM Serif Display', Georgia, serif;
  --font-sans: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;

  /* Layout — sections span full viewport; content centered to 1128px.
     Padding scales down on phones for more usable content width. */
  --container-max: 1440px;
  --content-width: 1128px;
  --container-pad: max(20px, calc((100vw - 1128px) / 2));
  /* For elements that need to stay aligned to the 1440px design-frame edge
     on wider viewports (hero shape, blob, etc) */
  --frame-edge-offset: max(0px, calc((100vw - 1440px) / 2));
}
@media (max-width: 600px) {
  :root {
    --container-pad: 16px;
  }
}

/* ============================================
   Page loader (FIRST-VISIT ONLY)
   Simple cinematic: the full logo (lightning-bolt + orange dot) sits
   centered on white for the loader's min-visible window, then the
   whole sheet fades out and the page emerges. No dot expand.
   Intra-session navigations skip this entirely — boot.js adds
   `is-revisit` to <html> so the loader never paints, and skeleton
   placeholders inside the page fill the gap instead.
   ============================================ */
.page-loader {
  position: fixed;
  inset: 0;
  background: #fff;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  transition:
    opacity 0.65s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s 0.65s;
}

/* Logo stack: SVG bolt + an HTML dot overlay positioned exactly where
   the SVG's brand-dot would be. Splitting them lets the dot's
   transform be uncontested (the bolt fades via opacity in its SVG,
   the dot scales via transform in its HTML element) — SVG inline
   transforms via transform-box/transform-origin are unreliable across
   browsers when an animation and a transition both touch transform. */
.page-loader-stack {
  position: relative;
  width: 35px;
  height: 56px;
}
/* SVG carries only the lightning bolt path — the orange dot is now
   the HTML overlay below, so we don't render two dots. */
.page-loader-logo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  transition: opacity 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}
.loader-bolt { /* path inside the SVG; nothing to do, color hard-coded */ }

/* Orange brand dot — positioned to coincide pixel-perfectly with where
   the original SVG dot would have been rendered (cx=20.2265, cy=35.6443
   r=4.0074 inside a 25×40 viewBox → at 35×56 render = center (28.31,
   49.9), diameter 11.2).

   NB: no CSS `animation` here. Declaring an animation on the element
   (even one that doesn't touch `transform`) makes Chromium skip the
   transform transition entirely — the dot would snap straight to
   scale(400) without animating. A subtle opacity pulse on the SVG
   parent gives us a similar "still alive" feel instead. */
.loader-dot {
  position: absolute;
  width: 11.2px;
  height: 11.2px;
  border-radius: 50%;
  background: var(--brand-orange, #F5941E);
  left: 22.71px;   /* 28.31 − 11.2/2 */
  top: 44.30px;    /* 49.9 − 11.2/2 */
  transform-origin: center center;
  will-change: transform;
  /* Soft orange halo + a subtle box-shadow pulse to give the dot a
     "warm bulb" feel while the loader sits. The box-shadow animates
     (not transform), so it doesn't interfere with the expand transition
     that boot.js drives via inline styles. */
  animation: loaderHaloPulse 2s ease-in-out infinite;
}
@keyframes loaderHaloPulse {
  0%, 100% {
    box-shadow:
      0 0 8px rgba(var(--brand-rgb), 0.55),
      0 0 18px rgba(var(--brand-rgb), 0.25);
  }
  50% {
    box-shadow:
      0 0 14px rgba(var(--brand-rgb), 0.85),
      0 0 32px rgba(var(--brand-rgb), 0.40);
  }
}

/* Backwards compat: hide old loader marks if the page still has them. */
.page-loader-mark { display: none; }

.page-loader.is-hidden,
html.is-revisit .page-loader {
  opacity: 0;
  visibility: hidden;
}
/* Bolt + dot transform are driven inline by boot.js — only the loader
   sheet's overall fade remains a CSS-class transition. */

/* While loading, hold the page slightly enlarged + invisible. On reveal
   it eases back to scale 1 — crossfades with the loader sheet for a
   smooth "logo dissolves into homepage" handoff. */
.page {
  transform-origin: center top;
  /* Bouncy curve so the page overshoots past scale 1 and settles in —
     the loader's logo zooms out + fades just before this fires. */
  transition:
    opacity 0.55s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.85s cubic-bezier(0.34, 1.56, 0.64, 1);
}
html.is-loading .page {
  opacity: 0;
  transform: scale(0.94);
}

/* First-visit: pause entry animations until the loader fades */
html.is-loading,
html.is-loading body {
  overflow: hidden;
}
html.is-loading .hero-inner > *,
html.is-loading .reveal {
  animation-play-state: paused !important;
}

@media (prefers-reduced-motion: reduce) {
  .page-loader-mark { animation: none; transition: none; }
  .page-loader { transition: none; }
  .page { transition: none; }
  html.is-loading .page { transform: none; opacity: 1; }
}

/* ============================================
   Skeleton placeholders
   Shown inside dynamic content containers until content-loader
   replaces them with real content.
   ============================================ */
.skel {
  position: relative;
  background: linear-gradient(90deg,
    rgba(0,0,0,0.04) 0%,
    rgba(0,0,0,0.07) 50%,
    rgba(0,0,0,0.04) 100%);
  background-size: 200% 100%;
  border-radius: 12px;
  overflow: hidden;
  animation: skelShimmer 1.4s ease-in-out infinite;
}
@keyframes skelShimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Project card skeleton — matches .project-card height */
.skel-card {
  height: 382px;
  border-radius: 20px;
}
/* Skill / client pill */
.skel-pill {
  display: inline-block;
  height: 36px;
  width: 110px;
  border-radius: 100px;
}
/* Experience row */
.skel-exp-row {
  height: 60px;
  border-radius: 8px;
}
/* Tool card */
.skel-tool {
  height: 150px;
  border-radius: 16px;
}
/* Fact row */
.skel-fact {
  height: 48px;
  border-radius: 6px;
}

@media (prefers-reduced-motion: reduce) {
  .skel { animation: none; }
}

/* ============================================
   Page transitions
   Smoother, flowy cross-document transitions.
   ============================================ */
/* Modern: View Transitions API — Chrome 126+, Edge, Safari TP */
@view-transition { navigation: auto; }

/* `--ease-flow` — cubic-bezier "ease-out-expo"-ish: snappy at start,
   long luxurious tail. Best general-purpose curve for entering motion. */
:root {
  --ease-flow: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-soft: cubic-bezier(0.22, 1, 0.36, 1);
  /* Aqua / iMac G3 — smooth s-curve, slow-out feels "liquid" */
  --ease-liquid: cubic-bezier(0.65, 0, 0.35, 1);
  /* Subtle dwell on settle — for hover lifts that "ride out" the gesture */
  --ease-dwell:  cubic-bezier(0.5, 0, 0.1, 1);
  /* Ease-out-back with pronounced overshoot — bounces past the target
     then settles. Use for entry animations to give a "thwack in" feel. */
  --ease-bounce: cubic-bezier(0.34, 2.2, 0.64, 1);
}

/* Pure crossfade — opacity-heavy with a tiny translate for direction. No
   scale (no GPU bitmap resampling), and the two animations overlap so the
   old page fades out while the new one is already fading in. The result
   is a continuous "dissolve" rather than two stepped phases. */
/* Sequential motion: old page fades + drifts upward, brief white pause,
   then new page drops down into place with a bounce. Background-
   attachment: fixed on the stripes pattern means the pattern stays at
   the same viewport position throughout — translates animate the
   snapshot bitmap but the stripes baseline doesn't shift. */
::view-transition-old(root) {
  animation: pageLeave 0.4s var(--ease-liquid) both;
}
::view-transition-new(root) {
  animation: pageEnter 0.75s var(--ease-bounce) 0.4s both;
}
/* Nav stays put across the transition so it doesn't flash */
.nav { view-transition-name: site-nav; }
::view-transition-old(site-nav),
::view-transition-new(site-nav),
::view-transition-old(theme-toggle-float),
::view-transition-new(theme-toggle-float) {
  animation: none;
}

/* "Inward" motion — the new page comes in from slightly closer/larger
   and settles into focus, like a magnification snapping to natural
   size. The old page recedes by shrinking slightly while fading. */
@keyframes pageLeave {
  from { opacity: 1; transform: scale(1);     }
  to   { opacity: 0; transform: scale(0.97);  }
}
@keyframes pageEnter {
  from { opacity: 0; transform: scale(1.05);  }
  to   { opacity: 1; transform: scale(1);     }
}
/* Mobile: drop the bouncy full-screen scale on cross-doc navigations.
   The GPU layer for the snapshot is expensive on lower-end phones and
   you barely see the bounce on a small screen. Plain opacity fade is
   cheap and looks fine. */
@media (max-width: 760px) {
  ::view-transition-old(root) { animation: pageLeaveMobile 0.18s linear both; }
  ::view-transition-new(root) { animation: pageEnterMobile 0.22s linear 0.16s both; }
  @keyframes pageLeaveMobile { to { opacity: 0; } }
  @keyframes pageEnterMobile { from { opacity: 0; } to { opacity: 1; } }
}

/* Theme toggle uses the same View Transitions plumbing, but the new
   theme wipes in as an expanding circle anchored at the click point —
   like the iOS dark-mode reveal. JS sets --theme-reveal-x/y to the
   center of the clicked button, --theme-reveal-r to the farthest
   viewport corner so the circle always finishes the reveal. */
html.is-theme-switching::view-transition-old(root) {
  animation: none;
  z-index: 1;
}
html.is-theme-switching::view-transition-new(root) {
  animation: theme-reveal 0.6s cubic-bezier(0.4, 0, 0.2, 1) both;
  z-index: 2;
}
/* Sweep the nav along with root during a theme flip so the toggle
   itself doesn't sit on a stale snapshot while the rest reveals. */
html.is-theme-switching .nav { view-transition-name: none; }
@keyframes theme-reveal {
  from {
    clip-path: circle(0 at var(--theme-reveal-x, 50vw) var(--theme-reveal-y, 50vh));
  }
  to {
    clip-path: circle(var(--theme-reveal-r, 150vmax) at var(--theme-reveal-x, 50vw) var(--theme-reveal-y, 50vh));
  }
}

/* Fallback (no View Transitions): body crossfade with the same
   inward motion. */
.no-vt body { animation: bodyIn 0.75s var(--ease-bounce) both; }
.no-vt body.page-leaving {
  opacity: 0;
  transform: scale(0.97);
  transition:
    opacity   0.4s var(--ease-liquid),
    transform 0.4s var(--ease-liquid);
}
@keyframes bodyIn {
  from { opacity: 0; transform: scale(1.05); }
  to   { opacity: 1; transform: scale(1);    }
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) { animation: none !important; }
  .no-vt body { animation: none; }
  .no-vt body.page-leaving { opacity: 1; transform: none; transition: none; }
}

/* ───── Mobile: same buttery crossfade as desktop ─────
   Pure opacity + 4px translate is compositor-only — no GPU bitmap
   resampling — so mobile can afford the longer fluid durations without
   dropping frames. The old page snapshot just fades while the new
   snapshot fades in over the top; both are stable bitmaps. */
@media (max-width: 760px) {
  ::view-transition-old(root) {
    animation: pageLeave 0.32s var(--ease-liquid) both;
  }
  ::view-transition-new(root) {
    animation: pageEnter 0.6s var(--ease-bounce) 0.32s both;
  }
  .no-vt body { animation: bodyIn 0.6s var(--ease-liquid) both; }
  .no-vt body.page-leaving {
    opacity: 0;
    transition: opacity 0.35s var(--ease-liquid);
  }
}

/* ============================================
   Reset & Base
   ============================================ */
* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; background: var(--surface-subtle); }
body {
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 27.2px;
  color: var(--text-secondary);
  background: var(--surface-subtle);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ============================================
   Global Animations
   ============================================ */
/* Compositor-only animation (transform + opacity) — never blur or filter,
   those force GPU re-rasterization every frame and cause scroll jank. */
@keyframes fadeUp {
  from {
    opacity: 0;
    transform: translate3d(0, 28px, 0) scale(0.985);
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1);
  }
}
@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes shimmerStripes {
  0%   { background-position: 0 0; }
  100% { background-position: 28px 0; }
}

/* Reveal on scroll — uses CSS animation (one-shot) so the delay only
   applies to the entry, never leaking into hover transitions.
   Long 1s curve with deep ease-out for a flowy "rising into focus" feel. */
.reveal {
  opacity: 0;
  transform: translate3d(0, 28px, 0);
  /* will-change only on elements actively animating; cleared after */
}
.reveal.is-visible {
  animation: revealIn 0.7s var(--ease-bounce, cubic-bezier(0.34, 2.2, 0.64, 1)) both;
  will-change: transform, opacity;
}
/* Pure opacity + translate — no scale so the GPU only composites, doesn't
   resample. Longer 1.1s curve gives the "fade up and exhale" feel. */
@keyframes revealIn {
  from {
    opacity: 0;
    transform: translate3d(0, 28px, 0);
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}
.reveal.is-visible[data-delay="1"] { animation-delay: 0.08s; }
.reveal.is-visible[data-delay="2"] { animation-delay: 0.16s; }
.reveal.is-visible[data-delay="3"] { animation-delay: 0.24s; }
.reveal.is-visible[data-delay="4"] { animation-delay: 0.32s; }
.reveal.is-visible[data-delay="5"] { animation-delay: 0.40s; }
.reveal.is-visible[data-delay="6"] { animation-delay: 0.48s; }

/* Mobile reveal: same buttery curve, slightly shorter motion path so
   long lists don't feel like they're slowly raining in. Still pure
   opacity + translate so the GPU just composites. */
@media (max-width: 760px) {
  .reveal {
    transform: translate3d(0, 20px, 0);
  }
  .reveal.is-visible {
    animation: revealInMobile 0.65s var(--ease-bounce) both;
  }
  .reveal.is-visible[data-delay="1"] { animation-delay: 0.06s; }
  .reveal.is-visible[data-delay="2"] { animation-delay: 0.12s; }
  .reveal.is-visible[data-delay="3"] { animation-delay: 0.18s; }
  .reveal.is-visible[data-delay="4"] { animation-delay: 0.24s; }
  .reveal.is-visible[data-delay="5"] { animation-delay: 0.30s; }
  .reveal.is-visible[data-delay="6"] { animation-delay: 0.36s; }
}
@keyframes revealInMobile {
  from { opacity: 0; transform: translate3d(0, 20px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; filter: none; }
}
img {
  display: block;
  max-width: 100%;
  /* Casual save protection — disables drag-to-save (Chrome/FF/Edge),
     long-press save menu (iOS Safari) and text-select highlight on
     image alt-text. Determined users can still screenshot, but the
     usual one-click "Save Image As…" routes are closed. */
  -webkit-user-drag: none;
  user-drag: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
a { color: inherit; text-decoration: none; }
button { font: inherit; background: none; border: 0; cursor: pointer; color: inherit; }

/* ============================================
   Typography Utilities
   ============================================ */
.h-xl { font-family: var(--font-serif); font-weight: 400; font-size: 80px; line-height: 84px; letter-spacing: -2.4px; }
.h-l  { font-family: var(--font-serif); font-weight: 400; font-size: clamp(36px, 6vw, 64px); line-height: 1.05; letter-spacing: -1.5px; }
.h-m  { font-family: var(--font-serif); font-weight: 400; font-size: 32px; line-height: 36px; letter-spacing: -0.5px; }
.h-s  { font-family: var(--font-serif); font-weight: 400; font-size: 24px; line-height: 28px; }
.h-xs { font-family: var(--font-serif); font-weight: 400; font-size: 22px; line-height: 26px; }

.t-l  { font-weight: 700; font-size: 20px; line-height: 24px; }
.t-s  { font-weight: 700; font-size: 16px; line-height: 20px; }
.body-l       { font-size: 16px; line-height: 27.2px; }
.body-m       { font-size: 15px; line-height: 25px; }
.body-s       { font-size: 14px; line-height: 22px; }
.body-xs      { font-size: 13px; line-height: 20px; }
.body-caption { font-size: 12px; line-height: 18px; }
.body-mono    { font-size: 10px; line-height: 14px; letter-spacing: 0.6px; }
.subtitle-label { font-weight: 600; font-size: 11px; line-height: 14px; letter-spacing: 1.5px; }

.text-tertiary { color: var(--text-tertiary); }
.text-secondary { color: var(--text-secondary); }
.text-primary { color: var(--text-primary); }
.text-orange { color: var(--brand-orange); }
.italic-serif { font-family: var(--font-serif); font-style: italic; }

/* ============================================
   Layout
   ============================================ */
.page {
  width: 100%;
  margin: 0 auto;
  background: var(--surface-subtle);
}
.container {
  width: 100%;
  max-width: var(--container-max);
  padding-left: var(--container-pad);
  padding-right: var(--container-pad);
  margin: 0 auto;
}
.section-divider {
  border-bottom: 1px solid var(--border-hairline);
}

/* ============================================
   Nav
   ============================================ */
.nav {
  /* Frosted glass — high-opacity white so the backdrop-filter has
     barely any variable input. (At 0.78 alpha the blur was picking
     up both page snapshots during view transitions and visibly
     shifting in tone.) Still reads as translucent because of the
     saturate(150%) + slight residual transparency. */
  background: rgba(255, 255, 255, 0.92);
  -webkit-backdrop-filter: saturate(150%) blur(14px);
  backdrop-filter: saturate(150%) blur(14px);
  box-shadow:
    inset 0 -1px 0 rgba(255,255,255,0.6),
    0 2px 20px rgba(0,0,0,0.05);
  position: sticky;
  top: 0;
  z-index: 100;
}
.nav-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 70px;
  padding: 14px var(--container-pad);
}
.nav-logo { width: 25px; height: 40px; display: block; color: #0E0E0E; }
.nav-logo img, .nav-logo svg { width: 100%; height: 100%; object-fit: contain; }
.nav-links { display: flex; align-items: center; gap: 36px; }
.nav-link {
  position: relative;
  font-size: 16px;
  line-height: 27.2px;
  padding: 10px 20px;
  color: var(--text-tertiary);
  transition: color 0.2s ease;
}
.nav-link::after {
  content: "";
  position: absolute;
  left: 20px;
  right: 20px;
  bottom: 4px;
  height: 2px;
  background: var(--brand-orange);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.35s cubic-bezier(.2,.7,.2,1);
}
.nav-link:hover { color: var(--text-primary); }
.nav-link:hover::after { transform: scaleX(1); }
.nav-link.is-active { color: var(--brand-orange); }
.nav-link.is-active::after { transform: scaleX(1); }

/* ============================================
   Stripes Background (decorative)
   Uses direct alpha — no mix-blend-mode or opacity which both force
   the browser to composite everything behind on every scroll frame.
   ============================================ */
.stripes-bg {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  /* No background-image anymore — boot.js fills this with individual
     <span class="stripe"> children so each stripe can fall in
     separately from above, staggered left-to-right. */
}
.stripes-bg .stripe {
  position: absolute;
  top: 0;
  width: 4px;
  height: 100%;
  background: rgba(0, 0, 0, 0.022);
  transform: translateY(-100%);
  animation: stripeDropDown 0.9s var(--ease-liquid) both;
  will-change: transform;
}
@keyframes stripeDropDown {
  from { transform: translateY(-100%); }
  to   { transform: translateY(0);     }
}
@media (prefers-reduced-motion: reduce) {
  .stripes-bg .stripe { animation: none; transform: none; }
}

/* ============================================
   Hero — Home
   ============================================ */
.hero {
  position: relative;
  overflow: hidden;
  background: #fff;
  border-bottom: 1px solid var(--border-hairline);
  /* No `contain: paint` — putting the hero on its own compositor layer
     causes the stripes pattern inside to snap to a different sub-pixel
     offset between view-transition snapshots and live rendering, which
     reads as a stripes-shift flicker on page navigation. Layout-only
     containment is fine. */
  contain: layout style;
}
.hero-blob {
  position: absolute;
  width: 882px;
  height: 882px;
  left: calc(var(--frame-edge-offset) - 241px);
  top: -449px;
  border-radius: 1000px;
  background: radial-gradient(circle at center, rgba(var(--brand-rgb), 0.12) 0%, rgba(var(--brand-rgb), 0) 70%);
  pointer-events: none;
}
.hero-inner {
  position: relative;
  z-index: 2;
  min-height: 600px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 90px var(--container-pad);
  text-align: center;
  gap: 36px;
}
.hero-inner > * {
  opacity: 0;
  animation: fadeUp 0.85s var(--ease-bounce, cubic-bezier(0.34, 2.2, 0.64, 1)) forwards;
}
.hero-inner > *:nth-child(1) { animation-delay: 0.10s; }
.hero-inner > *:nth-child(2) { animation-delay: 0.22s; }
.hero-inner > *:nth-child(3) { animation-delay: 0.36s; }
.hero-inner > *:nth-child(4) { animation-delay: 0.52s; }
@media (max-width: 760px) {
  .hero-inner > * { animation-duration: 0.5s; }
  .hero-inner > *:nth-child(1) { animation-delay: 0.05s; }
  .hero-inner > *:nth-child(2) { animation-delay: 0.12s; }
  .hero-inner > *:nth-child(3) { animation-delay: 0.20s; }
  .hero-inner > *:nth-child(4) { animation-delay: 0.28s; }
}
.hero-eyebrow {
  font-weight: 700;
  font-size: 20px;
  line-height: 24px;
  color: var(--text-tertiary);
}
.hero-title {
  font-family: var(--font-serif);
  font-weight: 400;
  /* Fluid: 36px on small phones, smoothly scales to 80px on desktop */
  font-size: clamp(36px, 7.2vw, 80px);
  line-height: 1.05;
  letter-spacing: -2.4px;
  color: var(--brand-orange);
}
.hero-title em {
  font-style: italic;
}
.hero-desc {
  font-size: 16px;
  line-height: 27.2px;
  color: var(--text-tertiary);
}
.hero-actions {
  display: flex;
  gap: 10px;
  align-items: center;
  justify-content: center;
}
/* Flat pill — vertical white-to-soft-grey gradient body, inset
   highlight ring, warm-tinted pillow drop-shadow, brand-orange
   underglow on hover. No wet-shine pseudo: matches the cleaner
   variant from the Figma source so the rim doesn't read as glow on
   dark backgrounds. */
.btn-pill {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  border-radius: 50px;
  background:
    linear-gradient(180deg, rgba(255,255,255,1) 0%, rgba(255,255,255,0.82) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,1),
    inset 0 -1px 0 rgba(0,0,0,0.03),
    inset 0 0 0 1px rgba(255,255,255,0.6),
    0 6px 18px rgba(0,0,0,0.12),
    0 22px 36px -10px rgba(0,0,0,0.18);
  color: #000;
  font-size: 16px;
  line-height: 27.2px;
  transition:
    transform   0.45s var(--ease-bounce),
    box-shadow  0.5s var(--ease-liquid),
    color       0.3s var(--ease-soft);
}
.btn-pill:hover {
  transform: translateY(-3px);
  /* Subtle warm-orange underglow on hover. */
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,1),
    inset 0 -1px 0 rgba(0,0,0,0.03),
    inset 0 0 0 1px rgba(255,255,255,0.7),
    0 10px 24px rgba(var(--brand-deep-rgb), 0.10),
    0 28px 50px -12px rgba(var(--brand-rgb), 0.20);
  color: var(--brand-orange);
}
.btn-pill:active { transform: translateY(-1px) scale(0.98); }

/* ============================================
   Hero — Works (smaller)
   ============================================ */
.hero-works {
  min-height: 350px;
  padding: 60px var(--container-pad);
  gap: 24px;
}
.hero-works .hero-title { /* same */ }

/* ============================================
   Hero — About
   ============================================ */
.hero-about {
  display: flex;
  gap: 80px;
  align-items: center;
  justify-content: center;
  padding: 60px var(--container-pad);
  position: relative;
  overflow: hidden;
  border-bottom: 1px solid var(--border-hairline);
  background: #fff;
}
.hero-about-content {
  flex: 1 0 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-height: 477px;
  position: relative;
  z-index: 2;
}
.hero-about .h-l { color: var(--brand-orange); }
.hero-about-eyebrow {
  font-weight: 700;
  font-size: 20px;
  line-height: 24px;
  color: var(--text-tertiary);
  margin-bottom: 14px;
}

/* ============================================
   Profile Card (About hero)
   ============================================ */
.profile-card {
  width: 360px;
  border-radius: 26px;          /* gumdrop curvature */
  overflow: hidden;
  background:
    linear-gradient(180deg, rgba(255,255,255,1) 0%, rgba(255,255,255,0.92) 100%);
  /* Neutral resting shadow — same vocabulary as project-card. */
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,1),
    inset 0 -1px 0 rgba(0,0,0,0.06),
    inset 0 0 0 1px rgba(255,255,255,0.55),
    0 1px 0 rgba(255,255,255,0.95),
    0 6px 16px rgba(0,0,0,0.10),
    0 22px 38px -10px rgba(0,0,0,0.14),
    0 46px 78px -22px rgba(0,0,0,0.18);
  flex-shrink: 0;
  position: relative;
  z-index: 2;
}
/* Wet sheen — strong upper catchlight (linear) + top-left hot spot
   (radial) to suggest internal light through the translucent shell. */
.profile-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 45%;
  border-radius: 26px 26px 0 0;
  background:
    linear-gradient(180deg,
      rgba(255,255,255,0.36) 0%,
      rgba(255,255,255,0.10) 55%,
      rgba(255,255,255,0)   100%);
  pointer-events: none;
  z-index: 5;
}
.profile-card::after {
  content: "";
  position: absolute;
  top: 0; left: 0;
  width: 60%; height: 50%;
  background:
    radial-gradient(120% 110% at 12% 0%,
      rgba(255,255,255,0.5) 0%,
      rgba(255,255,255,0)  55%);
  pointer-events: none;
  z-index: 6;
  border-radius: 26px 0 0 0;
}
.profile-image {
  position: relative;
  height: 382px;
  overflow: hidden;
}
.profile-image img,
.profile-image .photo-layer {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Two stacked layers cross-fade between photos. The active layer is the
   one currently visible (opacity 1). The other waits with opacity 0. */
.photo-layer {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity;
}
.photo-layer.is-active { opacity: 1; }
.profile-overlay {
  position: absolute;
  inset: auto 0 0 0;
  height: 92px;
  background: linear-gradient(to top, rgba(0,0,0,0.8), rgba(0,0,0,0));
}
/* Photo caption text swap animation (lives inside .profile-info .name) */
.profile-info .name {
  transition: opacity 0.22s ease, transform 0.35s cubic-bezier(.2,.7,.2,1);
}
.profile-card.is-switching .profile-info .name { opacity: 0; }
.profile-card.is-switching.to-next .profile-info .name { transform: translateX(-8px); }
.profile-card.is-switching.to-prev .profile-info .name { transform: translateX(8px); }
.profile-info {
  position: absolute;
  left: 20px;
  bottom: 20px;
  color: #fff;
  z-index: 2;
}
.profile-info .name {
  font-family: var(--font-serif);
  font-size: 32px;
  line-height: 36px;
  letter-spacing: -0.5px;
}
.profile-info .role {
  font-weight: 600;
  font-size: 11px;
  line-height: 14px;
  letter-spacing: 1.5px;
  color: rgba(255,255,255,0.82);
  margin-top: 6px;
  opacity: 0;
  max-height: 0;
  overflow: hidden;
  transition: opacity 0.22s ease, max-height 0.3s ease, margin-top 0.22s ease;
}
.profile-card.show-role .profile-info .role {
  opacity: 1;
  max-height: 20px;
}
.profile-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px 16px;
  /* Vertical "speaker grille" stripes — a quiet iMac G3 nod behind the
     prev/next buttons and reaction bar. 1px line every 5px so it reads
     as texture, not noise. Painted underneath the panel base color so
     the stripes sit a couple % below white. */
  background:
    repeating-linear-gradient(90deg,
      rgba(0,0,0,0.05) 0,
      rgba(0,0,0,0.05) 1px,
      transparent 1px,
      transparent 5px),
    #fff;
  border-top: 1px solid var(--border-hairline);
  position: relative;
  box-shadow: 0 -4px 5px rgba(0,0,0,0.08);
}
/* ============================================
   NavButton — Figma-accurate states
   Default / Hover (scale 1.042, darker orange, icon +22%) / Pressed (1px down, inverted inset)
   Color: Figma uses rgba(var(--brand-rgb), 0.7) over a dark canvas; over our white
   panel that renders too pale, so we use full-opacity brand orange.
   ============================================ */
.nav-btn {
  position: relative;
  width: 72px;
  height: 55px;
  border-radius: 100px;
  /* Bright candy orange — reads as solid Aqua jellybean against the
     light card surface. Dark mode overrides this further down with a
     darker, translucent variant that fits the warm-dark palette. */
  background: linear-gradient(180deg,
    var(--brand-light) 0%,
    var(--brand-deep) 100%);
  /* Stronger highlight + shadow gives a more pronounced "cushion" feel,
     matching the Figma render rather than the spec's 1px subtle inset. */
  box-shadow:
    2px 3px 6px rgba(0, 0, 0, 0.28),
    inset 1.5px 1.5px 3px rgba(255, 255, 255, 0.55),
    inset -1.5px -1.5px 3px rgba(0, 0, 0, 0.30);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition:
    background 0.22s ease,
    transform 0.22s cubic-bezier(.2,.7,.2,1),
    box-shadow 0.22s ease;
}
.nav-btn:hover {
  /* Hover warms up slightly — deeper bottom, brighter top */
  background: linear-gradient(180deg,
    var(--brand-hover-light) 0%,
    var(--brand-hover-deep) 100%);
  transform: scale(1.042);
  box-shadow:
    2.1px 3.1px 6.2px rgba(0, 0, 0, 0.30),
    inset 1.56px 1.56px 3.12px rgba(255, 255, 255, 0.6),
    inset -1.56px -1.56px 3.12px rgba(0, 0, 0, 0.32);
}
.nav-btn:active {
  /* Pressed: button moves 1px down, inset reversed → looks pushed in */
  transform: translate3d(0, 1px, 0);
  box-shadow:
    2px 3px 6px rgba(0, 0, 0, 0.28),
    inset -1.5px -1.5px 3px rgba(255, 255, 255, 0.7),
    inset 1.5px 1.5px 3px rgba(0, 0, 0, 0.32);
}
/* On hover+press, keep the bigger size but with the pressed inset */
.nav-btn:hover:active {
  transform: scale(1.042) translate3d(0, 1px, 0);
}
.reaction-btn {
  transition: transform 0.25s cubic-bezier(.2,.7,.2,1);
}
.reaction-btn:hover { transform: translateY(-3px) scale(1.1); }
.reaction-btn:active { transform: scale(0.95); }
/* Figma-shipped rounded triangle. Left button rotates 180° (same icon).
   Icon scales up 1.22× on hover, matching Figma's hover variant.
   mix-blend-mode: multiply blends the icon darker into the orange button,
   matching the deep red-orange triangle in Figma. (The SVG's internal
   `style="mix-blend-mode"` doesn't apply when loaded as <img>, so we
   apply it via CSS on the element itself.) */
.nav-btn-icon {
  width: 16px;
  height: 18px;
  display: block;
  /* Arrow SVG is now solid white (no saturation, no orange tint), so
     mix-blend-mode is unnecessary — and removing it keeps the white
     pure on top of whichever brand colour the button is wearing. */
  transition: transform 0.22s cubic-bezier(.2,.7,.2,1);
}
/* No CSS flip — arrow-left.svg is pre-mirrored in the file itself
   (via a `transform="scale(-1 1)"` group), which sidesteps the mobile
   Safari bug where a CSS transform on a mix-blend-mode <img> dropped
   the blend pass. */
.nav-btn:hover .nav-btn-icon { transform: scale(1.22); }
.reaction-bar {
  display: flex;
  gap: 10px;
  align-items: center;
  justify-content: center;
  padding: 0 20px;
  border-radius: 30px;
  /* Recessed groove — looks pressed INTO the surface (Aqua skeuo).
     Inverted gradient (darker top, lighter bottom) + inset shadows
     on the top/left sell the "cast shadow from above" illusion. */
  background:
    linear-gradient(180deg, rgba(200,200,200,0.92) 0%, rgba(225,225,225,0.85) 100%);
  height: 55px;
  width: 160px;
  position: relative;
  box-shadow:
    inset 0 1px 3px rgba(0,0,0,0.09),       /* top inner shadow — subtle depth */
    inset 0 1px 0 rgba(0,0,0,0.06),         /* top edge dark hairline */
    inset 0 -1px 0 rgba(255,255,255,0.5);   /* bottom edge light hairline */
}
.reaction-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  width: 32px;
  color: #343330;
  cursor: pointer;
}
.reaction-btn[disabled] { cursor: default; }
.reaction-btn .ic-wrap {
  position: relative;
  width: 24px;
  height: 24px;
  display: block;
}
.reaction-btn .ic {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transition: opacity 0.25s ease, transform 0.3s cubic-bezier(.2,.7,.2,1);
  transform-origin: center;
}
.reaction-btn .ic-filled { opacity: 0; transform: scale(0.6); }
.reaction-btn.is-reacted .ic-outline { opacity: 0; transform: scale(0.7); }
.reaction-btn.is-reacted .ic-filled  { opacity: 1; transform: scale(1); }

.reaction-btn[data-r="heart"].is-reacted { color: #E85814; }
.reaction-btn[data-r="smile"].is-reacted { color: #5AAA32; }
.reaction-btn[data-r="angry"].is-reacted { color: #D93F3F; }

.reaction-btn .count {
  font-weight: 700;
  font-size: 11px;
  line-height: 14px;
  letter-spacing: 0.6px;
  color: #1A1916;
  transition: color 0.25s ease;
}
.reaction-btn.is-reacted .count { color: currentColor; }

/* Pop bounce on react */
@keyframes reactionPop {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.3) translateY(-4px); }
  100% { transform: scale(1) translateY(0); }
}
.reaction-btn.pop .ic-wrap { animation: reactionPop 0.36s cubic-bezier(.2,.7,.2,1); }

/* ============================================
   Photo cycle transition — layered cross-fade
   Two stacked photo layers cross-fade between photos. All state is set via
   inline styles in JS for lock-free spam-proof navigation. Transitions
   below interpolate between whatever current value and the new target,
   so rapid clicks gracefully override in-flight animations.
   ============================================ */
.photo-layer {
  transition:
    opacity 0.4s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.profile-info .name,
.profile-info .role {
  transition:
    opacity 0.35s cubic-bezier(0.22, 1, 0.36, 1),
    max-height 0.35s ease;
}

/* ============================================
   Section Header
   ============================================ */
.section {
  padding: 60px var(--container-pad);
  position: relative;
  background: var(--surface-subtle);
  border-bottom: 1px solid var(--border-hairline);
  contain: layout style;
  /* Skip rendering when far off-screen — huge scroll perf win */
  content-visibility: auto;
  contain-intrinsic-size: 1px 600px;
}
.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 24px;
}
.section-header .left {
  font-weight: 700;
  font-size: 20px;
  line-height: 24px;
  color: var(--text-tertiary);
}
.section-header .right {
  font-size: 14px;
  color: var(--text-tertiary);
  transition: color 0.15s;
}
.section-header .right:hover { color: var(--brand-orange); }

/* ============================================
   Projects Grid
   ============================================ */
.projects-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  /* Section is always at least 800px tall (≈ a two-row card layout) so
     the contact CTA below stays put across filters. Cards sit at the
     top of their cells via align-items: start — they don't stretch
     vertically to fill the row's height. Fewer cards = empty space
     below; the cards keep their natural sizes. */
  align-items: start;
  min-height: 800px;
}
@media (max-width: 760px) {
  .projects-grid { min-height: 0; }
}
.projects-grid[hidden] { display: none; }

/* Empty-state — only the empty placeholder has a default height so the
   section doesn't collapse to nothing when a filter has 0 matches.
   Approximately matches a 2-row card view. */
.projects-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  min-height: 800px;
  text-align: center;
  padding: 40px 20px;
  font-family: var(--font-serif);
  font-size: 22px;
  line-height: 1.4;
  color: var(--text-tertiary);
  letter-spacing: 0.02em;
  animation: emptyIn 0.45s var(--ease-bounce) both;
}
.projects-empty-face {
  width: 88px;
  height: 88px;
  color: var(--brand-orange);
  /* The SVG strokes are `currentColor`, so setting color above tints
     the icon to brand orange — same accent as the <em>yet</em> below. */
}
.projects-empty-text { margin: 0; }
@media (max-width: 760px) {
  .projects-empty { min-height: 320px; }
  .projects-empty-face { width: 72px; height: 72px; }
}
.projects-empty em {
  color: var(--brand-orange);
  font-style: italic;
}
.projects-empty[hidden] { display: none; }

/* ============================================
   404 page
   ============================================ */
.hero-404 { min-height: 80vh; }
.notfound-inner {
  text-align: center;
  align-items: center;
}
.notfound-face {
  width: 120px;
  height: 120px;
  color: var(--brand-orange);
  margin-bottom: 8px;
  animation: emptyIn 0.55s var(--ease-bounce) both;
}
.hero-404 .hero-actions { justify-content: center; }
@media (max-width: 760px) {
  .notfound-face { width: 96px; height: 96px; }
}
@keyframes emptyIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0);  }
}

/* ============================================
   Project Cell — with Figma hover prototype
   Hover=OFF → Hover=ON:
     · stripes overlay fades to 0
     · image scales up + becomes brighter
     · title color: #575757 → #000
     · card lifts with bigger shadow
   ============================================ */
/* Filter cross-fade — drive opacity via CSS animations (not transitions).
   The `.reveal` scroll-in animation locks opacity to 1 via
   `animation-fill-mode: both`, and inside that lock plain transitions
   (even on `!important` properties) don't fade reliably. A new
   `animation` declaration REPLACES any prior animation, so we use
   keyframes to do the fade-out and fade-in. */
.project-card.is-fading {
  /* Exit stays smooth (no bounce) so things feel "dismissed" cleanly. */
  animation: cardFadeOut 0.42s var(--ease-liquid) forwards !important;
}
.project-card.is-entering {
  /* Enter has bouncy ease-out-back so cards "pop in" past their
     resting position then settle. */
  animation: cardFadeIn 0.55s var(--ease-bounce) forwards !important;
}
.project-card.is-hidden { display: none !important; }
/* "Fade down" — leaving cards drift downward as they fade out;
   entering cards drift down from above into place. */
@keyframes cardFadeOut {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(14px); }
}
@keyframes cardFadeIn {
  from { opacity: 0; transform: translateY(-14px); }
  to   { opacity: 1; transform: translateY(0); }
}

.project-card {
  position: relative;
  display: flex;
  flex-direction: column;
  border-radius: 26px;          /* gumdrop curvature */
  overflow: hidden;
  background: #fff;
  /* Aqua translucent plastic — neutral resting shadow; hover warms
     the shadow into orange. */
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,1),
    inset 0 -1px 0 rgba(0,0,0,0.06),
    inset 0 0 0 1px rgba(255,255,255,0.5),
    0 1px 0 rgba(255,255,255,0.95),
    0 4px 12px rgba(0,0,0,0.07),
    0 16px 28px -10px rgba(0,0,0,0.12),
    0 38px 60px -22px rgba(0,0,0,0.16);
  cursor: pointer;
  transition:
    transform  0.55s var(--ease-bounce),
    box-shadow 0.55s var(--ease-liquid),
    opacity    0.32s ease-out;
  contain: layout style;
}
/* Wet sheen — now two-layer for a stronger Aqua catchlight:
   ::before is a strong, narrow upper highlight (the "wet edge")
   ::after is a soft radial hot spot top-left (internal light through
   the translucent shell). */
.project-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 42%;
  border-radius: 26px 26px 0 0;
  background:
    linear-gradient(180deg,
      rgba(255,255,255,0.38) 0%,
      rgba(255,255,255,0.10) 50%,
      rgba(255,255,255,0)   100%);
  pointer-events: none;
  z-index: 5;
}
.project-card::after {
  content: "";
  position: absolute;
  top: 0; left: 0;
  width: 60%; height: 50%;
  background:
    radial-gradient(120% 110% at 12% 0%,
      rgba(255,255,255,0.45) 0%,
      rgba(255,255,255,0)   55%);
  pointer-events: none;
  z-index: 6;
  border-radius: 26px 0 0 0;
}
.project-card:hover {
  transform: translate3d(0, -8px, 0);
  /* Subtle warm-orange underglow only on hover. */
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,1),
    inset 0 -1px 0 rgba(0,0,0,0.06),
    inset 0 0 0 1px rgba(255,255,255,0.6),
    0 1px 0 rgba(255,255,255,1),
    0 8px 18px rgba(var(--brand-deep-rgb), 0.06),
    0 24px 42px -10px rgba(var(--brand-deep-rgb), 0.10),
    0 50px 80px -22px rgba(var(--brand-rgb), 0.22);
}
.project-pic {
  position: relative;
  height: 284px;
  overflow: hidden;
  border-radius: 20px 20px 0 0;
  background: var(--surface-muted);
}
/* White shine — anchored at TOP-LEFT corner */
.project-pic::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(
    circle at 0% 0%,
    rgba(255, 255, 255, 0.75) 0%,
    rgba(255, 255, 255, 0.35) 14%,
    transparent 60%
  );
  pointer-events: none;
  z-index: 3;
  mix-blend-mode: screen;
  opacity: 1;
  transition: opacity 0.4s cubic-bezier(.2,.7,.2,1);
}
.project-pic img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1);
  z-index: 1;
  transition: transform 0.5s cubic-bezier(.2,.7,.2,1);
}
/* Hover: fade the shine out so the photograph is fully visible */
.project-card:hover .project-pic::after { opacity: 0; }
.project-card:hover .project-pic img {
  transform: scale(1.06);
}
.project-pic .stripes-overlay {
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(
    to right,
    rgba(0, 0, 0, 0.035) 0,
    rgba(0, 0, 0, 0.035) 4px,
    transparent 4px,
    transparent 6px
  );
  pointer-events: none;
  opacity: 1;
  z-index: 4;
  transition: opacity 0.4s ease;
}
.project-card:hover .stripes-overlay { opacity: 0; }
.project-pic .label,
.bg-stubby .label,
.bg-ipt-content .ipt,
.bg-ipt-content .duck {
  transition: transform 0.45s cubic-bezier(.2,.7,.2,1);
  position: relative;
  z-index: 1;
}
.project-card:hover .bg-stubby .label,
.project-card:hover .bg-ipt-content .ipt,
.project-card:hover .bg-ipt-content .duck {
  transform: scale(1.06);
}
.project-card .project-tag {
  transition:
    transform 0.45s cubic-bezier(.2,.7,.2,1),
    box-shadow 0.3s ease;
}
.project-card:hover .project-tag {
  transform: translateY(-2px);
  box-shadow: 4px 8px 28px rgba(0,0,0,0.3),
              inset -1px -1px 2px rgba(0,0,0,0.25),
              inset 1px 1px 2px #fff;
}
.project-tag {
  position: absolute;
  left: 20px;
  top: 18px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px 10px 16px;
  border-radius: 100px;
  -webkit-backdrop-filter: blur(5px);
  backdrop-filter: blur(5px);
  box-shadow: 4px 4px 20px rgba(0,0,0,0.25),
              inset -1px -1px 2px rgba(0,0,0,0.25),
              inset 1px 1px 2px #fff;
  color: #fff;
  font-weight: 600;
  font-size: 13px;
  text-shadow: 2px 2px 10px rgba(0,0,0,0.5);
  z-index: 5;
}
.project-tag .dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: rgba(255,255,255,0.9);
  box-shadow: 0 0 6px rgba(255,255,255,0.6);
}
/* Tag color variants */
.tag-uiux       { background: rgba(38,58,148,0.4); }
.tag-graphic    { background: rgba(232,88,20,0.4); }
.tag-editorial  { background: rgba(110,45,140,0.4); }
.tag-illustration { background: rgba(0,148,168,0.4); }
.tag-branding   { background: rgba(90,170,50,0.4); }

.project-name {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 30px 20px 30px 30px;
  background: #fff;
  border-top: 1px solid var(--border-hairline);
  border-radius: 0 0 20px 20px;
  box-shadow: 0 -4px 5px rgba(0,0,0,0.08);
  position: relative;
  /* Fixed-min height so cards are uniform regardless of text length —
     accommodates a 2-line title + 1-line description + padding. */
  min-height: 142px;
}
.project-name .info {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}
.project-name .title {
  font-family: var(--font-serif);
  font-size: 24px;
  line-height: 28px;
  color: var(--text-secondary);
  transition: color 0.3s ease;
  /* Clamp to 2 lines max so very long titles don't blow the card
     height; shorter titles still take their natural 1 line. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.project-card:hover .project-name .title { color: #000; }
.project-name .desc {
  font-size: 12px;
  line-height: 18px;
  color: var(--text-tertiary);
  /* Clamp to 1 line so descriptions stay uniform. */
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.screw {
  width: 35.69px;
  height: 35.69px;
  border-radius: 30px;
  /* Pre-multiplied color = surface-screw multiplied against white background.
     Drops the mix-blend-mode (compositing cost on every paint). */
  background: #d5d5d5;
  position: relative;
  flex-shrink: 0;
  box-shadow:
    inset -1px -1px 1px rgba(255,255,255,0.2),
    inset 0.5px 0.5px 1px rgba(0,0,0,0.25);
  /* ease-out-back: snappy + slight overshoot before settling — feels like
     a real screw being driven home */
  transition: transform 0.32s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.screw::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 3.29px;
  height: 21.73px;
  background: var(--surface-screw-detail);
  transform: translate(-50%, -50%) rotate(-45deg);
  border-radius: 1px;
  box-shadow: inset 0 0 2px rgba(0,0,0,0.5);
  /* Slot lights up almost in lockstep with the rotation — tiny 0.05s
     lead so the color/glow ramp is felt during the turn rather than
     as a separate "after" beat. */
  transition:
    transform   0.32s cubic-bezier(0.34, 1.56, 0.64, 1),
    background  0.25s ease-out 0.05s,
    box-shadow  0.25s ease-out 0.05s;
}
.project-card:hover .screw::after {
  transform: translate(-50%, -50%) rotate(-90deg);
  background: var(--brand-orange);
  box-shadow:
    inset 0 0 2px rgba(0,0,0,0.25),
    0 0 6px rgba(var(--brand-rgb), 0.85),    /* tight bright bloom */
    0 0 14px rgba(var(--brand-rgb), 0.55);   /* soft outer halo */
}

/* Project image background variants (matches Figma colorful placeholders) */
.bg-stubby {
  background: linear-gradient(135deg, var(--brand-orange) 0%, var(--brand-sunset) 100%);
  display: flex;
  align-items: center;
  justify-content: center;
}
.bg-stubby .label {
  font-family: var(--font-serif);
  font-size: 96px;
  line-height: 1;
  letter-spacing: -3px;
  color: #fff;
  font-weight: 700;
  text-shadow: 4px 4px 0 rgba(0,0,0,0.15);
  position: relative;
  z-index: 4;
}
.bg-ipt {
  background: linear-gradient(180deg, #FFF6D8 0%, #FFE9A3 100%);
}
.bg-ipt-content {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  z-index: 4;
}
.bg-ipt-content .ipt {
  font-family: var(--font-serif);
  font-size: 88px;
  line-height: 1;
  color: var(--brand-orange);
  font-weight: 700;
}
.bg-ipt-content .duck {
  font-size: 100px;
}

/* ============================================
   Filter Bar
   ============================================ */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  justify-content: center;
  margin-bottom: 24px;
}
.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px 10px 16px;
  border-radius: 100px;
  border: 1px solid var(--border-hairline);
  /* Aqua mini-pill: vertical gradient + inner top highlight + pillow shadow. */
  background:
    linear-gradient(180deg, rgba(255,255,255,1) 0%, rgba(255,255,255,0.85) 100%);
  font-weight: 600;
  font-size: 13px;
  color: var(--text-primary);
  cursor: pointer;
  transition: background  0.45s var(--ease-liquid),
              color       0.35s var(--ease-soft),
              transform   0.45s var(--ease-bounce),
              border-color 0.35s var(--ease-soft),
              box-shadow  0.45s var(--ease-liquid);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.85),
    inset 0 -1px 0 rgba(0,0,0,0.02),
    0 2px 6px rgba(0,0,0,0.05);
}
.filter-chip:hover {
  transform: translateY(-2px);
  border-color: rgba(0,0,0,0.18);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.95),
    0 4px 10px rgba(0,0,0,0.08),
    0 12px 22px -10px rgba(var(--brand-rgb), 0.22);
}
.filter-chip .dot {
  width: 8px; height: 8px; border-radius: 50%;
  transition: background 0.3s ease;
}
.filter-chip[data-cat="all"] .dot          { background: #1A1916; }
.filter-chip[data-cat="ui-ux"] .dot        { background: var(--footer-dot-blue); }
/* Graphic stays the original sunset orange across every palette —
   uses --footer-dot-orange (hardcoded) instead of --brand-sunset
   which shifts with the easter-egg recolor. */
.filter-chip[data-cat="graphic"] .dot      { background: var(--footer-dot-orange); }
.filter-chip[data-cat="editorial"] .dot    { background: var(--footer-dot-purple); }
.filter-chip[data-cat="illustration"] .dot { background: var(--footer-dot-green); }
.filter-chip[data-cat="branding"] .dot     { background: var(--footer-dot-teal); }

/* Active state — chip takes the category color, dot + text turn white,
   and casts a soft halo in its own hue so each filter feels "lit" when
   selected. --chip-glow is set per category just below; the box-shadow
   on .is-active reads it. */
.filter-chip[data-cat="all"]          { --chip-glow: rgba(  0,   0,   0, 0.35); }
.filter-chip[data-cat="ui-ux"]        { --chip-glow: rgba( 38,  58, 148, 0.55); }
.filter-chip[data-cat="graphic"]      { --chip-glow: rgba(232,  88,  20, 0.55); }
.filter-chip[data-cat="editorial"]    { --chip-glow: rgba(110,  45, 140, 0.55); }
.filter-chip[data-cat="illustration"] { --chip-glow: rgba( 90, 170,  50, 0.55); }
.filter-chip[data-cat="branding"]     { --chip-glow: rgba(  0, 148, 168, 0.55); }
.filter-chip.is-active {
  color: #fff;
  border-color: transparent;
  box-shadow:
    0 4px 14px -4px rgba(0,0,0,0.28),
    0 0 24px 0 var(--chip-glow);
}
.filter-chip.is-active .dot { background: #fff; }
.filter-chip[data-cat="all"].is-active          { background: #1A1916; }
.filter-chip[data-cat="ui-ux"].is-active        { background: var(--footer-dot-blue); }
.filter-chip[data-cat="graphic"].is-active      { background: var(--footer-dot-orange); }
.filter-chip[data-cat="editorial"].is-active    { background: var(--footer-dot-purple); }
.filter-chip[data-cat="illustration"].is-active { background: var(--footer-dot-green); }
.filter-chip[data-cat="branding"].is-active     { background: var(--footer-dot-teal); }

/* ============================================
   About Rows
   ============================================ */
.about-rows {
  background: var(--surface-subtle);
  border-bottom: 1px solid var(--border-hairline);
}
.about-row {
  display: flex;
  gap: 60px;
  align-items: flex-start;
  padding: 48px var(--container-pad);
  border-bottom: 1px solid var(--border-hairline);
  contain: layout style;
}
.about-row:last-child { border-bottom: 0; }
.about-row .row-label {
  font-weight: 700;
  font-size: 16px;
  line-height: 20px;
  color: var(--text-tertiary);
  width: 180px;
  flex-shrink: 0;
}
.about-row .row-content {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.about-row .row-content p {
  font-size: 16px;
  line-height: 27.2px;
  color: var(--text-secondary);
}

/* Skill Pills (Row 02) */
.skill-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.skill-pill {
  padding: 8px 16px;
  border-radius: 100px;
  font-size: 13px;
  line-height: 20px;
  border: 1px solid var(--border-hairline);
  background:
    linear-gradient(180deg, rgba(255,255,255,0.9) 0%, rgba(247,246,244,0.9) 100%);
  color: var(--text-secondary);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.85),
    0 1px 2px rgba(0,0,0,0.03);
  transition: transform 0.4s var(--ease-bounce),
              border-color 0.35s var(--ease-soft),
              background 0.35s var(--ease-soft),
              box-shadow 0.4s var(--ease-liquid);
}
.skill-pill:hover {
  transform: translateY(-2px);
  border-color: rgba(0,0,0,0.18);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.95),
    0 4px 10px rgba(0,0,0,0.06),
    0 8px 18px -8px rgba(var(--brand-rgb), 0.2);
}
.skill-pill.is-primary {
  background: rgba(var(--brand-rgb), 0.08);
  border-color: rgba(var(--brand-rgb), 0.3);
  color: var(--brand-orange);
}
.skill-pill.is-primary:hover {
  background: rgba(var(--brand-rgb), 0.14);
  border-color: rgba(var(--brand-rgb), 0.5);
}
.client-pill {
  transition: transform 0.3s cubic-bezier(.2,.7,.2,1),
              border-color 0.3s ease,
              color 0.2s ease;
}
.client-pill:hover {
  transform: translateY(-2px);
  border-color: rgba(0,0,0,0.18);
  color: var(--text-primary);
}

/* Experience list (Row 03) */
.exp-list { display: flex; flex-direction: column; }
.exp-row {
  display: flex;
  gap: 24px;
  align-items: flex-start;
  padding: 20px 0;
  border-top: 1px solid var(--border-hairline);
}
.exp-row:first-child { border-top: 0; }
.exp-row .year {
  width: 110px;
  font-size: 12px;
  line-height: 18px;
  color: var(--text-tertiary);
  flex-shrink: 0;
}
.exp-row .role {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.exp-row .role-title {
  font-family: var(--font-serif);
  font-size: 22px;
  line-height: 26px;
  color: var(--text-primary);
}
.exp-row .role-place {
  font-size: 14px;
  line-height: 22px;
  color: var(--text-tertiary);
}
.exp-row .badge {
  font-size: 10px;
  line-height: 14px;
  letter-spacing: 0.6px;
  color: var(--text-tertiary);
  white-space: nowrap;
}

/* Tools grid (Row 04) */
.tools-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  width: 100%;
}
.tool-card {
  background:
    linear-gradient(180deg, rgba(255,255,255,1) 0%, rgba(255,255,255,0.92) 100%);
  border: 1px solid var(--border-hairline);
  border-radius: 16px;
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.85),
    0 4px 14px rgba(0,0,0,0.06),
    0 14px 28px -10px rgba(0,0,0,0.08);
  cursor: default;
  transition: transform 0.55s var(--ease-bounce),
              box-shadow 0.55s var(--ease-liquid),
              border-color 0.35s var(--ease-soft);
}
.tool-card:hover {
  transform: translateY(-4px);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.95),
    0 8px 22px rgba(0,0,0,0.08),
    0 22px 44px -14px rgba(var(--brand-rgb), 0.22);
  border-color: rgba(0,0,0,0.12);
}
.tool-icon {
  transition: transform 0.5s cubic-bezier(.2,.7,.2,1);
}
.tool-card:hover .tool-icon { transform: scale(1.08) rotate(-3deg); }
.tool-icon {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.tool-icon img { width: 100%; height: 100%; object-fit: cover; }
.tool-icon.bg-figma     { background: linear-gradient(135deg, #646464 0%, #000 71.4%); }
.tool-icon.bg-claude    { background: linear-gradient(135deg, #1A1916 0%, #575757 71.4%); }
.tool-icon.bg-photoshop { background: linear-gradient(135deg, #00949C 0%, #1E2A8A 71.4%); }
.tool-icon.bg-illustrator { background: linear-gradient(135deg, #6E2D8C 0%, #C93761 71.4%); }
.tool-icon.bg-indesign  { background: linear-gradient(135deg, #5AAA32 0%, #2F6418 71.4%); }
.tool-name {
  font-family: var(--font-serif);
  font-size: 22px;
  line-height: 26px;
  color: var(--text-primary);
}
.tool-desc {
  font-size: 12px;
  line-height: 18px;
  color: var(--text-tertiary);
}

/* Facts grid (Row 05) */
.facts-grid {
  display: flex;
  flex-direction: column;
  width: 100%;
  padding-top: 12px;
}
.fact-row {
  display: flex;
  gap: 16px;
  align-items: center;
  padding: 14px 0;
  border-bottom: 1px solid var(--border-hairline);
}
.fact-row .key {
  width: 90px;
  font-size: 10px;
  line-height: 14px;
  letter-spacing: 0.6px;
  color: var(--text-tertiary);
  flex-shrink: 0;
}
.fact-row .val {
  flex: 1;
  font-size: 15px;
  line-height: 25px;
  color: var(--text-primary);
}

/* Clients (Row 06) */
.client-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.client-pill {
  padding: 8px 16px;
  border-radius: 100px;
  font-size: 13px;
  line-height: 20px;
  border: 1px solid var(--border-hairline);
  background: var(--surface-subtle);
  color: var(--text-secondary);
}

/* ============================================
   Project Detail Page
   ============================================ */
.project-hero {
  position: relative;
  overflow: hidden;
  background: #fff;
  border-bottom: 1px solid var(--border-hairline);
}
.project-hero-inner {
  position: relative;
  z-index: 2;
  padding: 80px var(--container-pad) 60px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 48px;
  align-items: center;
  max-width: calc(var(--content-width) + 2 * var(--container-pad));
  margin: 0 auto;
  width: 100%;
}
.project-hero-text {
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-width: 0;
}
.project-hero-image {
  border-radius: 20px;
  overflow: hidden;
  background: var(--surface-muted);
  box-shadow: 0 20px 48px rgba(0,0,0,0.12);
  aspect-ratio: 1 / 1;
  position: relative;
}
.project-hero-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
@media (max-width: 900px) {
  .project-hero-inner {
    grid-template-columns: 1fr;
    gap: 28px;
  }
}
.back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  color: var(--text-tertiary);
  width: fit-content;
  padding: 6px 12px;
  margin-left: -12px;
  border-radius: 8px;
  transition: color 0.2s ease, background 0.2s ease;
}
.back-link:hover {
  color: var(--brand-orange);
  background: rgba(var(--brand-rgb), 0.06);
}
.back-link span { transition: transform 0.25s ease; display: inline-block; }
.back-link:hover span { transform: translateX(-3px); }

.project-tag-static {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  width: fit-content;
  padding: 8px 16px 8px 14px;
  border-radius: 100px;
  font-weight: 600;
  font-size: 13px;
  color: #fff;
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
  box-shadow: 4px 4px 20px rgba(0,0,0,0.18);
}
.project-tag-static .dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: rgba(255,255,255,0.9);
}
.project-tag-static.tag-uiux        { background: var(--footer-dot-blue); }
.project-tag-static.tag-graphic     { background: var(--brand-sunset); }
.project-tag-static.tag-editorial   { background: var(--footer-dot-purple); }
.project-tag-static.tag-illustration { background: var(--footer-dot-green); }
.project-tag-static.tag-branding    { background: var(--footer-dot-teal); }

.project-title {
  font-family: var(--font-serif);
  font-size: clamp(32px, 5.5vw, 56px);
  line-height: 1.05;
  letter-spacing: -1.5px;
  color: var(--text-primary);
}
.project-lead {
  font-size: 18px;
  line-height: 1.55;
  color: var(--text-secondary);
  max-width: 60ch;
}
.project-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 40px;
  align-items: flex-start;
  padding-top: 24px;
  margin-top: 8px;
  border-top: 1px solid var(--border-hairline);
}
.project-meta > div {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 100px;
}
.project-meta dt {
  font-size: 11px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-tertiary);
  font-weight: 600;
}
.project-meta dd {
  font-size: 15px;
  color: var(--text-primary);
}
.project-meta .meta-link {
  margin-left: auto;
  align-self: center;
}
.project-meta .meta-link a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 18px;
  border-radius: 100px;
  background: var(--brand-orange);
  color: #fff;
  font-weight: 600;
  font-size: 13px;
  transition: background 0.2s ease, transform 0.2s ease;
}
.project-meta .meta-link a:hover {
  background: var(--brand-sunset);
  transform: translateY(-2px);
}

/* (legacy .project-cover-wrap removed — hero image now sits inside .project-hero-inner) */

/* Project body — alternating text + image blocks */
.project-body {
  background: var(--surface-subtle);
  padding: 60px var(--container-pad) 80px;
  border-bottom: 1px solid var(--border-hairline);
  content-visibility: auto;
  contain-intrinsic-size: 1px 800px;
}
.project-body-inner {
  max-width: var(--content-width);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 40px;
}
.proj-block-text {
  display: grid;
  grid-template-columns: 180px 1fr;
  gap: 60px;
  align-items: flex-start;
  padding-top: 16px;
}
.proj-block-text .row-label {
  font-weight: 700;
  font-size: 16px;
  color: var(--text-tertiary);
}
.proj-block-body {
  display: flex;
  flex-direction: column;
  gap: 16px;
  font-size: 17px;
  line-height: 1.7;
  color: var(--text-secondary);
  max-width: 65ch;
}
.proj-block-image {
  margin: 0;
  border-radius: 16px;
  overflow: hidden;
  background: #fff;
  box-shadow: 0 12px 32px rgba(0,0,0,0.08);
}
.proj-block-image img {
  width: 100%;
  height: auto;
  display: block;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  cursor: zoom-in;
  /* Pre-promote to a GPU layer so the FIRST hover doesn't allocate one
     mid-animation (which causes the visible startup hitch). The empty
     translateZ(0) is the cheapest layer hint. */
  transform: translateZ(0);
  backface-visibility: hidden;
  transition: transform 0.6s cubic-bezier(.2,.7,.2,1);
}
.proj-block-image:hover img { transform: translateZ(0) scale(1.02); }
.proj-block-image figcaption {
  padding: 12px 20px 16px;
  font-size: 13px;
  color: var(--text-tertiary);
  background: var(--surface-subtle);
  border-top: 1px solid var(--border-hairline);
}

@media (max-width: 900px) {
  .proj-block-text { grid-template-columns: 1fr; gap: 12px; }
}

.project-more {
  padding: 60px var(--container-pad);
  background: var(--surface-subtle);
  border-bottom: 1px solid var(--border-hairline);
}
.project-more .section-header { margin-bottom: 24px; }

@media (max-width: 900px) {
  .project-title { font-size: 40px; }
  .project-about-grid { grid-template-columns: 1fr; gap: 16px; }
  .project-about-grid .row-label { position: static; }
  .project-gallery-grid { grid-template-columns: 1fr; }
  .project-gallery-grid .gallery-item:nth-child(3n+1) { grid-column: span 1; }
  .project-meta .meta-link { margin-left: 0; }
}

/* ============================================
   Contact CTA
   ============================================ */
.contact-cta {
  position: relative;
  overflow: hidden;
  padding: 60px var(--container-pad);
  background: var(--surface-subtle);
  border-top: 1px solid var(--border-hairline);
  box-shadow: 0 -4px 4px rgba(0,0,0,0.05);
  /* Same reason as .hero — drop paint containment so internal stripes
     don't snap to a different sub-pixel layer offset across transitions. */
  contain: layout style;
}
.contact-blob {
  position: absolute;
  bottom: -665px;
  right: calc(var(--frame-edge-offset) - 316px);
  width: 1195px;
  height: 1195px;
  border-radius: 1000px;
  background: radial-gradient(circle at center, rgba(var(--brand-rgb), 0.12) 0%, rgba(var(--brand-rgb), 0) 70%);
  pointer-events: none;
}
.contact-cta-inner { position: relative; z-index: 2; }
.contact-header {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 24px;
}
.contact-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-weight: 700;
  font-size: 20px;
  color: var(--text-tertiary);
}
.contact-divider {
  width: 100%;
  height: 1px;
  background: var(--border-hairline);
  border: 0;
}
.contact-title {
  font-family: var(--font-serif);
  font-size: clamp(36px, 7.2vw, 80px);
  line-height: 1.05;
  letter-spacing: -2.4px;
  color: var(--text-primary);
  margin-bottom: 24px;
}
.contact-title em {
  font-style: italic;
  color: var(--brand-orange);
  display: inline-block;
  transition: transform 0.5s cubic-bezier(.2,.7,.2,1);
}
.contact-title:hover em { transform: translateY(-4px) rotate(-2deg); }
.contact-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}
.contact-bottom .left {
  font-size: 16px;
  line-height: 27.2px;
  color: var(--text-tertiary);
  padding: 10px 0;
}
.contact-bottom .right {
  display: flex;
  align-items: center;
  gap: 10px;
}
.contact-email {
  padding: 10px 20px;
  color: var(--text-tertiary);
  font-size: 16px;
  line-height: 27.2px;
  transition: color 0.15s;
}
.contact-email:hover { color: var(--brand-orange); }
.linkedin-icon { width: 36px; height: 36px; display: block; }

/* ============================================
   Footer
   ============================================ */
.footer {
  background: var(--surface-subtle);
  border-top: 1px solid var(--border-hairline);
  box-shadow: 0 -4px 5px rgba(0,0,0,0.05);
  padding: 33px var(--container-pad) 32px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}
.footer .col {
  font-size: 10px;
  line-height: 14px;
  letter-spacing: 0.6px;
  color: var(--text-tertiary);
}
.footer .col.right { text-align: right; }
.footer-dots {
  display: flex;
  gap: 8px;
  align-items: center;
}
.footer-dot {
  width: 10px;
  height: 10px;
  border-radius: 5px;
  position: relative;
  background-image: linear-gradient(180deg, rgba(0,0,0,0.03) 0%, rgba(0,0,0,0.03) 12.5%, rgba(0,0,0,0) 12.5%, rgba(0,0,0,0) 87.5%);
  /* Easter egg: clicking a dot re-skins the whole site to that hue.
     page-transitions.js wires up the click + persists the choice. The
     subtle hover scale is the only hint that they're interactive. */
  cursor: pointer;
  transition: transform 0.2s cubic-bezier(.2,.7,.2,1);
}
.footer-dot:hover { transform: scale(1.6); }
.footer-dot:active { transform: scale(1.3); }
.footer-dot.purple { background-color: rgba(110,45,140,0.22); border: 1px solid rgba(110,45,140,0.3); }
.footer-dot.orange { background-color: rgba(232,88,20,0.2); border: 1px solid rgba(232,88,20,0.28); }
.footer-dot.teal   { background-color: rgba(0,148,168,0.2); border: 1px solid rgba(0,148,168,0.28); }
.footer-dot.blue   { background-color: rgba(38,58,148,0.2); border: 1px solid rgba(38,58,148,0.28); }
.footer-dot.green  { background-color: rgba(90,170,50,0.2); border: 1px solid rgba(90,170,50,0.25); }

/* ============================================
   Responsive
   ============================================ */

/* Tablet — small laptops & landscape iPads */
@media (max-width: 1024px) {
  .tools-grid { grid-template-columns: repeat(2, 1fr); }
  .hero-about { gap: 60px; }
  .profile-card { width: 320px; }
}

/* Tablet portrait & large phones */
@media (max-width: 768px) {
  .nav-links { gap: 6px; }
  .nav-link { padding: 10px 14px; font-size: 14px; }
  .hero-inner { min-height: 480px; padding: 70px var(--container-pad); gap: 28px; }
  .hero-works { min-height: 280px; padding: 50px var(--container-pad); }
  /* Project hero on detail page → stack columns */
  .project-hero-inner { grid-template-columns: 1fr; gap: 28px; padding: 56px var(--container-pad) 40px; }
  .project-hero-image { max-width: 480px; margin-left: auto; margin-right: auto; }
  .project-meta { gap: 24px; padding-top: 18px; }
  .project-meta .meta-link { margin-left: 0; }
  /* About hero stacks */
  .hero-about { flex-direction: column; gap: 32px; padding: 40px var(--container-pad); }
  .hero-about-content { flex: 0 1 auto; min-height: 0; gap: 18px; width: 100%; }
  .profile-card { width: 100%; max-width: 380px; align-self: center; }
  /* About rows: label on top of content */
  .about-row { flex-direction: column; gap: 14px; padding: 36px var(--container-pad); }
  .about-row .row-label { width: auto; }
  /* Contact CTA bottom stacks */
  .contact-bottom { flex-direction: column; align-items: flex-start; gap: 16px; }
  .contact-bottom .right { flex-wrap: wrap; }
  /* Footer stacks */
  .footer { flex-direction: column; align-items: flex-start; padding: 24px var(--container-pad); }
  .footer .col.right { text-align: left; }
  /* Section padding tighter */
  .section { padding: 50px var(--container-pad); }
  .project-body { padding: 50px var(--container-pad) 60px; }
  .project-more { padding: 50px var(--container-pad); }
  .contact-cta { padding: 50px var(--container-pad); }
  /* Content block text rows stack */
  .proj-block-text { grid-template-columns: 1fr; gap: 12px; }
  .proj-block-text .row-label { position: static; }
  /* Project gallery 1 col */
  .project-gallery-grid { grid-template-columns: 1fr; }
  .project-gallery-grid .gallery-item:nth-child(3n+1) { grid-column: span 1; }
}

/* Phones */
@media (max-width: 600px) {
  /* Tighter side padding handled by --container-pad: 16px */
  .nav-inner { height: 60px; padding: 10px var(--container-pad); }
  .nav-links { gap: 2px; }
  .nav-link { padding: 8px 10px; font-size: 13px; }
  .nav-link::after { left: 10px; right: 10px; }

  /* Projects single column */
  .projects-grid { grid-template-columns: 1fr; gap: 16px; }
  .project-pic { height: 220px; }
  .project-pic .label { font-size: 64px !important; }
  .bg-ipt-content .ipt { font-size: 64px !important; }
  .bg-ipt-content .duck { font-size: 72px !important; }
  .project-name { padding: 24px 20px; gap: 12px; }
  .project-name .title { font-size: 20px; }

  /* Hero buttons stack full width */
  .hero-actions { flex-direction: column; width: 100%; max-width: 320px; gap: 12px; }
  .btn-pill { width: 100%; justify-content: center; padding: 16px; }
  .hero-desc { font-size: 15px; }

  /* Filter bar — horizontal scroll on phones (instead of wrap).
     overflow-x: auto forces overflow-y to also clip, so the chip's
     coloured glow (up to ~24px) was being chopped at the top and
     bottom. Bumping the vertical padding gives the glow room inside
     the bar's box without needing to mess with the overflow rules. */
  .filter-bar {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    justify-content: flex-start;
    margin-left: calc(var(--container-pad) * -1);
    margin-right: calc(var(--container-pad) * -1);
    padding: 22px var(--container-pad) 22px;
    scroll-padding: var(--container-pad);
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .filter-bar::-webkit-scrollbar { display: none; }
  .filter-chip { flex-shrink: 0; }

  /* Project detail hero meta wraps */
  .project-hero-inner { padding: 40px var(--container-pad) 32px; gap: 24px; }
  .project-meta { gap: 18px; }
  .project-meta > div { min-width: 80px; }

  /* About hero & rows tighter */
  .hero-about { gap: 24px; padding: 32px var(--container-pad); }
  .hero-about-eyebrow { font-size: 15px; }
  .about-row { padding: 28px var(--container-pad); gap: 12px; }
  .about-row .row-label { font-size: 14px; }
  .about-row .row-content p { font-size: 15px; line-height: 1.6; }

  /* Section header */
  .section-header { flex-direction: column; align-items: flex-start; gap: 6px; margin-bottom: 18px; }
  .section { padding: 40px var(--container-pad); }
  .project-body { padding: 40px var(--container-pad) 50px; }
  .project-more { padding: 40px var(--container-pad); }
  .contact-cta { padding: 40px var(--container-pad); }
  .contact-bottom { gap: 12px; }
  .contact-email { padding: 8px 0; }

  /* Profile card full width on phone */
  .profile-card { max-width: none; }
  .profile-image { height: 320px; }

  /* Tools 1 col */
  .tools-grid { grid-template-columns: 1fr; }

  /* Experience row condensed */
  .exp-row { gap: 12px; flex-wrap: wrap; padding: 16px 0; }
  .exp-row .year { width: 90px; }
  .exp-row .badge { width: 100%; padding-left: 102px; margin-top: -4px; }

  /* Fact rows */
  .fact-row { gap: 10px; }
  .fact-row .key { width: 70px; font-size: 9px; }
  .fact-row .val { font-size: 14px; line-height: 1.45; }

  /* Skill / client pills slightly smaller */
  .skill-pill, .client-pill { font-size: 12.5px; padding: 6px 12px; }

  /* Body block text reduce */
  .proj-block-body { font-size: 16px; line-height: 1.65; }

  /* Touch targets — make sure tap areas are ≥44px */
  .reaction-btn { min-height: 44px; padding: 6px 0; }
  .filter-chip { min-height: 40px; }
}

/* ============================================
   Mobile performance overrides
   ============================================
   Properties that look great on desktop but cost frames on mid-range
   phones. Removing them on small viewports gives the page transitions
   and scroll the headroom they need. */
@media (max-width: 760px) {
  /* `backdrop-filter` is the most expensive GPU op on mobile (especially
     iOS Safari + mid-range Android). Replace with a slightly darker solid
     so the tags stay readable without the blur cost. */
  .project-tag,
  .project-tag-static {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  /* `mix-blend-mode: screen` forces a separate compositing layer for the
     whole element; on a long page of project cards this adds up. */
  .project-pic::after { mix-blend-mode: normal; opacity: 0.7; }

  /* Hover transitions don't fire on mobile, but the GPU still has to
     prep an image-scale layer when the card scrolls into view. Drop
     the transform-transition costs by reverting on hoverless devices. */
}
@media (hover: none) and (pointer: coarse) {
  .project-card:hover .project-pic img { transform: none; }
  .project-pic img { transition: none; }

  /* ───── Tap micro-feedback ─────
     On touch devices there's no hover state, so a brief 0.96 scale on
     :active gives a satisfying "press" feel without any extra layout
     work. Cheap (transform only) and universally smoothing. */
  .btn-pill,
  .nav-btn,
  .reaction-btn,
  .filter-chip,
  .project-card,
  .nav-link,
  .photo-card,
  .skill-pill,
  .client-pill {
    transition: transform 0.15s var(--ease-soft, ease-out);
  }
  .btn-pill:active,
  .nav-btn:active,
  .reaction-btn:active,
  .filter-chip:active,
  .project-card:active,
  .photo-card:active {
    transform: scale(0.97);
  }
  /* Buttons that already have transform animations (e.g. nav-btn has its
     own translate) shouldn't lose those — scope by selector instead. */
}

/* ============================================
   Image load fade — one-shot
   ============================================
   Images get `.img-fading` on insertion (CSS), then `.img-faded` once
   their natural-height resolves (toggled by page-transitions.js). This
   replaces the abrupt "image pops in" with a 450ms ease.

   IMPORTANT: uses @keyframes (not `transition`) so it doesn't clobber
   other transitions defined on `img` elements — e.g. the `transform`
   transition on `.proj-block-image img` would otherwise be wiped out
   by the `transition` shorthand here. */
img.img-fading       { opacity: 0; }
img.img-fading.img-faded {
  animation: imgFadeIn 0.45s var(--ease-soft, ease-out) both;
}
@keyframes imgFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ============================================
   Lightbox — full-screen image viewer
   ============================================
   Wired up by /lightbox.js. The overlay is appended to <body> on the
   first click on a `.proj-block-image img`. Mounted always-on for
   simplicity; pointer-events: none until `.is-open` so it doesn't eat
   clicks behind it. */
.lightbox-locked, .lightbox-locked body { overflow: hidden; }

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  /* Match the buttery page-transition curve so opening feels like a
     continuation of the same motion language. */
  transition: opacity 0.42s var(--ease-soft);
}
.lightbox.is-open { opacity: 1; pointer-events: auto; }

.lightbox-scrim {
  position: absolute;
  inset: 0;
  background: rgba(15, 15, 12, 0.92);
  cursor: zoom-out;
}

.lightbox-figure {
  position: relative;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  max-width: 92vw;
  max-height: 92vh;
  z-index: 2;
  /* Tiny scale-in so the image grows into place when the lightbox opens */
  transform: scale(0.96) translateY(8px);
  transition: transform 0.55s var(--ease-flow);
}
.lightbox.is-open .lightbox-figure { transform: scale(1) translateY(0); }

.lightbox-img {
  display: block;
  max-width: 92vw;
  max-height: calc(92vh - 60px); /* leave room for caption */
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 12px;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.45);
  background: #111;
  cursor: zoom-out;
  /* Smooth crossfade when navigating between images */
  transition: opacity 0.18s ease-out;
  /* Inherit the no-save protections from img {} but reinforce them
     here so the casual save paths stay closed for the big copy too. */
  -webkit-user-drag: none;
  user-drag: none;
  -webkit-touch-callout: none;
  user-select: none;
}

.lightbox-caption {
  text-align: center;
  color: rgba(255, 255, 255, 0.78);
  font-size: 13px;
  line-height: 1.5;
  max-width: 80vw;
  font-style: italic;
}
.lightbox-caption[hidden] { display: none; }

.lightbox-close,
.lightbox-prev,
.lightbox-next {
  position: absolute;
  width: 46px;
  height: 46px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.10);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 3;
  transition: background 0.18s, transform 0.18s, border-color 0.18s;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
.lightbox-close:hover,
.lightbox-prev:hover,
.lightbox-next:hover {
  background: rgba(255, 255, 255, 0.22);
  border-color: rgba(255, 255, 255, 0.32);
}
.lightbox-close { top: 22px; right: 22px; }
.lightbox-close:hover { transform: scale(1.06); }
.lightbox-prev { left: 22px; top: 50%; transform: translateY(-50%); }
.lightbox-next { right: 22px; top: 50%; transform: translateY(-50%); }
.lightbox-prev:hover { transform: translateY(-50%) scale(1.06); }
.lightbox-next:hover { transform: translateY(-50%) scale(1.06); }
.lightbox-prev[hidden],
.lightbox-next[hidden] { display: none; }

/* Mobile: lose the backdrop blur (expensive) + slightly smaller buttons */
@media (max-width: 600px) {
  .lightbox-close,
  .lightbox-prev,
  .lightbox-next {
    width: 40px;
    height: 40px;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background: rgba(0, 0, 0, 0.55);
    border-color: rgba(255, 255, 255, 0.12);
  }
  .lightbox-close { top: 14px; right: 14px; }
  .lightbox-prev { left: 10px; }
  .lightbox-next { right: 10px; }
  .lightbox-img { border-radius: 8px; max-height: calc(92vh - 52px); }
  .lightbox-caption { font-size: 12px; }
}

@media (prefers-reduced-motion: reduce) {
  .lightbox,
  .lightbox-figure,
  .lightbox-img { transition: none !important; }
  .lightbox-figure { transform: none !important; }
}

/* ============================================
   Scroll-velocity bounce
   ============================================
   page-transitions.js writes `--scroll-vel` (px-per-frame, clamped
   ±60) while scrolling and adds `.is-scroll-settled` to <html> ~110ms
   after the scroll stops. Big-block elements drag with the scroll
   direction (offset = velocity × multiplier) and then spring back to
   0 with the bouncy curve when the user stops. Subtle "weight" feel.

   Applied to large section containers (not interactive cards) so the
   transform doesn't fight existing hover transforms. */
.section,
.about-rows,
.contact-cta-inner,
.project-body-inner,
.project-hero-text,
.project-hero-image,
.project-more,
.hero-inner {
  transform: translate3d(0, calc(var(--scroll-vel, 0) * -0.32px), 0);
  will-change: transform;
}
html.is-scroll-settled .section,
html.is-scroll-settled .about-rows,
html.is-scroll-settled .contact-cta-inner,
html.is-scroll-settled .project-body-inner,
html.is-scroll-settled .project-hero-text,
html.is-scroll-settled .project-hero-image,
html.is-scroll-settled .project-more,
html.is-scroll-settled .hero-inner {
  transform: translate3d(0, 0, 0);
  transition: transform 0.7s var(--ease-bounce);
}

@media (prefers-reduced-motion: reduce) {
  .section,
  .about-rows,
  .contact-cta-inner,
  .project-body-inner,
  .project-hero-text,
  .project-hero-image,
  .project-more,
  .hero-inner { transform: none; transition: none; }
}

/* ============================================
   Dark mode — warm dark, brand orange preserved
   ============================================
   Toggle button (rendered by page-transitions.js) flips
   `[data-theme]` on <html> between "light" and "dark". boot.js sets
   the initial value before paint based on localStorage / system pref.

   Palette derives from a deep warm-brown bg with cream text:
   · bg          #14110F  (warm dark, anchors the page)
   · subtle      #1A1715  (section bands, slightly lighter than bg)
   · elevated    #211D19  (cards, profile, tool-card surfaces)
   · raised      #2B2622  (buttons, chips, reaction bar)
   · text        #F0EDE7  (warm cream — softer than pure white)
   · text-2      #B4B0A8
   · text-3      #7E7A72
   · hairline    rgba(255,255,255,0.07)

   We override the existing variables AND specific hardcoded #fff
   surfaces. Brand orange (#F5941E) stays identical. */

[data-theme="dark"] {
  --brand-orange: #F5941E;
  --brand-sunset: #E85814;

  --text-primary:   #F0EDE7;
  --text-secondary: #B4B0A8;
  --text-tertiary:  #7E7A72;
  --text-inverse:   #14110F;

  --surface-default: #211D19;
  --surface-subtle:  #1A1715;
  --surface-muted:   #3A332E;
  --surface-screw:   #2B2622;
  --surface-screw-detail: #B4B0A8;

  --decorative-stripe: #2A2622;
  --decorative-ink:    #FFFFFF;

  --border-hairline: rgba(255, 255, 255, 0.07);
}

/* Smooth theme switch — applies briefly when toggling so colors
   crossfade rather than snap. The transition class is added by JS
   only during the toggle (about 600ms), then removed so it doesn't
   interfere with other transitions like hover. */
html.is-theming-switch *,
html.is-theming-switch *::before,
html.is-theming-switch *::after {
  transition:
    background-color 0.45s ease,
    color 0.45s ease,
    border-color 0.45s ease,
    box-shadow 0.45s ease !important;
}

/* Same idea for the footer-dot palette swap — applies for ~700ms so
   every brand-tinted surface smoothly crossfades to the new hue. Also
   transitions `fill` so the logo's orange dot animates with the rest. */
html.is-palette-changing *,
html.is-palette-changing *::before,
html.is-palette-changing *::after {
  transition:
    background-color 0.55s ease,
    background-image 0.55s ease,
    color 0.55s ease,
    border-color 0.55s ease,
    box-shadow 0.55s ease,
    fill 0.55s ease !important;
}

/* ───── Body + Page wrapper ───── */
[data-theme="dark"] html { background: #14110F; }
[data-theme="dark"] body { background: #14110F; color: var(--text-primary); }
/* Paint the View Transition stage in the right theme color so the gap
   between the leaving and entering snapshots never shows a white flash. */
[data-theme="dark"] ::view-transition { background: #14110F; }
[data-theme="dark"] ::view-transition-group(root) { background: #14110F; }
[data-theme="dark"] .page { background: #14110F; }

/* ───── Nav (frosted glass darker variant) ───── */
[data-theme="dark"] .nav {
  background: rgba(33, 29, 25, 0.86);
  box-shadow:
    inset 0 -1px 0 rgba(255,255,255,0.06),
    0 2px 20px rgba(0,0,0,0.40);
}
[data-theme="dark"] .nav-link { color: var(--text-secondary); }
[data-theme="dark"] .nav-link.is-active { color: var(--brand-orange); }
[data-theme="dark"] .nav-link:hover { color: var(--text-primary); }

/* ───── Hero backgrounds ───── */
[data-theme="dark"] .hero,
[data-theme="dark"] .hero-works,
[data-theme="dark"] .hero-about,
[data-theme="dark"] .project-hero { background: #14110F; }
[data-theme="dark"] .hero-blob {
  background: radial-gradient(circle at center,
    rgba(var(--brand-rgb), 0.08) 0%,
    rgba(var(--brand-rgb), 0)   70%);
}
/* Stripes: light slivers on dark instead of dark slivers on light.
   Bumped a bit higher than the light-mode 0.022 because subtle white
   on near-black needs more alpha than subtle black on white to read
   visually the same way. */
[data-theme="dark"] .stripes-bg .stripe {
  background: rgba(255, 255, 255, 0.07);
}

/* ───── Section bands ───── */
[data-theme="dark"] .section,
[data-theme="dark"] .about-rows,
[data-theme="dark"] .project-body,
[data-theme="dark"] .project-more,
[data-theme="dark"] .contact-cta { background: #1A1715; }
[data-theme="dark"] .section,
[data-theme="dark"] .about-rows,
[data-theme="dark"] .project-body,
[data-theme="dark"] .project-more {
  border-bottom-color: var(--border-hairline);
}

/* ───── Project cards (gumdrop plastic, warm dark version) ───── */
[data-theme="dark"] .project-card {
  background: #211D19;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.08),
    inset 0 -1px 0 rgba(0,0,0,0.35),
    inset 0 0 0 1px rgba(255,255,255,0.04),
    0 1px 0 rgba(255,255,255,0.04),
    0 4px 12px rgba(0,0,0,0.45),
    0 16px 28px -10px rgba(0,0,0,0.55),
    0 38px 60px -22px rgba(0,0,0,0.65);
}
[data-theme="dark"] .project-card:hover {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.10),
    inset 0 -1px 0 rgba(0,0,0,0.35),
    inset 0 0 0 1px rgba(255,255,255,0.05),
    0 1px 0 rgba(255,255,255,0.06),
    0 8px 18px rgba(0,0,0,0.50),
    0 24px 42px -10px rgba(0,0,0,0.60),
    0 50px 80px -22px rgba(var(--brand-rgb), 0.30);
}
[data-theme="dark"] .project-card::before {
  background: linear-gradient(180deg,
    rgba(255,255,255,0.08) 0%,
    rgba(255,255,255,0)   100%);
}
[data-theme="dark"] .project-card::after {
  background: radial-gradient(120% 110% at 12% 0%,
    rgba(255,255,255,0.12) 0%,
    rgba(255,255,255,0)   55%);
}
[data-theme="dark"] .project-name {
  background: #211D19;
  border-top-color: var(--border-hairline);
  box-shadow: 0 -4px 5px rgba(0,0,0,0.18);
}
[data-theme="dark"] .project-name .title { color: var(--text-secondary); }
[data-theme="dark"] .project-card:hover .project-name .title { color: var(--text-primary); }
[data-theme="dark"] .project-name .desc { color: var(--text-tertiary); }

/* Screw on dark cards */
[data-theme="dark"] .screw { background: #3a332e; }
[data-theme="dark"] .screw::after { background: #1a1715; }

/* ───── Profile card (about) ───── */
[data-theme="dark"] .profile-card {
  background: linear-gradient(180deg, #2B2622 0%, #211D19 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.08),
    inset 0 -1px 0 rgba(0,0,0,0.35),
    inset 0 0 0 1px rgba(255,255,255,0.05),
    0 6px 16px rgba(0,0,0,0.40),
    0 22px 38px -10px rgba(0,0,0,0.55),
    0 46px 78px -22px rgba(0,0,0,0.65);
}
[data-theme="dark"] .profile-card::before {
  background: linear-gradient(180deg,
    rgba(255,255,255,0.08) 0%,
    rgba(255,255,255,0)   100%);
}
[data-theme="dark"] .profile-card::after {
  background: radial-gradient(120% 110% at 12% 0%,
    rgba(255,255,255,0.12) 0%,
    rgba(255,255,255,0)   55%);
}

/* Profile card bottom strip (prev/next + reaction bar) */
[data-theme="dark"] .profile-actions {
  background:
    repeating-linear-gradient(90deg,
      rgba(255,255,255,0.05) 0,
      rgba(255,255,255,0.05) 1px,
      transparent 1px,
      transparent 5px),
    #211D19;
  border-top-color: var(--border-hairline);
  box-shadow: 0 -4px 5px rgba(0,0,0,0.18);
}

/* Reaction bar (recessed groove) */
[data-theme="dark"] .reaction-bar {
  background: linear-gradient(180deg, #1A1715 0%, #221E1A 100%);
  box-shadow:
    inset 0 1px 3px rgba(0,0,0,0.50),
    inset 0 1px 0 rgba(0,0,0,0.40),
    inset 0 -1px 0 rgba(255,255,255,0.05);
}
[data-theme="dark"] .reaction-btn       { color: var(--text-secondary); }
[data-theme="dark"] .reaction-btn .count { color: var(--text-primary); }

/* ───── Buttons (Aqua jellybean, warm dark) ─────
   No outer-edge rim in dark mode — on near-black background, even a
   4% white inset rim reads as a glowing outline. The top inner
   highlight alone is enough to imply curvature. */
[data-theme="dark"] .btn-pill {
  background: linear-gradient(180deg, #2B2622 0%, #221E1A 100%);
  color: var(--text-primary);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.05),
    inset 0 -1px 0 rgba(0,0,0,0.35),
    0 6px 18px rgba(0,0,0,0.40),
    0 22px 36px -10px rgba(0,0,0,0.55);
}
[data-theme="dark"] .btn-pill:hover {
  color: var(--brand-orange);
  /* Pillow drop-shadow stays dark (so the lift still feels grounded),
     plus a brand-orange glow halo to mirror the warm underglow that
     light mode gets on hover. Bumped a touch higher than light mode
     since the dark surround eats some of the bloom. */
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.08),
    inset 0 -1px 0 rgba(0,0,0,0.35),
    0 6px 18px rgba(0,0,0,0.45),
    0 14px 28px rgba(var(--brand-rgb), 0.22),
    0 32px 60px -12px rgba(var(--brand-rgb), 0.30);
}

/* ───── Profile photo nav buttons (darker translucent in dark mode) ─────
   Built from --brand-rgb + --brand-deep-rgb (slightly muted alphas)
   so the easter-egg palette swap re-tints the dark-mode buttons too. */
[data-theme="dark"] .nav-btn {
  background: linear-gradient(180deg,
    rgba(var(--brand-rgb), 0.88) 0%,
    rgba(var(--brand-deep-rgb), 0.94) 100%);
}
[data-theme="dark"] .nav-btn:hover {
  background: linear-gradient(180deg,
    rgba(var(--brand-rgb), 0.96) 0%,
    rgba(var(--brand-deep-rgb), 1) 100%);
}

/* ───── Filter chips ───── */
[data-theme="dark"] .filter-chip {
  background: linear-gradient(180deg, #2B2622 0%, #221E1A 100%);
  color: var(--text-primary);
  border-color: rgba(255,255,255,0.06);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.08),
    inset 0 -1px 0 rgba(0,0,0,0.20),
    0 2px 6px rgba(0,0,0,0.30);
}
[data-theme="dark"] .filter-chip:hover { border-color: rgba(255,255,255,0.12); }
/* Active chip: keep the per-category background from light-mode rules
   (UI/UX = navy, Graphic = sunset, etc.) — only the text color needs
   to be re-asserted here because the dark-mode `.filter-chip` rule
   above sets `color: var(--text-primary)` for the inactive state.
   Glow is bumped a bit brighter in dark mode where the surrounding
   panel is near-black and the chip needs more light to "lift". */
[data-theme="dark"] .filter-chip.is-active {
  color: #fff;
  border-color: transparent;
  box-shadow:
    0 4px 14px -4px rgba(0,0,0,0.55),
    0 0 28px 2px var(--chip-glow);
}

/* ───── About: skill pills, tool cards, exp rows, fact rows, clients ───── */
[data-theme="dark"] .skill-pill {
  background: linear-gradient(180deg, #2B2622 0%, #1F1B17 100%);
  color: var(--text-secondary);
  border-color: var(--border-hairline);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.06),
    0 1px 2px rgba(0,0,0,0.30);
}
[data-theme="dark"] .skill-pill.is-primary {
  background: rgba(var(--brand-rgb), 0.18);
  border-color: rgba(var(--brand-rgb), 0.4);
  color: var(--brand-orange);
}
[data-theme="dark"] .tool-card {
  background: linear-gradient(180deg, #2B2622 0%, #211D19 100%);
  border-color: var(--border-hairline);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.08),
    0 4px 14px rgba(0,0,0,0.40),
    0 14px 28px -10px rgba(0,0,0,0.45);
}
[data-theme="dark"] .exp-row { border-color: var(--border-hairline); }
[data-theme="dark"] .exp-row .title { color: var(--text-primary); }
[data-theme="dark"] .exp-row .place { color: var(--text-tertiary); }
[data-theme="dark"] .exp-row .badge {
  background: rgba(255,255,255,0.06);
  color: var(--text-secondary);
}
[data-theme="dark"] .fact-row { border-color: var(--border-hairline); }
[data-theme="dark"] .fact-row .key { color: var(--text-tertiary); }
[data-theme="dark"] .client-pill {
  border-color: var(--border-hairline);
  color: var(--text-secondary);
}

/* ───── Headings, body copy ───── */
[data-theme="dark"] .h-l,
[data-theme="dark"] .h-m,
[data-theme="dark"] .h-s,
[data-theme="dark"] .h-xs,
[data-theme="dark"] .project-title,
[data-theme="dark"] .contact-title,
[data-theme="dark"] .section-header .left,
[data-theme="dark"] .row-label,
[data-theme="dark"] .contact-header-row { color: var(--text-primary); }
/* Keep the hero headline brand-orange in dark mode — same warm accent
   that anchors the page in light mode. */
[data-theme="dark"] .hero-title { color: var(--brand-orange); }
[data-theme="dark"] .hero-eyebrow { color: var(--text-tertiary); }
[data-theme="dark"] .hero-desc,
[data-theme="dark"] .row-content,
[data-theme="dark"] .row-content p,
[data-theme="dark"] .project-lead { color: var(--text-secondary); }
[data-theme="dark"] .section-header .right,
[data-theme="dark"] [data-projects-count] { color: var(--text-tertiary); }
[data-theme="dark"] .contact-divider { border-color: var(--border-hairline); }

/* ───── Loader (first visit) ───── */
[data-theme="dark"] .page-loader { background: #14110F; }
[data-theme="dark"] .loader-bolt { fill: #F0EDE7; }
[data-theme="dark"] .nav-logo { color: #F0EDE7; }

/* ───── Footer ───── */
[data-theme="dark"] .footer {
  background: #14110F;
  border-color: var(--border-hairline);
  color: var(--text-tertiary);
  box-shadow: 0 -4px 5px rgba(0,0,0,0.3);
}

/* ───── Project body blocks (project detail) ───── */
[data-theme="dark"] .proj-block-text .row-label { color: var(--text-tertiary); }
[data-theme="dark"] .proj-block-body { color: var(--text-secondary); }
[data-theme="dark"] .back-link { color: var(--text-tertiary); }
[data-theme="dark"] .back-link:hover { color: var(--brand-orange); }
[data-theme="dark"] .project-meta dt { color: var(--text-tertiary); }
[data-theme="dark"] .project-meta dd { color: var(--text-primary); }

/* ───── Lightbox + admin scrim already dark — leave alone ───── */

/* ============================================
   Theme toggle buttons
   ============================================
   Two variants injected by page-transitions.js:
   · `.theme-toggle.nav` — small icon in the top nav (desktop)
   · `.theme-toggle.floating` — fixed pill bottom-right (mobile)
   Both target the same handler; both render a sun OR moon depending
   on the current theme. Aqua glassy plastic surface to match the
   rest of the UI. */
.theme-toggle {
  all: unset;
  cursor: pointer;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
  background: linear-gradient(180deg, #ffffff 0%, #f5f4f1 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.85),
    inset 0 -1px 0 rgba(0,0,0,0.04),
    0 2px 6px rgba(0,0,0,0.06),
    0 8px 16px -6px rgba(0,0,0,0.08);
  transition:
    transform 0.4s var(--ease-bounce),
    color    0.3s var(--ease-soft),
    box-shadow 0.4s var(--ease-liquid);
}
.theme-toggle:hover { transform: translateY(-2px); color: var(--brand-orange); }
.theme-toggle:active { transform: translateY(0) scale(0.96); }
.theme-toggle svg { width: 18px; height: 18px; }
.theme-toggle .ico-sun,
.theme-toggle .ico-moon {
  position: absolute;
  transition: opacity 0.3s ease, transform 0.3s ease;
}
.theme-toggle .ico-sun  { opacity: 0; transform: rotate(-90deg) scale(0.6); }
.theme-toggle .ico-moon { opacity: 1; transform: rotate(0) scale(1); }
[data-theme="dark"] .theme-toggle .ico-sun  { opacity: 1; transform: rotate(0) scale(1); }
[data-theme="dark"] .theme-toggle .ico-moon { opacity: 0; transform: rotate(90deg) scale(0.6); }

/* Dark variant of the toggle surface */
[data-theme="dark"] .theme-toggle {
  background: linear-gradient(180deg, #2B2622 0%, #211D19 100%);
  color: var(--text-secondary);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.08),
    inset 0 -1px 0 rgba(0,0,0,0.30),
    0 2px 6px rgba(0,0,0,0.35),
    0 8px 16px -6px rgba(0,0,0,0.40);
}
[data-theme="dark"] .theme-toggle:hover { color: var(--brand-orange); }

/* Nav variant — sits in .nav-links, sized to match other nav items */
.theme-toggle.nav {
  margin-left: 12px;
  position: relative;
}

/* Floating variant — bottom-right, only on small screens by default.
   Its own view-transition group so the button doesn't fade out with
   the rest of the page on cross-document navigations. */
.theme-toggle.floating {
  view-transition-name: theme-toggle-float;
  position: fixed;
  right: 18px;
  bottom: 18px;
  width: 48px;
  height: 48px;
  z-index: 90;
  /* Slightly larger glassy shadow */
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.85),
    inset 0 -1px 0 rgba(0,0,0,0.04),
    0 4px 12px rgba(0,0,0,0.10),
    0 14px 28px -10px rgba(0,0,0,0.18);
}
[data-theme="dark"] .theme-toggle.floating {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.10),
    inset 0 -1px 0 rgba(0,0,0,0.30),
    0 4px 12px rgba(0,0,0,0.50),
    0 14px 28px -10px rgba(0,0,0,0.55);
}
.theme-toggle.floating svg { width: 22px; height: 22px; }

/* Only show floating toggle on small screens; nav toggle covers desktop */
@media (min-width: 761px) {
  .theme-toggle.floating { display: none; }
}
@media (max-width: 760px) {
  .theme-toggle.nav { display: none; }
}
