Files
portifolio-evandrodevbr/app/servicos/_components/ServiceRelatedCases.tsx
T
Evandro Fonseca Junior de6af74a0c feat(cases): prova certa por serviço, páginas internas com trabalho no topo
Diagnóstico sem case que não cabe; desktop com o GCC Installer como projeto próprio; IoT com o Twins Lab e a nota da Apolo. Mosaico de pôsteres em /servicos e /cases, faixa baixa em uma coluna no currículo, privacidade e laboratório; Laboratório como lista compacta; SEV começando por catálogo, pedido no WhatsApp e painel; Joinville e SC nos títulos; imagens do LCP com fetchPriority e sizes reais; pré-carregamento só no hover; 404 com Open Graph próprio.
2026-10-14 17:01:35 -03:00

115 lines
3.6 KiB
TypeScript

import { CaseCoverLink } from "@/app/cases/_components/CaseCoverLink";
import { secondaryButtonLight, textLink } from "@/app/components/button-styles";
import HoverPrefetchLink from "@/app/components/HoverPrefetchLink";
import { type CaseStudyContent, isClientCase } from "@/lib/site-content";
function ServiceCaseCard({
caseStudy,
relevance,
}: {
caseStudy: CaseStudyContent;
/** Por que este case interessa ao serviço; sem frase própria, vale o resumo do case. */
relevance?: string;
}) {
return (
<article className="ds-card">
<CaseCoverLink
id={caseStudy.id}
title={caseStudy.title}
demoUrl={caseStudy.demoUrl}
/>
<div className="ds-kicker">
{isClientCase(caseStudy)
? caseStudy.title
: `${caseStudy.title} · Projeto próprio`}
</div>
<h3 className="ds-card-title">{caseStudy.headline}</h3>
<p className="ds-kicker mt-sp-5">Relação com este serviço</p>
<p className="ds-copy !mt-sp-2">{relevance ?? caseStudy.summary}</p>
<div className="mt-sp-6 flex flex-wrap items-center gap-sp-3">
<HoverPrefetchLink
href={`/cases/${caseStudy.id}`}
className={secondaryButtonLight}
>
Ler case: {caseStudy.title}
</HoverPrefetchLink>
{caseStudy.demoUrl ? (
<a
href={caseStudy.demoUrl}
className={textLink}
target="_blank"
rel="noreferrer"
>
Abrir o site: {caseStudy.title}
</a>
) : null}
</div>
</article>
);
}
/**
* "Onde já apliquei isso": os cases ligados ao serviço, cada um com uma frase sobre o que ele tem a ver com ele,
* e a experiência profissional citada em texto (sem link). Sem case, fica só a nota de experiência, sem a frase
* que manda abrir o case; sem case nem experiência, não mostra nada.
*/
export function ServiceRelatedCases({
cases,
relevance,
experienceNote,
}: {
cases: readonly CaseStudyContent[];
/** Por que cada case interessa a este serviço (chave: id do case). */
relevance: Readonly<Record<string, string>>;
experienceNote?: string;
}) {
const hasCases = cases.length > 0;
if (!hasCases && !experienceNote) return null;
return (
<section
id="casos-relacionados"
className="ds-section"
aria-labelledby="casos-relacionados-titulo"
>
<div className="ds-section-shell">
<div className="ds-section-header">
<div className="ds-section-meta">
<p className="ds-eyebrow">Trabalhos parecidos</p>
<h2 id="casos-relacionados-titulo" className="ds-section-title">
Onde já apliquei isso
</h2>
{hasCases ? (
<p className="ds-section-description">
Cada projeto tem a ver com este serviço de um jeito. Abra o case
para ver o que foi feito e como ficou.
</p>
) : null}
</div>
</div>
{experienceNote ? (
<p className="ds-copy mb-sp-8 max-w-[72ch]">
<strong className="font-semibold text-[var(--ink)]">
Na prática:
</strong>{" "}
{experienceNote}
</p>
) : null}
{hasCases ? (
<div className="ds-grid-2">
{cases.map((caseStudy) => (
<ServiceCaseCard
key={caseStudy.id}
caseStudy={caseStudy}
relevance={relevance[caseStudy.id]}
/>
))}
</div>
) : null}
</div>
</section>
);
}