Passagens com fade e 32 px, cena anterior termina em opacidade 0, sem visibility nem clip-path. 3D do hero como fundo ambiente, sob demanda e desligado em toque, aparelho fraco, saveData e movimento reduzido. Lenis fora do toque e salto direto em âncoras distantes. Vitrine só de clientes, até 3 capítulos, fixa só no desktop e carrossel no celular; vídeo só do projeto em cena com preload metadata. Rótulos mono a partir de 12 px e limão só no contato.
66 lines
1.7 KiB
TypeScript
66 lines
1.7 KiB
TypeScript
import Image from "next/image";
|
|
import { getProjectVideo } from "@/lib/project-showcase";
|
|
import { ProjectMedia } from "./showcase/ProjectMedia";
|
|
|
|
type CoverProject = {
|
|
id: string;
|
|
title: string;
|
|
demoUrl?: string | null;
|
|
};
|
|
|
|
/** Capturas de tela publicadas dos projetos que têm interface pública. */
|
|
const SCREENSHOTS: Record<string, string | undefined> = {
|
|
nutriscan: "/assets/notebook/nutriscan.webp",
|
|
"sc-plus": "/assets/notebook/sc-plus.webp",
|
|
"sev-modaintima": "/assets/notebook/sev-modaintima.webp",
|
|
"as-servicos": "/assets/notebook/as-servicos.webp",
|
|
};
|
|
|
|
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 video = getProjectVideo(project.id);
|
|
const src = SCREENSHOTS[project.id];
|
|
|
|
if (video) return <ProjectMedia project={project} />;
|
|
|
|
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>
|
|
);
|
|
}
|