@@ -12,6 +12,8 @@ import ProfileHeader from './components/ProfileHeader';
1212import ProfileTab from './components/ProfileTab' ;
1313import RulesPage from './components/RulesPage' ;
1414import SettingsTab from './components/SettingsTab' ;
15+ import StepByStepTutorial , { BATTLE_TUTORIAL_STEPS } from './components/StepByStepTutorial' ;
16+ import TutorialTooltip , { COLLECTION_TUTORIAL_STEPS } from './components/TutorialTooltip' ;
1517import {
1618 ELEMENTAL_TYPES ,
1719 ELEMENTS ,
@@ -94,6 +96,12 @@ const App: React.FC = () => {
9496 const [ rulesPageOpen , setRulesPageOpen ] = useState ( false ) ;
9597 const [ musicVolume , setMusicVolume ] = useState ( 0.2 ) ;
9698
99+ // Tutorial states
100+ const [ showBattleTutorial , setShowBattleTutorial ] = useState ( false ) ;
101+ const [ showCollectionTutorial , setShowCollectionTutorial ] = useState ( false ) ;
102+ const [ hasSeenBattleTutorial , setHasSeenBattleTutorial ] = useState ( false ) ;
103+ const [ hasSeenCollectionTutorial , setHasSeenCollectionTutorial ] = useState ( false ) ;
104+
97105 // Load game state from localStorage on mount
98106 useEffect ( ( ) => {
99107 const saved = localStorage . getItem ( 'elementalGameState' ) ;
@@ -120,6 +128,18 @@ const App: React.FC = () => {
120128 // Failed to load audio settings - using defaults
121129 }
122130 }
131+
132+ // Load tutorial settings from localStorage
133+ const savedTutorialSettings = localStorage . getItem ( 'tutorialSettings' ) ;
134+ if ( savedTutorialSettings ) {
135+ try {
136+ const settings = JSON . parse ( savedTutorialSettings ) ;
137+ setHasSeenBattleTutorial ( settings . hasSeenBattleTutorial || false ) ;
138+ setHasSeenCollectionTutorial ( settings . hasSeenCollectionTutorial || false ) ;
139+ } catch ( error ) {
140+ // Failed to load tutorial settings - using defaults
141+ }
142+ }
123143 } , [ ] ) ;
124144
125145 // Save game state to localStorage whenever player data changes
@@ -140,6 +160,38 @@ const App: React.FC = () => {
140160 ) ;
141161 } , [ musicVolume ] ) ;
142162
163+ // Save tutorial settings to localStorage whenever they change
164+ useEffect ( ( ) => {
165+ localStorage . setItem (
166+ 'tutorialSettings' ,
167+ JSON . stringify ( {
168+ hasSeenBattleTutorial,
169+ hasSeenCollectionTutorial,
170+ } )
171+ ) ;
172+ } , [ hasSeenBattleTutorial , hasSeenCollectionTutorial ] ) ;
173+
174+ // Auto-show tutorial for new users
175+ useEffect ( ( ) => {
176+ if ( activeTab === 'battle' && ! hasSeenBattleTutorial && gameState . gamePhase === 'menu' ) {
177+ const timer = setTimeout ( ( ) => {
178+ setShowBattleTutorial ( true ) ;
179+ } , 5000 ) ; // Show after 5 second delay (after loading screen)
180+ return ( ) => clearTimeout ( timer ) ;
181+ }
182+ return undefined ;
183+ } , [ activeTab , hasSeenBattleTutorial , gameState . gamePhase ] ) ;
184+
185+ useEffect ( ( ) => {
186+ if ( activeTab === 'collection' && ! hasSeenCollectionTutorial ) {
187+ const timer = setTimeout ( ( ) => {
188+ setShowCollectionTutorial ( true ) ;
189+ } , 500 ) ; // Show after 0.5 second delay
190+ return ( ) => clearTimeout ( timer ) ;
191+ }
192+ return undefined ;
193+ } , [ activeTab , hasSeenCollectionTutorial ] ) ;
194+
143195 // Manage modal-open class for rules page
144196 useEffect ( ( ) => {
145197 if ( rulesPageOpen ) {
@@ -321,34 +373,12 @@ const App: React.FC = () => {
321373 gameState . player . selectedLocation as Location
322374 ) ;
323375
324- // For Free location, skip elemental selection and go directly to battle animation
325- if ( wager === 0 ) {
326- const opponentElement = opponent . element || getRandomElement ( ) ;
327- const battleResult = calculateBattleResult (
328- 0 , // baseWager is 0 for Free location
329- gameState . player . selectedElement as Element ,
330- null , // no elemental for Free battles
331- opponentElement ,
332- opponent . elemental || null ,
333- gameState . player . elementalCollection
334- ) ;
335-
336- setGameState ( prev => ( {
337- ...prev ,
338- currentOpponent : opponent ,
339- opponentElement,
340- initialBattleMana : prev . player . mana ,
341- battleResult : battleResult . winner ,
342- gamePhase : 'battleAnimation' ,
343- } ) ) ;
344- } else {
345- // For paid locations, show elemental selection
346- setGameState ( prev => ( {
347- ...prev ,
348- currentOpponent : opponent ,
349- gamePhase : 'elementalSelection' ,
350- } ) ) ;
351- }
376+ // All locations now go through elemental selection
377+ setGameState ( prev => ( {
378+ ...prev ,
379+ currentOpponent : opponent ,
380+ gamePhase : 'elementalSelection' ,
381+ } ) ) ;
352382 } , [
353383 gameState . player . selectedLocation ,
354384 gameState . player . selectedElement ,
@@ -685,11 +715,45 @@ const App: React.FC = () => {
685715 } ) ;
686716 } , [ ] ) ;
687717
718+ // Tutorial handlers
719+ const handleBattleTutorialComplete = useCallback ( ( ) => {
720+ setShowBattleTutorial ( false ) ;
721+ setHasSeenBattleTutorial ( true ) ;
722+ // Emit event to notify BattleComponent about tutorial completion
723+ window . dispatchEvent ( new CustomEvent ( 'battleTutorialCompleted' ) ) ;
724+ } , [ ] ) ;
725+
726+ const handleBattleTutorialSkip = useCallback ( ( ) => {
727+ setShowBattleTutorial ( false ) ;
728+ setHasSeenBattleTutorial ( true ) ;
729+ // Emit event to notify BattleComponent about tutorial completion
730+ window . dispatchEvent ( new CustomEvent ( 'battleTutorialCompleted' ) ) ;
731+ } , [ ] ) ;
732+
733+ const handleCollectionTutorialComplete = useCallback ( ( ) => {
734+ setShowCollectionTutorial ( false ) ;
735+ setHasSeenCollectionTutorial ( true ) ;
736+ } , [ ] ) ;
737+
738+ const handleCollectionTutorialSkip = useCallback ( ( ) => {
739+ setShowCollectionTutorial ( false ) ;
740+ setHasSeenCollectionTutorial ( true ) ;
741+ } , [ ] ) ;
742+
743+ const showBattleTutorialManually = useCallback ( ( ) => {
744+ setShowBattleTutorial ( true ) ;
745+ } , [ ] ) ;
746+
747+ const showCollectionTutorialManually = useCallback ( ( ) => {
748+ setShowCollectionTutorial ( true ) ;
749+ } , [ ] ) ;
750+
688751 // Reset cache and restore initial game state
689752 const resetGameCache = useCallback ( ( ) => {
690753 // Clear all localStorage data
691754 localStorage . removeItem ( 'elementalGameState' ) ;
692755 localStorage . removeItem ( 'audioSettings' ) ;
756+ localStorage . removeItem ( 'tutorialSettings' ) ;
693757
694758 // Reset game state to initial values
695759 setGameState ( {
@@ -705,6 +769,12 @@ const App: React.FC = () => {
705769 setMusicEnabled ( false ) ;
706770 setUserInteracted ( false ) ;
707771
772+ // Reset tutorial settings
773+ setHasSeenBattleTutorial ( false ) ;
774+ setHasSeenCollectionTutorial ( false ) ;
775+ setShowBattleTutorial ( false ) ;
776+ setShowCollectionTutorial ( false ) ;
777+
708778 // Reset other states
709779 setNewAchievements ( [ ] ) ;
710780 setLevelUp ( null ) ;
@@ -816,6 +886,8 @@ const App: React.FC = () => {
816886 setRulesPageOpen ( true ) ;
817887 } }
818888 onResetCache = { resetGameCache }
889+ onShowBattleTutorial = { showBattleTutorialManually }
890+ onShowCollectionTutorial = { showCollectionTutorialManually }
819891 />
820892 ) }
821893
@@ -895,6 +967,22 @@ const App: React.FC = () => {
895967
896968 { /* Navigation outside of app container */ }
897969 < Navigation activeTab = { activeTab } onTabChange = { handleTabChange } />
970+
971+ { /* Tutorial Components */ }
972+ < StepByStepTutorial
973+ isActive = { showBattleTutorial }
974+ currentPhase = { gameState . gamePhase }
975+ steps = { BATTLE_TUTORIAL_STEPS }
976+ onComplete = { handleBattleTutorialComplete }
977+ onSkip = { handleBattleTutorialSkip }
978+ />
979+
980+ < TutorialTooltip
981+ steps = { COLLECTION_TUTORIAL_STEPS }
982+ isActive = { showCollectionTutorial }
983+ onComplete = { handleCollectionTutorialComplete }
984+ onSkip = { handleCollectionTutorialSkip }
985+ />
898986 </ >
899987 ) ;
900988} ;
0 commit comments