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

+147 -188
View File
@@ -1,83 +1,73 @@
import Link from "next/link";
import type { ServiceLink } from "@/lib/site-navigation";
import { DrawLine } from "./animations/DrawLine";
import { primaryButton, secondaryButtonLight } from "./button-styles";
import Image from "next/image";
import { getShowcaseAssets } from "@/lib/project-showcase";
import { primaryButton, textLink } from "./button-styles";
import ContactOfferLink from "./ContactOfferLink";
import type { ContactKind } from "./contact-subject";
import "./conversion.css";
import HoverPrefetchLink from "./HoverPrefetchLink";
import { Reveal } from "./Reveal";
import { Section } from "./Section";
interface Tier {
interface Offer {
id: string;
badge: string;
title: string;
tagline: string;
description: string;
deliverables: string[];
/** Primeiro passo concreto, sem prazo nem preço. */
firstStep: string;
featured?: boolean;
/** Tipo de projeto que o botão deixa marcado no formulário de contato. */
kind: ContactKind;
ctaLabel: string;
ctaHref: string;
/** Case cujo pôster ilustra a oferta; sem ele, a miniatura é o cartão de checklist. */
project?: { id: string; title: string };
moreLabel: string;
moreHref: string;
}
const TIERS: Tier[] = [
/**
* As três ofertas da home, na ordem do H1: site que traz pedidos, sistema que tira a operação da planilha e revisão
* de sistema que já existe. Cada texto só diz o que o case ou a página de /ia citados já sustentam; sem prazo nem preço.
*/
const OFFERS: Offer[] = [
{
id: "build",
badge: "Do zero à produção",
title: "Um sistema para a sua operação",
tagline: "Quando as ferramentas prontas já não atendem.",
id: "site",
title: "Site que traz pedidos pelo WhatsApp",
description:
"Transforme regras de negócio, planilhas e processos dispersos em um sistema próprio, com os fluxos e as integrações que a empresa precisa.",
deliverables: [
"Definição de escopo e arquitetura",
"Interface, backend e banco de dados",
"Integrações e permissões de acesso",
"Validação, publicação e documentação",
],
firstStep:
"Primeira conversa para entender o problema. Depois, escopo e proposta por escrito.",
featured: true,
ctaLabel: "Conversar sobre meu sistema",
ctaHref: "/#contact",
"Catálogo ou tabela de preços, mensagem pronta no WhatsApp e um painel para você editar preços, textos e fotos sozinho, como na Lavação 047.",
kind: "site",
ctaLabel: "Começar pelo site",
project: { id: "lavacao-047", title: "Lavação 047" },
moreLabel: "Ver o case Lavação 047",
moreHref: "/cases/lavacao-047",
},
{
id: "discovery",
badge: "Antes de investir",
title: "Diagnóstico de um sistema existente",
tagline: "Entenda o que aproveitar e o que precisa mudar.",
id: "sistema",
title: "Sistema para tirar a operação da planilha",
description:
"Seu sistema ficou lento, frágil ou difícil de manter? Avalio o código e a operação para orientar a próxima decisão, inclusive em projetos iniciados com IA.",
deliverables: [
"Levantamento de riscos e dependências",
"Revisão de acesso aos dados",
"Análise dos gargalos da aplicação",
"Plano de correção por prioridade",
],
firstStep:
"Você me mostra o que já existe. Depois, recebe o levantamento de riscos e o plano de correção por prioridade.",
ctaLabel: "Avaliar meu projeto",
ctaHref: "/#contact",
"Pedidos, estoque e rotinas que hoje vivem em planilhas e mensagens passam para um painel próprio, feito para a sua equipe. Um exemplo é a SEV Moda Íntima, com cadastro de peças, pedidos e estoque por tamanho.",
kind: "sistema",
ctaLabel: "Começar pelo sistema",
project: { id: "sev-modaintima", title: "SEV Moda Íntima" },
moreLabel: "Ver o case SEV Moda Íntima",
moreHref: "/cases/sev-modaintima",
},
{
id: "evolution",
badge: "Continuidade",
title: "Integração e evolução",
tagline: "Faça o que já existe trabalhar melhor.",
id: "revisao",
title: "Revisão de sistema que já existe, inclusive feito com IA",
description:
"Conecte ferramentas, reduza tarefas manuais e acrescente funcionalidades ao sistema atual, com atenção às dependências e ao impacto de cada mudança.",
deliverables: [
"Integração entre sistemas e equipamentos",
"Novos fluxos e automações",
"Testes das partes afetadas",
"Planejamento de manutenção e operação",
],
firstStep:
"Primeira conversa sobre o sistema e as ferramentas que você já usa. Depois, escopo e proposta por escrito.",
ctaLabel: "Planejar a próxima etapa",
ctaHref: "/#contact",
"Avalio o que já existe, testo as falhas de acesso a dados e separo o que dá para aproveitar do que é melhor refazer, antes de o sistema receber mais clientes.",
kind: "revisao",
ctaLabel: "Começar pela revisão",
moreLabel: "Ver como funciona a revisão",
moreHref: "/ia",
},
];
/** O que a revisão entrega: os mesmos itens da página /ia, em versão curta para a miniatura da oferta. */
const REVIEW_CHECKLIST = [
"As falhas de acesso a dados",
"O que dá para aproveitar",
"O que é melhor refazer",
"Um orçamento para pôr em produção",
];
const PROCESS = [
{
title: "Entender a operação",
@@ -101,167 +91,136 @@ const PROCESS = [
},
];
function TierCard({ tier }: Readonly<{ tier: Tier }>) {
/** Miniatura da revisão, sem case para mostrar: o que o relatório traz, em um cartão escuro do mesmo tamanho dos pôsteres. */
function ReviewChecklist() {
return (
<article
className={`ds-card cv-press cv-lift relative flex h-full min-w-0 flex-col overflow-hidden max-md:w-full max-md:max-w-none max-md:px-sp-5 max-md:py-sp-5 ${
tier.featured ? "border-[var(--slash)] shadow-sm" : ""
}`}
style={{
borderWidth: tier.featured ? "2px" : "1px",
}}
>
<div className="flex min-w-0 items-start justify-between gap-sp-4">
<span className="ds-kicker">{tier.badge}</span>
</div>
<DrawLine delay={0.04} />
<div className="mt-sp-4 min-w-0">
<h3 className="ds-card-title !mt-0 text-balance !text-[clamp(19px,5vw,28px)] !leading-[1.02] !tracking-[-0.035em] [overflow-wrap:break-word] [word-break:normal]">
{tier.title}
</h3>
<p className="mt-sp-2 text-[15px] md:text-[13px] leading-[1.7] text-[var(--meta)]">
{tier.tagline}
</p>
</div>
<p
className="ds-copy !mt-sp-4 !text-[15px] !leading-[1.75] [overflow-wrap:break-word] [word-break:normal] md:!text-[13px]"
data-reel="right"
>
{tier.description}
<div className="flex h-full flex-col justify-center gap-sp-3 px-sp-5 py-sp-4 text-white">
<p className="font-display text-[15px] font-semibold leading-6 text-white">
O que você recebe
</p>
<ul
className="ds-list !mt-sp-5 !pl-[1.15rem] !text-[15px] !leading-[1.75] [overflow-wrap:break-word] [word-break:normal] md:!text-[12px]"
data-reel-stagger="fade"
>
{tier.deliverables.map((deliverable) => (
<li key={deliverable} className="pl-sp-1">
{deliverable}
<ul className="flex flex-col gap-sp-2">
{REVIEW_CHECKLIST.map((item) => (
<li
key={item}
className="flex items-start gap-sp-3 font-display text-[15px] leading-6 text-white/85"
>
<svg
aria-hidden="true"
viewBox="0 0 16 16"
className="mt-1 h-4 w-4 shrink-0 text-white/70"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<path d="m3 8.5 3.2 3L13 4.5" />
</svg>
{item}
</li>
))}
</ul>
</div>
);
}
<div className="mt-sp-6 space-y-sp-2 border-t border-[var(--border)] pt-sp-5">
<div className="font-display text-[clamp(17px,4.6vw,18px)] font-extrabold leading-none tracking-tight">
Primeiro passo
</div>
<div className="font-display text-[15px] md:text-[13px] leading-[1.7] text-[var(--ink-80)]">
{tier.firstStep}
</div>
/** Um único estilo de cartão para as três ofertas: miniatura, título com altura mínima, texto, botão e link do case. */
function OfferCard({ offer }: Readonly<{ offer: Offer }>) {
const poster = offer.project
? getShowcaseAssets(offer.project.id, "desktop")
: null;
return (
<article className="ds-card cv-press cv-lift relative h-full min-w-0 overflow-hidden">
<div className="aspect-[16/10] overflow-hidden border border-[var(--border)] bg-[var(--ink)] md:aspect-[21/9] lg:aspect-[16/10]">
{poster && offer.project ? (
<Image
src={poster.poster}
alt={`Site ${offer.project.title} aberto no computador`}
width={poster.width}
height={poster.height}
sizes="(min-width: 1024px) 33vw, 100vw"
loading="lazy"
className="block h-full w-full object-cover object-top"
/>
) : (
<ReviewChecklist />
)}
</div>
<Link
href={tier.ctaHref}
className={`mt-sp-6 w-full ${
tier.featured ? primaryButton : secondaryButtonLight
}`}
{/* min-h de 3 linhas no desktop: os títulos de 2 e 3 linhas terminam na mesma altura e os textos se alinham */}
<h3 className="ds-card-title !mt-sp-5 text-balance !text-[clamp(20px,5vw,26px)] !leading-[1.1] !tracking-[-0.03em] [overflow-wrap:break-word] [word-break:normal] lg:min-h-[3.3em] lg:!text-[22px] xl:min-h-[2.2em] xl:!text-[24px]">
{offer.title}
</h3>
<p
className="ds-copy !mt-sp-3 !text-[15px] !leading-[1.7] [overflow-wrap:break-word] [word-break:normal]"
data-reel="right"
>
{tier.ctaLabel}
</Link>
{offer.description}
</p>
<ContactOfferLink
kind={offer.kind}
className={`mt-sp-6 w-full ${primaryButton}`}
>
{offer.ctaLabel}
</ContactOfferLink>
<HoverPrefetchLink
href={offer.moreHref}
className={`${textLink} mt-sp-2 self-start text-[var(--ink-80)]`}
>
{offer.moreLabel}
</HoverPrefetchLink>
</article>
);
}
/** A home mostra só os três serviços principais, nesta ordem: sites, sistemas web e integrações. O hub tem os demais. */
const HOME_SERVICE_SLUGS = [
"criacao-de-sites",
"engenharia-de-software",
"integracao-hardware-software-iot",
];
export function WorkModalities({
serviceLinks = [],
}: {
/** Os serviços do hub; app/page.tsx os lê de lib/site-content.ts. */
serviceLinks?: ServiceLink[];
}) {
const mainServices = HOME_SERVICE_SLUGS.flatMap((slug) => {
const service = serviceLinks.find(({ href }) => href.endsWith(`/${slug}`));
return service ? [service] : [];
});
export function WorkModalities() {
return (
<Section
id="modalities"
eyebrow="Soluções"
title="Software feito a partir do seu negócio."
description="Para empresas que precisam de um sistema próprio, de ferramentas conectadas ou de uma avaliação técnica antes de dar o próximo passo."
title="Escolha por onde começar"
description="Três pontos de partida. Escolha o que mais se parece com o seu caso: o formulário de contato já abre com o tipo de projeto marcado."
disableChildrenAnimation
>
<div className="mb-sp-8 ds-card bg-[rgba(10,10,10,0.02)]">
<div className="flex flex-col gap-sp-4 lg:flex-row lg:items-end lg:justify-between">
<div className="max-w-[760px]">
<div className="ds-kicker">Reconhece algum destes cenários?</div>
<h3 className="ds-card-title !mt-sp-3" data-reel="left">
Planilhas demais. Sistemas isolados. Trabalho repetido.
</h3>
<p className="ds-copy !mt-sp-3 max-w-[62ch]" data-reel="right">
A operação cresceu, mas as ferramentas não acompanharam. Ou a
ideia precisa sair do protótipo para atender clientes. O projeto
começa pelo problema, sem exigir que você já saiba qual tecnologia
usar.
</p>
</div>
<Link href="/servicos" className={`shrink-0 ${secondaryButtonLight}`}>
Ver todos os serviços
</Link>
</div>
<div
className="mt-sp-6 grid gap-sp-3 md:grid-cols-3"
data-reel-stagger="left"
>
{mainServices.map((service) => (
<Link
key={service.href}
href={service.href}
className="min-w-0 rounded-[8px] border border-[var(--border)] bg-white/70 px-sp-4 py-sp-4 transition-colors duration-200 hover:border-[var(--slash)] hover:bg-white"
>
<div className="font-display text-base font-semibold leading-snug text-[var(--ink)]">
{service.label}
</div>
<div className="mt-sp-2 font-display text-[15px] md:text-sm leading-6 text-[var(--ink-80)]">
{service.description}
</div>
</Link>
))}
</div>
</div>
<div className="grid grid-cols-1 gap-sp-4 md:grid-cols-2 md:gap-sp-5 lg:grid-cols-3 lg:gap-sp-6">
{TIERS.map((tier, index) => (
<div className="grid grid-cols-1 gap-sp-5 lg:grid-cols-3 lg:gap-sp-6">
{OFFERS.map((offer, index) => (
<Reveal
key={tier.id}
key={offer.id}
y={20}
duration={0.75}
delay={0.08 * (index + 1)}
className="min-w-0"
>
<TierCard tier={tier} />
<OfferCard offer={offer} />
</Reveal>
))}
</div>
<div id="process" className="mt-sp-12 scroll-mt-24">
<h3 className="ds-card-title">Como o projeto acontece</h3>
<p className="ds-copy max-w-[65ch]">
Você acompanha as decisões e valida cada etapa. Prazo e investimento
dependem do escopo, das integrações e dos riscos identificados.
</p>
<ol className="mt-sp-6 grid gap-sp-6 sm:grid-cols-2 xl:grid-cols-4">
{/* Lista compacta: o título e a descrição de cada etapa numa linha só, sem a grade de quatro cartões. */}
<div
id="process"
className="mt-sp-12 grid scroll-mt-24 gap-sp-6 border-t border-[var(--border)] pt-sp-8 lg:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)] lg:gap-sp-12"
>
<div>
<h3 className="ds-card-title !mt-0">Como eu trabalho</h3>
<p className="ds-copy max-w-[40ch] !text-[15px]">
Você acompanha as decisões e valida cada etapa. Prazo e investimento
dependem do escopo, das integrações e dos riscos identificados.
</p>
</div>
<ol className="divide-y divide-[var(--border)] border-y border-[var(--border)]">
{PROCESS.map((step, index) => (
<li
key={step.title}
className="border-t border-[var(--border)] pt-sp-4"
>
<span className="ds-kicker">
<li key={step.title} className="flex gap-sp-4 py-sp-4">
<span className="w-7 shrink-0 font-mono text-[15px] leading-6 text-[var(--meta)]">
{String(index + 1).padStart(2, "0")}
</span>
<h4 className="mt-sp-3 text-base font-semibold">{step.title}</h4>
<p className="ds-copy">{step.description}</p>
<p className="min-w-0 font-display text-[15px] leading-6 text-[var(--ink-80)]">
<strong className="font-semibold text-[var(--ink)]">
{step.title}.
</strong>{" "}
{step.description}
</p>
</li>
))}
</ol>