// app.jsx — Branching funnel state machine (v2, peptide-first). // The flow is computed from answers (buildFlow) so each goal injects its own // deep-dive block and conditional screens appear/disappear. Navigation is by // screen id (not a fixed index) so insertion never desyncs the position. const { useState: appS, useEffect: appE, useRef: appR } = React; // Branch deep-dive blocks, keyed by primary goal. const GOAL_BLOCKS = { fat_loss: ['fl_bodytype','fl_bodyfat','fl_storage','fl_appetite','fl_glp1','fl_target'], muscle: ['mu_training','mu_bodytype','mu_target','mu_recovery','mu_priority'], recover: ['re_painmap','re_duration','re_cause','re_priortx','re_limiting'], aesthetics: ['ae_skin','ae_hair','ae_sun','ae_routine','ae_bioage'], mind: ['mi_focus','mi_fog','mi_mood','mi_memory','mi_sleeponset'], vitality: ['vi_crash','vi_libido','vi_morning'], unsure: [], }; // Screens that only appear under certain answers. const NEEDS_BODY = ['fat_loss','muscle','aesthetics']; // height/weight relevant function buildFlow(state) { const goal = state.goal; const ids = []; const push = (...x) => ids.push(...x); // 1 — Hook & trust push('landing', 'edu_peptides', 'source'); // 2 — Goal & ambition push('goal'); if (goal) push('subgoals', 'readiness'); // 3 — About you if (goal) { push('sex', 'age'); if (NEEDS_BODY.includes(goal)) push('height', 'weight'); // 4 — Goal deep-dive push(...(GOAL_BLOCKS[goal] || [])); // 5 — Lifestyle push('sleep', 'energy', 'stress', 'activity', 'diet', 'alcohol', 'caffeine', 'nicotine'); // 6 — Medical & readiness push('doctor_intro', 'peptide_history', 'injection_comfort', 'delivery_pref', 'meds', 'conditions'); if (state.sex === 'female') push('pregnancy'); push('supplements'); // 7 — Commitment & projection push('halfway', 'timeline', 'budget', 'projection'); // 8 — Lead capture push('name', 'dob', 'email', 'phone'); // 9 — Reveal & convert push('loading', 'result', 'education', 'cta'); } // id → screen config. Counted screens drive the progress bar. const NO_COUNT = new Set(['landing','edu_peptides','doctor_intro','halfway','projection','loading','result','education','cta']); const NO_BACK = new Set(['landing','loading','result']); return ids.map(id => ({ id, Comp: window['SCREEN_' + id], count: !NO_COUNT.has(id), back: !NO_BACK.has(id), })); } const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accent": "#E8B5A8", "scheme": "light" }/*EDITMODE-END*/; const ACCENT_SWATCHES = ['#E8B5A8','#C68B7C','#E8C76B','#6FD8FF','#FF4A1C','#0A0A0A']; function accentSet(hex) { const n = hex.replace('#',''); const r = parseInt(n.slice(0,2),16), g = parseInt(n.slice(2,4),16), b = parseInt(n.slice(4,6),16); return { soft: `rgba(${r}, ${g}, ${b}, 0.16)`, glow: `rgba(${r}, ${g}, ${b}, 0.45)` }; } function App() { const [curId, setCurId] = appS('landing'); const [state, setState] = appS({}); const [t, setT] = appS(TWEAK_DEFAULTS); // Refs so set()+next() in the same handler see the latest values synchronously. const stateRef = appR(state); stateRef.current = state; const curRef = appR(curId); curRef.current = curId; const setTweak = (k, v) => { const edits = typeof k === 'object' ? k : { [k]: v }; setT(prev => ({ ...prev, ...edits })); window.parent.postMessage({ type: '__edit_mode_set_keys', edits }, '*'); }; appE(() => { const a = accentSet(t.accent); const r = document.documentElement.style; r.setProperty('--accent', t.accent); r.setProperty('--accent-soft', a.soft); r.setProperty('--accent-glow', a.glow); }, [t.accent]); const set = (k, v) => { const ns = { ...stateRef.current, [k]: v }; stateRef.current = ns; setState(ns); }; const scrollTop = () => requestAnimationFrame(() => window.scrollTo({ top: 0, behavior: 'smooth' })); const goTo = (id) => { curRef.current = id; setCurId(id); scrollTop(); }; const next = () => { const flow = buildFlow(stateRef.current); const i = Math.max(0, flow.findIndex(f => f.id === curRef.current)); goTo(flow[Math.min(flow.length - 1, i + 1)].id); }; const back = () => { const flow = buildFlow(stateRef.current); const i = Math.max(0, flow.findIndex(f => f.id === curRef.current)); goTo(flow[Math.max(0, i - 1)].id); }; const flow = buildFlow(state); let idx = flow.findIndex(f => f.id === curId); if (idx < 0) idx = 0; const cur = flow[idx]; const counted = flow.filter(f => f.count); const total = counted.length; const step = flow.slice(0, idx + 1).filter(f => f.count).length; const showHeader = cur.id !== 'loading'; const canBack = cur.back !== false && idx > 0; // Hide the numeric progress until a goal is chosen — the flow expands then, // so an early "1/2" would look misleadingly near-complete. const showProgress = cur.count && !!state.goal; return ( <>
{showHeader && ( )}
{cur.Comp ? :

Missing screen: {cur.id}

}
setTweak('accent', v)} /> ({ value: f.id, label: f.id }))} onChange={(v) => goTo(v)} /> ); } window.SuperstarApp = App;