/* Brainstorm X — App shell (connected prototype). Real hash-routed app over the BXStore mock backend: #/ marketing landing #/login dummy magic-link sign-in #/app dashboard (your naming sessions) #/s// a session stage: wizard|invite|arena|shortlist|report Every screen reads/writes through BXStore, so state persists across reloads and each page is genuinely connected to the next. */ (function () { const { useState, useEffect } = React; const DS = window.BrainstormXDesignSystem_f2a458; const { Button, Card, NameCard, KeywordOrb, AvatarStack, Avatar, Badge, Input } = DS; const I = window.BXIcons; const Store = window.BXStore; const { useTweaks, TweaksPanel, TweakSection, TweakToggle, TweakSlider, TweakColor } = window; const STAGES_LIST = ['wizard', 'invite', 'arena', 'shortlist', 'report']; // ---- routing --------------------------------------------------------- function useHash() { const [h, setH] = useState(location.hash || '#/'); useEffect(() => { const f = () => setH(location.hash || '#/'); window.addEventListener('hashchange', f); return () => window.removeEventListener('hashchange', f); }, []); return h; } function parse(h) { const p = h.replace(/^#\/?/, '').split('/').filter(Boolean); if (!p.length) return { name: 'home' }; if (p[0] === 'login') return { name: 'login' }; if (p[0] === 'app') return { name: 'dashboard' }; if (p[0] === 's' && p[1]) return { name: 'session', sessionId: p[1], stage: STAGES_LIST.includes(p[2]) ? p[2] : 'arena' }; return { name: 'home' }; } const nav = (h) => { location.hash = h; }; function ago(t) { const m = Math.round((Date.now() - t) / 60000); if (m < 1) return 'just now'; if (m < 60) return m + 'm ago'; const hr = Math.round(m / 60); if (hr < 24) return hr + 'h ago'; return Math.round(hr / 24) + 'd ago'; } // ---- live user (reactive to store) ----------------------------------- function useUser() { const [u, setU] = useState(Store.snapshot().user); useEffect(() => Store.subscribe((db) => setU(db.user)), []); return u; } function Stars({ n = 50 }) { const stars = React.useMemo(() => Array.from({ length: n }, () => ({ top: Math.random() * 100, left: Math.random() * 100, s: Math.random() * 2 + 1, o: Math.random() * 0.6 + 0.15, glow: Math.random() > 0.88, })), [n]); return
{stars.map((s, i) => )}
; } function Logo({ size = 22 }) { return (
Brainstorm X
); } // ---- marketing landing ---------------------------------------------- function Landing({ user, motion }) { const cta = () => nav(user ? '#/app' : '#/login'); return (
Collaborative AI naming

Brainstorm brand names in a live AI-powered 3D space

Create a naming session, invite your team and watch ideas evolve as everyone votes, boosts and shortlists their favourites.

No password — just a magic link. Free to start.
); } // ---- login (dummy magic link) ---------------------------------------- function Login() { const [email, setEmail] = useState('you@studio.co.uk'); const [stage, setStage] = useState('enter'); // enter | sent | signing const send = (e) => { e.preventDefault(); if (email.trim()) setStage('sent'); }; const open = async () => { setStage('signing'); await Store.signIn(email.trim()); nav('#/app'); }; return (
{stage === 'enter' && (<> Welcome back

Sign in to Brainstorm X

Enter your email and we'll send a secure magic link. No password needed.

setEmail(e.target.value)} iconLeft={} />

Prototype — any email works, nothing is sent.

)} {(stage === 'sent' || stage === 'signing') && (<>

Check your inbox

We sent a magic link to {email}.

)}
); } // ---- dashboard ------------------------------------------------------- function Dashboard({ user }) { const [sessions, setSessions] = useState(null); const reload = () => Store.listSessions().then(setSessions); useEffect(() => { reload(); return Store.subscribe(() => reload()); }, []); return (
{user ? user.email : ''}
Your workspace

Naming sessions

