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.
231 lines
8.2 KiB
TypeScript
231 lines
8.2 KiB
TypeScript
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 Offer {
|
|
id: string;
|
|
title: string;
|
|
description: string;
|
|
/** Tipo de projeto que o botão deixa marcado no formulário de contato. */
|
|
kind: ContactKind;
|
|
ctaLabel: 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;
|
|
}
|
|
|
|
/**
|
|
* 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: "site",
|
|
title: "Site que traz pedidos pelo WhatsApp",
|
|
description:
|
|
"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: "sistema",
|
|
title: "Sistema para tirar a operação da planilha",
|
|
description:
|
|
"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: "revisao",
|
|
title: "Revisão de sistema que já existe, inclusive feito com IA",
|
|
description:
|
|
"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",
|
|
description:
|
|
"Quem usa, qual problema precisa resolver e quais dados e ferramentas já existem.",
|
|
},
|
|
{
|
|
title: "Definir a entrega",
|
|
description:
|
|
"Escopo, arquitetura, riscos, investimento e critérios de aceite antes de desenvolver.",
|
|
},
|
|
{
|
|
title: "Construir e validar",
|
|
description:
|
|
"Entregas por etapa, demonstrações e testes dos fluxos, das permissões e das integrações.",
|
|
},
|
|
{
|
|
title: "Publicar e dar continuidade",
|
|
description:
|
|
"Implantação, documentação e condições de manutenção combinadas para a operação.",
|
|
},
|
|
];
|
|
|
|
/** 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 (
|
|
<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="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>
|
|
);
|
|
}
|
|
|
|
/** 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>
|
|
|
|
{/* 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"
|
|
>
|
|
{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>
|
|
);
|
|
}
|
|
|
|
export function WorkModalities() {
|
|
return (
|
|
<Section
|
|
id="modalities"
|
|
eyebrow="Soluções"
|
|
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="grid grid-cols-1 gap-sp-5 lg:grid-cols-3 lg:gap-sp-6">
|
|
{OFFERS.map((offer, index) => (
|
|
<Reveal
|
|
key={offer.id}
|
|
y={20}
|
|
duration={0.75}
|
|
delay={0.08 * (index + 1)}
|
|
className="min-w-0"
|
|
>
|
|
<OfferCard offer={offer} />
|
|
</Reveal>
|
|
))}
|
|
</div>
|
|
|
|
{/* 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="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>
|
|
<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>
|
|
</div>
|
|
</Section>
|
|
);
|
|
}
|