feat(cases): cases de clientes, serviços ligados à prova e SEO por página

Cases da SEV Moda Íntima e da Lavação 047; case no formato cliente, problema, entrega, mídia e contato, com detalhes técnicos recolhidos; sem GitHub, ressalvas e fontes de commit. /cases separa clientes e Laboratório. Serviços mostram o case de cliente certo e o bloco de investimento e prazo só aparece preenchido. Metadata social própria por página, not-found com noindex, sitemap com /ia.
This commit is contained in:
Evandro Fonseca Junior committed 2026-10-14 03:31:57 -03:00
1 parent 3756905a1b
commit 6e1022b58c
44 files changed
+2691 -1424

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
"use client";
import type { ReactNode } from "react";
import { trackCvDownload } from "@/lib/analytics";
import { CV_DOWNLOAD_NAME, CV_URL } from "@/lib/site-navigation";
/** Link do PDF do currículo; mede o clique (só com consentimento de analytics). */
export default function CvDownloadLink({
className,
children,
}: {
className?: string;
children: ReactNode;
}) {
return (
<a
className={className}
href={CV_URL}
download={CV_DOWNLOAD_NAME}
onClick={() => trackCvDownload({ placement: "curriculo" })}
>
{children}
</a>
);
}
+30 -1
View File
@@ -1,6 +1,9 @@
import { render, screen, within } from "@testing-library/react";
import { fireEvent, render, screen, within } from "@testing-library/react";
import { trackCvDownload } from "@/lib/analytics";
import CurriculoPage from "./page";
jest.mock("@/lib/analytics", () => ({ trackCvDownload: jest.fn() }));
it("shows a matching breadcrumb and keeps consulting secondary to hiring", () => {
render(CurriculoPage());
@@ -23,3 +26,29 @@ it("shows a matching breadcrumb and keeps consulting secondary to hiring", () =>
Node.DOCUMENT_POSITION_FOLLOWING,
).toBeTruthy();
});
it("links to no GitHub profile", () => {
render(CurriculoPage());
// "GitHub Actions" continua como competência técnica; o que não pode haver é endereço do GitHub
expect(document.body.textContent).not.toMatch(/github\.com/i);
for (const link of screen.getAllByRole("link")) {
expect(link.getAttribute("href")).not.toMatch(/github/i);
}
});
it("offers the résumé PDF as a secondary action and measures the download", () => {
render(CurriculoPage());
const pdf = screen.getByRole("link", { name: "Baixar currículo em PDF" });
expect(pdf).toHaveAttribute("href", "/Profile.pdf");
expect(pdf).toHaveAttribute("download", "Evandro_Fonseca_Junior_CV.pdf");
// limão só para o contato
expect(pdf).not.toHaveClass("ds-link-accent");
expect(screen.getByRole("link", { name: /oportunidade/i })).toHaveClass(
"ds-link-accent",
);
fireEvent.click(pdf);
expect(trackCvDownload).toHaveBeenCalledWith({ placement: "curriculo" });
});
+24 -33
View File
@@ -1,9 +1,16 @@
import type { Metadata } from "next";
import Link from "next/link";
import { Breadcrumb } from "@/app/components/Breadcrumb";
import { pageTop } from "@/app/components/button-styles";
import { safeJsonLd } from "@/lib/jsonld";
import { portfolio } from "@/lib/portfolio-data";
import { buildBreadcrumbList, buildProfilePageJsonLd } from "@/lib/seo";
import {
buildBreadcrumbList,
buildPageSocialMetadata,
buildProfilePageJsonLd,
} from "@/lib/seo";
import { siteName, siteUrl } from "@/lib/site-content";
import CvDownloadLink from "./CvDownloadLink";
const pageUrl = `${siteUrl}/curriculo`;
@@ -11,16 +18,13 @@ export const metadata: Metadata = {
title: { absolute: `Currículo de Evandro Fonseca Junior | ${siteName}` },
description:
"Currículo profissional de Evandro Fonseca Junior, engenheiro da computação e desenvolvedor de software, com experiência, formação e competências técnicas.",
alternates: { canonical: pageUrl },
openGraph: {
locale: "pt_BR",
...buildPageSocialMetadata({
url: pageUrl,
title: `Currículo de Evandro Fonseca Junior | ${siteName}`,
description:
"Experiência profissional, formação e competências técnicas de Evandro Fonseca Junior para avaliação de contratação.",
url: pageUrl,
siteName,
type: "profile",
},
}),
};
export default function CurriculoPage() {
@@ -38,27 +42,14 @@ export default function CurriculoPage() {
return (
<main>
<script type="application/ld+json">{safeJsonLd(jsonLd)}</script>
<section className="ds-section ds-section-dark">
<section className={`ds-section ds-section-dark ${pageTop}`}>
<div className="ds-section-shell max-w-[960px]">
<nav aria-label="Breadcrumb" className="mb-sp-6">
<ol className="flex flex-wrap items-center gap-sp-2 text-sm">
<li>
<Link className="ds-link ds-link-muted" href="/">
Início
</Link>
</li>
<li className="flex items-center gap-sp-2">
<span aria-hidden="true">/</span>
<Link
aria-current="page"
className="ds-link ds-link-muted"
href="/curriculo"
>
Currículo
</Link>
</li>
</ol>
</nav>
<Breadcrumb
items={[
{ label: "Início", href: "/" },
{ label: "Currículo", href: "/curriculo" },
]}
/>
<p className="ds-eyebrow">Currículo profissional</p>
<h1 className="ds-section-title text-white">
Engenheiro da Computação e Desenvolvedor de Software.
@@ -69,13 +60,13 @@ export default function CurriculoPage() {
formação e competências técnicas para avaliação de contratação.
</p>
<div className="mt-sp-6 flex flex-wrap gap-sp-3">
<a className="ds-link ds-link-accent" href="/Profile.pdf" download>
Baixar currículo em PDF
</a>
<Link className="ds-link ds-link-muted" href="/#contact">
<Link className="ds-link ds-link-accent" href="/#contact">
Falar sobre uma oportunidade
</Link>
<Link className="ds-link ds-link-muted" href="/servicos">
<CvDownloadLink className="ds-link ds-link-muted">
Baixar currículo em PDF
</CvDownloadLink>
<Link className="ds-link ds-link-text" href="/servicos">
Procurando consultoria? Ver serviços
</Link>
</div>
@@ -131,7 +122,7 @@ export default function CurriculoPage() {
))}
</div>
<div className="mt-sp-8">
<Link className="ds-link ds-link-accent" href="/cases">
<Link className="ds-link ds-link-muted" href="/cases">
Ver projetos e cases
</Link>
</div>