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:
1 parent
e2b20bc699
commit
8b12e51bc5
35 files changed
+2359
-1299
No files matched your search
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user