// SHOT — Main App component (router + layout) (function(){ const { useState: useStateA, useEffect: useEffectA } = React; const { StatusBar, TopNav, BottomNav, Icon, SplashScreen, OnboardingScreen, SignupScreen, VerifyScreen, InterestsScreen } = window.SHOT_SCREENS_1; const { MapScreen, FeedScreen, BarDetailScreen } = window.SHOT_SCREENS_2; const { CheckinScreen, CheckinSuccessScreen, StoryViewerScreen, StoryCreateScreen, EventsScreen, EventDetailScreen, NotificationsScreen, SearchScreen, ProfileScreen, FriendsScreen, EditProfileScreen, SettingsScreen } = window.SHOT_SCREENS_3; const SCREEN_GROUPS = [ { label: 'Onboarding', items: [ { id: 'splash', n: '00', name: 'Splash' }, { id: 'onb', n: '01', name: 'Onboarding' }, { id: 'signup', n: '02', name: 'Sign-up' }, { id: 'verify', n: '03', name: 'Code de vérification' }, { id: 'interests', n: '04', name: 'Centres d\'intérêt' }, ] }, { label: 'Main app', items: [ { id: 'map', n: '05', name: 'Map (Home)' }, { id: 'feed', n: '06', name: 'Feed' }, { id: 'events', n: '07', name: 'Soirées' }, { id: 'profile', n: '08', name: 'Profil' }, ] }, { label: 'Détails & flows', items: [ { id: 'bar', n: '09', name: 'Détail d\'un bar' }, { id: 'event', n: '10', name: 'Détail d\'une soirée' }, { id: 'checkin', n: '11', name: 'Check-in' }, { id: 'checkin-ok', n: '12', name: 'Check-in · confirmé' }, { id: 'story', n: '13', name: 'Story · viewer' }, { id: 'story-create', n: '14', name: 'Story · création' }, ] }, { label: 'Comptes & social', items: [ { id: 'friends', n: '15', name: 'Amis & contacts' }, { id: 'notif', n: '16', name: 'Notifications' }, { id: 'search', n: '17', name: 'Recherche' }, { id: 'edit', n: '18', name: 'Édition profil' }, { id: 'settings', n: '19', name: 'Réglages' }, ] } ]; const SCREEN_META = { splash: { title: '00 · Splash', desc: 'Logo et tagline pendant le chargement initial. Vert sapin saturé pour ancrer la marque dès l\'ouverture.', flow: ['Démarrage app', 'Auto-redirect → Onboarding'] }, onb: { title: '01 · Onboarding', desc: 'Trois écrans pour présenter la promesse : carte vivante, check-in social, soirées. Dots + bouton "Passer".', flow: ['Splash', 'Onboarding 1/2/3', 'Sign-up'] }, signup: { title: '02 · Inscription', desc: 'Téléphone first — pas de mot de passe. On collecte le minimum, on filtre 18+.', flow: ['Onboarding', 'Sign-up', 'Vérification SMS'] }, verify: { title: '03 · Vérification', desc: 'Code à 6 chiffres reçu par SMS. UI rassurante avec re-send timer.', flow: ['Sign-up', 'Vérification', 'Intérêts'] }, interests: { title: '04 · Intérêts', desc: 'Personnalisation du feed et de la carte. Minimum 3 sélections pour valider.', flow: ['Vérification', 'Intérêts', 'Map (premier login)'] }, map: { title: '05 · Map · Home', desc: 'Écran principal. Pins colorés selon l\'ambiance, search bar et filtres en haut, bottom sheet "bars autour de toi".', flow: ['Tap pin → Bar detail', 'Tap search → Recherche', 'Tap + → Story create'] }, feed: { title: '06 · Feed', desc: 'Stories en haut, sections rythmées (Ça bouge / Soirées / Pour toi). Lisible verticalement.', flow: ['Tap story → Viewer', 'Tap bar → Bar detail', 'Tap event → Event detail'] }, bar: { title: '09 · Détail d\'un bar', desc: 'Hero photo + live badge, stories, "ambiance live" (4 scores), avis avec réponse du bar, CTA Check-in sticky en haut.', flow: ['Map / Feed', 'Bar detail', 'Check-in / Story viewer'] }, event: { title: '10 · Détail d\'une soirée', desc: 'Hero coloré, organisateur en pill, infos structurées (quand/où/RSVP), liste d\'avatars, toggle RSVP.', flow: ['Events / Feed', 'Event detail', 'RSVP confirmé'] }, checkin: { title: '11 · Check-in', desc: 'Sélection vibe (4 options visuelles), tag des potes, toggle public/amis. La friction est minimale.', flow: ['Bar detail', 'Check-in', 'Succès → retour Map'] }, 'checkin-ok': { title: '12 · Check-in confirmé', desc: 'Confirmation chaleureuse avec gain de badge. Auto-retour à la map après 2 sec.', flow: ['Check-in form', 'Succès', 'Retour Map (badge gagné)'] }, story: { title: '13 · Story viewer', desc: 'Fullscreen sombre, barres de progression, réponse rapide, like 1-tap.', flow: ['Tap story strip', 'Viewer', 'Auto-next / close'] }, 'story-create': { title: '14 · Story création', desc: 'Caméra + texte + sticker + pin location. La pin auto-détecte le bar courant.', flow: ['Tap + (bottom nav)', 'Compose', 'Publier'] }, events: { title: '07 · Soirées', desc: 'Liste verticale chronologique. Tag BDE/Promo, avatars RSVP, filtres en haut.', flow: ['Bottom nav', 'Events', 'Event detail'] }, profile: { title: '08 · Profil', desc: 'Avatar, stats, bio, tabs (Check-ins / Sauvés / Badges). Grille photo type Instagram.', flow: ['Bottom nav', 'Profil', 'Réglages / Édition'] }, notif: { title: '16 · Notifications', desc: 'Groupées par jour. Unread surligné, icon-bubble colorée selon type (check-in / event / system).', flow: ['Tap cloche', 'Notifications', 'Tap → écran lié'] }, search: { title: '17 · Recherche', desc: 'Recherches récentes, populaires (pills), résultats catégorisés (Bars/Soirées/Amis).', flow: ['Tap search bar', 'Recherche', 'Résultat'] }, friends: { title: '15 · Amis', desc: 'En live en haut (avec dot pulsée), tous les amis, suggestions en bas avec CTA "+ Ajouter".', flow: ['Profil → Amis', 'Amis', 'Détail ami / Ajouter'] }, edit: { title: '18 · Édition profil', desc: 'Tous les champs profil + photo avec icône caméra. Bouton Enregistrer dans la nav.', flow: ['Réglages', 'Édition', 'Retour Profil'] }, settings: { title: '19 · Réglages', desc: 'Groupés par sens : Compte / Confidentialité / Préférences / Aide. Toggle switches custom.', flow: ['Profil → engrenage', 'Réglages', 'Sous-écran'] } }; function App() { const [screen, setScreen] = useStateA('map'); const [tab, setTab] = useStateA('map'); const [barId, setBarId] = useStateA('b1'); const [eventId, setEventId] = useStateA('e1'); const [storyId, setStoryId] = useStateA('s1'); const [onbIdx, setOnbIdx] = useStateA(0); // Theme toggle useEffectA(() => { const saved = localStorage.getItem('shot.theme') || 'dark'; document.documentElement.dataset.theme = saved; const label = document.getElementById('themeLabel'); if (label) label.textContent = saved === 'dark' ? 'Dark' : 'Light'; const toggle = document.getElementById('themeToggle'); const handler = () => { const next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'; document.documentElement.dataset.theme = next; if (label) label.textContent = next === 'dark' ? 'Dark' : 'Light'; localStorage.setItem('shot.theme', next); }; if (toggle) toggle.addEventListener('click', handler); return () => { if (toggle) toggle.removeEventListener('click', handler); }; }, []); // Restore last screen from URL hash so users can deep-link useEffectA(() => { const hash = window.location.hash.slice(1); if (hash && (SCREEN_META[hash] || ['onb','splash','signup','verify','interests'].includes(hash))) { setScreen(hash); if (['map','feed','events','profile'].includes(hash)) setTab(hash); } }, []); useEffectA(() => { window.location.hash = screen; }, [screen]); // Bottom nav tab change const handleTab = (t) => { setTab(t); setScreen(t); }; const goBar = (id) => { setBarId(id); setScreen('bar'); }; const goEvent = (id) => { setEventId(id); setScreen('event'); }; const goStory = (id) => { setStoryId(id); setScreen('story'); }; // Determine whether to show the bottom nav const showBnav = ['map','feed','events','profile'].includes(screen); const hideStatusBar = ['splash','story','story-create'].includes(screen); let body; switch (screen) { case 'splash': body = setScreen('onb')}/>; break; case 'onb': body = setOnbIdx(i => Math.min(i+1, 2))} onSkip={() => setScreen('signup')} onSignup={() => setScreen('signup')} />; break; case 'signup': body = setScreen('onb')} onNext={() => setScreen('verify')}/>; break; case 'verify': body = setScreen('signup')} onNext={() => setScreen('interests')}/>; break; case 'interests': body = setScreen('verify')} onNext={() => { setOnbIdx(0); setScreen('map'); setTab('map'); }}/>; break; case 'map': body = setScreen('search')} goFilter={() => setScreen('search')}/>; break; case 'feed': body = setScreen('notif')}/>; break; case 'events': body = ; break; case 'profile': body = setScreen('friends')} goSettings={() => setScreen('settings')} goEdit={() => setScreen('edit')} goBar={goBar}/>; break; case 'bar': body = setScreen(tab)} goCheckin={() => setScreen('checkin')} goStory={goStory}/>; break; case 'event': body = setScreen('events')} onShare={() => {}}/>; break; case 'checkin': body = setScreen('bar')} onSubmit={() => setScreen('checkin-ok')}/>; break; case 'checkin-ok': body = { setTab('map'); setScreen('map'); }}/>; break; case 'story': body = setScreen(tab === 'feed' ? 'feed' : 'map')}/>; break; case 'story-create': body = setScreen(tab)} onShare={() => setScreen(tab)}/>; break; case 'friends': body = setScreen('profile')}/>; break; case 'notif': body = setScreen(tab)}/>; break; case 'search': body = setScreen(tab)} goBar={goBar}/>; break; case 'edit': body = setScreen('settings')}/>; break; case 'settings': body = setScreen('profile')} goEdit={() => setScreen('edit')}/>; break; default: body = setScreen('search')} goFilter={() => {}}/>; } const meta = SCREEN_META[screen] || SCREEN_META.map; return (
{/* LEFT: screen index */} {/* CENTER: phone */}
{!hideStatusBar && screen !== 'splash' && screen !== 'onb' && false}
{body} {showBnav && ( setScreen('story-create')} /> )}
{/* RIGHT: screen context */}
); } const root = ReactDOM.createRoot(document.getElementById('app-root')); root.render(); })();