{!sessions &&
Loading sessions…
}
{sessions && sessions.map((s) => ( nav('#/s/' + s.id + '/arena')} style={{ cursor: 'pointer', display: 'flex', flexDirection: 'column', gap: 14 }}>

{s.project}

{s.type} · {s.industry || 'naming'}
Wave {s.wave}
{s.nameCount} names 🔥 {s.onFire} ★ {s.shortlisted} shortlisted
updated {ago(s.updatedAt)}
))} {sessions && ( )}
); } // ---- stage nav (inside a session) ------------------------------------ const STAGE_META = [ { id: 'wizard', label: 'Wizard', ic: 'WandSparkles' }, { id: 'invite', label: 'Invite', ic: 'Users' }, { id: 'arena', label: 'Arena', ic: 'Zap' }, { id: 'shortlist', label: 'Shortlist', ic: 'Star' }, { id: 'report', label: 'Report', ic: 'Layers' }, ]; function StageNav({ sid, stage }) { return (
{STAGE_META.map((s) => { const Ic = I[s.ic]; const on = stage === s.id; return ( ); })}
); } function Loader({ label }) { return (
{label || 'Loading…'}
); } function sessionToMeta(s) { return { project: s.project, type: s.type, industry: s.industry, audience: s.audience, description: s.description, keywordsList: s.keywordsList, avoid: s.avoid, styles: s.styles, randomness: s.randomness, personality: s.personality, }; } // ---- a session + its active stage ------------------------------------ function SessionApp({ sid, stage, tweaks, setTweak }) { const isNew = sid === 'new'; const [session, setSession] = useState(null); const [loading, setLoading] = useState(!isNew); const [busy, setBusy] = useState(false); useEffect(() => { if (isNew) { setSession(null); setLoading(false); return; } let live = true; setLoading(true); Store.getSession(sid).then((s) => { if (live) { setSession(s); setLoading(false); } }); return () => { live = false; }; }, [sid, isNew]); // store-backed setters (persist on every change) const persist = (next) => { Store.saveSessionSync(next); return next; }; const setNames = (upd) => setSession((p) => persist({ ...p, names: typeof upd === 'function' ? upd(p.names) : upd })); const setKeywords = (upd) => setSession((p) => persist({ ...p, keywords: typeof upd === 'function' ? upd(p.keywords) : upd })); const setWave = (upd) => setSession((p) => persist({ ...p, wave: typeof upd === 'function' ? upd(p.wave) : upd })); const goto = (st) => nav('#/s/' + sid + '/' + st); // ---- wizard (create or edit) ---- if (stage === 'wizard') { const onComplete = async (meta) => { setBusy(true); if (isNew) { const sum = await Store.createSession(meta); nav('#/s/' + sum.id + '/invite'); } else { await Store.updateMeta(sid, meta); nav('#/s/' + sid + '/invite'); } }; if (loading) return ; return React.createElement(window.BXWizard, { initial: session ? sessionToMeta(session) : null, busy, onComplete, onCancel: () => nav('#/app'), }); } if (loading) return ; if (!session) { return (

That session could not be found.

); } const shared = { session, project: session.project, names: session.names, setNames, keywords: session.keywords, setKeywords, wave: session.wave, setWave, goto, addMembers: async (emails) => { const m = await Store.addMembers(sid, emails); setSession((p) => ({ ...p, members: m })); return m; }, }; let Screen = null; if (stage === 'invite') Screen = React.createElement(window.BXInvite, shared); else if (stage === 'arena') Screen = React.createElement(window.BXArena, { ...shared, motion: tweaks.motion, density: tweaks.density }); else if (stage === 'shortlist') Screen = React.createElement(window.BXShortlist, shared); else if (stage === 'report') Screen = React.createElement(window.BXReport, shared); return (<>
{Screen}
); } const ACCENTS = ['#2DE2E6', '#FF3DA6', '#8A5BFF', '#A8FF4D']; function Prototype() { const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accent": "#2DE2E6", "motion": true, "density": 8 }/*EDITMODE-END*/; const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const hash = useHash(); const route = parse(hash); const user = useUser(); const demoMode = /(?:[?&])demo=1\b/.test(location.search); // opt-in auto-login for deep links from the test hub (?demo=1) useEffect(() => { if (demoMode && !user) Store.signIn('demo@studio.co.uk'); }, [demoMode, user]); // auth gate useEffect(() => { if ((route.name === 'dashboard' || route.name === 'session') && !user && !demoMode) nav('#/login'); if (route.name === 'login' && user) nav('#/app'); }, [route.name, user, demoMode]); let View = null; if (route.name === 'home') View = ; else if (route.name === 'login') View = ; else if (route.name === 'dashboard') View = user ? : ; else if (route.name === 'session') View = user ? : ; return (
{View} setTweak('accent', v)} /> setTweak('motion', v)} /> setTweak('density', v)} />
); } if (!document.getElementById('bx-proto-kf')) { const s = document.createElement('style'); s.id = 'bx-proto-kf'; s.textContent = '@keyframes bxScreenIn{from{transform:translateY(12px);opacity:.4}to{transform:none;opacity:1}}'; document.head.appendChild(s); } window.BXPrototype = Prototype; })();