:root {
  --r-image: 0px;
  --r-note: 18px;
  --r-sticky: 14px;
  --r-window: 20px;
  --r-modal: 20px;
  --r-control: 12px;
  --r-folder-body: 18px;
  --r-folder-tab: 10px;

  --bg-light: #DCD8D2;
  --canvas-light: #EFEBE3;
  --ink: #1A1A1A;
  --ink-soft: #2D2D2D;
  --muted-light: #9E9990;
  --muted-light-2: #B8B3AA;
  --paper: #FDFBF6;
  --paper-2: #F8F4ED;

  --glass-paper: rgba(253, 251, 246, 0.55);
  --glass-paper-strong: rgba(253, 251, 246, 0.62);
  /* Slightly brighter than the canvas bg so the note reads as a
     lifted surface, but close enough that the rounded corners don't
     create a harsh color step. Similar tonal logic to the dock pill. */
  --note-bg: rgba(248, 244, 235, 0.85);

  --panel-material: rgba(253, 251, 246, 0.92);
  --panel-rim:      rgba(255, 255, 255, 0.55);
  --note-rim:       rgba(255, 255, 255, 0.55);

  --dock-bg:           rgba(245, 240, 230, 0.78);
  --dock-rim:          rgba(255, 255, 255, 0.55);
  --dock-highlight:    rgba(255, 255, 255, 0.6);
  --dock-fg:           rgba(26, 26, 26, 0.6);
  --dock-fg-active:    var(--ink);
  --dock-btn-hover:    rgba(0, 0, 0, 0.06);
  --dock-btn-active:   rgba(0, 0, 0, 0.10);

  --sticky-cream: #DFDDD4;
  --sticky-terracotta: #C6BBB3;
  --sticky-sage: #C9D2C1;
  --sticky-slate: #C4D0D3;
  --sticky-mauve: #D9CCCC;

  --folder-back-bg:       rgba(208, 214, 210, 0.62);
  --folder-front-bg:      rgba(232, 236, 230, 0.65);
  --paper-sheet:          #C8F060;
  --folder-arrow-bg:      rgba(0, 0, 0, 0.06);
  --folder-arrow-bg-hover:rgba(0, 0, 0, 0.10);

  --peek-1: #E6DFC1;
  --peek-2: #C8D5BC;

  --font-sans: 'Inter', -apple-system, system-ui, sans-serif;

  --ease-out: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-pop: cubic-bezier(0.2, 0.8, 0.2, 1);
  --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
}

body.dark {
  --bg-light: #14141A;
  --canvas-light: #1C1C22;
  --ink: #F0EDE6;
  --ink-soft: #D8D5CE;
  --muted-light: #6E6B66;
  --muted-light-2: #4F4D49;
  --paper: #25252C;
  --paper-2: #2C2C34;

  --glass-paper: rgba(40, 40, 48, 0.55);
  --glass-paper-strong: rgba(40, 40, 48, 0.62);
  /* Slight tonal lift over the dark canvas, parallel to light mode. */
  --note-bg: rgba(38, 40, 48, 0.85);

  --panel-material: rgba(37, 37, 44, 0.88);
  --panel-rim:      rgba(255, 255, 255, 0.07);
  --note-rim:       rgba(255, 255, 255, 0.06);

  --dock-bg:           rgba(28, 28, 34, 0.82);
  --dock-rim:          rgba(255, 255, 255, 0.06);
  --dock-highlight:    rgba(255, 255, 255, 0.04);
  --dock-fg:           rgba(240, 237, 230, 0.6);
  --dock-fg-active:    var(--ink);
  --dock-btn-hover:    rgba(255, 255, 255, 0.08);
  --dock-btn-active:   rgba(255, 255, 255, 0.13);

  --folder-back-bg:       rgba(34, 36, 42, 0.65);
  --folder-front-bg:      rgba(46, 48, 54, 0.68);
  --paper-sheet:          #B8E84C;
  --folder-arrow-bg:      rgba(255, 255, 255, 0.08);
  --folder-arrow-bg-hover:rgba(255, 255, 255, 0.16);

  --sticky-cream:      #37352E;
  --sticky-terracotta: #37322F;
  --sticky-sage:       #303931;
  --sticky-slate:      #2F3438;
  --sticky-mauve:      #393131;

  --peek-1: #3A352A;
  --peek-2: #2E3830;
}
/* Removed about-mode overrides: dark Home and dark About now share
   the same body and app-window backgrounds (--bg-light and --canvas-light)
   for cohesive visual treatment across views. */

* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; overflow: hidden; }
body {
  font-family: var(--font-sans);
  background: var(--bg-light);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  user-select: none;
  transition:
    background 0.5s var(--ease-out),
    color 0.5s var(--ease-out),
    --fade-base 0.5s var(--ease-out);
}
/* about-mode used to override the body background to #C8C4BC,
   but that created a subtle mismatch between Home and About.
   Removed so both views share the same body/app-window colors. */

body.cursor-active { cursor: none; }
body.cursor-active * { cursor: none !important; }

@property --cursor-scale {
  syntax: '<number>';
  inherits: false;
  initial-value: 1;
}

.cursor {
  position: fixed;
  top: 0; left: 0;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: white;
  pointer-events: none;
  --cursor-x: -100px;
  --cursor-y: -100px;
  --cursor-scale: 1;
  transform: translate3d(var(--cursor-x), var(--cursor-y), 0) translate(-50%, -50%) scale(var(--cursor-scale));
  transition:
    --cursor-scale 0.22s var(--ease-out),
    opacity 0.2s var(--ease-out);
  z-index: 9999;
  mix-blend-mode: difference;
  opacity: 0;
  will-change: transform;
}
body.cursor-active .cursor { opacity: 1; }
body.cursor-hover .cursor { --cursor-scale: 1.4; }
body.cursor-pressed .cursor { --cursor-scale: 0.7; }
body.cursor-hover.cursor-pressed .cursor { --cursor-scale: 1.1; }

@media (hover: none) {
  .cursor { display: none; }
  body.cursor-active { cursor: auto; }
  body.cursor-active * { cursor: auto !important; }
}

.app-window {
  position: fixed;
  top: 32px; left: 32px; right: 32px; bottom: 32px;
  background: var(--canvas-light);
  border-radius: var(--r-window);
  box-shadow:
    0 1px 0 rgba(255,255,255,0.5) inset,
    0 0 0 0.5px rgba(0,0,0,0.18),
    0 30px 80px rgba(0,0,0,0.22),
    0 8px 24px rgba(0,0,0,0.12);
  overflow: hidden;
  display: flex; flex-direction: column;
  transition: background 0.5s var(--ease-out);
}
/* about-mode used to override the .app-window background to #FDFBF6,
   but that created a subtle mismatch between Home (#EFEBE3) and
   About. Removed so both views share the same canvas-light background. */

@media (max-width: 720px) {
  .app-window { top: 12px; left: 12px; right: 12px; bottom: 12px; }
}

