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
+147
-188
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user