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

+138 -94
View File
@@ -1,32 +1,90 @@
import type { Metadata } from "next";
import Link from "next/link";
import HeroBackground from "@/app/components/cinematic/HeroBackground";
import MagneticButton from "@/app/components/cinematic/MagneticButton";
import { Breadcrumb } from "@/app/components/Breadcrumb";
import {
pageTop,
primaryButton,
secondaryButtonDark,
secondaryButtonLight,
textLink,
} from "@/app/components/button-styles";
import WhatsAppLink from "@/app/components/WhatsAppLink";
import { safeJsonLd } from "@/lib/jsonld";
import { buildBreadcrumbList } from "@/lib/seo";
import { caseStudies, intentCtas, siteName, siteUrl } from "@/lib/site-content";
import { buildBreadcrumbList, buildPageSocialMetadata } from "@/lib/seo";
import {
type CaseStudyContent,
clientCases,
labCases,
siteUrl,
} from "@/lib/site-content";
import { CaseCover } from "./_components/CaseCover";
import { getCaseMedia } from "./_components/case-media";
const pageUrl = `${siteUrl}/cases`;
const pageTitle = "Cases de Engenharia de Software, Next.js e IoT";
const pageTitle = "Cases: Sites e Sistemas Feitos para Empresas";
const pageDescription =
"Seis projetos mostram aplicações de engenharia de software em web, desktop, IoT, integrações e experiências orientadas a dados.";
"Veja o que já foi feito para empresas: loja virtual com pedido pelo WhatsApp, site com painel para o dono, páginas institucionais e mais. Confira cada projeto e converse sobre o seu.";
const allCases = [...clientCases, ...labCases];
export const metadata: Metadata = {
title: pageTitle,
description: pageDescription,
alternates: {
canonical: pageUrl,
},
openGraph: {
locale: "pt_BR",
...buildPageSocialMetadata({
url: pageUrl,
title: pageTitle,
description: pageDescription,
url: pageUrl,
siteName,
type: "website",
},
}),
};
function CaseCard({
caseStudy,
withCover,
ctaLabel,
}: {
caseStudy: CaseStudyContent;
withCover: boolean;
ctaLabel: string;
}) {
return (
<article className="ds-card !h-auto">
{withCover && getCaseMedia(caseStudy.id, "desktop") ? (
<Link
href={`/cases/${caseStudy.id}`}
aria-hidden="true"
tabIndex={-1}
className="mb-sp-5 block"
>
<CaseCover
id={caseStudy.id}
title={caseStudy.title}
demoUrl={caseStudy.demoUrl}
sizes="(min-width: 1024px) 560px, 100vw"
/>
</Link>
) : null}
<div className="ds-kicker">{caseStudy.title}</div>
<h3 className="ds-card-title">{caseStudy.headline}</h3>
<p className="ds-copy">{caseStudy.summary}</p>
<div className="mt-sp-6 flex flex-wrap items-center gap-sp-3">
<Link href={`/cases/${caseStudy.id}`} className={secondaryButtonLight}>
{ctaLabel}
</Link>
{caseStudy.demoUrl ? (
<a
href={caseStudy.demoUrl}
className={textLink}
target="_blank"
rel="noreferrer"
>
Abrir o site
</a>
) : null}
</div>
</article>
);
}
export default function CasesPage() {
const jsonLd = {
"@context": "https://schema.org",
@@ -40,7 +98,7 @@ export default function CasesPage() {
},
{
"@type": "ItemList",
itemListElement: caseStudies.map((caseStudy, index) => ({
itemListElement: allCases.map((caseStudy, index) => ({
"@type": "ListItem",
position: index + 1,
name: caseStudy.title,
@@ -58,32 +116,18 @@ export default function CasesPage() {
<main>
<script type="application/ld+json">{safeJsonLd(jsonLd)}</script>
<section className="ds-section ds-section-dark relative min-h-[340px] overflow-hidden">
<HeroBackground />
<div className="ds-section-shell relative z-10">
<section className={`ds-section ds-section-dark ${pageTop}`}>
<div className="ds-section-shell">
<div className="max-w-[860px]">
<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="/cases"
>
Cases
</Link>
</li>
</ol>
</nav>
<p className="ds-eyebrow">Selected work</p>
<h1 className="ds-section-title max-w-[14ch] text-balance text-white">
Cases em software, Next.js, integrações e produtos conectados.
<Breadcrumb
items={[
{ label: "Início", href: "/" },
{ label: "Cases", href: "/cases" },
]}
/>
<p className="ds-eyebrow">Trabalhos entregues</p>
<h1 className="ds-section-title max-w-[18ch] text-balance text-white">
Veja o que já fiz para empresas.
</h1>
<p className="ds-copy max-w-[64ch] !text-[rgba(255,255,255,0.78)]">
{pageDescription}
@@ -92,65 +136,65 @@ export default function CasesPage() {
</div>
</section>
<section className="ds-section">
<section className="ds-section" aria-labelledby="cases-clientes">
<div className="ds-section-shell">
<div className="ds-grid-2 items-start">
{caseStudies.map((caseStudy) => (
<article key={caseStudy.id} className="ds-card !h-auto">
<div className="ds-kicker">{caseStudy.title}</div>
<h2 className="ds-card-title">{caseStudy.subtitle}</h2>
<p className="ds-copy">{caseStudy.summary}</p>
<p className="mt-sp-4 text-[13px] leading-7 text-[var(--meta)]">
{caseStudy.impact}
</p>
<ul className="ds-tags mt-sp-6">
{caseStudy.stack.map((item) => (
<li key={item} className="ds-tag">
{item}
</li>
))}
</ul>
<div className="mt-sp-6 flex flex-wrap gap-sp-3">
<Link
href={`/cases/${caseStudy.id}`}
className="ds-link ds-link-accent"
>
Ler case
</Link>
{caseStudy.demoUrl ? (
<MagneticButton
as="a"
href={caseStudy.demoUrl}
className="ds-link ds-link-muted"
target="_blank"
rel="noreferrer"
strength={0.25}
>
Abrir projeto
</MagneticButton>
) : null}
</div>
</article>
<h2 id="cases-clientes" className="ds-card-title !mt-0">
Projetos de clientes
</h2>
<div className="ds-grid-2 mt-sp-8 items-start">
{clientCases.map((caseStudy) => (
<CaseCard
key={caseStudy.id}
caseStudy={caseStudy}
withCover
ctaLabel="Ver o case"
/>
))}
</div>
</div>
</section>
<div className="mt-sp-10 flex flex-wrap gap-sp-3">
<MagneticButton
as="a"
href={intentCtas.project.href}
className="ds-link ds-link-accent"
strength={0.25}
<section className="ds-section" aria-labelledby="cases-laboratorio">
<div className="ds-section-shell">
<h2 id="cases-laboratorio" className="ds-card-title !mt-0">
Laboratório
</h2>
<p className="ds-copy max-w-[64ch]">
Projetos pessoais e de estudo, feitos para praticar e testar ideias.
</p>
<div className="ds-grid-2 mt-sp-8 items-start">
{labCases.map((caseStudy) => (
<CaseCard
key={caseStudy.id}
caseStudy={caseStudy}
withCover={false}
ctaLabel="Ver o projeto"
/>
))}
</div>
</div>
</section>
<section className="ds-section ds-section-dark">
<div className="ds-section-shell">
<h2 className="ds-card-title !mt-0 text-white">
Quer um projeto parecido com algum destes?
</h2>
<p className="ds-copy max-w-[60ch]">
Conte o que você precisa. A conversa começa direto no WhatsApp, sem
formulário.
</p>
<div className="mt-sp-6 flex flex-wrap items-center gap-sp-3">
<WhatsAppLink
service="um projeto parecido com os cases do portfólio"
placement="cases_hub_footer"
className={primaryButton}
>
{intentCtas.project.label}
</MagneticButton>
<MagneticButton
as="a"
href={intentCtas.consulting.href}
className="ds-link ds-link-muted"
strength={0.2}
>
{intentCtas.consulting.label}
</MagneticButton>
Conversar sobre meu projeto
</WhatsAppLink>
<Link href="/servicos" className={secondaryButtonDark}>
Ver os serviços
</Link>
</div>
</div>
</section>