.titlebar {
  height: 0; flex-shrink: 0;
  position: relative; z-index: 20;
}
.traffic { display: flex; gap: 8px; }
.traffic-dot { width: 12px; height: 12px; border-radius: 50%; transition: opacity 0.2s; }
.traffic-dot.r { background: #ff5f57; }
.traffic-dot.y { background: #ffbd2e; }
.traffic-dot.g { background: #28c840; }
.traffic:hover .traffic-dot { opacity: 0.85; }

.canvas {
  flex: 1;
  position: relative;
  overflow: hidden;
  cursor: grab;
}
.canvas.panning { cursor: grabbing; }

/* Top and bottom fade overlays. Create a soft gradient from the
   window background to transparent, hiding the sharp edge where
   canvas content meets the rounded window border. Dock and theme
   toggle sit above these (higher z-index) so they remain visible.
   The CSS variable --canvas-light automatically switches between
   light and dark mode values via body.dark, so the same gradient
   declaration works for both modes. About-mode uses different
   background colors which we override explicitly. */
.canvas::before,
.canvas::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  pointer-events: none;
  z-index: 5;
  /* No transition: background here. The gradient itself doesn't
     transition smoothly when its color-mix inputs change because
     CSS treats the gradient image as an opaque value. Instead the
     COLOR variable --fade-base is registered as @property below
     and transitioned on the body element — the gradient inherits
     the interpolated color and stays perfectly in sync with the
     body/app-window background. */
}
/* Registered as @property so CSS can interpolate it across the
   theme transition. Plain CSS variables — even with --c: <color> —
   are treated as opaque strings inside gradients, so transitioning
   them doesn't animate the gradient. @property registration tells
   the browser "this is a color, please interpolate it." */
@property --fade-base {
  syntax: '<color>';
  inherits: true;
  initial-value: #EFEBE3;
}
:root          { --fade-base: #EFEBE3; }
body.dark      { --fade-base: #1C1C22; }
/* Removed about-mode overrides for --fade-base since About now uses
   the same canvas background as Home. */
/* Asymmetric heights — top is shorter (48px) than bottom (96px)
   because the top has more dark canvas above the first card row,
   so a tall fade reads as too much darkening there. The bottom
   sits closer to content density, where a 96px fade reads as
   the right amount of softening. */
.canvas::before { height: 48px; }
.canvas::after  { height: 96px; }
.canvas::before {
  top: 0;
  /* Multi-stop gradient simulates an ease curve — opaque rises
     quickly at the edge then slowly tapers, giving a softer
     perceptual handoff than a single linear ramp. Max opacity is
     85% (rather than 100%) so the fade is subtler overall.
     Uses --fade-base (registered @property color) so the colors
     interpolate during theme switch, keeping the gradient in
     perfect sync with the body background. */
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--fade-base) 85%, transparent) 0%,
    color-mix(in srgb, var(--fade-base) 78%, transparent) 18%,
    color-mix(in srgb, var(--fade-base) 60%, transparent) 40%,
    color-mix(in srgb, var(--fade-base) 30%, transparent) 65%,
    color-mix(in srgb, var(--fade-base) 8%, transparent) 85%,
    rgba(0,0,0,0) 100%
  );
}
.canvas::after {
  bottom: 0;
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--fade-base) 85%, transparent) 0%,
    color-mix(in srgb, var(--fade-base) 78%, transparent) 18%,
    color-mix(in srgb, var(--fade-base) 60%, transparent) 40%,
    color-mix(in srgb, var(--fade-base) 30%, transparent) 65%,
    color-mix(in srgb, var(--fade-base) 8%, transparent) 85%,
    rgba(0,0,0,0) 100%
  );
}

.canvas-inner {
  position: absolute;
  inset: 0;
  transform-origin: 0 0;
  will-change: transform;
}
/* Fade-in when switching views (clicking Home or About). Matches
   the reveal feel of the project index modal. The class is
   added at the start of a cross-view rebuild in goView() and
   removed on animationend so it can fire again next time. */
.canvas-inner.view-enter {
  animation: fadein 0.35s var(--ease-out);
}

.card {
  position: absolute;
  background: var(--paper);
  border-radius: var(--r-image);
  box-shadow:
    0 0.5px 0 rgba(0,0,0,0.04),
    0 1px 2px rgba(0,0,0,0.05),
    0 6px 18px rgba(0,0,0,0.06);
  cursor: grab;
  transition:
    box-shadow 0.4s var(--ease-out),
    background 0.5s var(--ease-out),
    transform 0.55s var(--ease-pop);
  overflow: hidden;
  --push-x: 0px;
  --push-y: 0px;
  --hover-y: 0px;
  --click-scale: 1;
  transform: translate(var(--push-x), var(--push-y)) translateY(var(--hover-y)) scale(var(--click-scale));
}
.card:hover {
  box-shadow:
    0 0.5px 0 rgba(0,0,0,0.04),
    0 4px 10px rgba(0,0,0,0.08),
    0 16px 40px rgba(0,0,0,0.13);
  z-index: 100 !important;
}
.card.pushed { transition: transform 0.6s var(--ease-pop), box-shadow 0.4s var(--ease-out); }
.card.clicked {
  --click-scale: 1.07;
  transition: transform 0.4s var(--ease-pop), box-shadow 0.4s var(--ease-out);
  z-index: 200 !important;
}

.card.dragging {
  cursor: grabbing;
  box-shadow:
    0 4px 12px rgba(0,0,0,0.12),
    0 24px 60px rgba(0,0,0,0.20) !important;
  z-index: 999 !important;
  transition: none;
}

.card.image {
  width: 220px; height: 320px;
  padding: 0;
  border-radius: var(--r-image);
}
.card.image:hover { --hover-y: -3px; }
.card.image .img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
  -webkit-user-drag: none;
  transition: transform 0.6s var(--ease-out);
}
.card.image:hover .img { transform: scale(1.03); }

/* No-crop variant: show the entire image without cropping. Used for
   tiles whose source image aspect doesn't match the tile (e.g. square
   art in a landscape slot, or vice versa). Letterbox area fills with
   a neutral tone so the gap reads as intentional. */
.card.image.no-crop {
  background: #F2F0EA;
}
body.dark .card.image.no-crop {
  background: #1E1E22;
}
.card.image.no-crop .img {
  object-fit: contain;
}
.card.image.no-crop:hover .img { transform: scale(1.02); }

.card.image .caption {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 60%;
  display: flex;
  align-items: flex-end;
  padding: 18px 18px 16px;
  background: linear-gradient(
    to bottom,
    rgba(0,0,0,0) 0%,
    rgba(0,0,0,0.02) 25%,
    rgba(0,0,0,0.10) 50%,
    rgba(0,0,0,0.30) 75%,
    rgba(0,0,0,0.62) 100%
  );
  color: white;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.005em;
  opacity: 0;
  transition: opacity 0.4s var(--ease-out);
  pointer-events: none;
}
.card.image:hover .caption { opacity: 1; }

/* Locked placeholder — replaces hero media for gated projects.
   Custom cover image (no project name, no project content).
   Cover-fit, center-anchored so the focal mark stays visible at
   the 16:9 homepage tile and the 4:3 stack thumbnail. */
.locked-placeholder {
  width: 100%;
  height: 100%;
  background-image: url('../assets/locked-cover.jpg');
  background-size: cover;
  background-position: center center;
  background-repeat: no-repeat;
}

/* Lock badge — appears top-right on locked project cards.
   Small, frosted, monochrome — reads as a status indicator
   rather than a CTA. */
.card.image .lock-badge {
  position: absolute;
  top: 10px; right: 10px;
  width: 24px; height: 24px;
  border-radius: 50%;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: flex; align-items: center; justify-content: center;
  color: white;
  z-index: 2;
  pointer-events: none;
}

