feat(home): ofertas que vendem o título, WhatsApp no hero e menu do celular acessível

Três ofertas (site que traz pedidos pelo WhatsApp, sistema para tirar a operação da planilha, revisão de sistema existente) com miniatura e formulário com o tipo escolhido; botão principal do hero abre o WhatsApp; moldura da SEV no hero do celular; menu fecha ao navegar, Esc, foco preso incluindo a barra de cookies e espaço para ela; CinematicHome como componente de servidor com ilha cliente; /ia com H1 de oferta e cartão do que você recebe; botão flutuante em aside com transform; AVIF antes de WebP; copy sem repetir contato direto.
This commit is contained in:
Evandro Fonseca Junior committed 2026-10-14 17:01:36 -03:00
1 parent de6af74a0c
commit f8545cd442
44 files changed
+2286 -571

No files matched your search

+49 -6
View File
@@ -1,7 +1,13 @@
import {
AI_REVIEW_SUBJECT,
buildWhatsAppUrl,
CASES_SUBJECT,
HOME_SUBJECT,
headerOpensWhatsApp,
isServiceOrCasePath,
RESUME_SUBJECT,
SERVICES_SUBJECT,
similarProjectSubject,
type WhatsAppContexts,
whatsAppSubjectForPath,
} from "./whatsapp";
@@ -18,10 +24,27 @@ describe("whatsAppSubjectForPath", () => {
);
});
it("cites the case on /cases/<slug>", () => {
it("cites the case on /cases/<slug>, with the phrase every page uses for a similar project", () => {
expect(whatsAppSubjectForPath("/cases/sev-modaintima", contexts)).toBe(
"um projeto parecido com o da SEV Moda Íntima",
"um projeto parecido com o SEV Moda Íntima",
);
expect(similarProjectSubject("Lavação 047")).toBe(
"um projeto parecido com o Lavação 047",
);
});
it("speaks of the page itself on the home and the index pages, the same as the body buttons of /cases and /servicos", () => {
expect(whatsAppSubjectForPath("/", contexts)).toBe(HOME_SUBJECT);
expect(whatsAppSubjectForPath("/cases", contexts)).toBe(CASES_SUBJECT);
expect(CASES_SUBJECT).toBe("um projeto parecido com os cases do portfólio");
expect(whatsAppSubjectForPath("/servicos", contexts)).toBe(
SERVICES_SUBJECT,
);
expect(SERVICES_SUBJECT).toBe("Sites, sistemas e aplicativos");
expect(whatsAppSubjectForPath("/curriculo", contexts)).toBe(RESUME_SUBJECT);
// sem contextos, a página-índice continua falando de si
expect(whatsAppSubjectForPath("/cases/")).toBe(CASES_SUBJECT);
expect(whatsAppSubjectForPath("/curriculo/")).toBe(RESUME_SUBJECT);
});
it("cites the AI review on /ia", () => {
@@ -36,11 +59,8 @@ describe("whatsAppSubjectForPath", () => {
});
it.each([
"/",
"/servicos",
"/cases",
"/curriculo",
"/privacidade",
"/notebook-smoke",
"/servicos/inexistente",
"/cases/inexistente",
"/servicos/criacao-de-sites/extra",
@@ -93,6 +113,29 @@ describe("isServiceOrCasePath", () => {
});
});
describe("headerOpensWhatsApp", () => {
it.each([
["/servicos/criacao-de-sites", true],
["/servicos/inexistente", true],
["/cases/sev-modaintima", true],
["/ia", true],
["/ia/", true],
["/cases", true],
["/servicos", true],
["/curriculo", true],
["/curriculo/", true],
// a home já tem o formulário na própria página; as demais rotas não têm assunto
["/", false],
["/privacidade", false],
["/cases/a/b", false],
["/ia/extra", false],
[null, false],
[undefined, false],
])("%s -> %s", (pathname, expected) => {
expect(headerOpensWhatsApp(pathname)).toBe(expected);
});
});
describe("buildWhatsAppUrl", () => {
it("encodes the message in the text parameter of a wa.me link", () => {
const url = new URL(buildWhatsAppUrl("Olá & tudo? #1"));