@@ -7,6 +7,13 @@ import Statistics from './Statistics';
import { useGameState } from '../lib/gameState' ;
import { sendGotifyNotification , submitRating } from '../app/actions' ;
// Plausible Analytics
declare global {
interface Window {
plausible ? : ( eventName : string , options ? : { props? : Record < string , string | number > } ) = > void ;
}
}
interface GameProps {
dailyPuzzle : {
id : number ;
@@ -103,6 +110,17 @@ export default function Game({ dailyPuzzle, genre = null, isSpecial = false, max
if ( song . id === dailyPuzzle . songId ) {
addGuess ( song . title , true ) ;
setHasWon ( true ) ;
// Track puzzle solved event
if ( typeof window !== 'undefined' && window . plausible ) {
window . plausible ( 'puzzle_solved' , {
props : {
genre : genre || 'Global' ,
attempts : gameState.guesses.length + 1 ,
score : gameState.score + 20 , // Include the win bonus
outcome : 'won'
}
} ) ;
}
// Notification sent after year guess or skip
if ( ! dailyPuzzle . releaseYear ) {
sendGotifyNotification ( gameState . guesses . length + 1 , 'won' , dailyPuzzle . id , genre , gameState . score ) ;
@@ -112,6 +130,17 @@ export default function Game({ dailyPuzzle, genre = null, isSpecial = false, max
if ( gameState . guesses . length + 1 >= maxAttempts ) {
setHasLost ( true ) ;
setHasWon ( false ) ;
// Track puzzle lost event
if ( typeof window !== 'undefined' && window . plausible ) {
window . plausible ( 'puzzle_solved' , {
props : {
genre : genre || 'Global' ,
attempts : maxAttempts ,
score : 0 ,
outcome : 'lost'
}
} ) ;
}
sendGotifyNotification ( maxAttempts , 'lost' , dailyPuzzle . id , genre , 0 ) ; // Score is 0 on failure
}
}
@@ -138,6 +167,17 @@ export default function Game({ dailyPuzzle, genre = null, isSpecial = false, max
if ( gameState . guesses . length + 1 >= maxAttempts ) {
setHasLost ( true ) ;
setHasWon ( false ) ;
// Track puzzle lost event
if ( typeof window !== 'undefined' && window . plausible ) {
window . plausible ( 'puzzle_solved' , {
props : {
genre : genre || 'Global' ,
attempts : maxAttempts ,
score : 0 ,
outcome : 'lost'
}
} ) ;
}
sendGotifyNotification ( maxAttempts , 'lost' , dailyPuzzle . id , genre , 0 ) ; // Score is 0 on failure
}
} ;
@@ -148,6 +188,17 @@ export default function Game({ dailyPuzzle, genre = null, isSpecial = false, max
giveUp ( ) ; // Ensure game is marked as failed and score reset to 0
setHasLost ( true ) ;
setHasWon ( false ) ;
// Track puzzle lost event
if ( typeof window !== 'undefined' && window . plausible ) {
window . plausible ( 'puzzle_solved' , {
props : {
genre : genre || 'Global' ,
attempts : gameState.guesses.length + 1 ,
score : 0 ,
outcome : 'lost'
}
} ) ;
}
sendGotifyNotification ( maxAttempts , 'lost' , dailyPuzzle . id , genre , 0 ) ;
} ;
@@ -156,6 +207,19 @@ export default function Game({ dailyPuzzle, genre = null, isSpecial = false, max
addYearBonus ( correct ) ;
setShowYearModal ( false ) ;
// Update the puzzle_solved event with year bonus result
if ( typeof window !== 'undefined' && window . plausible ) {
window . plausible ( 'puzzle_solved' , {
props : {
genre : genre || 'Global' ,
attempts : gameState.guesses.length ,
score : gameState.score + ( correct ? 10 : 0 ) , // Include year bonus if correct
outcome : 'won' ,
year_bonus : correct ? 'correct' : 'incorrect'
}
} ) ;
}
// Send notification now that game is fully complete
sendGotifyNotification ( gameState . guesses . length , 'won' , dailyPuzzle . id , genre , gameState . score + ( correct ? 10 : 0 ) ) ;
} ;
@@ -163,6 +227,20 @@ export default function Game({ dailyPuzzle, genre = null, isSpecial = false, max
const handleYearSkip = ( ) = > {
skipYearBonus ( ) ;
setShowYearModal ( false ) ;
// Update the puzzle_solved event with year bonus result
if ( typeof window !== 'undefined' && window . plausible ) {
window . plausible ( 'puzzle_solved' , {
props : {
genre : genre || 'Global' ,
attempts : gameState.guesses.length ,
score : gameState.score , // Score already includes win bonus
outcome : 'won' ,
year_bonus : 'skipped'
}
} ) ;
}
// Send notification now that game is fully complete
sendGotifyNotification ( gameState . guesses . length , 'won' , dailyPuzzle . id , genre , gameState . score ) ;
} ;
@@ -426,6 +504,7 @@ function ScoreDisplay({ score, breakdown }: { score: number, breakdown: Array<{
function YearGuessModal ( { correctYear , onGuess , onSkip } : { correctYear : number , onGuess : ( year : number ) = > void , onSkip : ( ) = > void } ) {
const [ options , setOptions ] = useState < number [ ] > ( [ ] ) ;
const [ feedback , setFeedback ] = useState < { show : boolean , correct : boolean , guessedYear? : number } > ( { show : false , correct : false } ) ;
useEffect ( ( ) = > {
const currentYear = new Date ( ) . getFullYear ( ) ;
@@ -454,6 +533,24 @@ function YearGuessModal({ correctYear, onGuess, onSkip }: { correctYear: number,
setOptions ( Array . from ( allOptions ) . sort ( ( a , b ) = > a - b ) ) ;
} , [ correctYear ] ) ;
const handleGuess = ( year : number ) = > {
const correct = year === correctYear ;
setFeedback ( { show : true , correct , guessedYear : year } ) ;
// Close modal after showing feedback
setTimeout ( ( ) = > {
onGuess ( year ) ;
} , 2500 ) ;
} ;
const handleSkip = ( ) = > {
setFeedback ( { show : true , correct : false } ) ;
setTimeout ( ( ) = > {
onSkip ( ) ;
} , 2000 ) ;
} ;
return (
< div style = { {
position : 'fixed' ,
@@ -477,6 +574,8 @@ function YearGuessModal({ correctYear, onGuess, onSkip }: { correctYear: number,
textAlign : 'center' ,
boxShadow : '0 20px 25px -5px rgba(0, 0, 0, 0.1)'
} } >
{ ! feedback . show ? (
< >
< h3 style = { { fontSize : '1.5rem' , fontWeight : 'bold' , marginBottom : '0.5rem' , color : '#1f2937' } } > Bonus Round ! < / h3 >
< p style = { { marginBottom : '1.5rem' , color : '#4b5563' } } > Guess the release year for < strong style = { { color : '#10b981' } } > + 10 points < / strong > ! < / p >
@@ -489,7 +588,7 @@ function YearGuessModal({ correctYear, onGuess, onSkip }: { correctYear: number,
{ options . map ( year = > (
< button
key = { year }
onClick = { ( ) = > on Guess( year ) }
onClick = { ( ) = > handle Guess( year ) }
style = { {
padding : '0.75rem' ,
background : '#f3f4f6' ,
@@ -510,7 +609,7 @@ function YearGuessModal({ correctYear, onGuess, onSkip }: { correctYear: number,
< / div >
< button
onClick = { on Skip}
onClick = { handle Skip}
style = { {
background : 'none' ,
border : 'none' ,
@@ -522,6 +621,34 @@ function YearGuessModal({ correctYear, onGuess, onSkip }: { correctYear: number,
>
Skip Bonus
< / button >
< / >
) : (
< div style = { { padding : '2rem 0' } } >
{ feedback . guessedYear ? (
feedback . correct ? (
< >
< div style = { { fontSize : '4rem' , marginBottom : '1rem' } } > 🎉 < / div >
< h3 style = { { fontSize : '2rem' , fontWeight : 'bold' , color : '#10b981' , marginBottom : '0.5rem' } } > Correct ! < / h3 >
< p style = { { fontSize : '1.2rem' , color : '#4b5563' } } > Released in { correctYear } < / p >
< p style = { { fontSize : '1.5rem' , fontWeight : 'bold' , color : '#10b981' , marginTop : '1rem' } } > + 10 Points ! < / p >
< / >
) : (
< >
< div style = { { fontSize : '4rem' , marginBottom : '1rem' } } > 😕 < / div >
< h3 style = { { fontSize : '2rem' , fontWeight : 'bold' , color : '#ef4444' , marginBottom : '0.5rem' } } > Not quite ! < / h3 >
< p style = { { fontSize : '1.2rem' , color : '#4b5563' } } > You guessed { feedback . guessedYear } < / p >
< p style = { { fontSize : '1.2rem' , color : '#4b5563' , marginTop : '0.5rem' } } > Actually released in < strong > { correctYear } < / strong > < / p >
< / >
)
) : (
< >
< div style = { { fontSize : '4rem' , marginBottom : '1rem' } } > ⏭ ️ < / div >
< h3 style = { { fontSize : '2rem' , fontWeight : 'bold' , color : '#6b7280' , marginBottom : '0.5rem' } } > Skipped < / h3 >
< p style = { { fontSize : '1.2rem' , color : '#4b5563' } } > Released in { correctYear } < / p >
< / >
) }
< / div >
) }
< / div >
< / div >
) ;