.card.sticky {
  width: 130px;
  min-height: 80px;
  padding: 12px 13px 13px;
  font-family: var(--font-sans);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 400;
  color: var(--ink);
  border-radius: 4px;
  box-shadow:
    0 0.5px 0 rgba(0,0,0,0.05),
    0 1px 3px rgba(0,0,0,0.06),
    0 8px 18px rgba(0,0,0,0.07);
  backdrop-filter: blur(20px) saturate(1.2);
  -webkit-backdrop-filter: blur(20px) saturate(1.2);
  position: absolute;
  /* Bulletproof z-index — stickies always above EVERY other card,
     including .clicked (200), .dragging (999), and any inline
     z-index the drag-to-front logic may have set on projects.
     99999 leaves headroom below the modal layer. */
  z-index: 99999 !important;
}
.card.sticky:hover {
  z-index: 99999 !important;
}
.card.sticky.cream      { background: var(--sticky-cream); }
.card.sticky.terracotta { background: var(--sticky-terracotta); }
.card.sticky.sage       { background: var(--sticky-sage); }
.card.sticky.slate      { background: var(--sticky-slate); }
.card.sticky.mauve      { background: var(--sticky-mauve); }
body:not(.dark) .card.sticky::before {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(135deg, transparent 0%, rgba(239, 235, 227, 0.6) 100%);
  pointer-events: none;
  border-radius: inherit;
}
.card.sticky { isolation: isolate; }
.card.sticky > * { position: relative; z-index: 1; }
.card.sticky {
  --rot: 0deg;
  transform: translate(var(--push-x), var(--push-y)) rotate(var(--rot)) scale(var(--click-scale));
}

.card.note {
  width: 220px;
  padding: 22px 22px 24px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--ink-soft);
  border-radius: var(--r-note);
  background: var(--note-bg);
  backdrop-filter: blur(28px) saturate(1.3);
  -webkit-backdrop-filter: blur(28px) saturate(1.3);
  overflow: hidden;
  box-shadow: none !important;
  /* Single soft diffuse shadow. The previous double drop-shadow
     (tight 1px + broad 22px) created a visible "tip" artifact at
     the squircle corners because the tight inner shadow pooled
     where the curve was sharpest. A single broader, gentler shadow
     spreads its weight evenly along the silhouette so the corners
     read as smooth. */
  filter: drop-shadow(0 6px 18px rgba(0,0,0,0.10));
  transition:
    filter 0.4s var(--ease-out),
    background 0.5s var(--ease-out),
    transform 0.55s var(--ease-pop);
}
.card.note:hover {
  filter: drop-shadow(0 12px 32px rgba(0,0,0,0.14));
}
/* Clickable notes (e.g. the "Currently" note routing to About) get
   a pointer cursor and a subtle hover lift so they read as interactive. */
.card.note.note-clickable { cursor: pointer; }
.card.note.note-clickable:hover { --hover-y: -2px; }
.card.note .note-title {
  font-size: 11px;
  font-weight: 500;
  color: var(--muted-light);
  margin-bottom: 14px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.card.note p + p { margin-top: 8px; }
.card.note.wide { width: 280px; }

.card.label {
  background: transparent;
  box-shadow: none;
  padding: 0;
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--muted-light);
  cursor: default;
  pointer-events: none;
  font-size: 11px;
  text-transform: uppercase;
  border-radius: 0;
}
.card.label:hover { box-shadow: none; }

.card.stack {
  width: 220px; height: 175px;
  background: transparent !important;
  box-shadow: none !important;
  overflow: visible !important;
  padding: 0;
  cursor: pointer;
  border-radius: 0;
  /* filter transition is what fades the drop-shadow smoothly on
     hover. Folders use filter: drop-shadow (not box-shadow) because
     their visible shape is non-rectangular due to the tab cutout,
     and box-shadow doesn't follow the visible shape. Without filter
     in this transition list the drop-shadow change snaps instantly. */
  transition:
    transform 0.5s var(--ease-pop),
    filter 0.5s var(--ease-out);
  filter: drop-shadow(0 1px 2px rgba(0,0,0,0.05))
          drop-shadow(0 6px 16px rgba(0,0,0,0.08));
}
.card.stack:hover {
  background: transparent !important;
  box-shadow: none !important;
  transform: translateY(-4px);
  filter: drop-shadow(0 2px 4px rgba(0,0,0,0.06))
          drop-shadow(0 18px 38px rgba(0,0,0,0.14));
}
.card.stack.dragging {
  background: transparent !important;
  box-shadow: none !important;
}

.folder-back {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--folder-back-bg);
  backdrop-filter: blur(36px) saturate(1.0);
  -webkit-backdrop-filter: blur(36px) saturate(1.0);
  transition: background 0.5s var(--ease-out);
}

.folder-items {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}
.folder-item {
  position: absolute;
  top: 0;
  height: 150px;
  width: 82%;
  left: 9%;
  border-radius: 8px;
  background: var(--paper-sheet);
  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
  transition:
    transform 0.55s var(--ease-pop),
    background 0.5s var(--ease-out);
  --rot: 0deg;
  --tx: 0px;
  --ty: 22px;
  --rot-hover: 0deg;
  --ty-hover: 8px;
  transform: translate(var(--tx), var(--ty)) rotate(var(--rot));
}
.card.stack:hover .folder-item {
  transform: translate(var(--tx), var(--ty-hover)) rotate(var(--rot-hover));
}

.folder-front {
  position: absolute;
  top: 22px;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  background: var(--folder-front-bg);
  /* saturate(1.0) keeps the blur effect but doesn't amplify the
     color of the paper sheets behind. Without this, the front
     cover picked up the gold/blue tint of the papers, making it
     look too colorful. */
  backdrop-filter: blur(36px) saturate(1.0);
  -webkit-backdrop-filter: blur(36px) saturate(1.0);
  transition:
    transform 0.45s var(--ease-pop),
    background 0.5s var(--ease-out);
}
.card.stack:hover .folder-front {
  transform: translateY(4px);
}

.folder-arrow {
  position: absolute;
  top: 36px;
  right: 14px;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--folder-arrow-bg);
  display: flex; align-items: center; justify-content: center;
  color: var(--ink);
  z-index: 5;
  opacity: 0.25;
  transition: opacity 0.4s var(--ease-out), background 0.4s var(--ease-out);
  pointer-events: none;
}
.folder-arrow svg { width: 13px; height: 13px; }
.card.stack:hover .folder-arrow {
  opacity: 1;
  background: var(--folder-arrow-bg-hover);
}

.folder-meta {
  position: absolute;
  bottom: 14px;
  left: 18px;
  right: 18px;
  z-index: 4;
}
.folder-count {
  font-size: 10px; color: var(--muted-light); font-weight: 500;
  letter-spacing: 0.08em; text-transform: uppercase;
}
.folder-name {
  font-size: 13px; font-weight: 500; color: var(--ink);
  margin-top: 2px; letter-spacing: -0.005em;
}

/* ============ MORPH CARD ============
   A self-contained card that morphs through three states:
   1. Resting: small card (280×360) with header pill, 4 fanned
      image thumbnails in the center, footer labels at the bottom.
   2. Expanded: click to grow the card to 560×560; the 4 thumbnails
      animate from their fanned positions into a 2×2 grid. Footer
      fades, close button appears.
   3. Image-zoomed: click any grid thumbnail to expand it to fill
      the expanded card; other thumbnails fade. */

