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

+22
View File
@@ -44,6 +44,28 @@ describe("next.config headers()", () => {
);
});
it("path: any environment -> the policy (still Report-Only) allows the domains the Google Ads tag uses after consent, in connect-src and img-src", async () => {
const headers = await siteHeaders();
const policy = header(headers, "Content-Security-Policy-Report-Only") ?? "";
const directive = (name: string) =>
policy.split("; ").find((part) => part.startsWith(`${name} `)) ?? "";
for (const name of ["connect-src", "img-src"]) {
for (const host of [
"https://pagead2.googlesyndication.com",
"https://googleads.g.doubleclick.net",
"https://www.googleadservices.com",
]) {
expect(directive(name)).toContain(host);
}
// o que já estava liberado segue liberado
expect(directive(name)).toContain("https://*.google-analytics.com");
}
// nada de curinga aberto e nada enforçado: a política continua só em Report-Only
expect(policy).not.toMatch(/\* |https:(?!\/)/);
expect(header(headers, "Content-Security-Policy")).toBeUndefined();
});
it("path: any environment -> the video rule keeps the long cache", async () => {
const rules = (await nextConfig.headers?.()) ?? [];
const videos = rules.find(({ source }) => source === "/videos/:path*");
+1 -1
View File
@@ -13,7 +13,7 @@ describe("next.config", () => {
expect(nextConfig.images).not.toHaveProperty("minimumCacheTTL");
});
it("keeps the CSP in Report-Only until Google Ads is allowed in it, never enforcing it by accident", async () => {
it("keeps the CSP in Report-Only (Google Ads is allowed in it now, but the owner activates it later), never enforcing it by accident", async () => {
const rules = (await nextConfig.headers?.()) ?? [];
const site = rules.find(({ source }) => source === "/(.*)");
const names = site?.headers.map(({ key }) => key) ?? [];
+1 -3
View File
@@ -1,8 +1,5 @@
import { portfolio } from "./portfolio-data";
/** Projetos feitos para clientes aparecem como prova; os pessoais e protótipos ficam no "Laboratório". */
export type ProjectKind = "cliente" | "laboratorio";
export interface Testimonial {
quote: string;
name: string;
@@ -20,6 +17,7 @@ export interface Testimonial {
*/
export const testimonials: Testimonial[] = [];
/** Projetos feitos para clientes (`kind: "cliente"`) aparecem como prova; os pessoais e protótipos ficam no "Laboratório". */
export function isClientProject(project: { kind?: string }) {
return project.kind === "cliente";
}
+2 -2
View File
@@ -26,10 +26,10 @@ describe("whatsAppSubjectForPath", () => {
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 SEV Moda Íntima",
"um projeto parecido com o case SEV Moda Íntima",
);
expect(similarProjectSubject("Lavação 047")).toBe(
"um projeto parecido com o Lavação 047",
"um projeto parecido com o case Lavação 047",
);
});
+2 -2
View File
@@ -39,9 +39,9 @@ export const CASES_SUBJECT = "um projeto parecido com os cases do portfólio";
export const SERVICES_SUBJECT = "Sites, sistemas e aplicativos";
export const RESUME_SUBJECT = "um projeto, depois de ver o seu currículo";
/** Frase padrão para quem quer algo como um projeto publicado (case de cliente ou de laboratório). */
/** Frase padrão para quem quer algo como um projeto publicado (case de cliente ou de laboratório). "O case" concorda com qualquer título: sem ele, saía "o Lavação 047" e "o SEV Moda Íntima". */
export function similarProjectSubject(title: string) {
return `um projeto parecido com o ${title}`;
return `um projeto parecido com o case ${title}`;
}
/** Assunto de cada página sem parâmetro, pelo caminho sem a barra final (só a home mantém a barra). */