Feature/image dialog #8
@@ -26,6 +26,12 @@ type Props = {
|
||||
* Optional preview image URL
|
||||
*/
|
||||
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.
|
||||
* 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 (
|
||||
<Card className={cn('group hover:shadow-brutal-xl transition-shadow duration-300')}>
|
||||
<CardSidebar
|
||||
@@ -49,7 +55,7 @@ export function ProjectCard({ title, year, description, tags, url, imageUrl }: P
|
||||
))}
|
||||
</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
|
||||
</Button>
|
||||
</div>
|
||||
@@ -57,7 +63,9 @@ export function ProjectCard({ title, year, description, tags, url, imageUrl }: P
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<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} />
|
||||
</div>
|
||||
</CardSidebar>
|
||||
|
||||
@@ -13,9 +13,14 @@ export default async function ProjectsSection() {
|
||||
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 (
|
||||
<div className="space-y-6 max-w-section">
|
||||
{items.map((project) => (
|
||||
{items.map((project, index) => (
|
||||
<ProjectCard
|
||||
key={project.id}
|
||||
title={project.title}
|
||||
@@ -24,6 +29,7 @@ export default async function ProjectsSection() {
|
||||
tags={project.stack}
|
||||
url={project.url}
|
||||
imageUrl={project.image ? buildFileUrl(project.collectionId, project.id, project.image) : undefined}
|
||||
priority={index === lcpIndex}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user