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:
1 parent
de6af74a0c
commit
f8545cd442
44 files changed
+2286
-571
No files matched your search
+57
-5
@@ -10,17 +10,24 @@ it("uses a short search title: with the layout suffix it fits in 44 characters",
|
||||
description: expect.any(String),
|
||||
alternates: { canonical: "https://evandro.dev.br/ia" },
|
||||
});
|
||||
// as prévias de compartilhamento não passam pelo template: o título delas já leva o sufixo
|
||||
expect(metadata.openGraph?.title).toBe(
|
||||
`Revisão de app feito com IA${suffix}`,
|
||||
);
|
||||
// como nas outras páginas, o título da prévia de compartilhamento é o mesmo da busca, sem o sufixo
|
||||
expect(metadata.openGraph?.title).toBe("Revisão de app feito com IA");
|
||||
expect(metadata.twitter?.title).toBe("Revisão de app feito com IA");
|
||||
});
|
||||
|
||||
it("uses no lime outside the contact actions (the quote on the paper background is ink)", () => {
|
||||
const { container } = render(<AiPage />);
|
||||
|
||||
expect(
|
||||
container.querySelector('[class*="border-[var(--slash)]"]'),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps the full AI content, with the cited incidents summarized only here", () => {
|
||||
render(<AiPage />);
|
||||
|
||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent(
|
||||
"Revisão de app feito com IA (Lovable, Bolt, Replit)",
|
||||
"Seu app feito com IA está pronto para receber clientes? Eu testo antes deles.",
|
||||
);
|
||||
for (const heading of [
|
||||
"Se a IA cria um sistema, por que contratar um desenvolvedor?",
|
||||
@@ -91,6 +98,51 @@ it("presents the review as an offer: what the person receives, with no price or
|
||||
expect(container.textContent).not.toMatch(/R\$|\d+ dias|em até \d+/);
|
||||
});
|
||||
|
||||
it("shows the report list in a card on the right half of the hero, next to the headline", () => {
|
||||
render(<AiPage />);
|
||||
|
||||
const card = screen.getByTestId("ia-review-card");
|
||||
expect(within(card).getByText("O que você recebe")).toBeInTheDocument();
|
||||
expect(
|
||||
within(card)
|
||||
.getAllByRole("listitem")
|
||||
.map((item) => item.textContent?.replace(/^\d+/, "")),
|
||||
).toEqual([
|
||||
"As falhas de acesso a dados",
|
||||
"O que dá para aproveitar",
|
||||
"O que é melhor refazer",
|
||||
"Um orçamento para pôr em produção",
|
||||
]);
|
||||
// o cartão divide a grade do hero com o título (metade direita no desktop) e não cria uma região de página
|
||||
const hero = screen.getByRole("heading", { level: 1 }).closest("section");
|
||||
expect(hero).toContainElement(card);
|
||||
expect(card.parentElement).toHaveClass(
|
||||
"lg:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]",
|
||||
);
|
||||
expect(screen.queryByRole("complementary")).toBeNull();
|
||||
// o título do cartão não repete o h2 "O que você recebe na revisão" da seção seguinte
|
||||
expect(
|
||||
screen.getAllByRole("heading", { name: /O que você recebe/ }),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("keeps the secondary hero button inside the page: the portfolio cases are not reviews of AI-built apps", () => {
|
||||
render(<AiPage />);
|
||||
|
||||
const hero = screen.getByRole("heading", { level: 1 }).closest("section");
|
||||
const secondary = within(hero as HTMLElement).getByRole("link", {
|
||||
name: "Ver como a revisão funciona",
|
||||
});
|
||||
expect(secondary).toHaveAttribute("href", "#ia-receber");
|
||||
expect(document.querySelector(`section[id="ia-receber"]`)).not.toBeNull();
|
||||
for (const link of within(hero as HTMLElement).getAllByRole("link")) {
|
||||
expect(link.getAttribute("href")).not.toMatch(/^\/cases/);
|
||||
}
|
||||
expect(
|
||||
screen.queryByRole("link", { name: "Ver projetos entregues" }),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps the incident news collapsed in a <details>, closed by default", () => {
|
||||
const { container } = render(<AiPage />);
|
||||
|
||||
|
||||
+64
-27
@@ -1,5 +1,4 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { Breadcrumb } from "@/app/components/Breadcrumb";
|
||||
import {
|
||||
pageTop,
|
||||
@@ -25,8 +24,7 @@ export const metadata: Metadata = {
|
||||
description: pageDescription,
|
||||
...buildPageSocialMetadata({
|
||||
url: pageUrl,
|
||||
// as prévias de compartilhamento não passam pelo template do título
|
||||
title: `${pageTitle} | Evandro.dev.br`,
|
||||
title: pageTitle,
|
||||
description: pageDescription,
|
||||
type: "website",
|
||||
}),
|
||||
@@ -140,32 +138,71 @@ export default function AiPage() {
|
||||
{ label: "Revisão de app feito com IA", href: "/ia" },
|
||||
]}
|
||||
/>
|
||||
<p className="ds-eyebrow">IA no desenvolvimento</p>
|
||||
<h1 className="ds-section-title max-w-[22ch] text-balance">
|
||||
Revisão de app feito com IA (Lovable, Bolt, Replit)
|
||||
</h1>
|
||||
<p className="ds-copy max-w-[62ch]">
|
||||
A IA permite testar ideias e construir protótipos mesmo sem
|
||||
experiência em programação. Quando o sistema passa a cuidar de
|
||||
clientes, dinheiro ou dados pessoais, alguém precisa avaliar o que
|
||||
foi gerado e validar como ele se comporta fora da demonstração.
|
||||
</p>
|
||||
<div className="mt-sp-6 flex flex-wrap items-center gap-x-sp-6 gap-y-sp-3">
|
||||
<WhatsAppLink
|
||||
className={primaryButton}
|
||||
service={WHATSAPP_SERVICE}
|
||||
placement="ia_hero"
|
||||
<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.
|
||||
</h1>
|
||||
<p className="ds-copy max-w-[62ch]">
|
||||
A IA permite testar ideias e construir protótipos mesmo sem
|
||||
experiência em programação. Quando o sistema passa a cuidar de
|
||||
clientes, dinheiro ou dados pessoais, alguém precisa avaliar o
|
||||
que foi gerado e validar como ele se comporta fora da
|
||||
demonstração. Revisão para apps criados com Lovable, Bolt,
|
||||
Replit e ferramentas parecidas.
|
||||
</p>
|
||||
<div className="mt-sp-6 flex flex-wrap items-center gap-x-sp-6 gap-y-sp-3">
|
||||
<WhatsAppLink
|
||||
className={primaryButton}
|
||||
service={WHATSAPP_SERVICE}
|
||||
placement="ia_hero"
|
||||
>
|
||||
Pedir a revisão do meu app
|
||||
</WhatsAppLink>
|
||||
{/* Segue na própria página: os cases do portfólio não são revisões de app feito com IA. */}
|
||||
<a href="#ia-receber" className={textLinkDark}>
|
||||
Ver como a revisão funciona
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-testid="ia-review-card"
|
||||
className="min-w-0 border border-white/15 bg-white/[0.04] p-sp-6 sm:p-sp-8"
|
||||
>
|
||||
Pedir a revisão do meu app
|
||||
</WhatsAppLink>
|
||||
<Link href="/cases" className={textLinkDark}>
|
||||
Ver projetos entregues
|
||||
</Link>
|
||||
<p className="font-display text-xl font-semibold leading-7 text-white">
|
||||
O que você recebe
|
||||
</p>
|
||||
<ol className="mt-sp-5 flex flex-col">
|
||||
{DELIVERABLES.map((item, index) => (
|
||||
<li
|
||||
key={item.title}
|
||||
className="flex items-start gap-sp-4 border-t border-white/12 py-sp-4 first:border-t-0 first:pt-0 last:pb-0"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="w-7 shrink-0 font-mono text-[15px] leading-6 text-white/60"
|
||||
>
|
||||
{String(index + 1).padStart(2, "0")}
|
||||
</span>
|
||||
<span className="min-w-0 font-display text-base leading-6 text-white/90">
|
||||
{item.title}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="ds-section" aria-labelledby="ia-receber-title">
|
||||
<section
|
||||
id="ia-receber"
|
||||
className="ds-section"
|
||||
aria-labelledby="ia-receber-title"
|
||||
>
|
||||
<div className="ds-section-shell">
|
||||
<h2 id="ia-receber-title" className="ds-section-title max-w-[26ch]">
|
||||
O que você recebe na revisão
|
||||
@@ -198,7 +235,7 @@ export default function AiPage() {
|
||||
<h2 id="ia-contratar-title" className="ds-section-title max-w-[26ch]">
|
||||
Se a IA cria um sistema, por que contratar um desenvolvedor?
|
||||
</h2>
|
||||
<div className="mt-sp-6 border-l-2 border-[var(--slash)] pl-sp-5">
|
||||
<div className="mt-sp-6 border-l-2 border-[var(--ink)] pl-sp-5">
|
||||
<p className="max-w-[75ch] text-lg leading-8 text-[var(--ink)]">
|
||||
Você contrata engenharia para transformar as regras do seu negócio
|
||||
em um sistema que possa ser testado, operado e mantido. A IA pode
|
||||
@@ -259,7 +296,7 @@ export default function AiPage() {
|
||||
{incident.links.map((link) => (
|
||||
<li key={link.href}>
|
||||
<a
|
||||
className="inline-flex min-h-11 items-center text-[15px] md:text-sm underline underline-offset-4"
|
||||
className="inline-flex min-h-11 items-center text-[15px] underline underline-offset-4"
|
||||
href={link.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@@ -298,7 +335,7 @@ export default function AiPage() {
|
||||
adequação.
|
||||
</p>
|
||||
<a
|
||||
className="mt-sp-4 inline-flex min-h-11 items-center text-[15px] md:text-sm underline underline-offset-4"
|
||||
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"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
||||
Reference in new issue
Block a user