feat(projects): prioritize LCP image, fix View Project button on mobile
- 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.
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user