@@ -5,6 +5,7 @@ import { useState, useEffect, useCallback } from "react";
55import { getDifficulty , saveDifficulty } from "../../lib/games/difficultyEngine" ;
66import { speakText } from "../../lib/audio/ttsHelper" ;
77import NavLogo from "../../components/NavLogo" ;
8+ import ThemeToggle from "../../components/ThemeToggle" ;
89
910type Screen = "start" | "play" | "result" ;
1011
@@ -75,6 +76,7 @@ export default function ColorSoundPage() {
7576 const [ elapsed , setElapsed ] = useState ( 0 ) ;
7677 const [ hintText , setHintText ] = useState ( "" ) ;
7778 const [ isPlaying , setIsPlaying ] = useState ( false ) ;
79+ const [ attemptsThisRound , setAttemptsThisRound ] = useState ( 0 ) ;
7880
7981 useEffect ( ( ) => {
8082 const saved =
@@ -99,6 +101,7 @@ export default function ColorSoundPage() {
99101 setTargetColor ( target ) ;
100102 setSelectedIndex ( null ) ;
101103 setFeedback ( null ) ;
104+ setAttemptsThisRound ( 0 ) ;
102105 setHintText ( `Tap the ${ target . name } color` ) ;
103106
104107 // Play tone + voice cue after a short delay
@@ -131,29 +134,48 @@ export default function ColorSoundPage() {
131134 return ( ) => clearInterval ( iv ) ;
132135 } , [ screen , startTime ] ) ;
133136
137+ const advanceRound = useCallback ( ( wasCorrect : boolean ) => {
138+ const nextRound = round + 1 ;
139+ if ( nextRound > maxRounds ) {
140+ const score = Math . round ( ( ( correct + ( wasCorrect ? 1 : 0 ) ) / maxRounds ) * 100 ) ;
141+ saveDifficulty ( "color-sound" , "default" , score ) ;
142+ setScreen ( "result" ) ;
143+ } else {
144+ setRound ( nextRound ) ;
145+ const config = getDifficulty ( "color-sound" , "default" ) ;
146+ generateRound ( config . level ) ;
147+ }
148+ } , [ round , maxRounds , correct , generateRound ] ) ;
149+
134150 const handleSelect = ( index : number ) => {
135151 if ( feedback !== null || ! targetColor ) return ;
136152
137153 setSelectedIndex ( index ) ;
138154 const isCorrect = displayColors [ index ] . name === targetColor . name ;
139- setFeedback ( isCorrect ? "correct" : "wrong" ) ;
140- if ( isCorrect ) setCorrect ( ( c ) => c + 1 ) ;
155+ const attempt = attemptsThisRound + 1 ;
156+ setAttemptsThisRound ( attempt ) ;
141157
142158 // Play the selected color's tone
143159 playTone ( displayColors [ index ] . frequency , 300 , setIsPlaying ) ;
144160
145- setTimeout ( ( ) => {
146- const nextRound = round + 1 ;
147- if ( nextRound > maxRounds ) {
148- const score = Math . round ( ( ( correct + ( isCorrect ? 1 : 0 ) ) / maxRounds ) * 100 ) ;
149- saveDifficulty ( "color-sound" , "default" , score ) ;
150- setScreen ( "result" ) ;
151- } else {
152- setRound ( nextRound ) ;
153- const config = getDifficulty ( "color-sound" , "default" ) ;
154- generateRound ( config . level ) ;
161+ if ( isCorrect ) {
162+ setCorrect ( ( c ) => c + 1 ) ;
163+ setFeedback ( "correct" ) ;
164+ setTimeout ( ( ) => advanceRound ( true ) , 800 ) ;
165+ } else {
166+ setFeedback ( "wrong" ) ;
167+ if ( attempt >= 2 ) {
168+ // Second wrong — show answer and auto-advance
169+ setTimeout ( ( ) => advanceRound ( false ) , 1200 ) ;
155170 }
156- } , 800 ) ;
171+ // First wrong — wait for user to click "Try Again" (no auto-advance)
172+ }
173+ } ;
174+
175+ const handleRetry = ( ) => {
176+ setFeedback ( null ) ;
177+ setSelectedIndex ( null ) ;
178+ replaySound ( ) ;
157179 } ;
158180
159181 const replaySound = ( ) => {
@@ -172,14 +194,7 @@ export default function ColorSoundPage() {
172194 < div className = "page" >
173195 < nav className = "nav" >
174196 < NavLogo />
175- < button
176- onClick = { ( ) => setTheme ( ( t ) => ( t === "light" ? "dark" : "light" ) ) }
177- className = "btn btn-outline"
178- style = { { minHeight : 40 , padding : "8px 16px" , fontSize : "0.9rem" } }
179- aria-label = "Toggle theme"
180- >
181- { theme === "light" ? "Dark" : "Light" }
182- </ button >
197+ < ThemeToggle theme = { theme } onToggle = { ( ) => setTheme ( ( t ) => ( t === "light" ? "dark" : "light" ) ) } />
183198 </ nav >
184199
185200 < div className = "main fade fade-1" style = { { maxWidth : 500 , padding : "40px 28px 80px" } } >
@@ -324,15 +339,28 @@ export default function ColorSoundPage() {
324339 </ div >
325340
326341 { feedback && (
327- < div
328- style = { {
329- marginTop : 20 ,
330- fontSize : "1rem" ,
331- fontWeight : 700 ,
332- color : feedback === "correct" ? "var(--sage-500)" : "var(--peach-300)" ,
333- } }
334- >
335- { feedback === "correct" ? "Correct!" : `That was ${ displayColors [ selectedIndex ! ] ?. name } . The answer was ${ targetColor ?. name } .` }
342+ < div style = { { marginTop : 20 , textAlign : "center" } } >
343+ < div
344+ style = { {
345+ fontSize : "1rem" ,
346+ fontWeight : 700 ,
347+ color : feedback === "correct" ? "var(--sage-500)" : "var(--peach-300)" ,
348+ marginBottom : 8 ,
349+ } }
350+ >
351+ { feedback === "correct"
352+ ? "Correct!"
353+ : `That was ${ displayColors [ selectedIndex ! ] ?. name } . The answer was ${ targetColor ?. name } .` }
354+ </ div >
355+ { feedback === "wrong" && attemptsThisRound < 2 && (
356+ < button
357+ onClick = { handleRetry }
358+ className = "btn btn-primary"
359+ style = { { minHeight : 44 , padding : "8px 24px" , fontSize : "0.95rem" } }
360+ >
361+ Try Again
362+ </ button >
363+ ) }
336364 </ div >
337365 ) }
338366 </ div >
0 commit comments