feat(projects): capa nos cards de projeto (captura real ou painel gerado em codigo)

- ProjectCover usa as capturas ja publicadas (next/image, webp/avif) e, sem captura, um painel com monograma e stack
- .ds-cover mantem proporcao 16:10 e nao encolhe no card flex

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
Evandro Fonseca JuniorandClaude Sonnet 5.5 committed 2026-10-04 23:40:25 -03:00
1 parent e008a7c241
commit d5f04162a8
4 files changed
+103 -1

No files matched your search

+52
View File
@@ -0,0 +1,52 @@
import Image from "next/image";
import { SCREENSHOT_BY_PROJECT_ID } from "./story/notebookProjects";
type CoverProject = { id: string; title: string };
const SCREENSHOTS: Record<string, string | undefined> =
SCREENSHOT_BY_PROJECT_ID;
function initials(title: string) {
return title
.split(/\s+/)
.filter((word) => word.length > 2 || /^[A-Z]/.test(word))
.slice(0, 2)
.map((word) => word[0])
.join("")
.toUpperCase();
}
/**
* Capa do card de projeto: a captura de tela real quando existe; sem captura, um painel gerado em código
* (só o monograma, sem repetir a stack que já aparece nas tags do card) em vez de uma imagem inventada.
*/
export function ProjectCover({
project,
priority = false,
}: {
project: CoverProject;
priority?: boolean;
}) {
const src = SCREENSHOTS[project.id];
if (src) {
return (
<div className="ds-cover">
<Image
src={src}
alt={`Captura de tela de ${project.title}`}
width={1600}
height={1000}
sizes="(min-width: 1360px) 33vw, (min-width: 1025px) 50vw, 100vw"
priority={priority}
/>
</div>
);
}
return (
<div className="ds-cover ds-cover-fallback" aria-hidden="true">
<span className="ds-cover-mark">{initials(project.title)}</span>
</div>
);
}
+2
View File
@@ -6,6 +6,7 @@ import type { PortfolioProjectView } from "@/lib/portfolio-data";
import { caseStudies } from "@/lib/site-content";
import { DrawLine } from "./animations/DrawLine";
import MagneticButton from "./cinematic/MagneticButton";
import { ProjectCover } from "./ProjectCover";
import { Section } from "./Section";
export default function Projects({
@@ -59,6 +60,7 @@ export default function Projects({
whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }}
transition={{ type: "spring", stiffness: 170, damping: 18 }}
>
<ProjectCover project={project} />
<div className="flex flex-wrap items-start justify-between gap-sp-6">
<div className="max-w-[560px]">
<div className="ds-kicker">{project.category}</div>
+2 -1
View File
@@ -20,7 +20,8 @@ const NOTEBOOK_ORDER = [
"as-servicos",
] as const;
const SCREENSHOT_BY_PROJECT_ID: Record<
/** Capturas de tela publicadas; os cards de projeto reutilizam as mesmas imagens. */
export const SCREENSHOT_BY_PROJECT_ID: Record<
(typeof NOTEBOOK_ORDER)[number],
string
> = {