feat(servicos): ofertas no topo de /servicos, capa por assunto e diagramas

/servicos abre com as 3 ofertas e depois outros serviços; H1 de criação de sites e de engenharia com o nome da oferta; capa de cada serviço pelo assunto (SEV fora do próprio case cai de ~11 para 2 imagens); diagrama de fluxo em SVG nos serviços sem case; cases com o que o cliente passou a poder fazer e contato logo depois de Como ficou; FAQ num componente só; lastmod por rota; ProfessionalService com areaServed.
This commit is contained in:
Evandro Fonseca Junior committed 2026-10-14 19:14:46 -03:00
1 parent e63f3c2ebd
commit fc44a69fb3
41 files changed
+2486 -550

No files matched your search

+35 -15
View File
@@ -11,6 +11,7 @@ import {
primaryButton,
secondaryButtonDark,
} from "@/app/components/button-styles";
import { Faq } from "@/app/components/Faq";
import HoverPrefetchLink from "@/app/components/HoverPrefetchLink";
import WhatsAppLink from "@/app/components/WhatsAppLink";
import { safeJsonLd } from "@/lib/jsonld";
@@ -20,12 +21,14 @@ import {
buildServiceJsonLd,
} from "@/lib/seo";
import {
buttonLabels,
caseStudies,
getRelatedCases,
getServicePage,
servicePages,
siteUrl,
} from "@/lib/site-content";
import { ServiceFaq } from "../_components/ServiceFaq";
import { ServiceDiagram } from "../_components/ServiceDiagram";
import { ServiceHeroCover } from "../_components/ServiceHeroCover";
import { ServiceOffer } from "../_components/ServiceOffer";
import { ServiceRelatedCases } from "../_components/ServiceRelatedCases";
@@ -75,9 +78,16 @@ export default async function ServicePage({ params }: ServicePageProps) {
}
const relatedCases = getRelatedCases(service.relatedCaseIds);
const heroCase = relatedCases.find((caseStudy) =>
getCaseMedia(caseStudy.id, "desktop"),
// capa do topo: o case escolhido para o serviço, se tiver captura gravada do aparelho pedido; senão, o esquema de fluxo
const heroDevice = service.heroCase?.device ?? "desktop";
const heroCase = caseStudies.find(
({ id }) =>
id === service.heroCase?.id && Boolean(getCaseMedia(id, heroDevice)),
);
const diagram = heroCase ? undefined : service.diagram;
const hasHeroSide = Boolean(heroCase || diagram);
// o case da capa é citado pela legenda dela, e não ganha cartão em "Onde já apliquei isso"
const hasCaseCards = relatedCases.some(({ id }) => id !== heroCase?.id);
const pageUrl = `${siteUrl}/servicos/${service.slug}`;
const jsonLd = {
@@ -98,11 +108,11 @@ export default async function ServicePage({ params }: ServicePageProps) {
<section
className={`ds-section ds-section-dark ${pageTop} ${
heroCase ? "" : compactHero
hasHeroSide ? "" : compactHero
}`}
>
<div
className={`ds-section-shell ${heroCase ? heroWithMediaGrid : ""}`}
className={`ds-section-shell ${hasHeroSide ? heroWithMediaGrid : ""}`}
>
<div className="max-w-[860px]">
<Breadcrumb
@@ -113,7 +123,7 @@ export default async function ServicePage({ params }: ServicePageProps) {
]}
/>
<p className="ds-eyebrow">{service.eyebrow}</p>
<h1 className="ds-section-title max-w-[14ch] text-balance text-white">
<h1 className="ds-section-title max-w-[22ch] text-balance text-white">
{service.headline}
</h1>
<p className="ds-copy max-w-[64ch] !text-[rgba(255,255,255,0.78)]">
@@ -125,21 +135,30 @@ export default async function ServicePage({ params }: ServicePageProps) {
placement="service_detail_hero"
className={primaryButton}
>
{service.ctaLabel} no WhatsApp
{buttonLabels.contact}
</WhatsAppLink>
{relatedCases.length > 0 ? (
{hasCaseCards ? (
<a href="#casos-relacionados" className={secondaryButtonDark}>
Ver projetos parecidos
</a>
) : null}
</div>
<p className="mt-sp-4 text-[15px] leading-6 text-white/70">
Fale diretamente com Evandro. Abre uma conversa, sem precisar
preencher um formulário. Garuva e Joinville (SC) · atendimento
remoto em todo o Brasil.
O botão abre uma conversa no WhatsApp, sem formulário. Garuva e
Joinville (SC) · atendimento remoto em todo o Brasil.
</p>
</div>
<ServiceHeroCover caseStudy={heroCase} />
{diagram ? (
<ServiceDiagram kind={diagram} />
) : (
<ServiceHeroCover
caseStudy={heroCase}
device={heroDevice}
relevance={
heroCase ? service.caseRelevance[heroCase.id] : undefined
}
/>
)}
</div>
</section>
@@ -147,6 +166,7 @@ export default async function ServicePage({ params }: ServicePageProps) {
cases={relatedCases}
relevance={service.caseRelevance}
experienceNote={service.experienceNote}
heroCaseId={heroCase?.id}
/>
<section className="ds-section">
@@ -232,7 +252,7 @@ export default async function ServicePage({ params }: ServicePageProps) {
<ServiceOffer service={service} />
<ServiceFaq faq={service.faq} />
<Faq items={service.faq} />
<section className="ds-section ds-section-dark">
<div className="ds-section-shell">
@@ -252,13 +272,13 @@ export default async function ServicePage({ params }: ServicePageProps) {
placement="service_detail_footer"
className={primaryButton}
>
{service.ctaLabel} no WhatsApp
{buttonLabels.contact}
</WhatsAppLink>
<HoverPrefetchLink
href="/servicos"
className={secondaryButtonDark}
>
Ver todos os serviços
{buttonLabels.allServices}
</HoverPrefetchLink>
</div>
</div>