Feature/image dialog #8

Merged
ilia merged 19 commits from feature/image-dialog into main 2026-05-23 10:16:55 +00:00
2 changed files with 18 additions and 4 deletions
Showing only changes of commit 9ebb515032 - Show all commits
@@ -26,6 +26,12 @@ type Props = {
* Optional preview image URL * Optional preview image URL
*/ */
imageUrl?: string; imageUrl?: string;
/**
* Skip lazy-loading the preview image. Set true for above-the-fold cards
* (typically the first card in the list) to improve LCP.
* @default false
*/
priority?: boolean;
}; };
/** /**
@@ -33,7 +39,7 @@ type Props = {
* Sidebar: year badge, stack tags, View Project button. * Sidebar: year badge, stack tags, View Project button.
* Main: title, optional image, description. * Main: title, optional image, description.
*/ */
export function ProjectCard({ title, year, description, tags, url, imageUrl }: Props) { export function ProjectCard({ title, year, description, tags, url, imageUrl, priority = false }: Props) {
return ( return (
<Card className={cn('group hover:shadow-brutal-xl transition-shadow duration-300')}> <Card className={cn('group hover:shadow-brutal-xl transition-shadow duration-300')}>
<CardSidebar <CardSidebar
@@ -49,7 +55,7 @@ export function ProjectCard({ title, year, description, tags, url, imageUrl }: P
))} ))}
</div> </div>
)} )}
<Button href={url} variant="primary" size="sm" className="w-full"> <Button href={url} variant="primary" size="sm" className="self-start lg:w-full lg:self-auto text-center">
View Project View Project
</Button> </Button>
</div> </div>
@@ -57,7 +63,9 @@ export function ProjectCard({ title, year, description, tags, url, imageUrl }: P
> >
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<CardTitle className="font-heading">{title}</CardTitle> <CardTitle className="font-heading">{title}</CardTitle>
{imageUrl && <ImageLightbox src={imageUrl} alt={title} className="brutal-border bg-blue" />} {imageUrl && (
<ImageLightbox src={imageUrl} alt={title} priority={priority} className="brutal-border bg-blue" />
)}
<RichText html={description} /> <RichText html={description} />
</div> </div>
</CardSidebar> </CardSidebar>
@@ -13,9 +13,14 @@ export default async function ProjectsSection() {
tags: ['projects'], tags: ['projects'],
}); });
/* Mark the first project that actually has an image as LCP-priority.
* Using `index === 0` alone misses the case where the first card has no
* image and the LCP candidate ends up being the next card's image. */
const lcpIndex = items.findIndex((project) => project.image);
return ( return (
<div className="space-y-6 max-w-section"> <div className="space-y-6 max-w-section">
{items.map((project) => ( {items.map((project, index) => (
<ProjectCard <ProjectCard
key={project.id} key={project.id}
title={project.title} title={project.title}
@@ -24,6 +29,7 @@ export default async function ProjectsSection() {
tags={project.stack} tags={project.stack}
url={project.url} url={project.url}
imageUrl={project.image ? buildFileUrl(project.collectionId, project.id, project.image) : undefined} imageUrl={project.image ? buildFileUrl(project.collectionId, project.id, project.image) : undefined}
priority={index === lcpIndex}
/> />
))} ))}
</div> </div>