.card.morph {
  width: 240px;
  height: 240px;
  padding: 0;
  border-radius: 22px;
  background: var(--morph-bg, #5a5240);
  overflow: hidden;
  cursor: pointer;
  /* Smooth ease-in-out — no overshoot, snappy response.
     `transform` is included so push-apart motion (when other
     cards on the homepage are clicked) animates smoothly. */
  transition:
    transform 0.55s var(--ease-pop),
    width 0.4s cubic-bezier(0.4, 0, 0.2, 1),
    height 0.4s cubic-bezier(0.4, 0, 0.2, 1),
    top 0.4s cubic-bezier(0.4, 0, 0.2, 1),
    left 0.4s cubic-bezier(0.4, 0, 0.2, 1),
    background 0.3s var(--ease-out),
    box-shadow 0.3s var(--ease-out);
  --hover-y: 0px;
}
.card.morph:not(.expanded):hover { --hover-y: -3px; }
/* During drag, disable all transitions so the card tracks the
   cursor instantly. Without this override, .card.morph's transition
   list (defined after .card.dragging in source order) would apply
   the 0.55s transform transition during drag, causing the laggy
   "resistance" feel. */
.card.morph.dragging {
  transition: none;
}
.card.morph.expanded {
  width: 560px;
  height: 624px;
  cursor: default;
  /* When expanded, sit above stickies (z-index: 99999 !important)
     so the focal card fully occludes them. */
  z-index: 100000 !important;
}
.card.morph.zoomed { cursor: zoom-out; }

/* Header pill */
.morph-header {
  position: absolute;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  width: 208px;                /* resting: card width 240 - 32 margin */
  background: #ffffff;
  color: #1c1c1e;
  padding: 10px 14px;
  border-radius: 14px;
  display: flex;
  align-items: center;
  gap: 10px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.12);
  z-index: 10;
  box-sizing: border-box;
  /* Single property animates: width grows from resting to expanded.
     Center stays anchored — no left/right switching, no ping-pong. */
  transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.card.morph.expanded .morph-header {
  width: 320px;                /* expanded: centered narrower pill */
}
.morph-header-icon {
  width: 32px; height: 32px;
  border-radius: 8px;
  flex-shrink: 0;
  background: #7a8550;
}
.morph-header-text { line-height: 1.25; min-width: 0; flex: 1; }
.morph-header-title {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: #1c1c1e;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.morph-header-sub {
  font-size: 11px;
  color: rgba(28,28,30,0.55);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 1px;
}

/* Image stack / grid container */
.morph-images {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.morph-thumb {
  position: absolute;
  width: 110px;
  height: 110px;
  border-radius: 14px;
  overflow: hidden;
  box-shadow:
    0 8px 28px rgba(0,0,0,0.35),
    0 2px 6px rgba(0,0,0,0.25);
  pointer-events: auto;
  cursor: pointer;
  /* Smooth ease-in-out — snappy but elegant */
  transition:
    transform 0.4s cubic-bezier(0.4, 0, 0.2, 1),
    width 0.4s cubic-bezier(0.4, 0, 0.2, 1),
    height 0.4s cubic-bezier(0.4, 0, 0.2, 1),
    top 0.4s cubic-bezier(0.4, 0, 0.2, 1),
    left 0.4s cubic-bezier(0.4, 0, 0.2, 1),
    border-radius 0.35s cubic-bezier(0.4, 0, 0.2, 1),
    box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1),
    opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  will-change: transform;
}
.morph-thumb img,
.morph-thumb video {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
}

/* RESTING: fanned stack center-anchored within the 240×240 card.
   Header pill occupies y=16-72; stack centers at y=155 to balance
   with the bottom whitespace below the cluster. */
.card.morph:not(.expanded) .morph-thumb {
  top: 100px;
  left: 65px;
}
.card.morph:not(.expanded) .morph-thumb[data-idx="0"] { transform: translate(-12px, -8px) rotate(-8deg); }
.card.morph:not(.expanded) .morph-thumb[data-idx="1"] { transform: translate(8px, -12px) rotate(5deg); }
.card.morph:not(.expanded) .morph-thumb[data-idx="2"] { transform: translate(-6px, 6px) rotate(-3deg); }
.card.morph:not(.expanded) .morph-thumb[data-idx="3"] { transform: translate(14px, 2px) rotate(7deg); }

/* EXPANDED: 2×2 grid with PERFECT 40px symmetry. Card is 560×624
   (slightly portrait to accommodate even spacing). Thumbnails are
   220×220 with 40px outer margin on all sides and 40px gaps between
   cells. Header zone occupies top 64px; first row of thumbs starts
   at y=104 (header_bottom + 40). */
.card.morph.expanded .morph-thumb {
  width: 220px;
  height: 220px;
  border-radius: 18px;
  box-shadow: 0 4px 18px rgba(0,0,0,0.25);
  transform: translate(0, 0) rotate(0deg);
}
.card.morph.expanded .morph-thumb[data-idx="0"] { top: 104px; left: 40px;  }
.card.morph.expanded .morph-thumb[data-idx="1"] { top: 104px; left: 300px; }
.card.morph.expanded .morph-thumb[data-idx="2"] { top: 364px; left: 40px;  }
.card.morph.expanded .morph-thumb[data-idx="3"] { top: 364px; left: 300px; }

/* ZOOMED: active thumb fills card, others fade out */
.card.morph.zoomed .morph-thumb {
  opacity: 0;
  pointer-events: none;
}
.card.morph.zoomed .morph-thumb.active {
  opacity: 1;
  pointer-events: auto;
  top: 32px;
  left: 32px;
  width: 496px;
  height: 560px;
  border-radius: 20px;
  box-shadow: 0 12px 40px rgba(0,0,0,0.4);
}

/* Footer labels — hidden (removed per design direction) */
.morph-footer {
  display: none;
}

.dock {
  /* Body-level placement (sibling of .app-window and .proj-index-bg)
     so its z-index can compete globally rather than being trapped
     inside .app-window's stacking context. position: fixed anchors
     it to the viewport. bottom: 50px = 32px (.app-window inset)
     + 18px (original gap), keeping the dock visually positioned
     where it always sat inside .canvas. */
  position: fixed;
  bottom: 50px; left: 50%; transform: translateX(-50%);
  display: flex; gap: 2px; padding: 5px;
  background: var(--dock-bg);
  backdrop-filter: blur(20px) saturate(1.4);
  -webkit-backdrop-filter: blur(20px) saturate(1.4);
  border: 0.5px solid var(--dock-rim);
  border-radius: 999px;
  box-shadow:
    0 0.5px 0 var(--dock-highlight) inset,
    0 8px 32px rgba(0,0,0,0.18);
  /* Now that the dock is at body level, its z-index actually
     competes with .proj-index-bg (200) and .modal-bg (200), so
     250 puts it above both. */
  z-index: 250;
  /* Match the global theme transition timing (body, app-window,
     canvas fade) so the dock's bg/border tint with the rest of
     the page when dark/light mode toggles. */
  transition:
    background 0.5s var(--ease-out),
    border-color 0.5s var(--ease-out),
    box-shadow 0.5s var(--ease-out);
}
.dock-btn {
  width: 36px;
  height: 36px;
  padding: 0;
  border-radius: 50%;
  background: transparent;
  border: none;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background 0.25s var(--ease-out), color 0.25s var(--ease-out), transform 0.25s var(--ease-out);
  color: var(--dock-fg);
}
.dock-btn:hover { background: var(--dock-btn-hover); color: var(--dock-fg-active); transform: translateY(-1px); }
.dock-btn:active { background: var(--dock-btn-active); transform: translateY(0) scale(0.95); }
.dock-btn.active { background: var(--dock-btn-active); color: var(--dock-fg-active); }
.dock-btn svg { width: 22px; height: 22px; flex-shrink: 0; }
.dock-btn span { display: none; }

/* ============ Custom scrollbars ============
   Styled to match the dock's frosted pill aesthetic. The track is
   transparent so the scrollbar reads as a floating capsule rather
   than a channel. The thumb uses the same hover/active treatment
   as the dock buttons for visual consistency. Modern Firefox uses
   the scrollbar-color shorthand; WebKit (Safari/Chrome) uses
   pseudo-elements for finer control. */

/* Firefox + modern spec */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--dock-fg) transparent;
}

