From 9ebb515032905a61a52944633be71bc29119d217 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sat, 23 May 2026 09:54:43 +0300 Subject: [PATCH] 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. --- .../project/ui/ProjectCard/ProjectCard.tsx | 14 +++++++++++--- .../ui/ProjectsSection/ProjectsSection.tsx | 8 +++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/entities/project/ui/ProjectCard/ProjectCard.tsx b/src/entities/project/ui/ProjectCard/ProjectCard.tsx index 2af0474..f498486 100644 --- a/src/entities/project/ui/ProjectCard/ProjectCard.tsx +++ b/src/entities/project/ui/ProjectCard/ProjectCard.tsx @@ -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 ( )} - @@ -57,7 +63,9 @@ export function ProjectCard({ title, year, description, tags, url, imageUrl }: P >
{title} - {imageUrl && } + {imageUrl && ( + + )}
diff --git a/src/widgets/ProjectsSection/ui/ProjectsSection/ProjectsSection.tsx b/src/widgets/ProjectsSection/ui/ProjectsSection/ProjectsSection.tsx index f71d3e0..97da991 100644 --- a/src/widgets/ProjectsSection/ui/ProjectsSection/ProjectsSection.tsx +++ b/src/widgets/ProjectsSection/ui/ProjectsSection/ProjectsSection.tsx @@ -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 (
- {items.map((project) => ( + {items.map((project, index) => ( ))}