import { useState } from "react"; const COLORS = { black: "#1c1c1b", taupe: "#979086", cream: "#f5f5f2", midTaupe: "#9d9990", coolGray: "#9d9999", }; const CHALLENGES = { foundations: { title: "Membership Foundations 101", tagline: "Every strong membership starts here.", description: "Whether you're brand new or have been running your membership for years — this is your starting point. Membership Foundations 101 is your audit and your blueprint. There's a good chance you've done some of this already. There's also a good chance there are foundational gaps quietly causing your bigger problems. Go through this first.", communityPrompt: "Introduce yourself in the community: \"I just took the Membership Diagnostics Quiz and I'm starting with Membership Foundations 101. I'm at [stage] and the thing I most need to nail down is [X]. Anyone else starting here?\"", }, sticky: { title: "Insanely Sticky Community Challenge", tagline: "Fix the leak before you pour more in.", description: "Visibility and sales mean nothing if members leave as fast as they join. This is your second priority — your retention OS. From first-week onboarding to re-engagement systems to pre-renewal sequences, this challenge fixes the leaky bucket so every new member you bring in actually stays.", communityPrompt: "Post in the community: \"After Membership Foundations 101, I'm heading into the Sticky Community Challenge. My current retention situation is [X] and the thing I most want to fix is [Y]. Who else is working on this?\"", }, visibility: { title: "Visibility Challenge", tagline: "30 days. 1 platform. 1,000 new ideal followers.", description: "Once your foundations are solid, this is your next move. The Visibility Challenge gives you a daily content system — viral content, authority content, ManyChat automation — so the right people find your membership every single day.", communityPrompt: "Post in the community: \"Foundations 101 first, then I'm going all in on the Visibility Challenge. My platform is [X] and I'm committing to [X posts/day]. Who's in this with me?\"", }, sales: { title: "Get Consistent Member Signups Challenge", tagline: "Build the system that fills your membership on repeat.", description: "You have an audience — now you need a system to convert them. After you've got your foundations solid, this challenge walks you through your messaging, your launch plan, your sales events, and the behind-the-scenes funnel that sells your membership even when you're not actively launching.", communityPrompt: "Post in the community: \"Foundations 101 is my first stop, then I'm diving into the Sales Challenge. My biggest sales block right now is [X]. Who else is working on consistent signups?\"", }, world: { title: "Building a World Challenge", tagline: "Turn your membership into something bigger than you.", description: "Your community is thriving, your sales are consistent, and your foundations are solid — now it's time to build the structure underneath. Leaders programs, community manager, paid roles, progression ladders. The path from founder doing everything to a membership that becomes its own entity.", communityPrompt: "Post in the community: \"My Diagnostics sent me to Building a World after Foundations 101. I have [X] members and I'm ready to start building [Y]. Anyone else in this challenge?\"", }, }; const QUESTIONS = [ { id: "has_membership", q: "First things first — do you have a membership right now?", type: "single", options: [ { id: "no", label: "No — I'm still in the idea or setup phase" }, { id: "yes_new", label: "Yes — I launched recently (under 6 months)" }, { id: "yes_established", label: "Yes — I've been running it for 6+ months" }, ], }, { id: "member_count", q: "How many paying members do you have right now?", type: "single", showIf: a => a.has_membership !== "no", options: [ { id: "under_25", label: "Under 25" }, { id: "25_to_100", label: "25–100" }, { id: "100_to_300", label: "100–300" }, { id: "300_plus", label: "300+" }, ], }, { id: "pains", q: "What's holding your membership back right now?", sub: "Select all that apply.", type: "multi", options: [ { id: "no_audience", label: "I don't have enough of an audience to sell to" }, { id: "not_selling", label: "I have an audience but I can't convert them to members consistently" }, { id: "churn", label: "Members join but don't stay" }, { id: "quiet", label: "My community feels quiet and inactive" }, { id: "systems_mess", label: "My systems are a mess — or basically non-existent" }, { id: "scaling_me", label: "Everything still runs on me and I can't scale past my own capacity" }, { id: "no_membership_yet", label: "I don't have a membership yet — I need to build from scratch" }, ], }, { id: "top_pain", q: "You picked a few — which one is costing you the most right now?", sub: "Pick just one. This is your needle-mover.", type: "single_from_multi", showIf: a => (a.pains || []).length > 1, }, { id: "community_vibe", q: "How would you honestly describe your community right now?", type: "single", showIf: a => a.has_membership !== "no", options: [ { id: "ghost_town", label: "Ghost town — barely anyone posts or shows up" }, { id: "some_life", label: "Some activity but I'm mostly driving it" }, { id: "active", label: "Active — members engage with each other regularly" }, { id: "self_sustaining", label: "Self-sustaining — it runs even when I step back" }, ], }, { id: "churn_situation", q: "What's your honest churn situation?", type: "single", showIf: a => a.has_membership !== "no", options: [ { id: "dont_know", label: "I honestly don't know — I've never tracked it" }, { id: "know_its_bad", label: "I know people are leaving but I don't know why" }, { id: "month_1_2", label: "Most people leave in month 1–2" }, { id: "month_4_5", label: "I see a big drop around month 4–5" }, { id: "under_control", label: "It's relatively under control — retention isn't my main issue" }, ], }, { id: "sales_situation", q: "How consistent are your member signups right now?", type: "single", showIf: a => a.has_membership !== "no", options: [ { id: "not_selling", label: "I'm not really selling consistently at all" }, { id: "launch_dependent", label: "I only get members during launches — nothing in between" }, { id: "some_consistency", label: "Some consistency but it's still unpredictable" }, { id: "solid_sales", label: "My sales system is solid — I get new members regularly" }, ], }, { id: "content_consistency", q: "How consistent is your content right now?", type: "single", options: [ { id: "barely_posting", label: "I'm barely posting — I don't know what to say" }, { id: "scattered", label: "I post sometimes but it's scattered and inconsistent" }, { id: "consistent_not_converting", label: "I'm consistent but it's not bringing in new members" }, { id: "solid", label: "My content is solid — that's not my problem" }, ], }, { id: "goal_90", q: "What does success look like for you in the next 90 days?", type: "single", options: [ { id: "launch_first", label: "Launch my membership for the first time" }, { id: "grow_audience", label: "Grow my audience so more people know I exist" }, { id: "more_members", label: "Add new members consistently every month" }, { id: "stop_churn", label: "Dramatically reduce how many members leave" }, { id: "build_systems", label: "Get real systems in place so it doesn't all run on me" }, ], }, { id: "open_struggle", q: "In your own words — what's the ONE thing that would change everything for your membership right now?", type: "open", placeholder: "e.g. If I could just get people to actually show up to my calls, everything would feel different...", }, ]; const PAIN_LABELS = { no_audience: "I don't have enough of an audience to sell to", not_selling: "I have an audience but I can't convert them consistently", churn: "Members join but don't stay", quiet: "My community feels quiet and inactive", systems_mess: "My systems are a mess — or basically non-existent", scaling_me: "Everything still runs on me — I can't scale past my own capacity", no_membership_yet: "I don't have a membership yet — I need to build from scratch", }; function getVisibleQuestions(answers) { return QUESTIONS.filter(q => { if (q.showIf && !q.showIf(answers)) return false; return true; }); } function score(answers) { const hm = answers.has_membership; const pains = answers.pains || []; const topPain = answers.top_pain || (pains.length === 1 ? pains[0] : null); const cv = answers.community_vibe; const churn = answers.churn_situation; const sales = answers.sales_situation; const cc = answers.content_consistency; const g = answers.goal_90; const hasMembersAlready = hm !== "no" && hm !== undefined; // Gate check for Building a World const communityStrong = cv === "active" || cv === "self_sustaining"; const churnStrong = churn === "under_control"; const salesStrong = sales === "solid_sales"; const canRecommendWorld = communityStrong && churnStrong && salesStrong; // If no membership yet — simple if (hm === "no" || topPain === "no_membership_yet" || g === "launch_first") { return { primary: CHALLENGES.foundations, secondary: null, hasMembersAlready: false }; } // Score secondary challenge let s = { sticky: 0, visibility: 0, sales: 0, world: 0 }; // Top pain (highest weight) const painMap = { no_audience: { visibility: 5 }, not_selling: { sales: 5 }, churn: { sticky: 5 }, quiet: { sticky: 4 }, systems_mess: { sticky: 2, sales: 2 }, scaling_me: { world: 5 }, }; if (topPain && painMap[topPain]) { Object.entries(painMap[topPain]).forEach(([k, v]) => s[k] += v); } pains.forEach(p => { if (p !== topPain && painMap[p]) { Object.entries(painMap[p]).forEach(([k, v]) => s[k] += v * 0.3); } }); // Community vibe if (cv === "ghost_town") s.sticky += 4; if (cv === "some_life") s.sticky += 2; if (cv === "self_sustaining") s.world += 2; // Churn if (churn === "dont_know" || churn === "know_its_bad") s.sticky += 3; if (churn === "month_1_2") s.sticky += 4; if (churn === "month_4_5") s.sticky += 2; // Sales if (sales === "not_selling" || sales === "launch_dependent") s.sales += 3; if (sales === "some_consistency") s.sales += 1; // Content if (cc === "barely_posting" || cc === "scattered") s.visibility += 3; if (cc === "consistent_not_converting") s.sales += 2; // Goal const goalMap = { grow_audience: "visibility", more_members: "sales", stop_churn: "sticky", build_systems: "world" }; if (g && goalMap[g]) s[goalMap[g]] += 3; // Gate world — only if truly ready if (!canRecommendWorld) s.world = -99; const sorted = Object.entries(s).sort((a, b) => b[1] - a[1]); const secondaryKey = sorted[0][0]; const secondary = CHALLENGES[secondaryKey]; return { primary: CHALLENGES.foundations, secondary, hasMembersAlready }; } export default function App() { const [started, setStarted] = useState(false); const [answers, setAnswers] = useState({}); const [history, setHistory] = useState([]); const [qIndex, setQIndex] = useState(0); const [openText, setOpenText] = useState(""); const [result, setResult] = useState(null); const reset = () => { setStarted(false); setAnswers({}); setHistory([]); setQIndex(0); setOpenText(""); setResult(null); }; const visibleQs = getVisibleQuestions(answers); const current = visibleQs[qIndex]; const progress = started ? Math.round((qIndex / visibleQs.length) * 100) : 0; const advance = (newAnswers) => { setHistory(h => [...h, qIndex]); const nextQs = getVisibleQuestions(newAnswers); if (qIndex + 1 >= nextQs.length) { setResult(score(newAnswers)); } else { setQIndex(i => i + 1); setOpenText(""); } }; const goBack = () => { if (history.length === 0) { setStarted(false); return; } const prev = history[history.length - 1]; setHistory(h => h.slice(0, -1)); setQIndex(prev); setOpenText(""); }; const handleSingle = (qid, val) => { const updated = { ...answers, [qid]: val }; setAnswers(updated); setTimeout(() => advance(updated), 200); }; const handleMulti = (optId) => { const cur = answers.pains || []; const updated = cur.includes(optId) ? cur.filter(v => v !== optId) : [...cur, optId]; setAnswers(a => ({ ...a, pains: updated })); }; const selectedPains = answers.pains || []; const st = { wrap: { fontFamily: "'Proxima Nova','Helvetica Neue',sans-serif", maxWidth: 600, margin: "0 auto", padding: "2rem 1.25rem", minHeight: "100vh", boxSizing: "border-box" }, logo: { fontSize: 11, letterSpacing: "0.15em", color: COLORS.taupe, textTransform: "uppercase", marginBottom: "1.75rem", display: "block" }, topRow: { display: "flex", alignItems: "center", gap: 10, marginBottom: "1.75rem" }, backBtn: { background: "none", border: "none", color: COLORS.taupe, fontSize: 13, cursor: "pointer", padding: 0, whiteSpace: "nowrap" }, progressWrap: { flex: 1, height: 2, background: "#e8e5e0", borderRadius: 2, overflow: "hidden" }, progressBar: (pct) => ({ height: "100%", background: COLORS.taupe, borderRadius: 2, width: `${pct}%`, transition: "width 0.4s ease" }), qNum: { fontSize: 11, color: COLORS.coolGray, whiteSpace: "nowrap" }, h1: { fontFamily: "'Oswald','Impact',sans-serif", fontSize: 26, fontWeight: 500, color: COLORS.black, lineHeight: 1.25, marginBottom: "0.5rem" }, sub: { fontSize: 14, color: COLORS.midTaupe, marginBottom: "1.25rem", lineHeight: 1.6 }, btn: { display: "block", width: "100%", padding: "13px 16px", border: "1px solid #dddbd7", borderRadius: 6, background: "#fff", color: COLORS.black, fontSize: 14, textAlign: "left", cursor: "pointer", marginBottom: 8, lineHeight: 1.5, transition: "all 0.15s" }, btnSel: { background: COLORS.black, color: COLORS.cream, borderColor: COLORS.black }, btnPrimary: { display: "block", width: "100%", padding: "14px 20px", border: "none", borderRadius: 6, background: COLORS.black, color: COLORS.cream, fontSize: 15, textAlign: "center", cursor: "pointer", marginTop: 12, fontFamily: "'Oswald','Impact',sans-serif", letterSpacing: "0.05em" }, textarea: { width: "100%", padding: "12px 14px", border: "1px solid #dddbd7", borderRadius: 6, fontSize: 14, fontFamily: "'Proxima Nova','Helvetica Neue',sans-serif", resize: "vertical", minHeight: 110, color: COLORS.black, outline: "none", boxSizing: "border-box", lineHeight: 1.6 }, skip: { background: "none", border: "none", color: COLORS.coolGray, fontSize: 13, cursor: "pointer", padding: 0, marginTop: 8, textDecoration: "underline", display: "block" }, tag: { display: "inline-block", fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", color: COLORS.taupe, border: `1px solid ${COLORS.taupe}`, borderRadius: 4, padding: "3px 10px", marginBottom: 14 }, divider: { border: "none", borderTop: "1px solid #e0ddd9", margin: "1.5rem 0" }, stepLabel: { display: "flex", alignItems: "center", gap: 8, marginBottom: 10 }, stepDot: { width: 22, height: 22, borderRadius: "50%", background: COLORS.taupe, color: "#fff", fontSize: 12, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, fontFamily: "sans-serif" }, stepText: { fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", color: COLORS.taupe }, resultH: { fontFamily: "'Oswald','Impact',sans-serif", fontSize: 28, fontWeight: 500, color: COLORS.black, lineHeight: 1.15, marginBottom: "0.4rem" }, resultTagline: { fontSize: 15, color: COLORS.taupe, fontStyle: "italic", marginBottom: "1rem" }, resultDesc: { fontSize: 15, color: COLORS.black, lineHeight: 1.7, marginBottom: "1.5rem" }, secondaryBox: { border: "1px solid #dddbd7", borderRadius: 8, padding: "16px 18px", marginBottom: "1.25rem", background: "#faf9f7" }, healthBox: { border: `1px solid ${COLORS.taupe}`, borderRadius: 8, padding: "16px 18px", marginBottom: "1.25rem", background: COLORS.cream }, cardLabel: { fontSize: 11, letterSpacing: "0.12em", textTransform: "uppercase", color: COLORS.taupe, marginBottom: 6 }, cardTitle: { fontSize: 16, fontWeight: 600, color: COLORS.black, marginBottom: 4 }, cardDesc: { fontSize: 14, color: COLORS.midTaupe, lineHeight: 1.6 }, communityBox: { background: COLORS.black, borderRadius: 8, padding: "18px 20px", marginBottom: "1.25rem" }, communityLabel: { fontSize: 11, letterSpacing: "0.12em", textTransform: "uppercase", color: COLORS.taupe, marginBottom: 8 }, communityText: { fontSize: 14, color: COLORS.cream, lineHeight: 1.7, fontStyle: "italic" }, linkBtn: { background: "none", border: "none", color: COLORS.taupe, fontSize: 13, cursor: "pointer", padding: 0, textDecoration: "underline" }, }; if (result) { let stepNum = 1; const totalSteps = [true, !!result.secondary, result.hasMembersAlready, true].filter(Boolean).length; return (
Foundations first. Always. That's how this works.
); } if (!started) { return (
10 questions. About 3 minutes. The more honest you are, the better your result.
); } return (
50% Complete
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.