/* WebKit (Safari, Chrome, Edge) */
::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
::-webkit-scrollbar-track {
  background: transparent;
  border-radius: 999px;
}
::-webkit-scrollbar-thumb {
  background: var(--dock-fg);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: padding-box;
  transition: background 0.2s var(--ease-out);
}
::-webkit-scrollbar-thumb:hover {
  background: var(--dock-fg-active);
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:active {
  background: var(--ink);
  background-clip: padding-box;
}
::-webkit-scrollbar-corner {
  background: transparent;
}

/* Project modal scrollbar — subtle dark capsule that sits inset
   from the rounded corners. The 16px track margin keeps the
   scrollbar clear of the corner radius zones at top and bottom;
   the 4px transparent border on the thumb creates a visible
   ~6px capsule with breathing room from the modal's right edge. */
.modal-card-scroll::-webkit-scrollbar {
  width: 14px;
}
.modal-card-scroll::-webkit-scrollbar-track {
  background: transparent;
  /* Generous safe-area margins so the scrollbar never reaches the
     rounded corners of the modal. Top: 90px (well below the X
     close button at top:24, h:28). Bottom: 40px (clears the
     bottom rounded corner with breathing room). */
  margin: 90px 0 40px 0;
}
.modal-card-scroll::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.18);
  border-radius: 999px;
  border: 4px solid transparent;
  background-clip: padding-box;
  min-height: 60px;
  transition: background 0.2s var(--ease-out);
}
.modal-card-scroll::-webkit-scrollbar-thumb:hover {
  background: rgba(255, 255, 255, 0.28);
  background-clip: padding-box;
}
/* Light-mode modal has a light surface — switch the thumb to dark
   tones so it reads against the lighter material. */
body:not(.dark) .modal-card-scroll::-webkit-scrollbar-thumb {
  background: rgba(0, 0, 0, 0.18);
  background-clip: padding-box;
}
body:not(.dark) .modal-card-scroll::-webkit-scrollbar-thumb:hover {
  background: rgba(0, 0, 0, 0.28);
  background-clip: padding-box;
}

.help {
  position: absolute;
  bottom: 70px;
  left: 50%;
  transform: translateX(-50%) translateY(8px);
  z-index: 49;
  background: var(--glass-paper-strong);
  backdrop-filter: blur(28px) saturate(1.3);
  -webkit-backdrop-filter: blur(28px) saturate(1.3);
  border: 0.5px solid rgba(255,255,255,0.4);
  color: var(--ink-soft);
  font-size: 12px;
  letter-spacing: 0.005em;
  padding: 10px 14px 10px 16px;
  border-radius: 999px;
  display: flex; align-items: center; gap: 12px;
  max-width: calc(100% - 280px);
  white-space: nowrap;
  box-shadow:
    0 0.5px 0 rgba(0,0,0,0.04),
    0 4px 14px rgba(0,0,0,0.08),
    0 14px 32px rgba(0,0,0,0.10);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.35s var(--ease-out), transform 0.45s var(--ease-pop);
}
.help.open {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
}
.help kbd {
  background: rgba(127,127,127,0.18);
  padding: 1px 6px;
  border-radius: 4px;
  font-family: var(--font-sans);
  font-size: 11px;
  color: var(--ink);
}
.help .close-x {
  margin-left: 2px;
  background: none; border: none; cursor: pointer;
  color: var(--muted-light);
  font-size: 16px;
  padding: 0 2px;
  line-height: 1;
  transition: color 0.2s var(--ease-out);
}
.help .close-x:hover { color: var(--ink); }

.theme-toggle {
  position: absolute;
  bottom: 24px;
  right: 24px;
  width: 36px; height: 36px;
  border-radius: 50%;
  border: none;
  cursor: pointer;
  background: rgba(127,127,127,0.12);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
  color: var(--ink);
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  z-index: 50;
  transition:
    background 0.25s var(--ease-out),
    transform 0.25s var(--ease-out),
    color 0.5s var(--ease-out);
}
.theme-toggle:hover {
  background: rgba(127,127,127,0.22);
  transform: scale(1.06);
}
.theme-toggle:active { transform: scale(0.95); }
.theme-toggle svg {
  width: 16px; height: 16px;
  transition: transform 0.5s var(--ease-pop);
}
body.dark .theme-toggle svg { transform: rotate(45deg); }

.modal-bg {
  position: absolute; inset: 0;
  background: rgba(0,0,0,0.72);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  z-index: 200;
  display: none;
  align-items: center; justify-content: center;
  animation: fadein 0.3s var(--ease-out);
}
.modal-bg.open { display: flex; }
@keyframes fadein { from { opacity: 0; } to { opacity: 1; } }

.modal-card {
  background: var(--panel-material);
  backdrop-filter: blur(40px) saturate(1.4);
  -webkit-backdrop-filter: blur(40px) saturate(1.4);
  border-radius: var(--r-modal);
  width: min(900px, 92%);
  max-height: 92%;
  /* overflow: hidden so the border-radius acts as a stencil — the
     scrollbar (which lives in the inner .modal-card-scroll wrapper
     below) is clipped by the rounded corners instead of overshooting
     past them. The actual scrolling happens on .modal-card-scroll. */
  overflow: hidden;
  position: relative;
  animation: modalin 0.4s var(--ease-pop);
  display: flex;
  box-shadow:
    0 30px 60px rgba(0,0,0,0.35),
    0 8px 20px rgba(0,0,0,0.18);
}
/* Inner scrollable wrapper. Sits inside .modal-card's rounded clip
   so its scrollbar gets stenciled by the rounded corners. */
.modal-card-scroll {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
}
@keyframes modalin {
  from { opacity: 0; transform: scale(0.96) translateY(12px); }
  to { opacity: 1; transform: scale(1) translateY(0); }
}
.modal-close {
  position: absolute; top: 16px; right: 16px;
  width: 32px; height: 32px;
  border-radius: 50%;
  background: var(--paper);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border: 0.5px solid rgba(127,127,127,0.18);
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: 18px; line-height: 1; color: var(--ink-soft);
  z-index: 10;
  box-shadow: 0 2px 8px rgba(0,0,0,0.15);
  transition: background 0.2s var(--ease-out), transform 0.2s var(--ease-out);
}
.modal-close:hover { background: var(--paper-2); transform: scale(1.05); }
.modal-close:active { transform: scale(0.95); }

.modal-pad         { padding: 56px 64px 64px; }
.modal-pad-narrow  { padding: 56px 64px 64px; max-width: 660px; margin: 0 auto; }

.m-eyebrow {
  font-size: 11px;
  color: var(--muted-light);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
  margin-bottom: 16px;
}
.m-title {
  font-size: 32px;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin-bottom: 16px;
  color: var(--ink);
}
.m-title-sm {
  font-size: 24px;
  font-weight: 500;
  letter-spacing: -0.015em;
  margin-bottom: 16px;
}
.m-lede {
  font-size: 18px;
  line-height: 1.55;
  color: var(--ink-soft);
  margin-bottom: 36px;
  max-width: 56ch;
  font-weight: 400;
}

