feat(site): blocos 3D da marca, novas ofertas e hero mais abrangente
Hero e movimento - cena 3D em tempo real com os blocos da marca (kit feito no Blender, 3d/blocos.py): marca, tela, integrações e dados; segue a rolagem, tem vida em repouso, reage ao cursor e, parada no topo, passa sozinha pelos estados com legenda - três níveis de aparelho (full, lite, static); sem WebGL fica o pôster - título novo: "Sites e sistemas sob medida para o jeito que a sua empresa trabalha." - faixa de clientes com o ramo, linha do primeiro passo, hero alinhado às seções - rótulos digitados, entrada dos blocos, cortina de blocos entre páginas e capa compartilhada entre a lista de cases e o case Ofertas - atendimento no WhatsApp com IA e nota fiscal para a reforma tributária (páginas de serviço, cartões da home, formulário e aviso com data de validade) - página /ia atualizada, com fontes - peças 3D em loop nos cartões e topos de página (3d/pecas.py) Design - hover por papel (bloco nos botões, barra nos links), rótulos sem caixa alta - um destino por rótulo: "Falar sobre meu projeto" leva ao formulário, "Chamar no WhatsApp" abre o WhatsApp - logo montado em blocos, 404 redesenhado, grades sem célula vazia - ícones do PWA e manifesto novos Specs em specs/, contexto de design em DESIGN.md e PRODUCT.md.
This commit is contained in:
1 parent
4671837c44
commit
c7d09d63d4
165 files changed
+7940
-3790
No files matched your search
+95
-8
@@ -1,5 +1,7 @@
|
||||
import { render, screen, within } from "@testing-library/react";
|
||||
import { offerLabels } from "@/lib/offer-labels";
|
||||
import { buttonLabels } from "@/lib/site-content";
|
||||
import { IA_PAGE_LABEL } from "@/lib/site-navigation";
|
||||
import AiPage, { metadata } from "./page";
|
||||
|
||||
it("uses a short search title: up to 44 characters, and 60 with the layout suffix", () => {
|
||||
@@ -50,15 +52,15 @@ it("keeps the full AI content, with the cited incidents summarized only here", (
|
||||
).toHaveAttribute("rel", "noopener noreferrer");
|
||||
});
|
||||
|
||||
it("offers WhatsApp conversations about reviewing an AI-built system", () => {
|
||||
it("offers WhatsApp conversations about reviewing an AI-built system, each one named 'Chamar no WhatsApp'", () => {
|
||||
render(<AiPage />);
|
||||
|
||||
const links = screen
|
||||
.getAllByRole("link")
|
||||
.filter((link) => link.getAttribute("href")?.startsWith("https://wa.me/"));
|
||||
expect(links.map((link) => link.textContent)).toEqual([
|
||||
"Pedir a revisão do meu app",
|
||||
"Avaliar meu projeto",
|
||||
buttonLabels.whatsapp,
|
||||
buttonLabels.whatsapp,
|
||||
]);
|
||||
for (const link of links) {
|
||||
const text = new URL(link.getAttribute("href") ?? "").searchParams.get(
|
||||
@@ -80,9 +82,10 @@ it("has breadcrumb navigation back to the home", () => {
|
||||
expect(
|
||||
within(breadcrumb).getByRole("link", { name: "Início" }),
|
||||
).toHaveAttribute("href", "/");
|
||||
expect(
|
||||
within(breadcrumb).getByText(offerLabels.revisao.name),
|
||||
).toHaveAttribute("aria-current", "page");
|
||||
expect(within(breadcrumb).getByText(IA_PAGE_LABEL)).toHaveAttribute(
|
||||
"aria-current",
|
||||
"page",
|
||||
);
|
||||
});
|
||||
|
||||
it("presents the review as an offer: what the person receives, with no price or deadline", () => {
|
||||
@@ -239,7 +242,86 @@ it("keeps the incident news collapsed in a <details>, closed by default", () =>
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("names the review like the offer on the home: the trail and the structured data use the offer name, not the search title", () => {
|
||||
it("lists the five incidents from 2025 and 2026, with the new sources opening in a new tab", () => {
|
||||
render(<AiPage />);
|
||||
|
||||
for (const title of [
|
||||
"Lovable: dados expostos em aplicações criadas na plataforma",
|
||||
"Replit: um agente apagou dados de uma aplicação",
|
||||
"Base44: aplicações privadas com falha de acesso",
|
||||
"Lovable, 2026: conversas e código de projetos públicos ficaram abertos",
|
||||
"RedAccess, 2026: aplicações internas abertas na internet",
|
||||
]) {
|
||||
expect(screen.getByRole("heading", { name: title })).toBeInTheDocument();
|
||||
}
|
||||
expect(
|
||||
screen.getByText(/São episódios de 2025 e 2026, não avaliações/),
|
||||
).toBeInTheDocument();
|
||||
for (const [name, href] of [
|
||||
[
|
||||
"Relato da Lovable (22/04/2026, em inglês)",
|
||||
"https://lovable.dev/blog/our-response-to-the-april-2026-incident",
|
||||
],
|
||||
[
|
||||
"Reportagem do Security Boulevard (07/05/2026, em inglês)",
|
||||
"https://securityboulevard.com/2026/05/thousands-of-vibe-coded-apps-exposing-corporate-personal-data-redaccess/",
|
||||
],
|
||||
[
|
||||
"Reportagem do Axios (em inglês)",
|
||||
"https://www.axios.com/2026/05/07/loveable-replit-vibe-coding-privacy",
|
||||
],
|
||||
]) {
|
||||
const link = screen.getByRole("link", { name });
|
||||
expect(link).toHaveAttribute("href", href);
|
||||
expect(link).toHaveAttribute("target", "_blank");
|
||||
expect(link.getAttribute("rel")).toContain("noopener");
|
||||
}
|
||||
});
|
||||
|
||||
it("adds the incident deadline to the LGPD card and a third card on who answers for what the AI writes", () => {
|
||||
render(<AiPage />);
|
||||
|
||||
const lgpd = screen
|
||||
.getByRole("heading", { name: "E a LGPD?" })
|
||||
.closest("article") as HTMLElement;
|
||||
expect(lgpd).toHaveTextContent(
|
||||
"a empresa precisa comunicar a ANPD e as pessoas afetadas em três dias úteis depois de saber do fato; agentes de pequeno porte têm o prazo em dobro.",
|
||||
);
|
||||
|
||||
const card = screen
|
||||
.getByRole("heading", { name: "Quem responde pelo que a IA escreve?" })
|
||||
.closest("article") as HTMLElement;
|
||||
expect(card).toHaveClass("ds-card");
|
||||
expect(card).toHaveTextContent("quem assina responde pelo que entrega.");
|
||||
// os três cartões dividem a mesma grade
|
||||
expect(card.parentElement).toBe(lgpd.parentElement);
|
||||
expect(card.parentElement?.querySelectorAll("article.ds-card")).toHaveLength(
|
||||
3,
|
||||
);
|
||||
expect(card.parentElement).toHaveClass("lg:grid-cols-3");
|
||||
// alinhados pelo topo: o .ds-copy (flex-grow: 1) não estica o cartão mais curto com um buraco no meio
|
||||
expect(card.parentElement).toHaveClass("items-start");
|
||||
|
||||
for (const [scope, name, href] of [
|
||||
[
|
||||
lgpd,
|
||||
"ANPD: Resolução nº 15/2024, arts. 6º e 9º",
|
||||
"https://www.in.gov.br/en/web/dou/-/resolucao-cd/anpd-n-15-de-24-de-abril-de-2024-556243024",
|
||||
],
|
||||
[
|
||||
card,
|
||||
"TST: notícia de 26/03/2026",
|
||||
"https://www.tst.jus.br/-/empresa-e-advogado-sao-multados-por-uso-de-ia-com-decisoes-falsas",
|
||||
],
|
||||
] as const) {
|
||||
const link = within(scope).getByRole("link", { name });
|
||||
expect(link).toHaveAttribute("href", href);
|
||||
expect(link).toHaveAttribute("target", "_blank");
|
||||
expect(link.getAttribute("rel")).toContain("noopener");
|
||||
}
|
||||
});
|
||||
|
||||
it("names the page with its own label in the trail and the structured data (the one in the menu and the footer), and keeps the offer name for the offer", () => {
|
||||
const { container } = render(<AiPage />);
|
||||
|
||||
const script = container.querySelector('script[type="application/ld+json"]');
|
||||
@@ -249,6 +331,11 @@ it("names the review like the offer on the home: the trail and the structured da
|
||||
);
|
||||
expect(
|
||||
breadcrumb.itemListElement.map(({ name }: { name: string }) => name),
|
||||
).toEqual(["Início", offerLabels.revisao.name]);
|
||||
).toEqual(["Início", "Revisão de app com IA"]);
|
||||
expect(IA_PAGE_LABEL).toBe("Revisão de app com IA");
|
||||
expect(offerLabels.revisao.name).toBe("Revisão de sistema que já existe");
|
||||
// sem rótulo sobre o título: a trilha logo acima já nomeia a página
|
||||
expect(
|
||||
container.querySelector("section")?.querySelector(".ds-eyebrow"),
|
||||
).toBeNull();
|
||||
});
|
||||
+82
-14
@@ -8,15 +8,16 @@ import {
|
||||
import HoverPrefetchLink from "@/app/components/HoverPrefetchLink";
|
||||
import WhatsAppLink from "@/app/components/WhatsAppLink";
|
||||
import { safeJsonLd } from "@/lib/jsonld";
|
||||
import { offerLabels } from "@/lib/offer-labels";
|
||||
import { buildBreadcrumbList, buildPageSocialMetadata } from "@/lib/seo";
|
||||
import { siteUrl } from "@/lib/site-content";
|
||||
import { buttonLabels, siteUrl } from "@/lib/site-content";
|
||||
import { IA_PAGE_LABEL } from "@/lib/site-navigation";
|
||||
import { AI_REVIEW_SUBJECT } from "@/lib/whatsapp";
|
||||
|
||||
const pageUrl = `${siteUrl}/ia`;
|
||||
/**
|
||||
* Título para busca: até 44 caracteres, e 60 com o " | Evandro.dev.br" que o template de app/layout.tsx acrescenta. Cobre a oferta
|
||||
* (revisão de sistema) e a busca por app feito com IA. O nome da oferta, o mesmo da home, é o da trilha e dos dados estruturados.
|
||||
* (revisão de sistema) e a busca por app feito com IA. A trilha e os dados estruturados usam o rótulo da página no menu e no
|
||||
* rodapé (`IA_PAGE_LABEL`); a oferta, nos cartões e no formulário, continua com o nome dela.
|
||||
*/
|
||||
const pageTitle = "Revisão de sistema e de app feito com IA";
|
||||
const pageDescription =
|
||||
@@ -121,7 +122,7 @@ const RESPONSIBILITIES = [
|
||||
},
|
||||
];
|
||||
|
||||
/** Três episódios de 2025, resumidos. Cada um traz o que foi relatado, o que não foi provado e o cuidado técnico. */
|
||||
/** Episódios de 2025 e 2026, resumidos. Cada um traz o que foi relatado, o que não foi provado e o cuidado técnico. */
|
||||
const INCIDENTS = [
|
||||
{
|
||||
title: "Lovable: dados expostos em aplicações criadas na plataforma",
|
||||
@@ -163,6 +164,35 @@ const INCIDENTS = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title:
|
||||
"Lovable, 2026: conversas e código de projetos públicos ficaram abertos",
|
||||
summary:
|
||||
"Em abril de 2026, a Lovable informou que, de 3 de fevereiro a 20 de abril, o histórico de conversa e o código-fonte de projetos públicos podiam ser acessados por qualquer usuário da plataforma que tivesse o link do projeto. A causa foi uma alteração interna que desfez uma proteção anterior. Pesquisadores avisaram a partir de 22 de fevereiro e os avisos foram encerrados sem chegar à equipe de segurança. A empresa diz que projetos privados não foram afetados e não informou quantos projetos foram de fato vistos por terceiros.",
|
||||
care: "não escrever senhas, chaves nem dados de clientes na conversa com a ferramenta e conferir quem pode ver o projeto, que é diferente de quem pode ver o site publicado.",
|
||||
links: [
|
||||
{
|
||||
href: "https://lovable.dev/blog/our-response-to-the-april-2026-incident",
|
||||
label: "Relato da Lovable (22/04/2026, em inglês)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "RedAccess, 2026: aplicações internas abertas na internet",
|
||||
summary:
|
||||
"Em maio de 2026, a empresa de segurança RedAccess disse ter encontrado cerca de 380 mil aplicações e outros arquivos públicos criados com Lovable, Base44, Netlify e Replit, dos quais cerca de 5 mil continham informação sensível de empresas. O site Axios conferiu alguns exemplos, entre eles informações financeiras internas de um banco no Brasil e conversas de atendimento de uma loja. As plataformas contestam: dizem que aplicação pública acessível é o comportamento esperado e não prova invasão nem falha de segurança.",
|
||||
care: "decidir antes de publicar quem precisa entrar em cada ferramenta interna e exigir login, em vez de confiar que ninguém vai achar o endereço.",
|
||||
links: [
|
||||
{
|
||||
href: "https://securityboulevard.com/2026/05/thousands-of-vibe-coded-apps-exposing-corporate-personal-data-redaccess/",
|
||||
label: "Reportagem do Security Boulevard (07/05/2026, em inglês)",
|
||||
},
|
||||
{
|
||||
href: "https://www.axios.com/2026/05/07/loveable-replit-vibe-coding-privacy",
|
||||
label: "Reportagem do Axios (em inglês)",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function AiPage() {
|
||||
@@ -171,7 +201,7 @@ export default function AiPage() {
|
||||
"@graph": [
|
||||
buildBreadcrumbList(pageUrl, [
|
||||
["Início", siteUrl],
|
||||
[offerLabels.revisao.name, pageUrl],
|
||||
[IA_PAGE_LABEL, pageUrl],
|
||||
] as const),
|
||||
],
|
||||
};
|
||||
@@ -185,12 +215,11 @@ export default function AiPage() {
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{ label: "Início", href: "/" },
|
||||
{ label: offerLabels.revisao.name, href: "/ia" },
|
||||
{ label: IA_PAGE_LABEL, href: "/ia" },
|
||||
]}
|
||||
/>
|
||||
<div className="grid items-center gap-sp-8 lg:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)] lg:gap-sp-12">
|
||||
<div className="min-w-0">
|
||||
<p className="ds-eyebrow">IA no desenvolvimento</p>
|
||||
<h1 className="ds-section-title max-w-[24ch] text-balance">
|
||||
Seu app feito com IA está pronto para receber clientes? Eu testo
|
||||
antes deles.
|
||||
@@ -209,7 +238,7 @@ export default function AiPage() {
|
||||
service={WHATSAPP_SERVICE}
|
||||
placement="ia_hero"
|
||||
>
|
||||
Pedir a revisão do meu app
|
||||
{buttonLabels.whatsapp}
|
||||
</WhatsAppLink>
|
||||
<a href="#ia-receber" className={textLinkDark}>
|
||||
Ver como a revisão funciona
|
||||
@@ -390,10 +419,10 @@ export default function AiPage() {
|
||||
</summary>
|
||||
<div className="pb-sp-8">
|
||||
<p className="ds-copy max-w-[75ch]">
|
||||
São episódios de 2025, não avaliações atuais das plataformas.
|
||||
Eles mostram riscos de configuração, permissões e operação; não
|
||||
provam que todo sistema feito com IA seja inseguro ou que um
|
||||
profissional elimine qualquer risco.
|
||||
São episódios de 2025 e 2026, não avaliações atuais das
|
||||
plataformas. Eles mostram riscos de configuração, permissões e
|
||||
operação; não provam que todo sistema feito com IA seja inseguro
|
||||
ou que um profissional elimine qualquer risco.
|
||||
</p>
|
||||
<div className="mt-sp-6 grid gap-sp-6 lg:grid-cols-3">
|
||||
{INCIDENTS.map((incident) => (
|
||||
@@ -426,7 +455,7 @@ export default function AiPage() {
|
||||
</section>
|
||||
|
||||
<section className="ds-section" aria-labelledby="ia-lgpd-title">
|
||||
<div className="ds-section-shell grid gap-sp-6 lg:grid-cols-2">
|
||||
<div className="ds-section-shell grid items-start gap-sp-6 lg:grid-cols-3">
|
||||
<article className="ds-card">
|
||||
<h2 id="ia-lgpd-title" className="ds-card-title !mt-0">
|
||||
E a LGPD?
|
||||
@@ -445,6 +474,14 @@ export default function AiPage() {
|
||||
contratar desenvolvimento não equivale a uma certificação de
|
||||
adequação.
|
||||
</p>
|
||||
<p className="ds-copy">
|
||||
Se houver um incidente com dados pessoais que possa trazer risco
|
||||
ou dano relevante, a empresa precisa comunicar a ANPD e as pessoas
|
||||
afetadas em três dias úteis depois de saber do fato; agentes de
|
||||
pequeno porte têm o prazo em dobro. Cumprir esse prazo depende de
|
||||
ter cópia de segurança, registro de acessos e alguém que saiba
|
||||
onde olhar.
|
||||
</p>
|
||||
<a
|
||||
className="mt-sp-4 inline-flex min-h-11 items-center text-[15px] underline underline-offset-4"
|
||||
href="https://www.gov.br/anpd/pt-br/acesso-a-informacao/institucional/atos-normativos/regulamentacoes_anpd/resolucao-cd-anpd-no-2-de-27-de-janeiro-de-2022"
|
||||
@@ -453,6 +490,37 @@ export default function AiPage() {
|
||||
>
|
||||
ANPD: Resolução nº 2/2022, arts. 6º, 12 e 13
|
||||
</a>
|
||||
<a
|
||||
className="mt-sp-4 inline-flex min-h-11 items-center text-[15px] underline underline-offset-4"
|
||||
href="https://www.in.gov.br/en/web/dou/-/resolucao-cd/anpd-n-15-de-24-de-abril-de-2024-556243024"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
ANPD: Resolução nº 15/2024, arts. 6º e 9º
|
||||
</a>
|
||||
</article>
|
||||
<article className="ds-card">
|
||||
<h2 className="ds-card-title !mt-0">
|
||||
Quem responde pelo que a IA escreve?
|
||||
</h2>
|
||||
<p className="ds-copy">
|
||||
Em março de 2026, a Sexta Turma do Tribunal Superior do Trabalho
|
||||
multou uma empresa de telecomunicações e o advogado dela por
|
||||
citarem, em um recurso, decisões que não existiam, atribuídas pelo
|
||||
tribunal ao uso de inteligência artificial.
|
||||
</p>
|
||||
<p className="ds-copy">
|
||||
O caso é de um processo, não de um sistema. A regra é a mesma:
|
||||
quem assina responde pelo que entrega.
|
||||
</p>
|
||||
<a
|
||||
className="mt-sp-4 inline-flex min-h-11 items-center text-[15px] underline underline-offset-4"
|
||||
href="https://www.tst.jus.br/-/empresa-e-advogado-sao-multados-por-uso-de-ia-com-decisoes-falsas"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
TST: notícia de 26/03/2026
|
||||
</a>
|
||||
</article>
|
||||
<article className="ds-card">
|
||||
<h2 className="ds-card-title !mt-0">
|
||||
@@ -475,7 +543,7 @@ export default function AiPage() {
|
||||
service={WHATSAPP_SERVICE}
|
||||
placement="ia_closing"
|
||||
>
|
||||
Avaliar meu projeto
|
||||
{buttonLabels.whatsapp}
|
||||
</WhatsAppLink>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
Reference in new issue
Block a user