@@ -4,33 +4,39 @@ let previousWord = null;
44let voice = null ;
55let currentMode = "easy" ;
66let filteredWords = [ ] ;
7- let wordStats = { } ; // { word: { failures: 0, successes: 0 } }
7+ let wordStats = { } ; // Track { word: {failures: 0, successes: 0} }
88
9- /* ===== Sound Feedback ===== */
10- const clickSound = new Audio ( "click.wav" ) ; // Put click.wav in same folder
9+ // ===== Sound effect =====
10+ const clickSound = new Audio ( "click.wav" ) ;
1111
12- /* ===== Glow Helper ===== */
13- function triggerGlow ( btn ) {
14- if ( ! btn ) return ;
15- btn . classList . remove ( "button-glow" ) ;
16- void btn . offsetWidth ; // Force reflow
17- btn . classList . add ( "button-glow" ) ;
18- clickSound . currentTime = 0 ;
19- clickSound . play ( ) ;
20- }
12+ // ===== License Popup Logic =====
13+ const licensePopup = document . getElementById ( "licensePopup" ) ;
14+ const closePopupBtn = document . getElementById ( "closePopup" ) ;
15+ const dontShowCheckbox = document . getElementById ( "dontShowAgain" ) ;
2116
22- /* ===== Stats & Word Logic ===== */
23- function isEasyWord ( word ) {
24- return word . length <= 7 ;
17+ // Show popup if user hasn't opted out
18+ if ( ! localStorage . getItem ( "hideLicensePopup" ) ) {
19+ licensePopup . style . display = "flex" ;
2520}
2621
22+ // Close popup function
23+ closePopupBtn . onclick = ( ) => {
24+ triggerGlow ( closePopupBtn ) ;
25+ if ( dontShowCheckbox . checked ) {
26+ localStorage . setItem ( "hideLicensePopup" , "true" ) ;
27+ }
28+ licensePopup . style . display = "none" ;
29+ clickSound . play ( ) ;
30+ } ;
31+
32+ // ===== Word Stats Storage =====
2733function loadWordStats ( ) {
28- const stored = localStorage . getItem ( " spellingBeeWordStats" ) ;
34+ const stored = localStorage . getItem ( ' spellingBeeWordStats' ) ;
2935 wordStats = stored ? JSON . parse ( stored ) : { } ;
3036}
3137
3238function saveWordStats ( ) {
33- localStorage . setItem ( " spellingBeeWordStats" , JSON . stringify ( wordStats ) ) ;
39+ localStorage . setItem ( ' spellingBeeWordStats' , JSON . stringify ( wordStats ) ) ;
3440}
3541
3642function initWordStat ( word ) {
@@ -67,12 +73,32 @@ function manuallyAddToPractice(word) {
6773 alert ( `"${ word } " has been added to practice mode!` ) ;
6874}
6975
76+ function clearCache ( ) {
77+ if ( confirm ( "Are you sure? This will delete all your progress and your unlock status." ) ) {
78+ localStorage . removeItem ( 'spellingBeeWordStats' ) ;
79+ localStorage . removeItem ( "hideLicensePopup" ) ;
80+ wordStats = { } ;
81+ currentMode = "easy" ;
82+ previousWord = null ;
83+ document . getElementById ( "difficultyMode" ) . value = "easy" ;
84+ filterWordsByMode ( ) ;
85+ updatePracticeModeLabel ( ) ;
86+ pickWord ( ) ;
87+ alert ( "Cache cleared! Your progress has been reset." ) ;
88+ }
89+ }
90+
91+ // ===== Word Filtering / Practice Mode =====
92+ function getStruggleWords ( ) {
93+ return words . map ( w => w . word ) . filter ( word => isTrulyStruggling ( word ) ) ;
94+ }
95+
7096function getUniqueWordsWithFailures ( ) {
71- return Object . keys ( wordStats ) . filter ( ( w ) => wordStats [ w ] . failures > 0 ) . length ;
97+ return Object . keys ( wordStats ) . filter ( word => wordStats [ word ] . failures > 0 ) . length ;
7298}
7399
74100function getUniqueStrugglingWords ( ) {
75- return Object . keys ( wordStats ) . filter ( isTrulyStruggling ) . length ;
101+ return Object . keys ( wordStats ) . filter ( word => isTrulyStruggling ( word ) ) . length ;
76102}
77103
78104function isPracticeModeUnlocked ( ) {
@@ -83,42 +109,20 @@ function shouldRelock() {
83109 return getUniqueStrugglingWords ( ) < 10 && currentMode === "practice" ;
84110}
85111
86- function getStruggleWords ( ) {
87- return words . map ( ( w ) => w . word ) . filter ( isTrulyStruggling ) ;
88- }
89-
90- /* ===== Filter Words ===== */
91- function filterWordsByMode ( ) {
92- if ( currentMode === "easy" ) {
93- filteredWords = words . filter ( ( w ) => isEasyWord ( w . word ) ) ;
94- } else if ( currentMode === "hard" ) {
95- filteredWords = words . filter ( ( w ) => ! isEasyWord ( w . word ) ) ;
96- } else if ( currentMode === "practice" ) {
97- if ( ! isPracticeModeUnlocked ( ) ) {
98- currentMode = "easy" ;
99- filteredWords = words . filter ( ( w ) => isEasyWord ( w . word ) ) ;
100- return ;
101- }
102- const strugglingList = getStruggleWords ( ) ;
103- filteredWords = words . filter ( ( w ) => strugglingList . includes ( w . word ) ) ;
104- }
105- }
106-
107- /* ===== Practice Mode Label ===== */
108112function updatePracticeModeLabel ( ) {
109113 const practiceOption = document . getElementById ( "practiceOption" ) ;
110114 if ( ! practiceOption ) return ;
111115
112116 const warningText = document . getElementById ( "practiceWarning" ) ;
113117
114118 if ( isPracticeModeUnlocked ( ) ) {
115- practiceOption . textContent = " Practice (Unlocked)" ;
119+ practiceOption . textContent = ` Practice (Unlocked)` ;
116120 practiceOption . style . fontWeight = "bold" ;
117121 practiceOption . style . color = "green" ;
118122 } else {
119- practiceOption . textContent = "Practice (Locked)" ;
120- practiceOption . style . fontWeight = "" ;
123+ practiceOption . textContent = `Practice (Locked)` ;
121124 practiceOption . style . color = "" ;
125+ practiceOption . style . fontWeight = "" ;
122126 }
123127
124128 if ( currentMode === "practice" ) {
@@ -128,15 +132,46 @@ function updatePracticeModeLabel() {
128132 }
129133}
130134
131- /* ===== Pick Word ===== */
135+ function filterWordsByMode ( ) {
136+ if ( currentMode === "easy" ) {
137+ filteredWords = words . filter ( w => w . word . length <= 7 ) ;
138+ } else if ( currentMode === "hard" ) {
139+ filteredWords = words . filter ( w => w . word . length > 7 ) ;
140+ } else if ( currentMode === "practice" ) {
141+ if ( ! isPracticeModeUnlocked ( ) ) {
142+ currentMode = "easy" ;
143+ filteredWords = words . filter ( w => w . word . length <= 7 ) ;
144+ return ;
145+ }
146+ const strugglingList = getStruggleWords ( ) ;
147+ filteredWords = words . filter ( w => strugglingList . includes ( w . word ) ) ;
148+ }
149+ }
150+
151+ // ===== Load words =====
152+ fetch ( "words.json" )
153+ . then ( res => res . json ( ) )
154+ . then ( data => {
155+ words = data ;
156+ loadWordStats ( ) ;
157+ updatePracticeModeLabel ( ) ;
158+ filterWordsByMode ( ) ;
159+ pickWord ( ) ;
160+ } ) ;
161+
162+ // ===== Pick Word =====
132163function pickWord ( ) {
133164 if ( filteredWords . length === 0 ) {
134- document . getElementById ( "feedback" ) . textContent =
135- currentMode === "practice"
136- ? getUniqueWordsWithFailures ( ) < 15
137- ? `Get 15 words wrong first to unlock Practice mode! You've got ${ getUniqueWordsWithFailures ( ) } /15.`
138- : "Great job! No struggling words yet. Keep practicing!"
139- : "No words available for this difficulty." ;
165+ if ( currentMode === "practice" ) {
166+ const wrongWords = getUniqueWordsWithFailures ( ) ;
167+ if ( wrongWords < 15 ) {
168+ document . getElementById ( "feedback" ) . textContent = `Get 15 words wrong first to unlock Practice mode! You've got ${ wrongWords } /15.` ;
169+ } else {
170+ document . getElementById ( "feedback" ) . textContent = "Great job! No struggling words yet. Keep practicing!" ;
171+ }
172+ } else {
173+ document . getElementById ( "feedback" ) . textContent = "No words available for this difficulty." ;
174+ }
140175 return ;
141176 }
142177
@@ -151,124 +186,97 @@ function pickWord() {
151186 document . getElementById ( "feedback" ) . textContent = "" ;
152187}
153188
154- /* ===== Text-to- Speech ===== */
155- function speak ( text ) {
189+ // ===== Speech =====
190+ function speak ( text , rate = 0.85 ) {
156191 speechSynthesis . cancel ( ) ;
157192 const utterance = new SpeechSynthesisUtterance ( text ) ;
158- utterance . rate = 0.85 ;
193+ utterance . rate = rate ;
159194 utterance . voice = voice ;
160195 speechSynthesis . speak ( utterance ) ;
161196}
162197
163- speechSynthesis . onvoiceschanged = ( ) => {
198+ function loadVoices ( ) {
164199 const voices = speechSynthesis . getVoices ( ) ;
165- voice = voices . find ( ( v ) => v . lang === "en-US" ) || voices [ 0 ] ;
166- } ;
200+ voice = voices . find ( v => v . lang === "en-US" ) || voices [ 0 ] ;
201+ }
202+ speechSynthesis . onvoiceschanged = loadVoices ;
203+
204+ // ===== Button Glow + Sound =====
205+ function triggerGlow ( button ) {
206+ button . classList . add ( "button-glow" ) ;
207+ clickSound . currentTime = 0 ;
208+ clickSound . play ( ) ;
209+ setTimeout ( ( ) => button . classList . remove ( "button-glow" ) , 300 ) ;
210+ }
211+
212+ document . querySelectorAll ( "button" ) . forEach ( ( btn ) => {
213+ if ( btn . id !== "closePopup" ) {
214+ btn . addEventListener ( "click" , ( ) => triggerGlow ( btn ) ) ;
215+ }
216+ } ) ;
167217
168- /* ===== Button & Input Events ===== */
218+ // ===== Button Events =====
169219document . getElementById ( "sayWord" ) . onclick = ( ) => speak ( current . word ) ;
170- document . getElementById ( "saySlow" ) . onclick = ( ) => {
171- const u = new SpeechSynthesisUtterance ( current . word ) ;
172- u . rate = 0.35 ;
173- u . voice = voice ;
174- speechSynthesis . speak ( u ) ;
175- } ;
220+ document . getElementById ( "saySlow" ) . onclick = ( ) => speak ( current . word , 0.35 ) ;
176221document . getElementById ( "saySentence" ) . onclick = ( ) => speak ( current . sentence ) ;
177222document . getElementById ( "sayDefinition" ) . onclick = ( ) => speak ( current . definition ) ;
178223
179224document . getElementById ( "check" ) . onclick = ( ) => {
180- triggerGlow ( document . getElementById ( "check" ) ) ;
181225 const guess = document . getElementById ( "answer" ) . value . trim ( ) . toLowerCase ( ) ;
182- if ( guess === current . word . toLowerCase ( ) ) {
183- document . getElementById ( "feedback" ) . textContent = "Correct!" ;
184- document . getElementById ( "feedback" ) . style . color = "green" ;
226+ const correct = current . word . toLowerCase ( ) ;
227+ const feedback = document . getElementById ( "feedback" ) ;
228+
229+ if ( guess === correct ) {
230+ feedback . textContent = "Correct!" ;
231+ feedback . style . color = "green" ;
185232 recordSuccess ( current . word ) ;
186233 } else {
187- document . getElementById ( " feedback" ) . textContent = `Incorrect! The correct spelling is ${ current . word } ` ;
188- document . getElementById ( " feedback" ) . style . color = "red" ;
234+ feedback . textContent = `Incorrect! The correct spelling is ${ current . word } ` ;
235+ feedback . style . color = "red" ;
189236 recordFailure ( current . word ) ;
190237 }
191-
192238 updatePracticeModeLabel ( ) ;
193239
194240 if ( shouldRelock ( ) ) {
195241 currentMode = "easy" ;
196242 document . getElementById ( "difficultyMode" ) . value = "easy" ;
197243 filterWordsByMode ( ) ;
198244 pickWord ( ) ;
199- alert (
200- "Practice mode re-locked! You have fewer than 10 struggling words. Master more words to unlock it again!"
201- ) ;
245+ alert ( "Practice mode re-locked! You have fewer than 10 struggling words. Master more words to unlock it again!" ) ;
202246 updatePracticeModeLabel ( ) ;
203247 }
204248} ;
205249
206- document . getElementById ( "next" ) . onclick = ( ) => {
207- triggerGlow ( document . getElementById ( "next" ) ) ;
208- pickWord ( ) ;
209- } ;
210-
211- document . getElementById ( "addToPractice" ) . onclick = ( ) => {
212- if ( current ) manuallyAddToPractice ( current . word ) ;
213- } ;
214-
215- document . getElementById ( "clearCache" ) . onclick = ( ) => {
216- if (
217- confirm (
218- "Are you sure? This will delete all your progress and your unlock status."
219- )
220- ) {
221- localStorage . removeItem ( "spellingBeeWordStats" ) ;
222- wordStats = { } ;
223- currentMode = "easy" ;
224- document . getElementById ( "difficultyMode" ) . value = "easy" ;
225- filterWordsByMode ( ) ;
226- pickWord ( ) ;
227- alert ( "Cache cleared! Progress reset." ) ;
228- }
229- } ;
250+ document . getElementById ( "next" ) . onclick = pickWord ;
251+ document . getElementById ( "addToPractice" ) . onclick = ( ) => current && manuallyAddToPractice ( current . word ) ;
252+ document . getElementById ( "clearCache" ) . onclick = clearCache ;
230253
254+ // ===== Mode Selector =====
231255document . getElementById ( "difficultyMode" ) . onchange = ( e ) => {
232- const selected = e . target . value ;
233- if ( selected === "practice" && ! isPracticeModeUnlocked ( ) ) {
234- alert (
235- `Practice mode unlocks after getting 15 words wrong!\nYou've got ${ getUniqueWordsWithFailures ( ) } /15.`
236- ) ;
237- e . target . value = currentMode ;
256+ const selectedMode = e . target . value ;
257+
258+ if ( selectedMode === "practice" && ! isPracticeModeUnlocked ( ) ) {
259+ const wrongWords = getUniqueWordsWithFailures ( ) ;
260+ alert ( `Practice mode unlocks after getting 15 words wrong!\nYou've got ${ wrongWords } /15.` ) ;
261+ document . getElementById ( "difficultyMode" ) . value = currentMode ;
238262 return ;
239263 }
240- currentMode = selected ;
264+
265+ currentMode = selectedMode ;
241266 filterWordsByMode ( ) ;
242267 updatePracticeModeLabel ( ) ;
243268 pickWord ( ) ;
244269} ;
245270
246- /* ===== Keyboard Shortcuts ===== */
271+ // ===== Keyboard Shortcuts =====
247272document . addEventListener ( "keydown" , ( e ) => {
248- if ( e . key === "Enter" ) {
249- e . preventDefault ( ) ;
250- document . getElementById ( "check" ) . click ( ) ;
251- }
252- if ( e . key === "Shift" ) {
253- e . preventDefault ( ) ;
254- document . getElementById ( "next" ) . click ( ) ;
255- }
256- } ) ;
257-
258- /* ===== Glow on all buttons (except popup) ===== */
259- document . querySelectorAll ( "button" ) . forEach ( ( btn ) => {
260- if ( btn . id !== "closePopup" ) {
261- btn . addEventListener ( "click" , ( ) => triggerGlow ( btn ) ) ;
273+ if ( e . target . tagName === "INPUT" ) {
274+ if ( e . key === "Enter" ) {
275+ e . preventDefault ( ) ;
276+ document . getElementById ( "check" ) . click ( ) ;
277+ } else if ( e . key === "Shift" ) {
278+ e . preventDefault ( ) ;
279+ document . getElementById ( "next" ) . click ( ) ;
280+ }
262281 }
263282} ) ;
264-
265- /* ===== Load Words ===== */
266- fetch ( "words.json" )
267- . then ( ( res ) => res . json ( ) )
268- . then ( ( data ) => {
269- words = data ;
270- loadWordStats ( ) ;
271- updatePracticeModeLabel ( ) ;
272- filterWordsByMode ( ) ;
273- pickWord ( ) ;
274- } ) ;
0 commit comments