.m-hero {
  width: 100%;
  border-radius: var(--r-image);
  display: block;
  margin-bottom: 36px;
}
.m-hero.tall { aspect-ratio: 4/5; object-fit: cover; }
.m-hero.wide { aspect-ratio: 16/9; object-fit: cover; }
.m-hero.video { width: 100%; }

.m-prose-block { font-size: 15px; line-height: 1.7; color: var(--ink-soft); }
.m-prose-block p { margin-bottom: 18px; max-width: 65ch; }
.m-prose-block p:last-child { margin-bottom: 0; }

.m-section-label {
  font-size: 11px;
  color: var(--muted-light);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
  margin: 56px 0 24px;
}

.m-fig {
  margin: 28px 0;
}
.m-fig .m-media {
  width: 100%;
  border-radius: var(--r-image);
  display: block;
}
.m-fig figcaption {
  margin-top: 10px;
  font-size: 11.5px;
  color: var(--muted-light);
  letter-spacing: 0.005em;
  font-weight: 500;
}

.m-row {
  display: grid;
  gap: 12px;
  margin: 28px 0;
}
.m-row.cols-2 { grid-template-columns: 1fr 1fr; }
.m-row.cols-3 { grid-template-columns: 1fr 1fr 1fr; }
.m-row .m-fig { margin: 0; }

.m-pull-quote {
  font-size: 22px;
  line-height: 1.4;
  font-weight: 400;
  letter-spacing: -0.01em;
  color: var(--ink);
  padding: 40px 0;
  margin: 32px 0;
  max-width: 50ch;
}
.m-pull-quote::before {
  content: '"'; font-size: 28px; color: var(--muted-light); margin-right: 4px;
}
.m-pull-quote::after {
  content: '"'; font-size: 28px; color: var(--muted-light); margin-left: 2px;
}

.m-link-callout {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 13px;
  color: var(--ink);
  border: 0.5px solid rgba(0,0,0,0.18);
  padding: 10px 16px;
  border-radius: 999px;
  text-decoration: none;
  margin: 16px 0 28px;
  transition: background 0.2s var(--ease-out), transform 0.2s var(--ease-out);
}
.m-link-callout:hover {
  background: rgba(0,0,0,0.04);
  transform: translateY(-1px);
}

/* Locked-modal specific styles — used by renderLockedTemplate.
   The pill is a quiet status indicator, not a CTA. */
.m-lock-pill {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 24px 0 28px;
  padding: 14px 18px;
  border-radius: 14px;
  background: rgba(127,127,127,0.10);
  border: 0.5px solid rgba(127,127,127,0.18);
  font-size: 13px;
  color: var(--ink-soft);
  letter-spacing: 0.005em;
}
.m-lock-pill svg {
  flex-shrink: 0;
  color: var(--ink-soft);
}

/* Password gate — sits inline beneath the lede.
   Styled to match the soft, paper-like aesthetic rather than
   read as a security UI. The pill rounds match m-link-callout. */
.m-lock-form {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 12px 0 10px;
  flex-wrap: wrap;
}
.m-lock-input {
  flex: 1;
  min-width: 200px;
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--ink);
  background: var(--paper);
  border: 0.5px solid rgba(127,127,127,0.28);
  border-radius: 999px;
  padding: 11px 18px;
  outline: none;
  transition: border-color 0.2s var(--ease-out), background 0.2s var(--ease-out);
  letter-spacing: 0.01em;
}
.m-lock-input::placeholder {
  color: var(--muted-light);
  letter-spacing: 0.005em;
}
.m-lock-input:focus {
  border-color: rgba(127,127,127,0.55);
  background: var(--paper-2);
}
.m-lock-submit {
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--paper);
  background: var(--ink);
  border: none;
  border-radius: 999px;
  padding: 11px 22px;
  cursor: pointer;
  letter-spacing: 0.01em;
  transition: opacity 0.2s var(--ease-out), transform 0.2s var(--ease-out);
}
.m-lock-submit:hover { opacity: 0.85; transform: translateY(-1px); }
.m-lock-submit:active { transform: translateY(0) scale(0.98); }
.m-lock-msg {
  font-size: 12px;
  color: var(--muted-light);
  letter-spacing: 0.005em;
  min-height: 16px;
  margin-bottom: 18px;
  transition: color 0.2s var(--ease-out);
}
.m-lock-msg.err { color: #C45A4E; }
body.dark .m-lock-msg.err { color: #E8857B; }
.m-lock-divider {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 28px 0 20px;
  font-size: 11px;
  color: var(--muted-light);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.m-lock-divider::before,
.m-lock-divider::after {
  content: '';
  flex: 1;
  height: 0.5px;
  background: rgba(127,127,127,0.22);
}

.m-credits {
  font-size: 12.5px;
  color: var(--muted-light);
  margin-top: 56px;
  padding-top: 24px;
  border-top: 0.5px solid rgba(0,0,0,0.1);
  line-height: 1.7;
  max-width: 65ch;
}
.m-credits .cred-label {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--muted-light);
  margin-bottom: 12px;
}
.m-credits strong { color: var(--ink); font-weight: 500; }
.m-credits p + p { margin-top: 14px; }

.m-visit {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 28px;
  font-size: 13px;
  color: var(--ink);
  border-bottom: 0.5px solid currentColor;
  text-decoration: none;
  letter-spacing: 0.01em;
  padding-bottom: 2px;
  transition: opacity 0.2s var(--ease-out);
}
.m-visit:hover { opacity: 0.6; }

.m-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px;
  margin: 24px 0;
}
.m-grid .item {
  border-radius: var(--r-image);
  overflow: hidden;
  aspect-ratio: 4/3;
  background: #eee;
  cursor: pointer;
  transition: transform 0.3s var(--ease-out);
  position: relative;
}
.m-grid .item:hover { transform: translateY(-3px); }
.m-grid .item img,
.m-grid .item video { width: 100%; height: 100%; object-fit: cover; display: block; }
.m-grid .item .lbl {
  position: absolute; bottom: 0; left: 0; right: 0;
  padding: 16px 14px 12px;
  background: linear-gradient(
    to bottom,
    rgba(0,0,0,0) 0%,
    rgba(0,0,0,0.04) 30%,
    rgba(0,0,0,0.18) 60%,
    rgba(0,0,0,0.55) 100%
  );
  color: white; font-size: 11.5px; font-weight: 500;
  opacity: 0; transition: opacity 0.3s var(--ease-out);
}
.m-grid .item:hover .lbl { opacity: 1; }

.stack-cards {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 36px;
  border-top: 0.5px solid rgba(127,127,127,0.18);
}
.stack-card {
  display: grid;
  grid-template-columns: 240px 1fr;
  gap: 28px;
  padding: 28px 0;
  border-bottom: 0.5px solid rgba(127,127,127,0.18);
  cursor: pointer;
  transition: padding 0.3s var(--ease-out);
}
.stack-card:hover { padding-left: 8px; }
.stack-card-media {
  aspect-ratio: 4/3;
  overflow: hidden;
  border-radius: var(--r-image);
  background: rgba(127,127,127,0.10);
  position: relative;
}
.stack-card-media img,
.stack-card-media video {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.5s var(--ease-out);
}
.stack-card:hover .stack-card-media img,
.stack-card:hover .stack-card-media video {
  transform: scale(1.03);
}
/* Lock badge inside stack-card media */
.stack-card-media .lock-badge {
  position: absolute;
  top: 8px; right: 8px;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: flex; align-items: center; justify-content: center;
  color: white;
  z-index: 2;
  pointer-events: none;
}
.stack-card-body {
  display: flex; flex-direction: column;
  justify-content: center;
}
.stack-card-eyebrow {
  font-size: 11px;
  color: var(--muted-light);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 500;
  margin-bottom: 8px;
}
.stack-card-title {
  font-size: 19px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin-bottom: 10px;
}
.stack-card-blurb {
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink-soft);
  max-width: 56ch;
}

