- ProjectCard accepts a `priority` prop forwarded to the thumbnail Image so
above-the-fold cards skip lazy-loading and get a preload hint.
- ProjectsSection marks the first card *with an image* as priority; handles
the case where the first project has no image and the LCP candidate ends
up being a later card.
- View Project button drops `w-full` on mobile (collapsed sidebar above the
card body), using `self-start` + `text-center` instead so it sizes to its
content. Restores column-filling on lg+ where the sidebar is its own
narrow column.
- Use the shared Modal for dialog mechanics; ImageLightbox keeps only the
view-transition coordination (just-in-time view-transition-name handoff
between thumb and dialog frame, ESC routed through onCancel).
- Switch to <Image> for both thumb and dialog image; thumb is priority/sizes-
driven for LCP, dialog image is lazy-loaded (deferred until open).
- New brutal-outline utility for the dialog frame: outline paints after
children so subpixel image bleed can't cover it; dialog gets overflow-
visible so the outline isn't clipped.
- lightbox-image utility caps image dimensions to viewport minus close-button
and footer headroom, with box-sizing: content-box.
- Lightbox dialog gets view-transition-name lightbox-dialog (z=20) and the
frame gets lightbox-frame (z=30) so both stack cleanly above the page
during the open/close transition. Footer drops its named VT group since
section-body no longer slides over it.
- Cream-tinted backdrop replaces the blur (Firefox-friendly), color-mix
with var(--cream) for the token reference.
- scrollbar-gutter: stable on html so locking body scroll doesn't shift
the layout.
The explicit fade-and-slide-left OLD animation left a visible ghost of the
previous section's content behind the incoming slide. Replacing it with an
instant opacity:0 keeps the transition clean while preserving the NEW
slide-in delay so the snap-out has a beat to register. Drops the now-dead
keyframes and --slide-section-body-out token.
Native <dialog>+showModal() wrapper with imperative open()/close() via ref,
body scroll lock, and backdrop-click/keyboard-close behaviors. Exposes
onCancel and onBackdropClose escape hatches for consumers that need to
wrap the close path (e.g. in a view transition).
Adds data-scroll-behavior="smooth" to <html> so Next disables the global
scroll-behavior during programmatic route-change scrolls while keeping
smooth behavior for user-driven anchor jumps. Silences the Next 15 warning.
ghost now means transparent bg (no fill); outline keeps cream bg with subtle border.
Remove magnify icon overlay from ImageLightbox thumbnail — hover cursor-zoom-in is sufficient.
Close button updated to variant="outline" for cream-on-blue contrast in the dialog.
Main: px-4 py-6 on mobile (was px-8 py-12). Section accordion:
mb/py on inactive links tightened to 1/1 on mobile, space-y-0
between sections. Active title text-xl on mobile to prevent
wrapping at ~400px, matches inactive title size.