// onboarding.jsx — First-entry flow: splash → carousel → auth → setup → reveal const { useState: useStateO, useEffect: useEffectO, useRef: useRefO } = React; const { Card, Chip, DisplayHeading, Icon } = window.LAUNCH_SCREENS_1; // Genre options — for selection step const GENRES = [ { id: 'pop', l: 'POP', pal: ['#C72820', '#FBBF24'] }, { id: 'rb', l: 'R&B / SOUL', pal: ['#7A5AE0', '#C72820'] }, { id: 'hiphop', l: 'HIP HOP / RAP', pal: ['#0A0A0A', '#C72820'] }, { id: 'electronic',l: 'ELECTRONIC', pal: ['#CAFF33', '#7A5AE0'] }, { id: 'indie', l: 'INDIE / ALT', pal: ['#FBBF24', '#7A5AE0'] }, { id: 'country', l: 'COUNTRY', pal: ['#C72820', '#FBBF24'] }, { id: 'rock', l: 'ROCK', pal: ['#0A0A0A', '#7A5AE0'] }, { id: 'jazz', l: 'JAZZ / NEO', pal: ['#7A5AE0', '#FBBF24'] }, ]; const GOALS = [ { id: 'release', l: 'Release my first single', icon: '🎵' }, { id: 'grow', l: 'Grow my audience', icon: '📈' }, { id: 'monetize', l: 'Make money from music', icon: '💰' }, { id: 'craft', l: 'Sharpen my craft', icon: '🎙' }, { id: 'collab', l: 'Find collaborators', icon: '🤝' }, { id: 'label', l: 'Get signed', icon: '⭐' }, ]; const EXPERIENCE = [ { id: 'starting', l: 'Just starting out', sub: 'Less than 6 months in' }, { id: 'demoing', l: 'Making demos', sub: '6 months — 2 years' }, { id: 'releasing', l: 'Released a few tracks', sub: '2+ years, some music out' }, { id: 'building', l: 'Building real momentum', sub: 'Touring, streams, fans' }, ]; // ──────────────────────────────────────────────────────── // 0. SPLASH — pure black with the LAUNCH logo, modern + minimal // ──────────────────────────────────────────────────────── function SplashScreen({ theme, onContinue }) { // Auto-advance after the entry animation useEffectO(() => { const t = setTimeout(onContinue, 3200); return () => clearTimeout(t); }, [onContinue]); return (
{/* Faint star drift — purely atmospheric */} {Array.from({ length: 50 }, (_, i) => { const x = (i * 71) % 400; const y = (i * 137) % 880; const r = (i % 7 === 0) ? 1.6 : (i % 3 === 0) ? 1 : 0.5; return ( ); })} {/* Soft red bloom anchored where the flame is */}
{/* Logo — the hero */}
LAUNCH
{/* Tagline */}
★ ARTIST DEVELOPMENT CO ★
For artists who don't wait to be discovered.
{/* Loader bar */}
IGNITION · TAP TO SKIP
); } // ──────────────────────────────────────────────────────── // 1. WELCOME CAROUSEL — 3 swipeable value-prop slides // ──────────────────────────────────────────────────────── function WelcomeCarousel({ theme, onContinue, onSignIn }) { const [step, setStep] = useStateO(0); const slides = [ { kicker: '★ WHY LAUNCH', title: <>FROM
BEDROOM
TO BILLBOARD., blurb: 'A development company built for artists in their twenties. Lessons, mentors, and a squad that actually wants you to win.', art: 'rocket', }, { kicker: '★ REAL MENTORS', title: <>SIGNED PROS.
HONEST
FEEDBACK., blurb: '4× Grammy producers. Top 40 songwriters. A&R reps from the labels you actually want to be on. Coaching, not influencer fluff.', art: 'orbit', }, { kicker: '★ THE SQUAD', title: <>BUILD WITH
2K+ ARTISTS
LIKE YOU., blurb: 'Drop tracks. Get feedback. Climb the leaderboard. Find your collaborators before you find your label.', art: 'constellation', }, ]; const cur = slides[step]; // Art renderers const renderArt = (kind) => { if (kind === 'rocket') { return ( {/* stars */} {[[30,40,1],[200,60,1.2],[60,160,1],[180,150,0.8],[120,30,0.7]].map(([x,y,r], i) => )} {/* planet */} {/* trail */} {/* rocket body */} {/* fins */} {/* flame */} ); } if (kind === 'orbit') { return ( {/* orbit rings */} {/* sun */} {/* small planets / mentors */} {[ { x: 220, y: 100, r: 8, c: '#7A5AE0' }, { x: 50, y: 100, r: 7, c: '#CAFF33' }, { x: 120, y: 62, r: 6, c: '#FBBF24' }, { x: 170, y: 126, r: 5, c: '#C72820' }, ].map((p, i) => ( ))} {/* stars */} {[[30,30,0.8],[210,40,1],[40,170,0.7],[200,170,0.9]].map(([x,y,r], i) => )} ); } // constellation return ( {/* connecting lines */} {/* member nodes */} {[ [40,60,'SV','#CAFF33'], [90,40,'KP','#C72820'], [130,80,'TM','#7A5AE0'], [180,50,'IL','#FBBF24'], [210,100,'MS','#C72820'], [160,140,'JE','#22C55E'], [100,150,'NB','#EC4899'], [50,120,'RL','#06B6D4'], ].map(([x,y,lbl,c], i) => ( {lbl} ))} ); }; return (
{/* bg gradient */}
{/* stars */} {Array.from({ length: 50 }, (_, i) => { const x = (i * 71) % 400; const y = (i * 157) % 880; const r = (i % 5 === 0) ? 1.2 : 0.6; return ; })} {/* Skip / sign in */}
LAUNCH.
{/* Art */}
{renderArt(cur.art)}
{/* Copy */}
{cur.kicker}
{cur.title}
{cur.blurb}
{/* Dots + CTA */}
{slides.map((_, i) => (
))}
); } // ──────────────────────────────────────────────────────── // 2. AUTH — sign up / sign in // ──────────────────────────────────────────────────────── function AuthScreen({ theme, mode = 'signup', onContinue, onBack, onSwitchMode, onOAuth, busy, errorMsg, noticeMsg }) { const [email, setEmail] = useStateO(''); const [password, setPassword] = useStateO(''); const [name, setName] = useStateO(''); const [handle, setHandle] = useStateO(''); const [keepSignedIn, setKeepSignedIn] = useStateO(true); const inputStyle = { width: '100%', padding: '14px 16px', borderRadius: 12, background: 'rgba(255,255,255,0.06)', border: '1px solid rgba(255,255,255,0.12)', color: '#fff', fontFamily: theme.fontBody, fontSize: 15, outline: 'none', boxSizing: 'border-box', }; return (
{/* stars */} {Array.from({ length: 40 }, (_, i) => { const x = (i * 53) % 400; const y = (i * 113) % 880; const r = (i % 5 === 0) ? 1.2 : 0.6; return ; })} {/* Top bar */}
{mode === 'signup' ? 'CREATE ACCOUNT' : 'WELCOME BACK'}
{/* Hero */}
★ {mode === 'signup' ? 'STEP 1 OF 4' : 'BOARDING PASS'}
{mode === 'signup' ? <>LET'S GET
YOU IN. : <>WELCOME
BACK.}
{/* OAuth */}
{[ { l: 'CONTINUE WITH APPLE', p: 'apple', icon: ( )}, { l: 'CONTINUE WITH GOOGLE', p: 'google', icon: ( )}, ].map((opt, i) => ( ))}
{/* Divider */}
OR EMAIL
{/* Form */}
{mode === 'signup' && (
setName(e.target.value)} placeholder="e.g. Maya Stokes" style={inputStyle} />
)} {mode === 'signup' && (
@ setHandle(e.target.value.replace(/\s/g, '.').toLowerCase())} placeholder="your.name" style={{ ...inputStyle, paddingLeft: 32 }} />
)}
setEmail(e.target.value)} placeholder="you@email.com" type="email" style={inputStyle} />
setPassword(e.target.value)} placeholder={mode === 'signup' ? 'At least 6 characters' : 'Your password'} type="password" style={inputStyle} />
{errorMsg && (
{errorMsg}
)} {noticeMsg && (
📧{noticeMsg}
)} {/* Keep me signed in */}
{/* Switch mode */}
); } // ──────────────────────────────────────────────────────── // 3. SETUP STEPS — genre, experience, goal // ──────────────────────────────────────────────────────── function SetupScreen({ theme, onContinue, onBack }) { const [step, setStep] = useStateO(0); const [genres, setGenres] = useStateO([]); const [experience, setExperience] = useStateO(null); const [goals, setGoals] = useStateO([]); const steps = ['genre', 'experience', 'goal']; const cur = steps[step]; // Toggle an id in/out of a multi-select array. const toggle = (setFn) => (id) => setFn(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]); const toggleGenre = toggle(setGenres); const toggleGoal = toggle(setGoals); const advance = () => { if (step < steps.length - 1) setStep(step + 1); // Keep `genre`/`goal` (first pick) for backward compatibility, and pass // the full multi-select arrays as `genres`/`goals`. else onContinue({ genre: genres[0], experience, goal: goals[0], genres, goals }); }; const canAdvance = (cur === 'genre' && genres.length > 0) || (cur === 'experience' && experience) || (cur === 'goal' && goals.length > 0); return (
{/* Top bar */}
{/* Progress */}
{steps.map((_, i) => (
))}
{step + 2}/4
{/* Step content */}
{cur === 'genre' && ( <>
★ YOUR SOUND
WHAT DO YOU
MAKE?
Pick every genre you make. You can refine later.
{GENRES.map(g => { const on = genres.includes(g.id); return ( ); })}
)} {cur === 'experience' && ( <>
★ YOUR ORBIT
WHERE YOU
AT NOW?
Honesty unlocks better matches.
{EXPERIENCE.map(e => { const on = experience === e.id; return ( ); })}
)} {cur === 'goal' && ( <>
★ YOUR MISSION
WHAT'S YOUR
NORTH STAR?
Pick everything that matters to you right now.
{GOALS.map(g => { const on = goals.includes(g.id); return ( ); })}
)}
{/* CTA */}
); } // ──────────────────────────────────────────────────────── // 4. REVEAL — "You're in" hype screen → enter app // ──────────────────────────────────────────────────────── function RevealScreen({ theme, onEnter }) { return (
{/* dramatic bg */}
{/* stars */} {Array.from({ length: 60 }, (_, i) => { const x = (i * 47) % 400; const y = (i * 131) % 880; const r = (i % 5 === 0) ? 1.5 : 0.7; return ; })} {/* Confetti-ish glyphs */} {[ [60, 200, '#CAFF33'], [320, 180, '#FBBF24'], [80, 380, '#7A5AE0'], [310, 420, theme.accent], [40, 580, '#FBBF24'], [340, 600, '#CAFF33'], ].map(([x, y, c], i) => ( ))} {/* Top */}
★ LIFTOFF CONFIRMED ★
YOU'RE
IN.
Your roadmap is ready. Your squad is waiting.
Day 1 starts now.
{/* Stat strip */}
{[ { n: '90', l: 'DAY\nPATH' }, { n: '4', l: 'MENTORS\nMATCHED' }, { n: '2K+', l: 'SQUAD\nMEMBERS' }, ].map((s, i) => (
{s.n}
{s.l}
))}
{/* CTA */}
); } // ──────────────────────────────────────────────────────── // Welcome-back splash — for returning, already-signed-in users // ──────────────────────────────────────────────────────── function WelcomeBackSplash({ theme, handle, onContinue }) { const user = (window.LAUNCH_USER && window.LAUNCH_USER.loadProfile()) || { streak: 0 }; useEffectO(() => { const t = setTimeout(onContinue, 1800); return () => clearTimeout(t); }, [onContinue]); return (
{/* stars */} {Array.from({ length: 40 }, (_, i) => { const x = (i * 71) % 400; const y = (i * 137) % 880; const r = (i % 5 === 0) ? 1.2 : 0.5; return ; })} {/* red bloom */}
{/* Logo */}
LAUNCH
{/* Welcome back */}
★ WELCOME BACK ★
@{handle}
{user.streak > 1 ? `Day ${user.streak} of the streak. Let's go.` : "Let's get that streak started."}
{/* Loader */}
SYNCING · TAP TO ENTER
); } // ──────────────────────────────────────────────────────── // Onboarding shell — orchestrates all 4 steps // ──────────────────────────────────────────────────────── function OnboardingFlow({ theme, onDone }) { const U = window.LAUNCH_USER; // Returning user? Skip to a quick "welcome back" splash then app. const wasSignedIn = (() => { try { return localStorage.getItem('launch.signedIn') === '1'; } catch (e) { return false; } })(); const savedHandle = (() => { try { return localStorage.getItem('launch.handle') || 'maya.sounds'; } catch (e) { return 'maya.sounds'; } })(); const [phase, setPhase] = useStateO(wasSignedIn ? 'welcomeback' : 'splash'); const [authMode, setAuthMode] = useStateO('signup'); const [authData, setAuthData] = useStateO({ handle: '', name: '' }); const [busy, setBusy] = useStateO(false); const [errorMsg, setErrorMsg] = useStateO(''); const [noticeMsg, setNoticeMsg] = useStateO(''); // friendly (non-error) message, e.g. "check your email" // Normalize a typed name/handle into a clean handle. const cleanHandle = (h, n) => { let v = (h || '').trim().toLowerCase().replace(/^@/, '').replace(/\s+/g, '.'); if (!v && n) v = n.trim().toLowerCase().replace(/\s+/g, '.'); return v; }; const finishAuth = async ({ keepSignedIn, handle, name, email, password }, isReturning) => { const h = cleanHandle(handle, name) || (email ? email.split('@')[0] : 'maya.sounds'); setAuthData({ handle: h, name: (name || '').trim() }); setErrorMsg(''); setNoticeMsg(''); try { if (keepSignedIn) { localStorage.setItem('launch.signedIn', '1'); localStorage.setItem('launch.handle', h); } else { localStorage.removeItem('launch.signedIn'); } } catch (e) {} // LIVE auth via Supabase if (U.isConfigured()) { if (isReturning && (!email || !password)) { setErrorMsg('Enter your email and password.'); return; } if (!isReturning && (!email || !password)) { setErrorMsg('Email and password are required.'); return; } setBusy(true); try { if (isReturning) { const { error } = await U.signIn({ email, password }); if (error) { setErrorMsg(error.message || 'Could not sign in.'); setBusy(false); return; } setBusy(false); setPhase('reveal'); } else { const { error, needsConfirmation } = await U.signUp({ email, password, handle: h, displayName: (name || U.nameFromHandle(h)).trim(), }); if (error) { setErrorMsg(error.message || 'Could not create account.'); setBusy(false); return; } setBusy(false); if (needsConfirmation) { setErrorMsg(''); setNoticeMsg('We sent a confirmation link to your email. Click it, then sign in to continue.'); setAuthMode('signin'); return; } setPhase('setup'); } } catch (e) { setBusy(false); setErrorMsg('Something went wrong. Check your Supabase keys in config.js.'); } return; } // DEMO auth (no backend configured) if (isReturning) { U.saveProfile({ handle: h, ...(name ? { displayName: name.trim() } : {}), isNew: false }); setPhase('reveal'); } else { U.saveProfile({ handle: h, displayName: (name || U.nameFromHandle(h)).trim(), isNew: true }); setPhase('setup'); } }; const doOAuth = async (provider) => { setErrorMsg(''); setNoticeMsg(''); if (U.isConfigured()) { setBusy(true); const { error } = await U.signInWithProvider(provider); if (error) { setErrorMsg(error.message || 'OAuth failed.'); setBusy(false); } // On success the browser redirects to the provider; nothing more to do. return; } // Demo: pretend OAuth succeeded → go through setup. try { localStorage.setItem('launch.signedIn', '1'); } catch (e) {} U.saveProfile({ handle: provider + '.artist', displayName: U.nameFromHandle(provider + '.artist'), isNew: true }); setPhase('setup'); }; const finishSetup = ({ genre, experience, goal, genres, goals }) => { U.saveProfile({ genre, experience, goal, genres, goals }); setPhase('reveal'); }; if (phase === 'welcomeback') { return ; } if (phase === 'splash') { return setPhase('welcome')} />; } if (phase === 'welcome') { return { setAuthMode('signup'); setPhase('auth'); }} onSignIn={() => { setAuthMode('signin'); setPhase('auth'); }} />; } if (phase === 'auth') { return finishAuth(data, authMode === 'signin')} onOAuth={doOAuth} busy={busy} errorMsg={errorMsg} noticeMsg={noticeMsg} onBack={() => setPhase('welcome')} onSwitchMode={() => { setErrorMsg(''); setNoticeMsg(''); setAuthMode(authMode === 'signup' ? 'signin' : 'signup'); }} />; } if (phase === 'setup') { return setPhase('auth')} />; } return ; } window.LAUNCH_SCREENS_7 = { OnboardingFlow, SplashScreen, WelcomeBackSplash, WelcomeCarousel, AuthScreen, SetupScreen, RevealScreen };