.proj-index-bg {
  position: absolute; inset: 0;
  background: rgba(0,0,0,0.72);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  z-index: 200;
  display: none;
  align-items: center; justify-content: center;
  animation: fadein 0.3s var(--ease-out);
}
.proj-index-bg.open { display: flex; }

.proj-index-panel {
  background: var(--panel-material);
  backdrop-filter: blur(40px) saturate(1.4);
  -webkit-backdrop-filter: blur(40px) saturate(1.4);
  border-radius: var(--r-modal);
  width: min(560px, 92%);
  max-height: 88%;
  overflow: auto;
  position: relative;
  padding: 56px 56px 56px;
  animation: modalin 0.4s var(--ease-pop);
  box-shadow:
    0 30px 60px rgba(0,0,0,0.35),
    0 8px 20px rgba(0,0,0,0.18);
}
.proj-index-panel .index-eyebrow {
  font-size: 11px;
  color: var(--muted-light);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
  margin-bottom: 12px;
}
.proj-index-panel .index-title {
  font-size: 24px;
  font-weight: 500;
  letter-spacing: -0.015em;
  margin-bottom: 32px;
  color: var(--ink);
}
.proj-index-panel .index-close {
  position: absolute; top: 16px; right: 16px;
  width: 32px; height: 32px;
  border-radius: 50%;
  background: var(--paper);
  border: 0.5px solid rgba(127,127,127,0.18);
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: 18px; line-height: 1; color: var(--ink-soft);
  z-index: 10;
  box-shadow: 0 2px 8px rgba(0,0,0,0.12);
  transition: background 0.2s var(--ease-out), transform 0.2s var(--ease-out);
}
.proj-index-panel .index-close:hover { background: var(--paper-2); transform: scale(1.05); }

.proj-index-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.proj-index-list li {
  display: grid;
  grid-template-columns: 36px 1fr auto;
  gap: 16px;
  align-items: baseline;
  padding: 18px 0;
  border-top: 0.5px solid rgba(127,127,127,0.18);
  cursor: pointer;
  transition: padding 0.3s var(--ease-out);
}
.proj-index-list li:last-child { border-bottom: 0.5px solid rgba(127,127,127,0.18); }
.proj-index-list li:hover {
  padding-left: 8px;
}
.proj-index-list .num {
  font-size: 11px;
  color: var(--muted-light);
  font-weight: 500;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}
.proj-index-list .title {
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.005em;
  line-height: 1.3;
  transition: color 0.2s var(--ease-out);
}
.proj-index-list li:hover .title { color: var(--ink); }
.proj-index-list .meta {
  font-size: 12px;
  color: var(--muted-light);
  letter-spacing: 0.005em;
  text-align: right;
  white-space: nowrap;
}
/* Locked indicator in the project index list */
.proj-index-list .lock-icon {
  display: inline-flex;
  align-items: center;
  margin-left: 6px;
  color: var(--muted-light);
  vertical-align: -2px;
}

@media (max-width: 720px) {
  .proj-index-panel { padding: 56px 24px 40px; max-height: 100%; height: 100%; border-radius: 0; width: 100%; }
  .proj-index-panel .index-title { font-size: 20px; }
  .proj-index-list li { grid-template-columns: 28px 1fr; padding: 16px 0; }
  .proj-index-list .meta { display: none; }
  .proj-index-list li:hover { padding-left: 0; }
}

.scratchpad {
  position: absolute;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 380px; min-height: 240px;
  background: var(--panel-material);
  backdrop-filter: blur(40px) saturate(1.4);
  -webkit-backdrop-filter: blur(40px) saturate(1.4);
  border: 0.5px solid var(--panel-rim);
  border-radius: var(--r-window);
  padding: 22px;
  box-shadow:
    0 0.5px 0 rgba(255,255,255,0.4) inset,
    0 30px 80px rgba(0,0,0,0.25),
    0 8px 24px rgba(0,0,0,0.12);
  z-index: 300;
  display: none;
  flex-direction: column;
  animation: modalin 0.3s var(--ease-pop);
}
.scratchpad.open { display: flex; }
.scratchpad .sp-header {
  font-size: 11px; color: var(--muted-light);
  letter-spacing: 0.06em; text-transform: uppercase;
  margin-bottom: 12px;
  display: flex; justify-content: space-between; align-items: center;
}
.scratchpad textarea {
  flex: 1; min-height: 180px;
  border: none; outline: none;
  resize: none;
  font-family: var(--font-sans);
  font-size: 14px; line-height: 1.6;
  background: transparent;
  color: var(--ink);
}

.about-doc {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  overflow-y: auto;
  overflow-x: hidden;
  /* flex-direction: column means the main axis is vertical:
     the inner is sized by its content (not stretched to the
     container height), so its margin-bottom correctly extends
     the scrollable area. With the default flex-direction: row,
     align-items: stretch caps the inner at exactly the parent's
     height, making margin-bottom invisible. */
  padding: 80px 40px 0;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.about-doc-inner {
  width: 100%;
  max-width: 680px;
  /* Body type — single size used by all paragraphs, contact block,
     and role list for typographic cohesion. Previously the lede
     was 19px, body 17px, role-list 16px, contact-block 16px —
     four different sizes for what's essentially the same reading
     register. Unified to 17px with 1.6 leading. */
  font-size: 17px;
  line-height: 1.6;
  font-weight: 400;
  color: var(--ink);
  letter-spacing: -0.005em;
  user-select: text;
  /* Bottom margin here (NOT padding on .about-doc) caps the maximum
     scroll position. Padding on the flex+scroll parent doesn't
     extend the scrollable area in Chrome/Safari — margin on the
     inner does. 360px gives generous air below the phone number,
     clearing both the fade overlay (96px) and the dock pill so
     the last contact line stops well above the navigation. */
  margin-bottom: 360px;
}
.about-doc-inner p { margin-bottom: 18px; }
.about-doc-inner p.spacer { height: 14px; margin-bottom: 18px; }
/* Lede no longer enlarged — was 19px, now inherits the body 17px.
   This isn't editorial content with a pull-quote opening; it's a
   continuous personal statement, so all paragraphs read at the
   same scale. */
.about-doc-inner .lede { line-height: 1.6; }
/* Location/role subtitle — secondary metadata, 14px to differentiate
   from body 17px without competing. */
.about-doc-inner .role-line {
  color: var(--muted-light);
  font-size: 14px;
  letter-spacing: 0.02em;
  margin-bottom: 30px;
}
/* Section labels (EDUCATION, CAPABILITIES, TOOLS, LET'S TALK) and
   the display name (VINÍCIUS NALDI) share the same eyebrow
   treatment — 12px uppercase, muted. Treating them as visual
   siblings reinforces the document's structural rhythm. */
.about-doc-inner .section-label,
.about-doc-inner h1.about-name {
  color: var(--muted-light);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
}
.about-doc-inner .section-label {
  margin-top: 56px;
  margin-bottom: 18px;
}
.about-doc-inner h1.about-name {
  margin-bottom: 12px;
}
.about-doc-inner .role-list { list-style: none; padding: 0; }
.about-doc-inner .role-list li {
  display: grid;
  grid-template-columns: 110px 1fr;
  gap: 24px;
  padding: 14px 0;
  border-top: 0.5px solid rgba(0,0,0,0.08);
  /* Matches body 17px — role-list rows ARE body content, just
     with a year/title layout grid. */
  font-size: 17px;
  line-height: 1.5;
}
.about-doc-inner .role-list li:last-child { border-bottom: 0.5px solid rgba(0,0,0,0.08); }
/* Year and sub-description: 14px metadata register, same as role-line. */
.about-doc-inner .role-list .yr { color: var(--muted-light); font-size: 14px; padding-top: 2px; }
.about-doc-inner .role-list strong { font-weight: 500; }
.about-doc-inner .role-list .sub { color: var(--muted-light); font-size: 14px; margin-top: 2px; }
.about-doc-inner .cap-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px 32px;
  /* Capabilities are short bullet items — at body 17px they'd
     dominate. 15px keeps them readable but secondary to the
     prose paragraphs above. */
  font-size: 15px; line-height: 1.4;
}
.about-doc-inner .cap-grid .cap-item {
  padding-left: 18px;
  position: relative;
}
.about-doc-inner .cap-grid .cap-item::before {
  content: ''; position: absolute;
  left: 0; top: 9px;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--ink);
}
/* Contact block at body 17px — it's a primary read, not metadata.
   Was 16px, which felt one click off from the paragraphs above. */
