feat: refine portfolio showcase, services and quality lab
CI / verify (push) Failing after 5m9s

Align the notebook browser with the physical LCD, refresh real project captures and navigation, simplify service content, and stabilize hero hydration.
This commit is contained in:
Evandro Fonseca Junior committed 2026-09-05 14:20:54 -03:00
1 parent e2b20bc699
commit 8b12e51bc5
35 files changed
+2359 -1299

No files matched your search

+2 -2
View File
@@ -40,8 +40,8 @@ export default async function Image({
return new ImageResponse(
<OgCard
eyebrow={service.eyebrow}
title={service.headline}
description={service.answer}
title={service.navLabel}
description={service.intro}
/>,
OG_SIZE,
);
+14 -9
View File
@@ -91,10 +91,18 @@ it("renders answer-first consulting, reciprocal case links and minimal schema",
});
const { container } = render(ui);
const heading = screen.getByRole("heading", { level: 1 });
const answer = screen.getByText(service.answer);
expect(answer).toBeVisible();
expect(heading.nextElementSibling).toBe(answer);
for (const item of [
...service.outcomes,
...service.examples,
...service.process,
]) {
expect(screen.getByText(item)).toBeVisible();
}
for (const detail of service.technicalDetails) {
expect(screen.getByText(detail)).not.toBeVisible();
}
const primaryCtas = screen.getAllByRole("link", {
name: service.ctaLabel,
@@ -201,11 +209,6 @@ it("renders answer-first consulting, reciprocal case links and minimal schema",
it("keeps hub cards and structured items aligned", () => {
const { container } = render(<ServicesIndexPage />);
const heading = screen.getByRole("heading", { level: 1 });
expect(heading.nextElementSibling).toHaveTextContent(
/desenvolvimento.+consultoria.+engenharia de software/i,
);
const visibleBreadcrumb = screen.getByRole("navigation", {
name: "Breadcrumb",
});
@@ -225,7 +228,7 @@ it("keeps hub cards and structured items aligned", () => {
"BreadcrumbList",
]);
const itemList = graph[1] as {
itemListElement: Array<{ position: number; url: string }>;
itemListElement: Array<{ position: number; url: string; name: string }>;
};
expect(itemList.itemListElement.map(({ position }) => position)).toEqual([
1, 2, 3, 4, 5, 6,
@@ -236,7 +239,9 @@ it("keeps hub cards and structured items aligned", () => {
expect([...structuredUrls].sort()).toEqual(
publicServiceHrefs.map((href) => `${siteUrl}${href}`).sort(),
);
const visibleCards = screen.getAllByRole("link", { name: "Abrir página" });
const visibleCards = itemList.itemListElement.map(({ name }) =>
screen.getByRole("link", { name }),
);
const visibleHrefs = visibleCards.map((link) => link.getAttribute("href"));
expect(visibleHrefs).toHaveLength(6);
expect(new Set(visibleHrefs).size).toBe(6);
+56 -49
View File
@@ -147,11 +147,8 @@ export default async function ServicePage({ params }: ServicePageProps) {
<div className="ds-section-shell">
<div className="ds-grid-2">
<article className="ds-card">
<div className="ds-kicker">Para quem faz sentido</div>
<h2 className="ds-card-title">
Contextos em que esse serviço encaixa melhor
</h2>
<ul className="ds-list mt-sp-5 pl-[1.15rem]">
<h2 className="ds-card-title !mt-0">Para quem faz sentido</h2>
<ul className="ds-list">
{service.audience.map((item) => (
<li key={item}>{item}</li>
))}
@@ -159,11 +156,8 @@ export default async function ServicePage({ params }: ServicePageProps) {
</article>
<article className="ds-card">
<div className="ds-kicker">O que muda</div>
<h2 className="ds-card-title">
Resultados que eu busco destravar
</h2>
<ul className="ds-list mt-sp-5 pl-[1.15rem]">
<h2 className="ds-card-title !mt-0">O que podemos entregar</h2>
<ul className="ds-list">
{service.outcomes.map((item) => (
<li key={item}>{item}</li>
))}
@@ -176,48 +170,59 @@ export default async function ServicePage({ params }: ServicePageProps) {
<section className="ds-section">
<div className="ds-section-shell">
<div className="ds-grid-2">
<article className="ds-card">
<div className="ds-kicker">Como eu estruturo</div>
<h2 className="ds-card-title">Processo de trabalho</h2>
<ol className="list-none mt-sp-5 space-y-sp-4 text-[14px] leading-8 text-[var(--ink-80)]">
{service.process.map((step, index) => (
<li
key={step}
className="border-t border-[var(--border)] pt-sp-4 first:border-t-0 first:pt-0"
>
<span className="mr-sp-3 inline-flex h-7 min-w-7 items-center justify-center rounded-full border border-[var(--border)] text-[11px] font-semibold text-[var(--meta)]">
{index + 1}
</span>
<article className="min-w-0">
<div className="ds-kicker">Da conversa à entrega</div>
<h2 className="ds-card-title">Como trabalhamos</h2>
<ol className="mt-sp-5 list-decimal space-y-sp-4 pl-sp-5 text-sm leading-8 text-[var(--ink-80)]">
{service.process.map((step) => (
<li key={step} className="pl-sp-2">
{step}
</li>
))}
</ol>
</article>
<article className="ds-card">
<div className="ds-kicker">Stack e repertório</div>
<h2 className="ds-card-title">
Tecnologias e capacidades envolvidas
</h2>
<article className="min-w-0">
<div className="ds-kicker">Possibilidades de uso</div>
<h2 className="ds-card-title">Imagine na sua rotina</h2>
<p className="ds-copy">
A stack entra como meio para resolver o problema certo, com
critério de manutenção, observabilidade e evolução do produto.
Exemplos ilustrativos para ajudar a explicar o serviço, não
projetos já entregues. O seu escopo será definido na conversa.
</p>
<ul className="ds-tags">
{service.stack.map((item) => (
<MagneticButton
key={item}
as="li"
className="ds-tag"
strength={0.15}
>
{item}
</MagneticButton>
<ul className="ds-list">
{service.examples.map((example) => (
<li key={example}>{example}</li>
))}
</ul>
</article>
</div>
</div>
<div className="ds-section-shell mt-sp-8">
<details className="border border-[var(--border)] bg-white/70 p-sp-5 md:p-sp-6">
<summary className="cursor-pointer py-sp-2 text-base font-semibold leading-relaxed focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[var(--ink)]">
Detalhes técnicos e tecnologias
</summary>
<div className="max-w-[80ch]">
<ul className="ds-list">
{service.technicalDetails.map((detail) => (
<li key={detail}>{detail}</li>
))}
</ul>
<p className="ds-copy">
Tecnologias consideradas neste serviço. A seleção depende do
problema e do ambiente; não é uma lista obrigatória para todo
projeto.
</p>
<ul className="ds-tags" aria-label="Tecnologias do serviço">
{service.stack.map((item) => (
<li key={item} className="ds-tag !whitespace-normal">
{item}
</li>
))}
</ul>
</div>
</details>
</div>
</section>
<section className="ds-section">
@@ -226,11 +231,12 @@ export default async function ServicePage({ params }: ServicePageProps) {
<div className="ds-section-meta">
<p className="ds-eyebrow">Casos relacionados</p>
<h2 className="ds-section-title">
Projetos que reforçam esse tipo de entrega
Projetos para conhecer meu trabalho
</h2>
<p className="ds-section-description">
Alguns exemplos de contexto técnico em que esse serviço se
conecta a entregas reais.
Referências de problemas e soluções relacionados. Cada projeto
usa as tecnologias indicadas no próprio case; não representa
todas as plataformas ou tecnologias oferecidas neste serviço.
</p>
</div>
</div>
@@ -246,7 +252,7 @@ export default async function ServicePage({ params }: ServicePageProps) {
</p>
<ul className="ds-tags mt-sp-6">
{caseStudy.stack.map((item) => (
<li key={item} className="ds-tag">
<li key={item} className="ds-tag !whitespace-normal">
{item}
</li>
))}
@@ -265,7 +271,7 @@ export default async function ServicePage({ params }: ServicePageProps) {
target="_blank"
rel="noreferrer"
>
Abrir projeto
Abrir projeto: {caseStudy.title}
</a>
) : null}
{caseStudy.githubUrl ? (
@@ -275,7 +281,7 @@ export default async function ServicePage({ params }: ServicePageProps) {
target="_blank"
rel="noreferrer"
>
Ver código
Ver código: {caseStudy.title}
</a>
) : null}
</div>
@@ -290,12 +296,13 @@ export default async function ServicePage({ params }: ServicePageProps) {
<div className="ds-card border-white/10 bg-[rgba(255,255,255,0.03)]">
<div className="ds-kicker">Próximo passo</div>
<h2 className="ds-card-title text-white">
Se esse serviço parece aderente ao seu contexto, a conversa
inicial já pode ser objetiva.
Conte o que você precisa resolver.
</h2>
<p className="ds-copy !text-[rgba(255,255,255,0.76)]">
Traga o sistema, o gargalo e o objetivo de negócio. Eu estruturo a
leitura técnica e indico o caminho mais seguro para avançar.
Pode ser uma ideia, uma tarefa que toma tempo ou um sistema que
precisa melhorar. Não é preciso chegar com uma solução técnica.
Atendo a partir de Garuva, no Norte de Santa Catarina, e
remotamente.
</p>
<div className="mt-sp-6 flex flex-wrap gap-sp-3">
<MagneticButton