// Multi-step modal — 3 steps + lead capture
const CARGO_OPTIONS = [
"Founder/CEO",
"CTO/Head de Tech",
"CFO/Head Financeiro",
"Head de Produto",
"Diretor de Inovação",
"Outro",
];
const SEGMENT_OPTIONS = [
{ id: "marketplace", label: "Marketplace / Plataforma" },
{ id: "saas", label: "SaaS / Software" },
{ id: "varejo", label: "Varejo / E-commerce" },
{ id: "educacao", label: "Educação / Health / Serviços" },
{ id: "fintech", label: "Fintech / Banking" },
{ id: "outro", label: "Outro" },
];
const CLIENTES_OPTIONS = [
{ id: "<500", label: "Menos de 500" },
{ id: "500-2k", label: "500 a 2.000" },
{ id: "2k-10k", label: "2.000 a 10.000" },
{ id: "10k-50k", label: "10.000 a 50.000" },
{ id: "50k+", label: "Mais de 50.000" },
];
const FATURAMENTO_OPTIONS = [
{ id: "<100k", label: "Até R$ 100k" },
{ id: "100k-500k", label: "R$ 100k a R$ 500k" },
{ id: "500k-2M", label: "R$ 500k a R$ 2M" },
{ id: "2M-10M", label: "R$ 2M a R$ 10M" },
{ id: "10M+", label: "Mais de R$ 10M" },
];
function Chip({ selected, label, onClick }) {
return (
);
}
function ProductCard({ selected, product, onClick }) {
return (
);
}
// ---------- STEP 1: Business ----------
function Step1({ form, setForm }) {
return (
Passo 1 de 3
Vamos calibrar pelo seu negócio
Três perguntas pra dimensionar a janela de receita. Nada é guardado — selecione e siga.
01 Segmento
Que tipo de negócio você tem?
{SEGMENT_OPTIONS.map(o => (
setForm({...form, segmento: o.id})}/>
))}
02 Tamanho da base
Quantos clientes ativos sua operação tem hoje?
{CLIENTES_OPTIONS.map(o => (
setForm({...form, clientes: o.id})}/>
))}
03 Faturamento
Qual seu faturamento mensal médio?
{FATURAMENTO_OPTIONS.map(o => (
setForm({...form, faturamento: o.id})}/>
))}
);
}
// ---------- STEP 2: Products ----------
function Step2({ form, setForm }) {
const toggle = (pid) => {
const has = form.produtos.includes(pid);
const next = has ? form.produtos.filter(x => x !== pid) : [...form.produtos, pid];
setForm({...form, produtos: next});
};
return (
Passo 2 de 3
Quais frentes financeiras fazem sentido?
Escolha quantas quiser, pelo menos uma. A gente calcula a receita potencial em cada uma separadamente.
{PRODUCTS.map(p => (
toggle(p.id)}/>
))}
);
}
// ---------- Teaser animado Step 3 ----------
function CalcTeaser() {
const [digits, setDigits] = React.useState("000.000,00");
React.useEffect(() => {
let frame;
const chars = "0123456789";
let tick = 0;
const animate = () => {
tick++;
// gera número aleatório formatado que "parece" calculando
const n = Math.floor(Math.random() * 9000000) + 100000;
const fmt = n.toLocaleString("pt-BR", {minimumFractionDigits:2, maximumFractionDigits:2});
setDigits(fmt);
if (tick < 18) frame = setTimeout(animate, tick < 8 ? 60 : tick < 14 ? 100 : 180);
};
frame = setTimeout(animate, 300);
return () => clearTimeout(frame);
}, []);
return (
Seu potencial estimado
R$ {digits}
Complete o cadastro para liberar
);
}
// ---------- STEP 3: Lead capture ----------
function Step3({ form, setForm, errors, setErrors }) {
// live validate email
const onEmail = (e) => {
const v = e.target.value;
setForm({...form, email: v});
if (errors.email) setErrors({...errors, email: undefined});
};
const onEmailBlur = () => {
const v = form.email;
if (!v) return;
const r = validateEmail(v);
if (!r.ok) {
setErrors({...errors, email: r.kind});
} else if (errors.email) {
setErrors({...errors, email: undefined});
}
};
const emailMsg = (() => {
if (errors.email === "personal") return { type: "info", msg: "Use o e-mail da empresa — é pra você receber o diagnóstico com qualidade" };
if (errors.email === "format") return { type: "err", msg: "E-mail inválido" };
if (errors.email === "empty") return { type: "err", msg: "Preenche aí" };
return null;
})();
return (
Quase lá · Ver resultado
Complete para liberar seu potencial
Preencha seus dados para ver o número. Um especialista SH entra em contato para transformar esse potencial em plano real.
);
}
// ---------- MODAL CONTAINER ----------
function CalcModal({ open, onClose, form, setForm, onSubmit }) {
const [step, setStep] = React.useState(1);
const [errors, setErrors] = React.useState({});
// reset step when modal closes
React.useEffect(() => {
if (!open) { setStep(1); setErrors({}); }
}, [open]);
// ESC to close
React.useEffect(() => {
if (!open) return;
const h = (e) => { if (e.key === "Escape") onClose(); };
window.addEventListener("keydown", h);
return () => window.removeEventListener("keydown", h);
}, [open, onClose]);
if (!open) return null;
const step1Done = !!form.segmento && !!form.clientes && !!form.faturamento;
const step2Done = form.produtos.length >= 1;
// step 3 form validity
const emailRes = validateEmail(form.email);
const step3Done =
form.nome.trim().length >= 3 &&
emailRes.ok &&
isPhoneValid(form.whatsapp) &&
form.empresa.trim().length >= 2 &&
!!form.cargo &&
!!form.consent;
const progress = step === 1 ? 33 : step === 2 ? 66 : 100;
const validateStep3 = () => {
const errs = {};
if (form.nome.trim().length < 3) errs.nome = "Preenche aí (mín. 3 chars)";
const er = validateEmail(form.email);
if (!er.ok) errs.email = er.kind;
if (!isPhoneValid(form.whatsapp)) errs.whatsapp = "Número incompleto";
if (form.empresa.trim().length < 2) errs.empresa = "Preenche aí";
if (!form.cargo) errs.cargo = "Selecione um cargo";
if (!form.consent) errs.consent = "É preciso aceitar pra liberar o resultado";
setErrors(errs);
return Object.keys(errs).length === 0;
};
const next = () => {
if (step === 1 && step1Done) setStep(2);
else if (step === 2 && step2Done) setStep(3);
else if (step === 3) {
if (validateStep3()) onSubmit();
}
};
const back = () => { if (step > 1) setStep(step - 1); };
const onBackdrop = (e) => { if (e.target === e.currentTarget) onClose(); };
return (
Passo {step} de 3
{step === 1 && }
{step === 2 && }
{step === 3 && }
{step > 1 ? (
) :
// 90 segundos · sem spam
}
);
}
Object.assign(window, { CalcModal });