// screens.jsx — LAUNCH app screens // All five screens + shared primitives. Exports to window for app.jsx. const { useState } = React; // ───────────────────────────────────────────────────────────── // Social links — edit the URLs here to point at the real profiles. // (TikTok not added yet — drop a new entry here with its profile URL.) // ───────────────────────────────────────────────────────────── const SOCIAL_LINKS = [ { id: 'facebook', label: 'Facebook', url: 'https://www.facebook.com/profile.php?id=61588404807330', icon: () => ( ), }, { id: 'instagram', label: 'Instagram', url: 'https://www.instagram.com/launchnash/', icon: () => ( ), }, ]; // Reusable row of social icons — used at the top and bottom of Home. function SocialBar({ theme }) { return (
{SOCIAL_LINKS.map(s => ( {s.icon()} ))}
); } // ───────────────────────────────────────────────────────────── // Theme helpers // ───────────────────────────────────────────────────────────── function useTheme(t) { const dark = t.dark; return { dark, bg: dark ? '#0A0A0A' : '#EDE7DC', surface: dark ? '#161616' : '#FFFFFF', surface2: dark ? '#1F1F1F' : '#DDD4C2', border: dark ? 'rgba(255,255,255,0.08)' : 'rgba(10,10,10,0.10)', text: dark ? '#FFFFFF' : '#0A0A0A', muted: dark ? 'rgba(255,255,255,0.55)' : 'rgba(10,10,10,0.55)', faint: dark ? 'rgba(255,255,255,0.30)' : 'rgba(10,10,10,0.30)', accent: t.accent, onAccent: '#0A0A0A', accent2: '#CAFF33', fontDisplay: t.fontDisplay, fontBody: t.fontBody, fontMono: '"JetBrains Mono", ui-monospace, monospace', pad: t.density === 'compact' ? 12 : t.density === 'comfy' ? 22 : 16, gap: t.density === 'compact' ? 10 : t.density === 'comfy' ? 18 : 14, radius: 18 }; } // Copy bank — toggles between hype and pro voice const COPY = { hype: { greeting: "WHAT'S GOOD", streakCaption: 'DAY STREAK · DON\'T BREAK IT', todayKicker: "TODAY'S FOCUS", todayCTA: 'BOOK SESSION', upNext: 'UP NEXT', drops: 'DROPS FROM THE SQUAD', completeCTA: 'SUBMIT FOR REVIEW · CLAIM XP', submit: 'DROP YOUR TAKE', roadmapKicker: 'YOUR LAUNCH PATH', roadmapTitle: 'FROM BEDROOM TO BILLBOARD', communityTitle: 'THE FEED', profileTier: 'RISING ARTIST', profileSub: '11 months in · keep cooking' }, pro: { greeting: 'Good morning', streakCaption: 'consecutive practice days', todayKicker: "Today's focus", todayCTA: 'Book session', upNext: 'Up next', drops: 'Recent releases', completeCTA: 'Submit for review', submit: 'Submit your work', roadmapKicker: 'Career roadmap', roadmapTitle: 'Your development path', communityTitle: 'Community', profileTier: 'Rising Artist', profileSub: 'Member since June 2025' } }; // ───────────────────────────────────────────────────────────── // Shared bits // ───────────────────────────────────────────────────────────── // Deterministic hash → keeps the same celestial body for the same label function hashLabel(s) { let h = 0; const str = String(s || ''); for (let i = 0; i < str.length; i++) h = ((h << 5) - h + str.charCodeAt(i)) | 0; return Math.abs(h); } function CoverArt({ palette, label, sub, size = 56, radius = 10 }) { // Space-themed cover backed by the JWST deep-field photo. // Each label gets a deterministic crop offset + tint so tiles feel varied // but stable — same artist always looks the same. const h = hashLabel(label); const variant = h % 5; // 0=planet, 1=ringed, 2=crescent moon, 3=constellation, 4=binary const [c1, c2] = palette; const isLarge = size > 90; // Pan + scale across the deep-field so adjacent tiles don't repeat const bgScale = 2.6; const bgX = -((h % 71) / 71) * (bgScale - 1) * 100; const bgY = -(((h >>> 5) % 79) / 79) * (bgScale - 1) * 100; const rotate = ((h >>> 9) % 360); const bodyId = `cv-${h}`; const haloId = `hl-${h}`; return (
{/* JWST deep-field background — scaled + rotated for variety */}
{/* Color tint overlay — uses the artist palette so every cover still reads as "their" tile while sharing the cosmic backdrop. */}
{/* Bottom darkening for text legibility */}
{/* Celestial subject — sits on top of the deep field */} {/* Variant 0 — solid planet */} {variant === 0 && ( )} {/* Variant 1 — ringed (Saturn) */} {variant === 1 && ( )} {/* Variant 2 — crescent moon */} {variant === 2 && ( )} {/* Variant 3 — constellation */} {variant === 3 && ( {[[28,22],[50,30],[72,24],[78,42],[58,52],[42,48]].map(([x,y], i) => ( ))} )} {/* Variant 4 — binary planets */} {variant === 4 && ( )} {/* Label */}
{label}
{sub && isLarge && (
{sub}
)}
); } function Chip({ children, theme, accent = false, mono = true, size = 'sm' }) { return ( {children}); } function Card({ children, theme, style = {} }) { return (
{children}
); } function DisplayHeading({ children, theme, size = 36, style = {} }) { return (

{children}

); } function Waveform({ accent, muted, bars = 42, played = 0.4 }) { // deterministic pseudo-random heights const heights = Array.from({ length: bars }, (_, i) => { const v = Math.sin(i * 1.3) * 0.5 + Math.cos(i * 0.7) * 0.4 + Math.sin(i * 0.31) * 0.3; return Math.max(0.15, Math.min(1, (v + 1.2) / 2.2)); }); return (
{heights.map((h, i) =>
)}
); } // SVG icon set — flat, 2px stroke const Icon = { home: (c) => , play: (c) => , target: (c) => , community: (c) => , user: (c) => , flame: (c) => , bolt: (c) => , check: (c) => , lock: (c) => , heart: (c, f) => , chat: (c) => , share: (c) => , arrow: (c) => , search: (c) => , bell: (c) => , plus: (c) => , pen: (c) => }; // ───────────────────────────────────────────────────────────── // Tab bar — bottom navigation // ───────────────────────────────────────────────────────────── function TabBar({ active, onChange, theme }) { const tabs = [ { id: 'home', icon: Icon.home, label: 'Home' }, { id: 'create', icon: Icon.pen, label: 'Create' }, { id: 'feed', icon: Icon.community, label: 'Feed' }, { id: 'profile', icon: Icon.user, label: 'You' }]; return (
{tabs.map((t) => { const on = active === t.id; return ( ); })}
); } // ───────────────────────────────────────────────────────────── // Top bar — used per screen (kicker + actions) // ───────────────────────────────────────────────────────────── function TopBar({ left, right, theme }) { return (
{left}
{right}
); } function IconBtn({ children, theme, onClick }) { return ( ); } // ───────────────────────────────────────────────────────────── // 1. HOME // ───────────────────────────────────────────────────────────── function HomeScreen({ theme, copy, onOpenLesson, logged, onToggleToday, onOpenReview, onOpenMessages, onOpenLeaderboard, onOpenCoachQueue, onOpenEvaluations, onOpenPlans, unreadCount = 0 }) { const HabitTracker = window.LAUNCH_SCREENS_2 && window.LAUNCH_SCREENS_2.HabitTracker; const ProReviewCTA = window.LAUNCH_SCREENS_3 && window.LAUNCH_SCREENS_3.ProReviewCTA; const LeaderboardCard = window.LAUNCH_SCREENS_6 && window.LAUNCH_SCREENS_6.LeaderboardCard; const ProfilePlanCard = window.LAUNCH_SCREENS_4 && window.LAUNCH_SCREENS_4.ProfilePlanCard; const user = window.LAUNCH_USER.useUser(); const plan = window.LAUNCH_USER.usePlan ? window.LAUNCH_USER.usePlan() : { plan: 'free', active: false, expiresAt: null }; const isCoach = user.role === 'coach' || user.role === 'admin'; // Notice when a new coach review has arrived (vs the last one the user opened). const [newReview, setNewReview] = React.useState(null); // null | { id } React.useEffect(() => { let alive = true; if (window.LAUNCH_USER.fetchMyEvaluations) { window.LAUNCH_USER.fetchMyEvaluations().then(list => { if (!alive || !Array.isArray(list) || !list.length) return; const latestId = String(list[0].id); let seen = ''; try { seen = localStorage.getItem('launch.seenReview') || ''; } catch (e) {} if (latestId !== seen) setNewReview({ id: latestId }); }); } return () => { alive = false; }; }, []); const openReviews = () => { if (newReview) { try { localStorage.setItem('launch.seenReview', newReview.id); } catch (e) {} setNewReview(null); } onOpenEvaluations && onOpenEvaluations(); }; return (
MON · MAY 11
{copy.greeting}, {user.firstName.toUpperCase()}
} right={<> {Icon.search(theme.text)}
{unreadCount > 0 && (
{unreadCount}
)}
} /> {/* Membership — surfaced near the top so it's one of the first things seen */} {ProfilePlanCard && onOpenPlans && !isCoach &&
{plan.active ? '★ YOUR MEMBERSHIP' : '★ UNLOCK EVERYTHING'}
} {/* Coach-only: review queue entry point */} {isCoach && onOpenCoachQueue && (
)} {/* New coach review notice */} {newReview && onOpenEvaluations && (
)} {/* Social links — top */}
{/* Streak hero band */}
{user.streak}
{copy.streakCaption}
{user.streak >= 7 ? "YOU'RE ON FIRE" : user.streak <= 1 ? 'DAY ONE · LET\u2019S GO' : 'KEEP IT LIT'}
{Icon.flame(theme.onAccent)}
{/* Today's mission card */}
{copy.todayKicker} DAY {user.dayOfPath} / {user.pathLength}
{/* deep field bg */}
PRO REVIEW BLIND MATCH
GET YOUR
NEXT SONG
HEARD.
$11
Submit to a pro
VERIFIED INDUSTRY PRO · ANONYMOUS MATCH
{/* Habit tracker — compact */} {HabitTracker &&
} {/* Stat strip */}
{[ { n: String(user.reviewsPending), l: 'REVIEWS\nPENDING' }, { n: String(user.sessionsThisWeek), l: 'SESSIONS\nTHIS WEEK' }, { n: user.xpEarnedToday.toLocaleString(), l: 'XP\nEARNED' }]. map((s, i) =>
{s.n}
{s.l}
)}
{/* Pro review CTA */} {ProReviewCTA && onOpenReview &&
} {/* Weekly leaderboard */} {LeaderboardCard && onOpenLeaderboard &&
} {/* Up next — hidden */} {false &&
{copy.upNext}
{[ { kind: '1:1 MENTOR', t: 'Topline check-in with PG', s: 'Wed · 4:00 PM · 60 min · $100', pal: ['#7A5AE0', '#FBBF24'], lbl: 'PG', tag: 'TOMORROW' }, { kind: 'REVIEW', t: 'A&R feedback ready', s: 'From Jay Brunswick · played 2× already', pal: ['#C72820', '#7A1A0A'], lbl: 'JB', tag: 'NEW' }, { kind: 'A&R PANEL', t: 'Live demo review · open mic', s: 'Fri · 7 PM EST · 4 slots left', pal: ['#CAFF33', '#161616'], lbl: 'AR', tag: 'LIVE' }]. map((u, i) =>
{u.kind}
{u.t}
{u.s}
{u.tag}
)}
} {/* Drops from the squad */}
{copy.drops} COMING SOON
{[ { lbl: 'KP', pal: ['#C72820', '#0A0A0A'], t: 'NEON HOURS', a: 'Kai Park' }, { lbl: 'SV', pal: ['#CAFF33', '#7A5AE0'], t: 'SOFT POWER', a: 'Sasha Vee' }, { lbl: 'TM', pal: ['#FBBF24', '#C72820'], t: 'OUTER ROOMS', a: 'Theo M.' }, { lbl: 'IL', pal: ['#7A5AE0', '#CAFF33'], t: 'LOW TIDE', a: 'Isla Loop' }]. map((d, i) =>
{d.t}
{d.a.toUpperCase()}
)}
{/* Social links — bottom */}
); } // ───────────────────────────────────────────────────────────── // 2. TODAY'S LESSON // ───────────────────────────────────────────────────────────── function LessonScreen({ theme, copy, onBack, onOpenReview }) { const ProReviewCTA = window.LAUNCH_SCREENS_3 && window.LAUNCH_SCREENS_3.ProReviewCTA; const [chapter, setChapter] = useState(2); const chapters = [ { t: 'Intro: why mixing matters', dur: '2:14', done: true }, { t: 'Setting up your session', dur: '4:30', done: true }, { t: 'Gain staging your stems', dur: '6:10', done: false, current: true }, { t: 'EQ: carve, don\'t boost', dur: '5:45', done: false }, { t: 'Glue compression on the bus', dur: '3:20', done: false }]; return (
{/* Hero / video */}
MODULE 4 · LESSON 12
{/* Play button */}
{/* Title */}
22 MIN · 5 CHAPTERS · +120 XP
MIXING YOUR
FIRST SINGLE
{/* Instructor row */}
JB
Jay Brunswick
PRODUCER · A&R · VINTAGE MUSIC GROUP
{/* Chapters */}
CHAPTERS
{chapters.map((c, i) => { const isCur = i === chapter; return (
setChapter(i)} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 12px', borderRadius: 12, background: isCur ? theme.surface : 'transparent', border: `1px solid ${isCur ? theme.border : 'transparent'}`, cursor: 'pointer' }}>
{c.done ? Icon.check(theme.onAccent) : isCur ? : {i + 1} }
{c.t}
{c.dur}
); })}
{/* Assignment */}
ASSIGNMENT
{Icon.bolt(theme.accent)} DUE FRIDAY · +250 XP
MIX A 30-SEC SNIPPET
OF YOUR OWN TRACK
Use what you learned. Drop it in the feed for Noah + the squad to review. Best mix gets pinned this week.
{ProReviewCTA && onOpenReview &&
}
{/* Complete CTA */}
); } // Shared avatar: shows the member's photo when available, else their initials on // their color. Used everywhere a person's face appears so profile photos show up // app-wide. `m` is a member/profile object ({ avatarUrl, lbl/initials, color }). function Avatar({ m, src, lbl, color, size = 40, radius = 99, fontSize, theme, style }) { const url = src || (m && m.avatarUrl); const label = lbl || (m && (m.lbl || m.initials)) || ''; const bg = color || (m && m.color) || '#888'; const base = { width: size, height: size, borderRadius: radius, flexShrink: 0, ...style }; if (url) { return
; } return (
{label}
); } window.LAUNCH_SCREENS_1 = { HomeScreen, LessonScreen, useTheme, COPY, TabBar, Icon, Card, Chip, CoverArt, Waveform, TopBar, IconBtn, DisplayHeading, Avatar };