// ─────────────────────────────────────────────────────────────
// ABA ANÁLISE DE COMPORTAMENTO
// Re-skin da ferramenta do Dr. Ulisses no design do app.
// A LÓGICA (perguntas, perfis, diagnóstico) vem 100% de
// analise-logic.js (APP_DATA + diagnoseProfile) — NÃO é alterada aqui.
// Estado e localStorage TOTALMENTE separados dos Hábitos.
// ─────────────────────────────────────────────────────────────
const { useState: useStateAnalise, useEffect: useEffectAnalise, useRef: useRefAnalise } = React;
const ANALISE_KEY = "analise_progresso_v1"; // chave própria — não toca em "habitos_v1"
function AnaliseScreen({ t, onBack, initialToken, acessoLivre }) {
// ── estado isolado (começa SEMPRE do zero; não resume sessão antiga) ──
// Pelo botão: exige a senha (GRAXA). Pelo link público ou link com token
// (?token=...): pula o portão e cai direto no aviso legal.
const [step, setStep] = useStateAnalise(() => {
if (acessoLivre) { window.USER_TOKEN = null; return "legal"; }
if (initialToken) { window.USER_TOKEN = initialToken; return "legal"; }
return "token";
});
const [qi, setQi] = useStateAnalise(0);
const [answers, setAnswers] = useStateAnalise({});
const [resultCode, setResultCode] = useStateAnalise(null);
const [tokenInput, setTokenInput] = useStateAnalise("");
const [burning, setBurning] = useStateAnalise(false);
const [tokenErro, setTokenErro] = useStateAnalise(null);
const QUESTIONS = APP_DATA.questions;
const total = QUESTIONS.length;
const setAnswer = (id, val) => setAnswers((a) => ({ ...a, [id]: val }));
const isValid = (q) => {
const v = answers[q.id];
if (q.type === "slider") return true;
if (v === undefined || v === null || v === "") return false;
if (q.type === "text") return String(v).trim().length > 0;
if (q.type === "number") {
const n = parseFloat(v);
if (isNaN(n)) return false;
if (q.min !== undefined && n < q.min) return false;
if (q.max !== undefined && n > q.max) return false;
return true;
}
if (q.type === "single") return !!v;
if (q.type === "multiple") return Array.isArray(v) && v.length > 0;
return true;
};
// avança SEM revalidar (usado pelo clique em opção única — evita o bug de
// estado defasado que pedia 2 cliques)
const goForward = () => {
if (qi < total - 1) { setQi(qi + 1); scrollTop(); }
else { setStep("evaluating"); scrollTop(); }
};
const next = () => {
const q = QUESTIONS[qi];
if (!isValid(q)) { setShake(true); setTimeout(() => setShake(false), 480); return; }
goForward();
};
const back = () => { if (qi > 0) { setQi(qi - 1); scrollTop(); } };
const [shake, setShake] = useStateAnalise(false);
const scrollRef = useRefAnalise(null);
const scrollTop = () => { if (scrollRef.current) scrollRef.current.scrollTop = 0; };
// ── PROGRESSO ───────────────────────────────────────────────
const pct =
step === "question" ? Math.round(((qi + 1) / total) * 100)
: ["evaluating", "result", "microcopy"].includes(step) ? 100
: 0;
// ── TOKEN GATE → vai pro Aviso Legal (a queima acontece ao confirmar o legal,
// a validação/queima acontece JÁ AQUI — código inválido é barrado
// na hora, antes do Aviso Legal.) ──
const entrarToken = async (e) => {
e.preventDefault();
const token = tokenInput.trim();
if (!token) { setShake(true); setTimeout(() => setShake(false), 480); return; }
window.USER_TOKEN = token;
setTokenErro(null);
// senha GRAXA / link livre → acesso livre, NÃO queima, vai pro legal
if (typeof tokenEhLivre === "function" && tokenEhLivre(token)) {
window.USER_TOKEN_BURNED = false;
setStep("legal"); scrollTop(); return;
}
// valida + queima AGORA; só avança se o servidor confirmar SUCESSO
setBurning(true);
try {
const resp = await fetch("burn_token.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token }),
});
const data = await resp.json();
if (resp.ok && data && data.status === "success") {
window.USER_TOKEN_BURNED = true;
setBurning(false); setStep("legal"); scrollTop();
} else {
setTokenErro("invalido");
setShake(true); setTimeout(() => setShake(false), 480);
setBurning(false);
}
} catch (err) {
// Sem servidor (preview/local): NÃO libera código aleatório.
setTokenErro("invalido");
setShake(true); setTimeout(() => setShake(false), 480);
setBurning(false);
}
};
// CONFIRMAÇÃO do Aviso Legal: o token já foi validado/queimado no gate
// (ou é acesso livre / link com token). Aqui só queima o token de LINK
// que ainda não passou pelo gate, e segue.
const confirmarLegal = async () => {
const token = window.USER_TOKEN;
// acesso livre (GRAXA / link público) ou token já queimado no gate → segue
if (acessoLivre || window.USER_TOKEN_BURNED || (typeof tokenEhLivre === "function" && tokenEhLivre(token))) {
if (window.track) track("iniciou");
setStep("intro0"); scrollTop(); return;
}
// entrou por LINK com ?token= (não passou pelo gate) → queima aqui
setBurning(true); setTokenErro(null);
try {
const resp = await fetch("burn_token.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token }),
});
const data = await resp.json();
if (resp.ok && data && data.status === "success") {
if (window.track) track("iniciou");
window.USER_TOKEN_BURNED = true;
setBurning(false); setStep("intro0"); scrollTop();
} else {
setTokenErro("invalido");
setBurning(false); setStep("token");
}
} catch (e) {
setTokenErro("invalido");
setBurning(false); setStep("token");
}
};
// ════════════════════════════════════════════════════════════
return (
{/* keyframes locais */}
{/* TOPO STICKY */}
Análise de Comportamento
{/* BARRA DE PROGRESSO */}
{/* CONTEÚDO POR ETAPA */}
{step === "token" && }
{step === "legal" && }
{step === "intro0" && { setStep("intro1"); scrollTop(); }} />}
{step === "intro1" && { setStep("question"); setQi(0); scrollTop(); }} />}
{step === "question" && }
{step === "evaluating" && {
let code;
try { code = diagnoseProfile(answers); } catch (e) { code = "I"; }
if (window.track) track("concluiu");
setResultCode(code); setStep("result"); scrollTop();
}} />}
{step === "result" && resultCode && { setStep("microcopy"); scrollTop(); }} />}
{step === "microcopy" && resultCode && }
);
}
// ── helper: HTML controlado vindo do APP_DATA (apenas
e ) ──
function htm(s) { return { dangerouslySetInnerHTML: { __html: s } }; }
// ─────────────────────────────────────────────────────────────
// TOKEN GATE
// ─────────────────────────────────────────────────────────────
function TokenGate({ t, value, onChange, onSubmit, shake, erro }) {
return (
Acesso
exclusivo
Digite o código fornecido pelo
Dr. Ulisses Nakagawa para iniciar
{erro && (
Código inválido!
Converse com o Dr. Ulisses Nakagawa.
)}
);
}
// ── Prova social: "473 pessoas já fizeram" (conta real + base) ──
function SocialProof({ t }) {
const base = (window.LINKS && LINKS.analise_total_feitas) || 473;
const [n, setN] = useStateAnalise(base);
useEffectAnalise(() => {
let vivo = true;
(async () => {
try {
const r = await fetch("eventos.php", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "count", base, evento: "iniciou" }),
});
const d = await r.json();
if (vivo && d && d.status === "success" && typeof d.total === "number") setN(d.total);
} catch (e) { /* sem servidor: mantém a base */ }
})();
return () => { vivo = false; };
}, []);
return (
{[0, 1, 2].map((i) => (
))}
{n.toLocaleString("pt-BR")} pessoas já fizeram
);
}
// ─────────────────────────────────────────────────────────────
// AVISO LEGAL (CONFIG_ALERTAS.tela1)
// ─────────────────────────────────────────────────────────────
function LegalCard({ t, onOk, busy }) {
const c = CONFIG_ALERTAS.tela1;
return (
);
}
// ─────────────────────────────────────────────────────────────
// CARDS DE INTRO
// ─────────────────────────────────────────────────────────────
function IntroCard({ t, data, label, cta, onNext }) {
return (
{label} · Antes de começar
{data.title &&
}
);
}
// ─────────────────────────────────────────────────────────────
// PERGUNTA
// ─────────────────────────────────────────────────────────────
function QuestionCard({ t, q, index, total, answers, setAnswer, onNext, onAuto, onBack, isValid, shake }) {
const val = answers[q.id];
const valid = isValid(q);
return (
Pergunta {index + 1} / {total}
{q.description &&
{q.description}
}
{!q.description &&
}
{/* INPUTS */}
{(q.type === "text" || q.type === "number") && (
setAnswer(q.id, e.target.value)}
onKeyDown={(e) => { if (e.key === "Enter") onNext(); }}
placeholder="Digite aqui..."
min={q.min} max={q.max}
autoFocus
style={{
width: "100%", padding: "16px 18px",
background: t.bg1, border: `1px solid ${t.border}`, borderRadius: RADIUS.md,
color: t.ink1, fontFamily: TYPE.sans, fontSize: 16, fontWeight: 600,
boxSizing: "border-box", outline: "none",
}}
/>
)}
{q.type === "single" && (
{q.options.map((opt, i) => {
const sel = val === opt;
return (
);
})}
)}
{q.type === "multiple" && (
{q.options.map((opt, i) => {
const arr = Array.isArray(val) ? val : [];
const sel = arr.includes(opt);
return (
);
})}
)}
{q.type === "slider" && (
setAnswer(q.id, n)} />
)}
{/* NAV */}
{q.type !== "single" && (
)}
);
}
function SliderInput({ t, q, val, onChange }) {
return (
{val}
/ {q.max}
onChange(parseInt(e.target.value, 10))}
style={{ width: "100%", accentColor: t.accent, height: 6, cursor: "pointer" }}
/>
{q.min}{q.max}
);
}
// ─────────────────────────────────────────────────────────────
// AVALIANDO (8s — fiel ao original)
// ─────────────────────────────────────────────────────────────
function Evaluating({ t, onDone }) {
const ringRef = useRefAnalise(null);
useEffectAnalise(() => {
const c = ringRef.current;
if (c) { c.getBoundingClientRect(); requestAnimationFrame(() => { c.style.strokeDashoffset = "0"; }); }
const id = setTimeout(onDone, 8000);
return () => clearTimeout(id);
}, []);
return (
Estou avaliando
tuas respostas
Um momento, por favor.
Tua análise merece atenção e respeito.
);
}
// ─────────────────────────────────────────────────────────────
// RESULTADO
// ─────────────────────────────────────────────────────────────
function ResultCard({ t, code, onNext }) {
const p = APP_DATA.results[code];
return (
);
}
// ─────────────────────────────────────────────────────────────
// MICROCOPY / CTA FINAL
// ─────────────────────────────────────────────────────────────
function MicrocopyCard({ t, code }) {
return (
Entender é o primeiro passo.
Resolver é o próximo.
Leve essa análise pra uma consulta comigo
e a gente monta o teu plano de verdade.
{/* BOTÃO IDÊNTICO AO DA ABA CONSULTA */}
{ if (window.track) track("clicou_consulta"); }}
className="cta-agendar"
style={{
position: "relative", overflow: "hidden",
display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 4,
padding: "20px 20px",
background: t.ink1, color: t.bg0,
borderRadius: RADIUS.md, textDecoration: "none",
fontFamily: TYPE.sans, textAlign: "center",
boxShadow: "0 10px 28px rgba(0,0,0,0.4)",
}}
>
Agendar Consulta
Com o Dr. Ulisses Nakagawa
{/* COMPARTILHAR NOS STORIES — opcional, nunca invasivo */}
{/* CONTATO OPCIONAL — recolhido por padrão, totalmente pulável */}
);
}
// ── Captura de contato opcional (recolhida; nada obrigatório) ──
function LeadCapture({ t }) {
const [open, setOpen] = useStateAnalise(false);
const [nome, setNome] = useStateAnalise("");
const [wpp, setWpp] = useStateAnalise("");
const [done, setDone] = useStateAnalise(false);
const [busy, setBusy] = useStateAnalise(false);
const enviar = async () => {
const digits = (wpp || "").replace(/\D/g, "");
if (!nome.trim() || digits.length < 10) { return; }
setBusy(true);
try {
await fetch("leads.php", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ nome: nome.trim(), whatsapp: digits }),
keepalive: true,
});
} catch (e) {}
if (window.track) track("lead");
setBusy(false); setDone(true);
};
if (done) {
return (
Recebido. O Dr. Ulisses vai falar com você. ✓
);
}
if (!open) {
return (
);
}
const inp = {
width: "100%", padding: "13px 15px", boxSizing: "border-box",
background: t.bg0, border: `1px solid ${t.border}`, borderRadius: RADIUS.md,
color: t.ink1, fontFamily: TYPE.sans, fontSize: 14, fontWeight: 600, outline: "none",
};
return (
);
}
// ── Botão "Compartilhar nos Stories" (gera a imagem 1080×1920) ──
function ShareStoriesButton({ t, code }) {
const [busy, setBusy] = useStateAnalise(false);
const onShare = async () => {
if (busy) return;
setBusy(true);
try {
if (typeof compartilharResultado === "function") await compartilharResultado(code);
} catch (e) {}
setBusy(false);
};
return (
);
}
// ── estilos compartilhados ──────────────────────────────────
function primaryBtn(t) {
return {
width: "100%", marginTop: 26, padding: "18px",
background: t.ink1, color: t.bg0, border: "none", borderRadius: RADIUS.md,
cursor: "pointer", fontFamily: TYPE.sans, fontSize: 14, fontWeight: 800,
letterSpacing: "0.12em", textTransform: "uppercase",
};
}
function primaryBtnInline(t) {
return {
padding: "16px 22px", background: t.ink1, color: t.bg0, border: "none", borderRadius: RADIUS.md,
cursor: "pointer", fontFamily: TYPE.sans, fontSize: 13, fontWeight: 800,
letterSpacing: "0.1em", textTransform: "uppercase",
};
}
function optBtn(t, sel, withBox) {
return {
width: "100%", padding: withBox ? "15px 16px" : "16px 18px",
background: sel ? t.accentMuted : t.bg1,
border: `1.5px solid ${sel ? t.accent : t.border}`, borderRadius: RADIUS.md,
color: sel ? t.ink1 : t.ink2, cursor: "pointer",
fontFamily: TYPE.sans, fontSize: 14.5, fontWeight: 600, lineHeight: 1.4,
textAlign: withBox ? "left" : "center",
display: "flex", alignItems: "center", gap: 12,
justifyContent: withBox ? "flex-start" : "center",
transition: "border-color .12s ease, background .12s ease",
};
}
Object.assign(window, { AnaliseScreen });