.about-doc-inner .contact-block { font-size: 17px; line-height: 1.7; }
.about-doc-inner .contact-block a {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 0.5px solid var(--ink);
  transition: opacity 0.2s var(--ease-out);
}
.about-doc-inner .contact-block a:hover { opacity: 0.6; }
.about-doc-inner .signature {
  margin-top: 80px;
  margin-bottom: 120px;
  font-size: 16px;
  color: var(--muted-light);
  letter-spacing: 0.02em;
}

@media (max-width: 720px) {
  body {
    overflow: auto;
    user-select: text;
    -webkit-tap-highlight-color: transparent;
  }
  html, body { height: auto; overflow: auto; }

  .app-window {
    position: relative;
    top: 0; left: 0; right: 0; bottom: auto;
    min-height: 100vh;
    border-radius: 0;
    box-shadow: none;
  }

  .titlebar { display: none; }

  .canvas {
    overflow: visible;
    height: auto;
    min-height: calc(100vh - 48px);
    cursor: default;
  }
  .canvas-inner {
    position: relative;
    transform: none !important;
    height: auto;
    padding: 20px 16px 120px;
    display: flex;
    flex-direction: column;
    gap: 14px;
  }

  .card {
    position: relative !important;
    left: auto !important;
    top: auto !important;
    transform: none !important;
    margin: 0;
    width: 100% !important;
    max-width: 100%;
    cursor: pointer;
  }

  .card:hover { box-shadow:
    0 0.5px 0 rgba(0,0,0,0.04),
    0 1px 2px rgba(0,0,0,0.05),
    0 6px 18px rgba(0,0,0,0.06); }
  .card.image:hover { transform: none; }
  .card.image:hover .img { transform: none; }
  .card.image:active { transform: scale(0.98); transition: transform 0.15s; }

  .card.image { height: 240px !important; }
  .card.image:nth-of-type(3n) { height: 320px !important; }
  .card.image:nth-of-type(4n) { height: 200px !important; }

  .card.image .caption {
    opacity: 1;
    padding: 22px 16px 14px;
    font-size: 12px;
    background: linear-gradient(
      to bottom,
      rgba(0,0,0,0) 0%,
      rgba(0,0,0,0.25) 60%,
      rgba(0,0,0,0.65) 100%
    );
  }

  .card.sticky {
    width: 100% !important;
    min-height: auto;
    padding: 18px 20px;
    font-size: 14px;
    line-height: 1.5;
  }

  body:not(.dark) .card.sticky::before {
    background: linear-gradient(135deg, transparent 0%, rgba(239, 235, 227, 0.4) 100%);
  }

  .card.note {
    width: 100% !important;
    padding: 22px 20px 24px;
    font-size: 14px;
    line-height: 1.65;
  }
  .card.note .note-title { font-size: 11px; margin-bottom: 12px; }

  .card.stack {
    width: 100% !important;
    height: 200px !important;
    margin-bottom: 4px;
  }

  .card.label {
    display: block;
    font-size: 10px;
    margin: 8px 0 4px;
    opacity: 0.7;
  }

  .help { display: none; }

  .theme-toggle {
    position: fixed;
    bottom: 16px;
    right: 16px;
    width: 36px; height: 36px;
  }
  .theme-toggle svg { width: 16px; height: 16px; }

  .dock {
    position: fixed;
    bottom: 30px;
    left: 50%;
    transform: translateX(-50%);
    padding: 5px;
    gap: 2px;
  }
  .dock-btn {
    width: 40px; height: 40px;
  }
  .dock-btn svg { width: 23px; height: 23px; }

  .about-doc { padding: 32px 20px 0; }
  .about-doc-inner { font-size: 17px; line-height: 1.6; }
  .about-doc-inner .lede { font-size: 19px; }
  .about-doc-inner .role-line { font-size: 14px; }
  .about-doc-inner .role-list li {
    grid-template-columns: 1fr;
    gap: 4px;
    padding: 16px 0;
  }
  .about-doc-inner .role-list .yr {
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
  }
  .about-doc-inner .cap-grid {
    grid-template-columns: 1fr;
    gap: 12px;
    font-size: 14px;
  }
  .about-doc-inner .section-label { font-size: 11px; margin-top: 44px; }
  .about-doc-inner .signature { margin-top: 60px; }

  .modal-bg { padding: 0; }
  .modal-card {
    width: 100%;
    max-width: 100%;
    max-height: 100%;
    height: 100%;
    border-radius: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  .modal-pad,
  .modal-pad-narrow {
    padding: 64px 20px 80px;
    max-width: 100%;
  }
  .modal-close {
    top: 14px; right: 14px;
    width: 36px; height: 36px;
  }
  .m-title { font-size: 24px; line-height: 1.2; }
  .m-title-sm { font-size: 20px; }
  .m-lede { font-size: 16px; }
  .m-prose-block { font-size: 14px; line-height: 1.7; }
  .m-pull-quote { font-size: 18px; padding: 28px 0; margin: 24px 0; }
  .m-section-label { margin: 40px 0 16px; }
  .m-row.cols-2,
  .m-row.cols-3 { grid-template-columns: 1fr; }
  .m-grid { grid-template-columns: 1fr 1fr; gap: 8px; }
  .m-credits { font-size: 12px; }

  .stack-card {
    grid-template-columns: 1fr;
    gap: 16px;
    padding: 22px 0;
  }
  .stack-card:hover { padding-left: 0; }
  .stack-card-title { font-size: 17px; }
  .stack-card-blurb { font-size: 13px; }

  .scratchpad {
    width: calc(100% - 32px);
    max-width: 380px;
  }
  body.modal-open {
    overflow: hidden;
    position: fixed;
    width: 100%;
  }
}

@media (max-width: 380px) {
  .canvas-inner { padding: 16px 12px 120px; }
  .card.image { height: 220px !important; }
  .card.image:nth-of-type(3n) { height: 280px !important; }
  .card.image:nth-of-type(4n) { height: 180px !important; }
  .modal-pad, .modal-pad-narrow { padding: 60px 16px 72px; }
  .m-title { font-size: 22px; }
  .about-doc { padding: 28px 16px 0; }
}
