fix(home): oferta leva ao formulário, ofertas logo depois da vitrine

O botão das ofertas rola até o contato com o tipo marcado e não quebra mais as âncoras; ofertas com o que você recebe e primeiro passo, logo depois da vitrine; bloco de IA fora da home; cartão largo da SEV no hero do celular; separador da faixa de clientes só entre itens; favicon de 13 para 1,8 KB; pré-carregamento só no hover e nunca de /#... fora da home; X do menu e cabeçalho opaco; WhatsApp com "o case"; /ia com 3 passos e modelo de relatório ilustrativo; Sobre alinhado ao currículo; CSP Report-Only com os domínios do Ads.
This commit is contained in:
Evandro Fonseca Junior committed 2026-10-14 19:14:47 -03:00
1 parent fc44a69fb3
commit 9e2d65a926
51 files changed
+1730 -420

No files matched your search

+76 -13
View File
@@ -1,6 +1,24 @@
import { render, screen } from "@testing-library/react";
import { fireEvent, render, screen } from "@testing-library/react";
import Hero from "./Hero";
import { HERO_CAPTIONS } from "./hero-captions";
// o Link do Next, trocado por um <a> que mostra o `prefetch` recebido: HoverPrefetchLink começa em false
jest.mock("next/link", () => ({
__esModule: true,
default: ({
children,
prefetch,
...props
}: { children?: React.ReactNode; prefetch?: boolean | null } & Record<
string,
unknown
>) => (
<a data-prefetch={String(prefetch)} {...props}>
{children}
</a>
),
}));
const clients = [
{ id: "sev-modaintima", name: "SEV Moda Íntima" },
@@ -22,9 +40,9 @@ describe("Hero", () => {
render(<Hero />);
const contactLink = screen.getByRole("link", {
name: /conversar/i,
name: /falar sobre meu projeto/i,
});
expect(contactLink).toHaveAccessibleName("Conversar sobre meu projeto");
expect(contactLink).toHaveAccessibleName("Falar sobre meu projeto");
expect(contactLink.getAttribute("href")).toMatch(
/^https:\/\/wa\.me\/\d+\?/,
);
@@ -88,13 +106,12 @@ describe("Hero", () => {
expect(
screen.getByText(
"Do catálogo com pedido no WhatsApp ao painel da sua equipe. Você fala direto com quem desenvolve.",
"Do catálogo com pedido no WhatsApp ao painel da sua equipe. Você fala com quem desenvolve.",
),
).toBeInTheDocument();
expect(screen.getByRole("link", { name: /conversar/i })).toHaveAttribute(
"id",
"hero-contact-cta",
);
expect(
screen.getByRole("link", { name: /falar sobre meu projeto/i }),
).toHaveAttribute("id", "hero-contact-cta");
});
it("shows only the years of experience, with no repository numbers", () => {
@@ -142,18 +159,64 @@ describe("Hero", () => {
expect(original(images[1])).toBe("/videos/as-servicos/mobile.webp");
});
it("shows a small phone frame with the mobile poster of SEV below the buttons on small screens", () => {
it("shows a wide card to the SEV case below the buttons on small screens", () => {
render(<Hero clients={clients} />);
const small = screen.getByTestId("hero-work-mobile");
expect(small).toHaveClass("lg:hidden");
const image = small.querySelector("img") as HTMLImageElement;
expect(image).toHaveAccessibleName(
"Site da SEV Moda Íntima aberto no celular",
);
expect(small).toHaveAttribute("href", "/cases/sev-modaintima");
expect(small).toHaveAccessibleName("Site da SEV Moda Íntima Ver case");
const buttons = screen.getByRole("link", { name: /ver projetos/i });
expect(
buttons.compareDocumentPosition(small) & Node.DOCUMENT_POSITION_FOLLOWING,
).toBeTruthy();
});
it("keeps the label above the headline in the display font, so the mono font is not downloaded before the LCP", () => {
render(<Hero />);
const label = screen.getByText(
/Evandro Fonseca Junior · Engenharia de software/,
);
expect(label.className).toContain("font-display");
expect(label.className).not.toContain("font-mono");
});
it("uses no mono font anywhere in the first fold (label, years, scroll cue)", () => {
const { container } = render(
<Hero clients={clients} stats={{ years: 5 }} />,
);
expect(container.querySelector(".font-mono")).toBeNull();
});
it("pre-fetches 'Ver projetos entregues', the clients and the SEV card only when reached (hover, focus or touch), never by viewport", () => {
render(<Hero clients={clients} />);
const links = [
screen.getByRole("link", { name: /ver projetos/i }),
screen.getByRole("link", { name: "SEV Moda Íntima" }),
screen.getByTestId("hero-work-mobile"),
];
for (const link of links) {
expect(link).toHaveAttribute("data-prefetch", "false");
fireEvent.mouseEnter(link);
expect(link).toHaveAttribute("data-prefetch", "null");
}
});
it("writes the range of each dive caption (--from and --to, from hero-captions.ts) on its own item, in order", () => {
const { container } = render(<Hero clients={clients} />);
const items = Array.from(
container.querySelectorAll<HTMLElement>(".hero-captions li"),
);
expect(items).toHaveLength(HERO_CAPTIONS.length);
items.forEach((item, index) => {
const { title, text, from, to } = HERO_CAPTIONS[index];
expect(item).toHaveTextContent(`${title}${text}`);
expect(item.style.getPropertyValue("--from")).toBe(String(from));
expect(item.style.getPropertyValue("--to")).toBe(String(to));
});
});
});