// 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 */}
{/* Tagline */}
★ ARTIST DEVELOPMENT CO ★
For artists who don't wait to be discovered.
{/* Loader bar */}
);
}
// ────────────────────────────────────────────────────────
// 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+ ARTISTSLIKE 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 */}
{/* Art */}
{/* Copy */}
{cur.kicker}
{cur.title}
{cur.blurb}
{/* Dots + CTA */}
{slides.map((_, i) => (
))}
step < slides.length - 1 ? setStep(step + 1) : onContinue()}
style={{
width: '100%', background: theme.accent, color: theme.onAccent,
border: 'none', borderRadius: 14,
fontFamily: theme.fontDisplay, fontSize: 16, letterSpacing: 1,
padding: '18px', cursor: 'pointer', textTransform: 'uppercase',
}}>
{step < slides.length - 1 ? 'NEXT →' : 'LET\'S GO →'}
);
}
// ────────────────────────────────────────────────────────
// 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 .>
: <>WELCOMEBACK .>}
{/* OAuth */}
{[
{ l: 'CONTINUE WITH APPLE', p: 'apple', icon: (
)},
{ l: 'CONTINUE WITH GOOGLE', p: 'google', icon: (
)},
].map((opt, i) => (
onOAuth && onOAuth(opt.p)} style={{
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10,
padding: '14px 18px', borderRadius: 12,
background: 'rgba(255,255,255,0.06)', border: '1px solid rgba(255,255,255,0.12)',
color: '#fff', fontFamily: 'JetBrains Mono, monospace', fontSize: 12, fontWeight: 700,
letterSpacing: 1, cursor: 'pointer',
}}>{opt.icon}{opt.l}
))}
{/* Divider */}
{/* Form */}
{mode === 'signup' && (
YOUR NAME
setName(e.target.value)}
placeholder="e.g. Maya Stokes"
style={inputStyle}
/>
)}
{mode === 'signup' && (
)}
EMAIL
setEmail(e.target.value)}
placeholder="you@email.com"
type="email"
style={inputStyle}
/>
PASSWORD
setPassword(e.target.value)}
placeholder={mode === 'signup' ? 'At least 6 characters' : 'Your password'}
type="password"
style={inputStyle}
/>
{errorMsg && (
{errorMsg}
)}
{noticeMsg && (
📧 {noticeMsg}
)}
!busy && onContinue({ keepSignedIn, handle, name, email, password })}
style={{
width: '100%', background: theme.accent, color: theme.onAccent,
border: 'none', borderRadius: 14,
fontFamily: theme.fontDisplay, fontSize: 16, letterSpacing: 1,
padding: '16px', cursor: busy ? 'wait' : 'pointer', textTransform: 'uppercase',
marginTop: 6, opacity: busy ? 0.7 : 1,
}}>
{busy ? 'ONE SEC…' : (mode === 'signup' ? 'CREATE ACCOUNT →' : 'SIGN IN →')}
{/* Keep me signed in */}
setKeepSignedIn(v => !v)}
style={{
display: 'flex', alignItems: 'center', gap: 10,
background: 'transparent', border: 'none', padding: '4px 0',
cursor: 'pointer', marginTop: 4, alignSelf: 'flex-start',
}}>
Keep me signed in
SKIP THE LOGIN NEXT TIME · UNCHECK ON SHARED DEVICES
{/* Switch mode */}
{mode === 'signup'
? <>Already have an account? Sign in >
: <>New to Launch? Create account >}
);
}
// ────────────────────────────────────────────────────────
// 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 */}
step > 0 ? setStep(step - 1) : onBack()} style={{
width: 38, height: 38, borderRadius: 12,
background: theme.surface, border: `1px solid ${theme.border}`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
cursor: 'pointer', flexShrink: 0,
}}>
{/* 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 (
toggleGenre(g.id)} style={{
padding: 0, border: 'none', cursor: 'pointer',
borderRadius: 14, overflow: 'hidden', textAlign: 'left',
position: 'relative', aspectRatio: '1.2',
background: `linear-gradient(135deg, ${g.pal[0]} 0%, ${g.pal[1]} 100%)`,
boxShadow: on ? `0 0 0 3px ${theme.text}, 0 0 0 5px ${theme.accent}` : 'none',
transition: 'box-shadow .15s',
}}>
);
})}
>
)}
{cur === 'experience' && (
<>
★ YOUR ORBIT
WHERE YOU AT NOW?
Honesty unlocks better matches.
{EXPERIENCE.map(e => {
const on = experience === e.id;
return (
setExperience(e.id)} style={{
padding: 16, borderRadius: 14, textAlign: 'left',
border: on ? `2px solid ${theme.accent}` : `1px solid ${theme.border}`,
background: on ? `${theme.accent}15` : theme.surface,
cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 12,
}}>
{e.l}
{e.sub.toUpperCase()}
);
})}
>
)}
{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 (
toggleGoal(g.id)} style={{
padding: '14px 16px', borderRadius: 14, textAlign: 'left',
border: on ? `2px solid ${theme.accent}` : `1px solid ${theme.border}`,
background: on ? `${theme.accent}15` : theme.surface,
cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 12,
}}>
{g.icon}
{g.l}
{on && (
)}
);
})}
>
)}
{/* CTA */}
{step < steps.length - 1 ? 'NEXT →' : 'BUILD MY ROADMAP →'}
);
}
// ────────────────────────────────────────────────────────
// 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'REIN.
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) => (
))}
{/* CTA */}
ENTER LAUNCH →
);
}
// ────────────────────────────────────────────────────────
// 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 */}
{/* Welcome back */}
★ WELCOME BACK ★
@{handle}
{user.streak > 1 ? `Day ${user.streak} of the streak. Let's go.` : "Let's get that streak started."}
{/* Loader */}
);
}
// ────────────────────────────────────────────────────────
// 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 };