feat(home): posicionamento com prova, menu sem recarregar e WhatsApp em todo o site
H1 'Sites e sistemas que trazem pedidos pelo WhatsApp e tiram sua operação da planilha.'; HeroWork maior com a SEV; menu Cases, Serviços, Como trabalho, Sobre e IA com next/link e pré-carregamento no hover; menu do celular em tela cheia; WhatsApp flutuante e no rodapé com a mensagem da rota; rodapé em todas as páginas; FAQ da home; /ia como oferta de revisão de app feito com IA; formulário com noscript, validação do mailto e plano B do pop-up; cartões sem foco indevido; sem a fonte Monument.
This commit is contained in:
1 parent
e80f73d075
commit
10f170abf4
56 files changed
+3099
-906
No files matched your search
@@ -1,11 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { motion, type Variants } from "motion/react";
|
||||
import Link from "next/link";
|
||||
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||
import type { ServiceLink } from "@/lib/site-navigation";
|
||||
import { DrawLine } from "./animations/DrawLine";
|
||||
import { primaryButton, secondaryButtonLight } from "./button-styles";
|
||||
import "./conversion.css";
|
||||
import { Reveal } from "./Reveal";
|
||||
import { Section } from "./Section";
|
||||
|
||||
interface Tier {
|
||||
@@ -15,8 +13,8 @@ interface Tier {
|
||||
tagline: string;
|
||||
description: string;
|
||||
deliverables: string[];
|
||||
pricing: string;
|
||||
pricingNote: string;
|
||||
/** Primeiro passo concreto, sem prazo nem preço. */
|
||||
firstStep: string;
|
||||
featured?: boolean;
|
||||
ctaLabel: string;
|
||||
ctaHref: string;
|
||||
@@ -36,8 +34,8 @@ const TIERS: Tier[] = [
|
||||
"Integrações e permissões de acesso",
|
||||
"Validação, publicação e documentação",
|
||||
],
|
||||
pricing: "Proposta por projeto",
|
||||
pricingNote: "Escopo, etapas e investimento definidos antes da execução.",
|
||||
firstStep:
|
||||
"Primeira conversa para entender o problema. Depois, escopo e proposta por escrito.",
|
||||
featured: true,
|
||||
ctaLabel: "Conversar sobre meu sistema",
|
||||
ctaHref: "/#contact",
|
||||
@@ -55,8 +53,8 @@ const TIERS: Tier[] = [
|
||||
"Análise dos gargalos da aplicação",
|
||||
"Plano de correção por prioridade",
|
||||
],
|
||||
pricing: "Escopo de análise definido",
|
||||
pricingNote: "Profundidade e prazo combinados conforme o sistema.",
|
||||
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",
|
||||
},
|
||||
@@ -73,9 +71,8 @@ const TIERS: Tier[] = [
|
||||
"Testes das partes afetadas",
|
||||
"Planejamento de manutenção e operação",
|
||||
],
|
||||
pricing: "Etapas acordadas",
|
||||
pricingNote:
|
||||
"Prioridades, suporte e responsabilidades definidos em contrato.",
|
||||
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",
|
||||
},
|
||||
@@ -105,26 +102,14 @@ const PROCESS = [
|
||||
];
|
||||
|
||||
function TierCard({ tier }: Readonly<{ tier: Tier }>) {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
return (
|
||||
<motion.article
|
||||
className={`ds-card relative flex h-full min-w-0 flex-col overflow-hidden outline-none max-md:w-full max-md:max-w-none max-md:px-sp-5 max-md:py-sp-5 ${
|
||||
<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",
|
||||
}}
|
||||
whileHover={
|
||||
shouldReduceMotion
|
||||
? undefined
|
||||
: {
|
||||
y: -4,
|
||||
boxShadow: "0 24px 50px rgba(0, 0, 0, 0.08)",
|
||||
}
|
||||
}
|
||||
whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }}
|
||||
transition={{ type: "spring", stiffness: 180, damping: 18 }}
|
||||
>
|
||||
<div className="flex min-w-0 items-start justify-between gap-sp-4">
|
||||
<span className="ds-kicker">{tier.badge}</span>
|
||||
@@ -136,20 +121,20 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
|
||||
<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-[13px] leading-[1.7] text-[var(--meta)]">
|
||||
<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-[13px] !leading-[1.75] [overflow-wrap:break-word] [word-break:normal] md:!text-[13px]"
|
||||
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}
|
||||
</p>
|
||||
|
||||
<ul
|
||||
className="ds-list !mt-sp-5 !pl-[1.15rem] !text-[13px] !leading-[1.75] [overflow-wrap:break-word] [word-break:normal] md:!text-[12px]"
|
||||
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) => (
|
||||
@@ -161,10 +146,10 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
|
||||
|
||||
<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">
|
||||
{tier.pricing}
|
||||
Primeiro passo
|
||||
</div>
|
||||
<div className="font-display text-[12px] leading-[1.7] text-[var(--meta)]">
|
||||
{tier.pricingNote}
|
||||
<div className="font-display text-[15px] md:text-[13px] leading-[1.7] text-[var(--ink-80)]">
|
||||
{tier.firstStep}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -176,41 +161,27 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
|
||||
>
|
||||
{tier.ctaLabel}
|
||||
</Link>
|
||||
</motion.article>
|
||||
</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 shouldReduceMotion = useReducedMotion();
|
||||
|
||||
const containerVariants: Variants = {
|
||||
hidden: {},
|
||||
visible: {
|
||||
transition: {
|
||||
delayChildren: 0.08,
|
||||
staggerChildren: 0.08,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const itemVariants: Variants = {
|
||||
hidden: shouldReduceMotion
|
||||
? { opacity: 0 }
|
||||
: { opacity: 0, y: 20, clipPath: "inset(100% 0 0 0)" },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
clipPath: "inset(0% 0 0 0)",
|
||||
transition: shouldReduceMotion
|
||||
? { duration: 0.25 }
|
||||
: { duration: 0.75, ease: [0.16, 1, 0.3, 1] },
|
||||
},
|
||||
};
|
||||
const mainServices = HOME_SERVICE_SLUGS.flatMap((slug) => {
|
||||
const service = serviceLinks.find(({ href }) => href.endsWith(`/${slug}`));
|
||||
return service ? [service] : [];
|
||||
});
|
||||
|
||||
return (
|
||||
<Section
|
||||
@@ -235,15 +206,15 @@ export function WorkModalities({
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/servicos" className={`shrink-0 ${secondaryButtonLight}`}>
|
||||
Conhecer todos os serviços
|
||||
Ver todos os serviços
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="mt-sp-6 grid gap-sp-3 md:grid-cols-2 xl:grid-cols-3"
|
||||
className="mt-sp-6 grid gap-sp-3 md:grid-cols-3"
|
||||
data-reel-stagger="left"
|
||||
>
|
||||
{serviceLinks.map((service) => (
|
||||
{mainServices.map((service) => (
|
||||
<Link
|
||||
key={service.href}
|
||||
href={service.href}
|
||||
@@ -252,7 +223,7 @@ export function WorkModalities({
|
||||
<div className="font-display text-base font-semibold leading-snug text-[var(--ink)]">
|
||||
{service.label}
|
||||
</div>
|
||||
<div className="mt-sp-2 font-display text-sm leading-6 text-[var(--ink-80)]">
|
||||
<div className="mt-sp-2 font-display text-[15px] md:text-sm leading-6 text-[var(--ink-80)]">
|
||||
{service.description}
|
||||
</div>
|
||||
</Link>
|
||||
@@ -260,19 +231,19 @@ export function WorkModalities({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<motion.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"
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
variants={containerVariants}
|
||||
>
|
||||
{TIERS.map((tier) => (
|
||||
<motion.div key={tier.id} variants={itemVariants} className="min-w-0">
|
||||
<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) => (
|
||||
<Reveal
|
||||
key={tier.id}
|
||||
y={20}
|
||||
duration={0.75}
|
||||
delay={0.08 * (index + 1)}
|
||||
className="min-w-0"
|
||||
>
|
||||
<TierCard tier={tier} />
|
||||
</motion.div>
|
||||
</Reveal>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div id="process" className="mt-sp-12 scroll-mt-24">
|
||||
<h3 className="ds-card-title">Como o projeto acontece</h3>
|
||||
|
||||
Reference in new issue
Block a user