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:
1 parent
fc44a69fb3
commit
9e2d65a926
51 files changed
+1730
-420
No files matched your search
@@ -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*");
|
||||
|
||||
@@ -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
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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
@@ -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). */
|
||||
|
||||
Reference in new issue
Block a user