AptlyDigital commited on
Commit
b50bc23
·
verified ·
1 Parent(s): 4181ad5

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +681 -816
index.html CHANGED
@@ -3,20 +3,22 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Phonics Pro - IPA Accurate Pronunciation</title>
7
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
  <style>
9
  :root {
10
- --primary: #4361ee;
11
- --primary-light: #4895ef;
12
- --secondary: #3f37c9;
13
- --accent: #f72585;
 
14
  --light: #f8f9fa;
15
  --dark: #212529;
16
- --success: #4cc9f0;
17
- --border-radius: 12px;
18
- --shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
19
- --transition: all 0.3s ease;
 
20
  }
21
 
22
  * {
@@ -27,957 +29,820 @@
27
  }
28
 
29
  body {
30
- background-color: #f0f2f5;
31
  color: var(--dark);
32
- line-height: 1.6;
 
33
  }
34
 
35
  .container {
36
- max-width: 1200px;
37
  margin: 0 auto;
38
- padding: 20px;
39
  }
40
 
41
  header {
42
  text-align: center;
 
43
  padding: 30px 20px;
44
- background: linear-gradient(135deg, var(--primary), var(--secondary));
45
- color: white;
46
  border-radius: var(--border-radius);
47
- margin-bottom: 30px;
48
  box-shadow: var(--shadow);
 
 
49
  }
50
 
51
- .main-content {
52
- display: grid;
53
- grid-template-columns: 250px 1fr;
54
- gap: 20px;
 
 
 
 
55
  }
56
 
57
- @media (max-width: 768px) {
58
- .main-content {
59
- grid-template-columns: 1fr;
60
- }
61
  }
62
 
63
- .categories-sidebar, .content-area {
64
- background-color: white;
65
- border-radius: var(--border-radius);
66
- padding: 20px;
67
- box-shadow: var(--shadow);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
69
 
70
- .category-item {
71
- padding: 15px;
72
- margin-bottom: 8px;
73
- background-color: #f8f9fa;
74
- border-radius: 10px;
 
 
75
  cursor: pointer;
76
  transition: var(--transition);
77
- font-weight: 600;
78
  display: flex;
79
- justify-content: space-between;
80
  align-items: center;
 
 
81
  }
82
 
83
- .category-item:hover {
84
- background-color: #e9ecef;
85
- transform: translateX(3px);
86
  }
87
 
88
- .category-item.active {
89
- background-color: var(--primary);
90
- color: white;
91
  }
92
 
93
- .sound-grid {
94
- display: grid;
95
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
96
- gap: 15px;
97
- margin-top: 20px;
98
  }
99
 
100
- .sound-card {
101
- background-color: #f8f9fa;
102
- border-radius: var(--border-radius);
103
- padding: 20px;
104
- transition: var(--transition);
105
- border: 2px solid transparent;
 
 
 
 
 
 
 
 
 
 
106
  cursor: pointer;
107
- position: relative;
108
- overflow: hidden;
 
109
  }
110
 
111
- .sound-card:hover {
112
- transform: translateY(-5px);
113
- box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
114
- border-color: var(--primary-light);
115
  }
116
 
117
- .sound-card.playing {
118
- background-color: #e3f2fd;
119
- border-color: var(--success);
120
- animation: pulse 1.5s infinite;
121
  }
122
 
123
- @keyframes pulse {
124
- 0% { box-shadow: 0 0 0 0 rgba(76, 201, 240, 0.4); }
125
- 70% { box-shadow: 0 0 0 10px rgba(76, 201, 240, 0); }
126
- 100% { box-shadow: 0 0 0 0 rgba(76, 201, 240, 0); }
 
127
  }
128
 
129
- .sound-pattern {
130
- font-size: 2.2rem;
131
- font-weight: bold;
132
- color: var(--primary);
133
- text-align: center;
134
- margin-bottom: 10px;
135
  }
136
 
137
- .sound-ipa {
138
- font-size: 0.9rem;
139
- color: var(--accent);
140
- text-align: center;
141
- font-weight: 600;
142
- margin-bottom: 8px;
143
- font-family: monospace;
144
- background-color: #f0f0f0;
145
- padding: 3px 8px;
146
- border-radius: 4px;
147
- display: inline-block;
148
- margin-left: 5px;
149
  }
150
 
151
- .sound-pronunciation {
152
- font-size: 0.9rem;
153
- color: #666;
 
154
  text-align: center;
155
- margin-bottom: 5px;
156
- font-style: italic;
 
 
157
  }
158
 
159
- .sound-examples {
160
- font-size: 0.95rem;
161
- color: var(--dark);
162
- text-align: center;
163
- margin-bottom: 10px;
164
  }
165
 
166
- .sound-examples span {
167
- display: inline-block;
168
- background-color: white;
169
- padding: 4px 10px;
170
- border-radius: 15px;
171
- margin: 3px;
172
- border: 1px solid #e0e0e0;
 
 
 
 
 
173
  }
174
 
175
- .ipa-key {
176
- background-color: #f8f9fa;
177
- border-radius: 8px;
178
- padding: 15px;
179
- margin-top: 20px;
180
- border-left: 4px solid var(--accent);
181
  }
182
 
183
- .ipa-key h4 {
184
- color: var(--accent);
185
- margin-bottom: 10px;
 
186
  }
187
 
188
- .ipa-item {
189
- display: inline-block;
190
- margin-right: 15px;
191
- margin-bottom: 8px;
 
 
192
  }
193
 
194
- .ipa-symbol {
195
- font-family: monospace;
196
  font-weight: bold;
197
- background-color: white;
198
- padding: 2px 6px;
199
- border-radius: 3px;
200
- border: 1px solid #ddd;
201
  }
202
 
203
- .play-icon {
204
- position: absolute;
205
- top: 10px;
206
- right: 10px;
207
- color: var(--success);
208
- opacity: 0.7;
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  font-size: 1.2rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  }
211
 
212
- .audio-status {
213
- position: fixed;
214
  bottom: 20px;
215
- right: 20px;
216
- background-color: var(--primary);
217
- color: white;
218
- padding: 10px 20px;
219
- border-radius: 50px;
220
- display: none;
221
- align-items: center;
222
- gap: 10px;
 
 
223
  box-shadow: var(--shadow);
224
- z-index: 1000;
225
  }
226
 
227
- .audio-status.show {
228
- display: flex;
229
- animation: slideIn 0.3s ease;
 
230
  }
231
 
232
- @keyframes slideIn {
233
- from { transform: translateX(100px); opacity: 0; }
234
- to { transform: translateX(0); opacity: 1; }
235
  }
236
 
237
- .search-box {
238
- margin-bottom: 20px;
 
 
 
 
239
  }
240
 
241
- .search-box input {
242
  width: 100%;
243
- padding: 12px 20px;
244
- border: 2px solid #e0e0e0;
245
- border-radius: 50px;
246
- font-size: 1rem;
247
- transition: var(--transition);
 
248
  }
249
 
250
- .search-box input:focus {
251
- outline: none;
252
- border-color: var(--primary);
253
- box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
 
254
  }
255
 
256
- .instructions {
257
- background-color: #e8f4fd;
258
- border-radius: 10px;
259
- padding: 15px;
260
- margin-bottom: 20px;
 
 
 
261
  font-size: 0.9rem;
262
- color: #2c5282;
263
- border-left: 4px solid var(--primary);
264
  }
265
 
266
- .warning {
267
- background-color: #fff3cd;
268
- border-radius: 10px;
269
- padding: 10px;
270
- margin-top: 10px;
271
- font-size: 0.8rem;
272
- color: #856404;
273
- border-left: 4px solid #ffc107;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  }
275
  </style>
276
  </head>
277
  <body>
278
  <div class="container">
279
  <header>
280
- <h1><i class="fas fa-language"></i> Phonics Pro with IPA</h1>
281
- <p>IPA phoneme tags ensure accurate pronunciation for every sound</p>
282
- </header>
283
-
284
- <div class="instructions">
285
- <i class="fas fa-info-circle"></i> Click any phonics pattern to hear its CORRECT pronunciation using IPA phoneme tags.
286
- <div class="warning">
287
- <i class="fas fa-exclamation-triangle"></i> IPA tags work best in Chrome with Google voices. Other browsers may fall back to text-to-speech.
288
- </div>
289
- </div>
290
-
291
- <div class="main-content">
292
- <div class="categories-sidebar">
293
- <h3><i class="fas fa-filter"></i> Filter by Type</h3>
294
- <div class="category-list" id="categoryList">
295
- <!-- Categories will be populated by JavaScript -->
296
- </div>
297
- </div>
298
-
299
- <div class="content-area">
300
- <div class="search-box">
301
- <input type="text" id="searchInput" placeholder="Search phonics patterns (e.g., 'tion', 'ture', 'ch')...">
302
- </div>
303
 
304
- <h3 id="currentCategory">All Phonics Patterns</h3>
305
- <div class="sound-grid" id="soundGrid">
306
- <!-- Sound cards will be populated by JavaScript -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
  </div>
308
 
309
- <div class="ipa-key">
310
- <h4><i class="fas fa-key"></i> IPA Pronunciation Key</h4>
311
- <div class="ipa-item"><span class="ipa-symbol">ʃ</span> = "sh"</div>
312
- <div class="ipa-item"><span class="ipa-symbol">tʃ</span> = "ch"</div>
313
- <div class="ipa-symbol">ʒ</span> = "zh" (vision)</div>
314
- <div class="ipa-item"><span class="ipa-symbol">θ</span> = "th" (thin)</div>
315
- <div class="ipa-item"><span class="ipa-symbol">ð</span> = "th" (this)</div>
316
- <div class="ipa-item"><span class="ipa-symbol">ŋ</span> = "ng"</div>
317
- <div class="ipa-item"><span class="ipa-symbol">ə</span> = "uh" (schwa)</div>
318
  </div>
319
  </div>
 
 
 
 
 
320
  </div>
321
 
322
- <div class="audio-status" id="audioStatus">
323
- <i class="fas fa-volume-up"></i>
324
- <div>
325
- <div id="statusText">Playing IPA pronunciation...</div>
326
- <div id="ipaDetail" style="font-size: 0.8rem; opacity: 0.9;"></div>
327
- </div>
 
 
 
 
 
 
 
328
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  </div>
330
 
331
  <script>
332
- // Phonics data with IPA phoneme tags for ACCURATE pronunciation
333
  const phonicsData = [
334
- {
335
- id: "all",
336
- name: "All Sounds",
337
- sounds: [
338
- // Consonant Digraphs with IPA
339
- {
340
- pattern: "sh",
341
- ipa: "/ʃ/",
342
- ipaTag: '<phoneme alphabet="ipa" ph="ʃ">sh</phoneme>',
343
- examples: ["ship", "wish", "shell"],
344
- pronunciation: "as in 'shoe'",
345
- category: "Consonant"
346
- },
347
- {
348
- pattern: "ch",
349
- ipa: "/tʃ/",
350
- ipaTag: '<phoneme alphabet="ipa" ph="tʃ">ch</phoneme>',
351
- examples: ["chat", "chip", "lunch"],
352
- pronunciation: "as in 'chair'",
353
- category: "Consonant"
354
- },
355
- {
356
- pattern: "th (thin)",
357
- ipa: "/θ/",
358
- ipaTag: '<phoneme alphabet="ipa" ph="θ">th</phoneme>',
359
- examples: ["thin", "think", "bath"],
360
- pronunciation: "voiceless as in 'think'",
361
- category: "Consonant"
362
- },
363
- {
364
- pattern: "th (this)",
365
- ipa: "/ð/",
366
- ipaTag: '<phoneme alphabet="ipa" ph="ð">th</phoneme>',
367
- examples: ["this", "that", "mother"],
368
- pronunciation: "voiced as in 'this'",
369
- category: "Consonant"
370
- },
371
- {
372
- pattern: "ph",
373
- ipa: "/f/",
374
- ipaTag: '<phoneme alphabet="ipa" ph="f">f</phoneme>',
375
- examples: ["phone", "graph", "elephant"],
376
- pronunciation: "as 'f' in 'phone'",
377
- category: "Consonant"
378
- },
379
- {
380
- pattern: "ng",
381
- ipa: "/ŋ/",
382
- ipaTag: '<phoneme alphabet="ipa" ph="ŋ">ng</phoneme>',
383
- examples: ["song", "king", "ring"],
384
- pronunciation: "nasal as in 'song'",
385
- category: "Consonant"
386
- },
387
-
388
- // -tion, -sion, -ture endings with EXACT IPA
389
- {
390
- pattern: "tion",
391
- ipa: "/ʃən/",
392
- ipaTag: '<phoneme alphabet="ipa" ph="ʃən">shun</phoneme>',
393
- examples: ["action", "station", "nation"],
394
- pronunciation: "as 'shun' in 'station'",
395
- category: "Endings"
396
- },
397
- {
398
- pattern: "sion (vision)",
399
- ipa: "/ʒən/",
400
- ipaTag: '<phoneme alphabet="ipa" ph="ʒən">zhun</phoneme>',
401
- examples: ["vision", "decision", "confusion"],
402
- pronunciation: "as 'zhun' in 'vision'",
403
- category: "Endings"
404
- },
405
- {
406
- pattern: "sion (mission)",
407
- ipa: "/ʃən/",
408
- ipaTag: '<phoneme alphabet="ipa" ph="ʃən">shun</phoneme>',
409
- examples: ["mission", "passion", "session"],
410
- pronunciation: "as 'shun' in 'mission'",
411
- category: "Endings"
412
- },
413
- {
414
- pattern: "cian",
415
- ipa: "/ʃən/",
416
- ipaTag: '<phoneme alphabet="ipa" ph="ʃən">shun</phoneme>',
417
- examples: ["musician", "magician", "physician"],
418
- pronunciation: "as 'shun' in 'musician'",
419
- category: "Endings"
420
- },
421
- {
422
- pattern: "ture",
423
- ipa: "/tʃər/",
424
- ipaTag: '<phoneme alphabet="ipa" ph="tʃər">chur</phoneme>',
425
- examples: ["picture", "future", "nature"],
426
- pronunciation: "as 'chur' in 'picture'",
427
- category: "Endings"
428
- },
429
-
430
- // Vowel Teams with IPA
431
- {
432
- pattern: "ai",
433
- ipa: "/eɪ/",
434
- ipaTag: '<phoneme alphabet="ipa" ph="">ay</phoneme>',
435
- examples: ["rain", "train", "wait"],
436
- pronunciation: "as 'ay' in 'rain'",
437
- category: "Vowels"
438
- },
439
- {
440
- pattern: "ay",
441
- ipa: "/eɪ/",
442
- ipaTag: '<phoneme alphabet="ipa" ph="">ay</phoneme>',
443
- examples: ["day", "play", "say"],
444
- pronunciation: "as 'ay' in 'day'",
445
- category: "Vowels"
446
- },
447
- {
448
- pattern: "ee",
449
- ipa: "/iː/",
450
- ipaTag: '<phoneme alphabet="ipa" ph="iː">ee</phoneme>',
451
- examples: ["tree", "see", "sleep"],
452
- pronunciation: "long 'e' as in 'see'",
453
- category: "Vowels"
454
- },
455
- {
456
- pattern: "ea",
457
- ipa: "/iː/",
458
- ipaTag: '<phoneme alphabet="ipa" ph="iː">ee</phoneme>',
459
- examples: ["eat", "sea", "team"],
460
- pronunciation: "long 'e' as in 'sea'",
461
- category: "Vowels"
462
- },
463
- {
464
- pattern: "oa",
465
- ipa: "/oʊ/",
466
- ipaTag: '<phoneme alphabet="ipa" ph="oʊ">oh</phoneme>',
467
- examples: ["boat", "coat", "road"],
468
- pronunciation: "long 'o' as in 'boat'",
469
- category: "Vowels"
470
- },
471
- {
472
- pattern: "ou",
473
- ipa: "/aʊ/",
474
- ipaTag: '<phoneme alphabet="ipa" ph="aʊ">ow</phoneme>',
475
- examples: ["out", "cloud", "found"],
476
- pronunciation: "as 'ow' in 'out'",
477
- category: "Vowels"
478
- },
479
- {
480
- pattern: "ow (cow)",
481
- ipa: "/aʊ/",
482
- ipaTag: '<phoneme alphabet="ipa" ph="aʊ">ow</phoneme>',
483
- examples: ["cow", "town", "down"],
484
- pronunciation: "as 'ow' in 'cow'",
485
- category: "Vowels"
486
- },
487
- {
488
- pattern: "ow (snow)",
489
- ipa: "/oʊ/",
490
- ipaTag: '<phoneme alphabet="ipa" ph="oʊ">oh</phoneme>',
491
- examples: ["snow", "grow", "show"],
492
- pronunciation: "long 'o' as in 'snow'",
493
- category: "Vowels"
494
- },
495
- {
496
- pattern: "oi",
497
- ipa: "/ɔɪ/",
498
- ipaTag: '<phoneme alphabet="ipa" ph="ɔɪ">oy</phoneme>',
499
- examples: ["coin", "boil", "soil"],
500
- pronunciation: "as 'oy' in 'coin'",
501
- category: "Vowels"
502
- },
503
- {
504
- pattern: "oy",
505
- ipa: "/ɔɪ/",
506
- ipaTag: '<phoneme alphabet="ipa" ph="ɔɪ">oy</phoneme>',
507
- examples: ["toy", "boy", "enjoy"],
508
- pronunciation: "as 'oy' in 'toy'",
509
- category: "Vowels"
510
- },
511
- {
512
- pattern: "oo (moon)",
513
- ipa: "/uː/",
514
- ipaTag: '<phoneme alphabet="ipa" ph="uː">oo</phoneme>',
515
- examples: ["moon", "spoon", "tooth"],
516
- pronunciation: "long 'oo' as in 'moon'",
517
- category: "Vowels"
518
- },
519
- {
520
- pattern: "oo (book)",
521
- ipa: "/ʊ/",
522
- ipaTag: '<phoneme alphabet="ipa" ph="ʊ">oo</phoneme>',
523
- examples: ["book", "look", "foot"],
524
- pronunciation: "short 'oo' as in 'book'",
525
- category: "Vowels"
526
- },
527
- {
528
- pattern: "ew",
529
- ipa: "/juː/",
530
- ipaTag: '<phoneme alphabet="ipa" ph="juː">yoo</phoneme>',
531
- examples: ["few", "dew", "new"],
532
- pronunciation: "as 'yoo' in 'few'",
533
- category: "Vowels"
534
- },
535
-
536
- // R-controlled vowels
537
- {
538
- pattern: "ar",
539
- ipa: "/ɑːr/",
540
- ipaTag: '<phoneme alphabet="ipa" ph="ɑːr">ar</phoneme>',
541
- examples: ["car", "star", "farm"],
542
- pronunciation: "as 'ar' in 'car'",
543
- category: "R-Controlled"
544
- },
545
- {
546
- pattern: "er",
547
- ipa: "/ɜːr/",
548
- ipaTag: '<phoneme alphabet="ipa" ph="ɜːr">ur</phoneme>',
549
- examples: ["her", "fern", "verb"],
550
- pronunciation: "as 'ur' in 'her'",
551
- category: "R-Controlled"
552
- },
553
- {
554
- pattern: "ir",
555
- ipa: "/ɜːr/",
556
- ipaTag: '<phoneme alphabet="ipa" ph="ɜːr">ur</phoneme>',
557
- examples: ["bird", "girl", "first"],
558
- pronunciation: "as 'ur' in 'bird'",
559
- category: "R-Controlled"
560
- },
561
- {
562
- pattern: "or",
563
- ipa: "/ɔːr/",
564
- ipaTag: '<phoneme alphabet="ipa" ph="ɔːr">or</phoneme>',
565
- examples: ["fork", "corn", "sport"],
566
- pronunciation: "as 'or' in 'fork'",
567
- category: "R-Controlled"
568
- },
569
- {
570
- pattern: "ur",
571
- ipa: "/ɜːr/",
572
- ipaTag: '<phoneme alphabet="ipa" ph="ɜːr">ur</phoneme>',
573
- examples: ["burn", "turn", "surf"],
574
- pronunciation: "as 'ur' in 'burn'",
575
- category: "R-Controlled"
576
- },
577
-
578
- // Other important patterns
579
- {
580
- pattern: "tch",
581
- ipa: "/tʃ/",
582
- ipaTag: '<phoneme alphabet="ipa" ph="tʃ">ch</phoneme>',
583
- examples: ["catch", "watch", "patch"],
584
- pronunciation: "as 'ch' in 'catch'",
585
- category: "Consonant"
586
- },
587
- {
588
- pattern: "dge",
589
- ipa: "/dʒ/",
590
- ipaTag: '<phoneme alphabet="ipa" ph="dʒ">j</phoneme>',
591
- examples: ["bridge", "fudge", "badge"],
592
- pronunciation: "as 'j' in 'bridge'",
593
- category: "Consonant"
594
- },
595
- {
596
- pattern: "ck",
597
- ipa: "/k/",
598
- ipaTag: '<phoneme alphabet="ipa" ph="k">k</phoneme>',
599
- examples: ["duck", "clock", "sock"],
600
- pronunciation: "as 'k' in 'duck'",
601
- category: "Consonant"
602
- },
603
- {
604
- pattern: "wh",
605
- ipa: "/w/",
606
- ipaTag: '<phoneme alphabet="ipa" ph="w">w</phoneme>',
607
- examples: ["when", "whale", "whisper"],
608
- pronunciation: "as 'w' in 'when'",
609
- category: "Consonant"
610
- },
611
- {
612
- pattern: "kn",
613
- ipa: "/n/",
614
- ipaTag: '<phoneme alphabet="ipa" ph="n">n</phoneme>',
615
- examples: ["knee", "knock", "know"],
616
- pronunciation: "silent 'k' as in 'knee'",
617
- category: "Silent"
618
- },
619
- {
620
- pattern: "wr",
621
- ipa: "/r/",
622
- ipaTag: '<phoneme alphabet="ipa" ph="r">r</phoneme>',
623
- examples: ["write", "wrap", "wrong"],
624
- pronunciation: "silent 'w' as in 'write'",
625
- category: "Silent"
626
- }
627
- ]
628
- },
629
- {
630
- id: "consonant",
631
- name: "Consonant Digraphs",
632
- sounds: []
633
- },
634
- {
635
- id: "vowels",
636
- name: "Vowel Teams",
637
- sounds: []
638
- },
639
- {
640
- id: "endings",
641
- name: "Word Endings",
642
- sounds: []
643
- },
644
- {
645
- id: "rcontrolled",
646
- name: "R-Controlled",
647
- sounds: []
648
- },
649
- {
650
- id: "silent",
651
- name: "Silent Letters",
652
- sounds: []
653
- }
654
  ];
655
 
656
- // Populate category-specific sounds
657
- function organizeSoundsByCategory() {
658
- const allSounds = phonicsData[0].sounds;
659
-
660
- phonicsData.forEach(category => {
661
- if (category.id !== "all") {
662
- category.sounds = allSounds.filter(sound => {
663
- if (category.id === "consonant") return sound.category === "Consonant";
664
- if (category.id === "vowels") return sound.category === "Vowels";
665
- if (category.id === "endings") return sound.category === "Endings";
666
- if (category.id === "rcontrolled") return sound.category === "R-Controlled";
667
- if (category.id === "silent") return sound.category === "Silent";
668
- return false;
669
- });
670
- }
671
- });
672
- }
673
-
674
  // App state
675
- let currentCategory = phonicsData[0];
676
- let isSpeaking = false;
 
 
 
677
 
678
  // DOM Elements
679
- const categoryList = document.getElementById('categoryList');
680
- const soundGrid = document.getElementById('soundGrid');
681
- const currentCategoryElement = document.getElementById('currentCategory');
682
- const audioStatus = document.getElementById('audioStatus');
683
- const statusText = document.getElementById('statusText');
684
- const ipaDetail = document.getElementById('ipaDetail');
685
- const searchInput = document.getElementById('searchInput');
686
-
687
- // Initialize the app
 
 
 
 
688
  function initApp() {
689
- organizeSoundsByCategory();
690
- renderCategories();
691
- renderSounds(currentCategory);
692
-
693
- // Search functionality
694
- searchInput.addEventListener('input', handleSearch);
695
-
696
- // Cancel speech when clicking outside or pressing Escape
697
- document.addEventListener('click', (e) => {
698
- if (!e.target.closest('.sound-card') && !e.target.closest('.audio-status')) {
699
- stopAllSpeech();
700
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
701
  });
702
-
703
- document.addEventListener('keydown', (e) => {
704
- if (e.key === 'Escape') {
705
- stopAllSpeech();
706
- }
707
- });
708
-
709
- // Check if browser supports SSML
710
- checkSSMLSupport();
711
  }
712
-
713
- // Check if browser supports SSML
714
- function checkSSMLSupport() {
715
- const testUtterance = new SpeechSynthesisUtterance();
716
- testUtterance.text = '<phoneme alphabet="ipa" ph="ʃ">test</phoneme>';
717
 
718
- // Try to detect if SSML is supported
719
- const supportsSSML = !testUtterance.text.includes('<phoneme');
 
720
 
721
- if (!supportsSSML) {
722
- console.log("Browser may not support IPA phoneme tags. Using fallback.");
723
- }
724
- }
725
-
726
- // Render category list
727
- function renderCategories() {
728
- categoryList.innerHTML = '';
729
- phonicsData.forEach(category => {
730
- const div = document.createElement('div');
731
- div.className = `category-item ${category.id === currentCategory.id ? 'active' : ''}`;
732
- div.innerHTML = `
733
- <span>${category.name}</span>
734
- <span>${category.sounds.length}</span>
735
- `;
736
-
737
- div.addEventListener('click', () => {
738
- currentCategory = category;
739
- renderSounds(category);
740
- updateActiveCategory();
741
- });
742
 
743
- categoryList.appendChild(div);
744
- });
745
- }
746
-
747
- // Update active category
748
- function updateActiveCategory() {
749
- document.querySelectorAll('.category-item').forEach(item => {
750
- item.classList.remove('active');
751
- });
752
-
753
- document.querySelectorAll('.category-item').forEach(item => {
754
- if (item.textContent.includes(currentCategory.name)) {
755
- item.classList.add('active');
756
  }
757
- });
758
-
759
- currentCategoryElement.textContent = currentCategory.name;
760
- searchInput.value = '';
761
- }
762
-
763
- // Render sounds for the current category
764
- function renderSounds(category) {
765
- soundGrid.innerHTML = '';
766
-
767
- category.sounds.forEach(sound => {
768
- const card = document.createElement('div');
769
- card.className = 'sound-card';
770
- card.dataset.pattern = sound.pattern;
771
 
772
- card.innerHTML = `
773
- <div class="play-icon"><i class="fas fa-play-circle"></i></div>
774
- <div class="sound-pattern">${sound.pattern}</div>
775
- <div>
776
- <span class="sound-pronunciation">${sound.pronunciation}</span>
777
- <span class="sound-ipa">${sound.ipa}</span>
778
- </div>
779
- <div class="sound-examples">
780
- ${sound.examples.slice(0, 3).map(example =>
781
- `<span>${example}</span>`
782
- ).join('')}
 
 
 
 
 
 
 
 
 
 
 
 
 
783
  </div>
784
  `;
785
 
786
- // Click to play sound with IPA
787
- card.addEventListener('click', (e) => {
788
- e.stopPropagation();
789
- playSoundWithIPA(sound);
 
 
 
 
 
 
 
790
  });
791
 
792
- soundGrid.appendChild(card);
793
  });
794
- }
795
-
796
- // Play sound using IPA phoneme tags for ACCURATE pronunciation
797
- function playSoundWithIPA(sound) {
798
- // Stop any currently playing speech
799
- stopAllSpeech();
800
 
801
- // Remove playing class from all cards
802
- document.querySelectorAll('.sound-card').forEach(card => {
803
- card.classList.remove('playing');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
804
  });
 
 
 
 
 
 
805
 
806
- // Add playing class to current card
807
- const currentCard = document.querySelector(`[data-pattern="${sound.pattern}"]`);
808
- if (currentCard) {
809
- currentCard.classList.add('playing');
 
810
  }
811
 
812
- // Show audio status with IPA info
813
- statusText.textContent = `Pronouncing: ${sound.pattern}`;
814
- ipaDetail.textContent = `IPA: ${sound.ipa}`;
815
- audioStatus.classList.add('show');
816
-
817
- // Create the speech utterance with IPA tags
818
- const utterance = new SpeechSynthesisUtterance();
819
-
820
- // Use IPA phoneme tag for EXACT pronunciation
821
- utterance.text = sound.ipaTag;
822
-
823
- // Configure for clear speech
824
- utterance.rate = 0.8; // Slightly slower for clarity
825
- utterance.pitch = 1.0;
826
- utterance.volume = 1;
827
-
828
- // Try to get the best voice for IPA
829
- const voices = speechSynthesis.getVoices();
830
-
831
- // Prefer voices that likely support SSML/IPA
832
- const preferredVoices = [
833
- "Google UK English Female",
834
- "Google US English",
835
- "Samantha",
836
- "Microsoft David - English (United States)",
837
- "Microsoft Zira - English (United States)"
838
- ];
839
 
840
- let selectedVoice = voices[0];
841
- for (const voiceName of preferredVoices) {
842
- const voice = voices.find(v => v.name.includes(voiceName));
843
- if (voice) {
844
- selectedVoice = voice;
845
- break;
846
- }
847
  }
848
 
849
- utterance.voice = selectedVoice;
850
-
851
- // Event handlers
852
- utterance.onstart = () => {
853
- isSpeaking = true;
854
- };
 
855
 
856
- utterance.onend = () => {
857
- isSpeaking = false;
858
-
859
- // Also play an example word
860
- setTimeout(() => {
861
- if (sound.examples && sound.examples[0]) {
862
- const exampleUtterance = new SpeechSynthesisUtterance(
863
- `as in ${sound.examples[0]}`
864
- );
865
- exampleUtterance.rate = 0.8;
866
- exampleUtterance.voice = selectedVoice;
867
- speechSynthesis.speak(exampleUtterance);
868
- }
869
- }, 300);
870
-
871
- // Hide status after a delay
872
- setTimeout(() => {
873
- audioStatus.classList.remove('show');
874
- if (currentCard) {
875
- currentCard.classList.remove('playing');
876
- }
877
- }, 2000);
878
- };
879
 
880
- utterance.onerror = (event) => {
881
- console.error('Speech synthesis error:', event);
882
- isSpeaking = false;
883
- audioStatus.classList.remove('show');
884
- if (currentCard) {
885
- currentCard.classList.remove('playing');
886
- }
887
-
888
- // Fallback: Try without IPA tags
889
- const fallbackUtterance = new SpeechSynthesisUtterance(
890
- `${sound.pattern} makes the ${sound.pronunciation} sound`
891
- );
892
- fallbackUtterance.rate = 0.8;
893
- fallbackUtterance.voice = selectedVoice;
894
- speechSynthesis.speak(fallbackUtterance);
895
- };
896
 
897
- // Start speaking with IPA
898
- speechSynthesis.speak(utterance);
899
- }
900
-
901
- // Stop all speech
902
- function stopAllSpeech() {
903
- if (speechSynthesis.speaking) {
904
- speechSynthesis.cancel();
905
- isSpeaking = false;
906
  }
907
 
908
- document.querySelectorAll('.sound-card').forEach(card => {
909
- card.classList.remove('playing');
 
 
 
 
 
910
  });
911
-
912
- audioStatus.classList.remove('show');
 
 
 
 
 
 
 
 
 
 
 
 
913
  }
914
-
915
- // Handle search
916
- function handleSearch(event) {
917
- const searchTerm = event.target.value.toLowerCase().trim();
918
-
919
- if (!searchTerm) {
920
- renderSounds(currentCategory);
921
- return;
 
 
 
 
 
 
 
922
  }
923
-
924
- // Search in all sounds
925
- const allSounds = phonicsData[0].sounds;
926
- const filteredSounds = allSounds.filter(sound =>
927
- sound.pattern.toLowerCase().includes(searchTerm) ||
928
- sound.ipa.toLowerCase().includes(searchTerm) ||
929
- sound.examples.some(example => example.toLowerCase().includes(searchTerm)) ||
930
- sound.pronunciation.toLowerCase().includes(searchTerm)
931
- );
932
-
933
- // Render filtered results
934
- soundGrid.innerHTML = '';
935
-
936
- if (filteredSounds.length === 0) {
937
- soundGrid.innerHTML = `
938
- <div style="grid-column: 1/-1; text-align: center; padding: 40px; color: #666;">
939
- <i class="fas fa-search" style="font-size: 2rem; margin-bottom: 10px;"></i>
940
- <p>No phonics patterns found for "${searchTerm}"</p>
941
- </div>
942
- `;
943
- return;
944
  }
 
 
 
 
 
945
 
946
- filteredSounds.forEach(sound => {
947
- const card = document.createElement('div');
948
- card.className = 'sound-card';
949
- card.dataset.pattern = sound.pattern;
950
-
951
- card.innerHTML = `
952
- <div class="play-icon"><i class="fas fa-play-circle"></i></div>
953
- <div class="sound-pattern">${sound.pattern}</div>
954
- <div>
955
- <span class="sound-pronunciation">${sound.pronunciation}</span>
956
- <span class="sound-ipa">${sound.ipa}</span>
957
- </div>
958
- <div class="sound-examples">
959
- ${sound.examples.slice(0, 3).map(example =>
960
- `<span>${example}</span>`
961
- ).join('')}
962
- </div>
963
- `;
964
 
965
- card.addEventListener('click', (e) => {
966
- e.stopPropagation();
967
- playSoundWithIPA(sound);
 
968
  });
969
 
970
- soundGrid.appendChild(card);
971
  });
972
  }
973
-
974
- // Initialize when page loads
975
- document.addEventListener('DOMContentLoaded', initApp);
976
 
977
- // Ensure voices are loaded
978
- if (speechSynthesis.onvoiceschanged !== undefined) {
979
- speechSynthesis.onvoiceschanged = initApp;
 
 
 
 
 
 
 
 
 
 
 
980
  }
 
 
 
981
  </script>
982
  </body>
983
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Phonics Flash Cards - 100 Patterns</title>
7
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
  <style>
9
  :root {
10
+ --primary: #6a11cb;
11
+ --secondary: #2575fc;
12
+ --accent: #ff416c;
13
+ --success: #38ef7d;
14
+ --warning: #ffb347;
15
  --light: #f8f9fa;
16
  --dark: #212529;
17
+ --card-front: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
18
+ --card-back: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%);
19
+ --border-radius: 20px;
20
+ --shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
21
+ --transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
22
  }
23
 
24
  * {
 
29
  }
30
 
31
  body {
32
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
33
  color: var(--dark);
34
+ min-height: 100vh;
35
+ padding: 20px;
36
  }
37
 
38
  .container {
39
+ max-width: 1400px;
40
  margin: 0 auto;
 
41
  }
42
 
43
  header {
44
  text-align: center;
45
+ margin-bottom: 40px;
46
  padding: 30px 20px;
47
+ background: white;
 
48
  border-radius: var(--border-radius);
 
49
  box-shadow: var(--shadow);
50
+ position: relative;
51
+ overflow: hidden;
52
  }
53
 
54
+ header::before {
55
+ content: '';
56
+ position: absolute;
57
+ top: 0;
58
+ left: 0;
59
+ right: 0;
60
+ height: 5px;
61
+ background: linear-gradient(90deg, var(--primary), var(--secondary), var(--accent));
62
  }
63
 
64
+ .header-content {
65
+ max-width: 800px;
66
+ margin: 0 auto;
 
67
  }
68
 
69
+ h1 {
70
+ font-size: 2.8rem;
71
+ background: linear-gradient(90deg, var(--primary), var(--secondary));
72
+ -webkit-background-clip: text;
73
+ background-clip: text;
74
+ color: transparent;
75
+ margin-bottom: 10px;
76
+ }
77
+
78
+ .subtitle {
79
+ font-size: 1.2rem;
80
+ color: #666;
81
+ margin-bottom: 25px;
82
+ }
83
+
84
+ .stats-bar {
85
+ display: flex;
86
+ justify-content: center;
87
+ gap: 30px;
88
+ flex-wrap: wrap;
89
+ margin-top: 25px;
90
+ }
91
+
92
+ .stat-item {
93
+ background: var(--light);
94
+ padding: 15px 25px;
95
+ border-radius: 15px;
96
+ text-align: center;
97
+ box-shadow: 0 5px 15px rgba(0,0,0,0.05);
98
+ min-width: 140px;
99
+ }
100
+
101
+ .stat-number {
102
+ font-size: 2.2rem;
103
+ font-weight: bold;
104
+ color: var(--primary);
105
+ }
106
+
107
+ .stat-label {
108
+ font-size: 0.9rem;
109
+ color: #666;
110
+ margin-top: 5px;
111
+ }
112
+
113
+ .controls {
114
+ display: flex;
115
+ justify-content: center;
116
+ gap: 15px;
117
+ flex-wrap: wrap;
118
+ margin-bottom: 30px;
119
  }
120
 
121
+ .btn {
122
+ padding: 14px 28px;
123
+ background: white;
124
+ border: none;
125
+ border-radius: 50px;
126
+ font-weight: 600;
127
+ font-size: 1rem;
128
  cursor: pointer;
129
  transition: var(--transition);
 
130
  display: flex;
 
131
  align-items: center;
132
+ gap: 10px;
133
+ box-shadow: var(--shadow);
134
  }
135
 
136
+ .btn-primary {
137
+ background: linear-gradient(90deg, var(--primary), var(--secondary));
138
+ color: white;
139
  }
140
 
141
+ .btn-secondary {
142
+ background: white;
143
+ color: var(--dark);
144
  }
145
 
146
+ .btn:hover {
147
+ transform: translateY(-5px);
148
+ box-shadow: 0 15px 30px rgba(0, 0, 0, 0.2);
 
 
149
  }
150
 
151
+ .btn:active {
152
+ transform: translateY(-2px);
153
+ }
154
+
155
+ .filter-tags {
156
+ display: flex;
157
+ justify-content: center;
158
+ flex-wrap: wrap;
159
+ gap: 10px;
160
+ margin-bottom: 30px;
161
+ }
162
+
163
+ .tag {
164
+ padding: 10px 20px;
165
+ background: white;
166
+ border-radius: 50px;
167
  cursor: pointer;
168
+ transition: var(--transition);
169
+ font-weight: 500;
170
+ box-shadow: 0 5px 15px rgba(0,0,0,0.05);
171
  }
172
 
173
+ .tag.active {
174
+ background: linear-gradient(90deg, var(--primary), var(--secondary));
175
+ color: white;
 
176
  }
177
 
178
+ .tag:hover {
179
+ transform: translateY(-3px);
 
 
180
  }
181
 
182
+ .flashcards-grid {
183
+ display: grid;
184
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
185
+ gap: 25px;
186
+ margin-bottom: 40px;
187
  }
188
 
189
+ @media (max-width: 768px) {
190
+ .flashcards-grid {
191
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
192
+ }
 
 
193
  }
194
 
195
+ .flashcard {
196
+ height: 320px;
197
+ perspective: 1500px;
198
+ cursor: pointer;
 
 
 
 
 
 
 
 
199
  }
200
 
201
+ .flashcard-inner {
202
+ position: relative;
203
+ width: 100%;
204
+ height: 100%;
205
  text-align: center;
206
+ transition: var(--transition);
207
+ transform-style: preserve-3d;
208
+ border-radius: var(--border-radius);
209
+ box-shadow: var(--shadow);
210
  }
211
 
212
+ .flashcard.flipped .flashcard-inner {
213
+ transform: rotateY(180deg);
 
 
 
214
  }
215
 
216
+ .flashcard-front, .flashcard-back {
217
+ position: absolute;
218
+ width: 100%;
219
+ height: 100%;
220
+ -webkit-backface-visibility: hidden;
221
+ backface-visibility: hidden;
222
+ border-radius: var(--border-radius);
223
+ display: flex;
224
+ flex-direction: column;
225
+ justify-content: center;
226
+ align-items: center;
227
+ padding: 30px;
228
  }
229
 
230
+ .flashcard-front {
231
+ background: var(--card-front);
232
+ color: white;
 
 
 
233
  }
234
 
235
+ .flashcard-back {
236
+ background: var(--card-back);
237
+ color: white;
238
+ transform: rotateY(180deg);
239
  }
240
 
241
+ .pattern {
242
+ font-size: 4rem;
243
+ font-weight: bold;
244
+ margin-bottom: 20px;
245
+ font-family: 'Comic Sans MS', 'Chalkboard SE', cursive;
246
+ text-shadow: 0 5px 15px rgba(0,0,0,0.2);
247
  }
248
 
249
+ .pronunciation {
250
+ font-size: 2.5rem;
251
  font-weight: bold;
252
+ margin-bottom: 20px;
253
+ text-shadow: 0 5px 15px rgba(0,0,0,0.2);
 
 
254
  }
255
 
256
+ .hint {
257
+ font-size: 1rem;
258
+ opacity: 0.9;
259
+ margin-top: 20px;
260
+ font-style: italic;
261
+ }
262
+
263
+ .examples {
264
+ display: flex;
265
+ flex-direction: column;
266
+ gap: 15px;
267
+ width: 100%;
268
+ margin-top: 20px;
269
+ }
270
+
271
+ .example-word {
272
+ background: rgba(255, 255, 255, 0.2);
273
+ padding: 12px;
274
+ border-radius: 12px;
275
  font-size: 1.2rem;
276
+ font-weight: 600;
277
+ backdrop-filter: blur(10px);
278
+ border: 1px solid rgba(255, 255, 255, 0.3);
279
+ }
280
+
281
+ .category-badge {
282
+ position: absolute;
283
+ top: 15px;
284
+ right: 15px;
285
+ background: rgba(255, 255, 255, 0.2);
286
+ padding: 5px 15px;
287
+ border-radius: 20px;
288
+ font-size: 0.8rem;
289
+ font-weight: 600;
290
+ backdrop-filter: blur(5px);
291
  }
292
 
293
+ .flip-icon {
294
+ position: absolute;
295
  bottom: 20px;
296
+ font-size: 1.5rem;
297
+ opacity: 0.7;
298
+ }
299
+
300
+ .instructions {
301
+ text-align: center;
302
+ margin: 30px 0;
303
+ padding: 20px;
304
+ background: white;
305
+ border-radius: var(--border-radius);
306
  box-shadow: var(--shadow);
 
307
  }
308
 
309
+ .instructions p {
310
+ font-size: 1.1rem;
311
+ color: #666;
312
+ margin-bottom: 10px;
313
  }
314
 
315
+ .instructions strong {
316
+ color: var(--primary);
 
317
  }
318
 
319
+ footer {
320
+ text-align: center;
321
+ margin-top: 50px;
322
+ padding: 20px;
323
+ color: #666;
324
+ font-size: 0.9rem;
325
  }
326
 
327
+ .progress-container {
328
  width: 100%;
329
+ max-width: 500px;
330
+ height: 10px;
331
+ background: #e0e0e0;
332
+ border-radius: 5px;
333
+ margin: 20px auto;
334
+ overflow: hidden;
335
  }
336
 
337
+ .progress-bar {
338
+ height: 100%;
339
+ background: linear-gradient(90deg, var(--primary), var(--secondary));
340
+ width: 0%;
341
+ transition: width 0.5s ease;
342
  }
343
 
344
+ .card-counter {
345
+ position: absolute;
346
+ top: 15px;
347
+ left: 15px;
348
+ background: rgba(0,0,0,0.2);
349
+ color: white;
350
+ padding: 5px 12px;
351
+ border-radius: 20px;
352
  font-size: 0.9rem;
353
+ font-weight: 600;
 
354
  }
355
 
356
+ .thinking-time {
357
+ margin-top: 15px;
358
+ font-size: 0.9rem;
359
+ opacity: 0.8;
360
+ }
361
+
362
+ .thinking-timer {
363
+ font-weight: bold;
364
+ color: #ffd166;
365
+ }
366
+
367
+ .completed-badge {
368
+ position: absolute;
369
+ top: 50%;
370
+ left: 50%;
371
+ transform: translate(-50%, -50%);
372
+ background: rgba(255, 255, 255, 0.9);
373
+ color: var(--success);
374
+ padding: 10px 20px;
375
+ border-radius: 50px;
376
+ font-weight: bold;
377
+ font-size: 1.5rem;
378
+ display: none;
379
+ z-index: 10;
380
+ box-shadow: 0 10px 30px rgba(0,0,0,0.3);
381
+ }
382
+
383
+ .flashcard.completed .completed-badge {
384
+ display: block;
385
  }
386
  </style>
387
  </head>
388
  <body>
389
  <div class="container">
390
  <header>
391
+ <div class="header-content">
392
+ <h1><i class="fas fa-lightbulb"></i> Phonics Flash Cards</h1>
393
+ <p class="subtitle">Flip cards to reveal pronunciation and example words. Think of your own examples first!</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
 
395
+ <div class="stats-bar">
396
+ <div class="stat-item">
397
+ <div class="stat-number" id="totalCards">100</div>
398
+ <div class="stat-label">Total Cards</div>
399
+ </div>
400
+ <div class="stat-item">
401
+ <div class="stat-number" id="flippedCards">0</div>
402
+ <div class="stat-label">Flipped</div>
403
+ </div>
404
+ <div class="stat-item">
405
+ <div class="stat-number" id="completedCards">0</div>
406
+ <div class="stat-label">Mastered</div>
407
+ </div>
408
+ <div class="stat-item">
409
+ <div class="stat-number" id="remainingCards">100</div>
410
+ <div class="stat-label">Remaining</div>
411
+ </div>
412
  </div>
413
 
414
+ <div class="progress-container">
415
+ <div class="progress-bar" id="progressBar"></div>
 
 
 
 
 
 
 
416
  </div>
417
  </div>
418
+ </header>
419
+
420
+ <div class="instructions">
421
+ <p><strong>How to use:</strong> Look at the phonics pattern on the front of the card. Think of at least 3 words that use this pattern. Then flip the card to check the pronunciation and example words.</p>
422
+ <p>Mark cards as "Mastered" when you can think of examples without flipping.</p>
423
  </div>
424
 
425
+ <div class="controls">
426
+ <button class="btn btn-primary" id="flipAllBtn">
427
+ <i class="fas fa-sync-alt"></i> Flip All Back
428
+ </button>
429
+ <button class="btn btn-secondary" id="shuffleBtn">
430
+ <i class="fas fa-random"></i> Shuffle Cards
431
+ </button>
432
+ <button class="btn btn-secondary" id="resetBtn">
433
+ <i class="fas fa-redo"></i> Reset Progress
434
+ </button>
435
+ <button class="btn btn-secondary" id="markAllBtn">
436
+ <i class="fas fa-check-double"></i> Mark All Mastered
437
+ </button>
438
  </div>
439
+
440
+ <div class="filter-tags" id="filterTags">
441
+ <!-- Filter tags will be added by JavaScript -->
442
+ </div>
443
+
444
+ <div class="flashcards-grid" id="flashcardsGrid">
445
+ <!-- Flashcards will be added by JavaScript -->
446
+ </div>
447
+
448
+ <footer>
449
+ <p>Phonics Flash Cards - 100 Essential 2nd Grade Patterns | Designed for Active Learning</p>
450
+ <p>Try to think of your own examples before flipping each card!</p>
451
+ </footer>
452
  </div>
453
 
454
  <script>
455
+ // 100 Phonics Patterns - Even 100!
456
  const phonicsData = [
457
+ // Consonant Digraphs (15)
458
+ { pattern: "sh", pronunciation: "shh", examples: ["ship", "wish", "shell"], category: "Consonant", color: "#6a11cb" },
459
+ { pattern: "ch", pronunciation: "ch", examples: ["chat", "chip", "lunch"], category: "Consonant", color: "#6a11cb" },
460
+ { pattern: "th", pronunciation: "th (soft)", examples: ["thin", "think", "bath"], category: "Consonant", color: "#6a11cb" },
461
+ { pattern: "th", pronunciation: "th (hard)", examples: ["this", "that", "mother"], category: "Consonant", color: "#6a11cb" },
462
+ { pattern: "wh", pronunciation: "wh", examples: ["when", "whale", "whisper"], category: "Consonant", color: "#6a11cb" },
463
+ { pattern: "ph", pronunciation: "f", examples: ["phone", "graph", "elephant"], category: "Consonant", color: "#6a11cb" },
464
+ { pattern: "ng", pronunciation: "ng", examples: ["song", "king", "ring"], category: "Consonant", color: "#6a11cb" },
465
+ { pattern: "ck", pronunciation: "k", examples: ["duck", "clock", "sock"], category: "Consonant", color: "#6a11cb" },
466
+ { pattern: "tch", pronunciation: "ch", examples: ["catch", "watch", "patch"], category: "Consonant", color: "#6a11cb" },
467
+ { pattern: "dge", pronunciation: "j", examples: ["bridge", "fudge", "badge"], category: "Consonant", color: "#6a11cb" },
468
+ { pattern: "gh", pronunciation: "f", examples: ["enough", "laugh", "rough"], category: "Consonant", color: "#6a11cb" },
469
+ { pattern: "gn", pronunciation: "n", examples: ["gnaw", "sign", "gnome"], category: "Consonant", color: "#6a11cb" },
470
+ { pattern: "kn", pronunciation: "n", examples: ["knee", "know", "knock"], category: "Consonant", color: "#6a11cb" },
471
+ { pattern: "wr", pronunciation: "r", examples: ["write", "wrong", "wrap"], category: "Consonant", color: "#6a11cb" },
472
+ { pattern: "mb", pronunciation: "m", examples: ["comb", "thumb", "climb"], category: "Consonant", color: "#6a11cb" },
473
+
474
+ // Vowel Teams (30)
475
+ { pattern: "ai", pronunciation: "ay", examples: ["rain", "train", "wait"], category: "Vowels", color: "#ff416c" },
476
+ { pattern: "ay", pronunciation: "ay", examples: ["day", "play", "say"], category: "Vowels", color: "#ff416c" },
477
+ { pattern: "ee", pronunciation: "ee", examples: ["tree", "see", "sleep"], category: "Vowels", color: "#ff416c" },
478
+ { pattern: "ea", pronunciation: "ee", examples: ["eat", "sea", "team"], category: "Vowels", color: "#ff416c" },
479
+ { pattern: "ie", pronunciation: "ee", examples: ["piece", "chief", "field"], category: "Vowels", color: "#ff416c" },
480
+ { pattern: "y", pronunciation: "ee", examples: ["happy", "baby", "funny"], category: "Vowels", color: "#ff416c" },
481
+ { pattern: "y", pronunciation: "eye", examples: ["cry", "fly", "my"], category: "Vowels", color: "#ff416c" },
482
+ { pattern: "igh", pronunciation: "eye", examples: ["night", "light", "high"], category: "Vowels", color: "#ff416c" },
483
+ { pattern: "oa", pronunciation: "oh", examples: ["boat", "coat", "road"], category: "Vowels", color: "#ff416c" },
484
+ { pattern: "oe", pronunciation: "oh", examples: ["toe", "hoe", "foe"], category: "Vowels", color: "#ff416c" },
485
+ { pattern: "ow", pronunciation: "oh", examples: ["snow", "grow", "show"], category: "Vowels", color: "#ff416c" },
486
+ { pattern: "ou", pronunciation: "ow", examples: ["out", "cloud", "found"], category: "Vowels", color: "#ff416c" },
487
+ { pattern: "ow", pronunciation: "ow", examples: ["cow", "town", "down"], category: "Vowels", color: "#ff416c" },
488
+ { pattern: "oi", pronunciation: "oy", examples: ["coin", "boil", "soil"], category: "Vowels", color: "#ff416c" },
489
+ { pattern: "oy", pronunciation: "oy", examples: ["toy", "boy", "enjoy"], category: "Vowels", color: "#ff416c" },
490
+ { pattern: "oo", pronunciation: "oo (long)", examples: ["moon", "spoon", "tooth"], category: "Vowels", color: "#ff416c" },
491
+ { pattern: "oo", pronunciation: "oo (short)", examples: ["book", "look", "foot"], category: "Vowels", color: "#ff416c" },
492
+ { pattern: "ew", pronunciation: "yoo", examples: ["few", "dew", "new"], category: "Vowels", color: "#ff416c" },
493
+ { pattern: "ue", pronunciation: "yoo", examples: ["blue", "true", "clue"], category: "Vowels", color: "#ff416c" },
494
+ { pattern: "au", pronunciation: "aw", examples: ["haul", "autumn", "launch"], category: "Vowels", color: "#ff416c" },
495
+ { pattern: "aw", pronunciation: "aw", examples: ["saw", "draw", "claw"], category: "Vowels", color: "#ff416c" },
496
+ { pattern: "ei", pronunciation: "ay", examples: ["veil", "eight", "weigh"], category: "Vowels", color: "#ff416c" },
497
+ { pattern: "ey", pronunciation: "ay", examples: ["they", "prey", "grey"], category: "Vowels", color: "#ff416c" },
498
+ { pattern: "ei", pronunciation: "ee", examples: ["receive", "ceiling", "deceive"], category: "Vowels", color: "#ff416c" },
499
+ { pattern: "ey", pronunciation: "ee", examples: ["key", "monkey", "honey"], category: "Vowels", color: "#ff416c" },
500
+ { pattern: "ie", pronunciation: "eye", examples: ["pie", "tie", "die"], category: "Vowels", color: "#ff416c" },
501
+ { pattern: "ea", pronunciation: "eh", examples: ["bread", "head", "ready"], category: "Vowels", color: "#ff416c" },
502
+ { pattern: "ou", pronunciation: "oo", examples: ["soup", "group", "youth"], category: "Vowels", color: "#ff416c" },
503
+ { pattern: "ui", pronunciation: "oo", examples: ["fruit", "juice", "suit"], category: "Vowels", color: "#ff416c" },
504
+ { pattern: "ew", pronunciation: "oo", examples: ["blew", "grew", "flew"], category: "Vowels", color: "#ff416c" },
505
+
506
+ // Word Endings (15)
507
+ { pattern: "tion", pronunciation: "shun", examples: ["action", "station", "nation"], category: "Endings", color: "#38ef7d" },
508
+ { pattern: "sion", pronunciation: "zhun", examples: ["vision", "decision", "confusion"], category: "Endings", color: "#38ef7d" },
509
+ { pattern: "sion", pronunciation: "shun", examples: ["mission", "passion", "session"], category: "Endings", color: "#38ef7d" },
510
+ { pattern: "cian", pronunciation: "shun", examples: ["musician", "magician", "physician"], category: "Endings", color: "#38ef7d" },
511
+ { pattern: "ture", pronunciation: "chur", examples: ["picture", "future", "nature"], category: "Endings", color: "#38ef7d" },
512
+ { pattern: "le", pronunciation: "ul", examples: ["table", "candle", "little"], category: "Endings", color: "#38ef7d" },
513
+ { pattern: "ous", pronunciation: "us", examples: ["famous", "nervous", "dangerous"], category: "Endings", color: "#38ef7d" },
514
+ { pattern: "cious", pronunciation: "shus", examples: ["delicious", "suspicious", "precious"], category: "Endings", color: "#38ef7d" },
515
+ { pattern: "tious", pronunciation: "shus", examples: ["ambitious", "cautious", "nutritious"], category: "Endings", color: "#38ef7d" },
516
+ { pattern: "cial", pronunciation: "shul", examples: ["special", "social", "official"], category: "Endings", color: "#38ef7d" },
517
+ { pattern: "tial", pronunciation: "shul", examples: ["partial", "essential", "initial"], category: "Endings", color: "#38ef7d" },
518
+ { pattern: "age", pronunciation: "ij", examples: ["cage", "page", "stage"], category: "Endings", color: "#38ef7d" },
519
+ { pattern: "age", pronunciation: "azh", examples: ["garage", "massage", "mirage"], category: "Endings", color: "#38ef7d" },
520
+ { pattern: "ine", pronunciation: "in", examples: ["engine", "imagine", "medicine"], category: "Endings", color: "#38ef7d" },
521
+ { pattern: "ine", pronunciation: "ine", examples: ["pine", "fine", "line"], category: "Endings", color: "#38ef7d" },
522
+
523
+ // R-Controlled (15)
524
+ { pattern: "ar", pronunciation: "ar", examples: ["car", "star", "farm"], category: "R-Controlled", color: "#ffb347" },
525
+ { pattern: "er", pronunciation: "ur", examples: ["her", "fern", "verb"], category: "R-Controlled", color: "#ffb347" },
526
+ { pattern: "ir", pronunciation: "ur", examples: ["bird", "girl", "first"], category: "R-Controlled", color: "#ffb347" },
527
+ { pattern: "or", pronunciation: "or", examples: ["fork", "corn", "sport"], category: "R-Controlled", color: "#ffb347" },
528
+ { pattern: "ur", pronunciation: "ur", examples: ["burn", "turn", "surf"], category: "R-Controlled", color: "#ffb347" },
529
+ { pattern: "air", pronunciation: "air", examples: ["chair", "fair", "stair"], category: "R-Controlled", color: "#ffb347" },
530
+ { pattern: "are", pronunciation: "air", examples: ["care", "share", "dare"], category: "R-Controlled", color: "#ffb347" },
531
+ { pattern: "ear", pronunciation: "eer", examples: ["hear", "fear", "near"], category: "R-Controlled", color: "#ffb347" },
532
+ { pattern: "ear", pronunciation: "air", examples: ["bear", "wear", "pear"], category: "R-Controlled", color: "#ffb347" },
533
+ { pattern: "ear", pronunciation: "ur", examples: ["earth", "learn", "pearl"], category: "R-Controlled", color: "#ffb347" },
534
+ { pattern: "eer", pronunciation: "eer", examples: ["deer", "cheer", "peer"], category: "R-Controlled", color: "#ffb347" },
535
+ { pattern: "ier", pronunciation: "eer", examples: ["pier", "tier", "fierier"], category: "R-Controlled", color: "#ffb347" },
536
+ { pattern: "ore", pronunciation: "or", examples: ["more", "store", "before"], category: "R-Controlled", color: "#ffb347" },
537
+ { pattern: "ure", pronunciation: "yoor", examples: ["cure", "pure", "secure"], category: "R-Controlled", color: "#ffb347" },
538
+ { pattern: "our", pronunciation: "or", examples: ["four", "pour", "your"], category: "R-Controlled", color: "#ffb347" },
539
+
540
+ // Blends & Special (25)
541
+ { pattern: "sc", pronunciation: "s", examples: ["scene", "science", "scissors"], category: "Blends", color: "#2575fc" },
542
+ { pattern: "sch", pronunciation: "sk", examples: ["school", "scheme", "schedule"], category: "Blends", color: "#2575fc" },
543
+ { pattern: "scr", pronunciation: "scr", examples: ["scrub", "scream", "scroll"], category: "Blends", color: "#2575fc" },
544
+ { pattern: "shr", pronunciation: "shr", examples: ["shrink", "shred", "shrimp"], category: "Blends", color: "#2575fc" },
545
+ { pattern: "spl", pronunciation: "spl", examples: ["splash", "split", "splinter"], category: "Blends", color: "#2575fc" },
546
+ { pattern: "spr", pronunciation: "spr", examples: ["spring", "spray", "spread"], category: "Blends", color: "#2575fc" },
547
+ { pattern: "squ", pronunciation: "skw", examples: ["square", "squash", "squirrel"], category: "Blends", color: "#2575fc" },
548
+ { pattern: "str", pronunciation: "str", examples: ["string", "street", "strong"], category: "Blends", color: "#2575fc" },
549
+ { pattern: "thr", pronunciation: "thr", examples: ["three", "throw", "thread"], category: "Blends", color: "#2575fc" },
550
+ { pattern: "bl", pronunciation: "bl", examples: ["blue", "black", "blanket"], category: "Blends", color: "#2575fc" },
551
+ { pattern: "cl", pronunciation: "cl", examples: ["clap", "clock", "cloud"], category: "Blends", color: "#2575fc" },
552
+ { pattern: "fl", pronunciation: "fl", examples: ["flag", "flower", "fly"], category: "Blends", color: "#2575fc" },
553
+ { pattern: "gl", pronunciation: "gl", examples: ["glad", "glue", "globe"], category: "Blends", color: "#2575fc" },
554
+ { pattern: "pl", pronunciation: "pl", examples: ["play", "plant", "plane"], category: "Blends", color: "#2575fc" },
555
+ { pattern: "sl", pronunciation: "sl", examples: ["sleep", "slide", "slow"], category: "Blends", color: "#2575fc" },
556
+ { pattern: "br", pronunciation: "br", examples: ["bread", "brick", "brother"], category: "Blends", color: "#2575fc" },
557
+ { pattern: "cr", pronunciation: "cr", examples: ["crab", "cry", "cross"], category: "Blends", color: "#2575fc" },
558
+ { pattern: "dr", pronunciation: "dr", examples: ["dress", "drink", "drive"], category: "Blends", color: "#2575fc" },
559
+ { pattern: "fr", pronunciation: "fr", examples: ["frog", "fruit", "friend"], category: "Blends", color: "#2575fc" },
560
+ { pattern: "gr", pronunciation: "gr", examples: ["green", "grass", "grow"], category: "Blends", color: "#2575fc" },
561
+ { pattern: "pr", pronunciation: "pr", examples: ["pray", "prince", "present"], category: "Blends", color: "#2575fc" },
562
+ { pattern: "tr", pronunciation: "tr", examples: ["tree", "truck", "train"], category: "Blends", color: "#2575fc" },
563
+ { pattern: "sw", pronunciation: "sw", examples: ["swim", "sweet", "swing"], category: "Blends", color: "#2575fc" },
564
+ { pattern: "tw", pronunciation: "tw", examples: ["twin", "twelve", "twist"], category: "Blends", color: "#2575fc" },
565
+ { pattern: "qu", pronunciation: "kw", examples: ["queen", "quick", "quiet"], category: "Blends", color: "#2575fc" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
566
  ];
567
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
568
  // App state
569
+ let flashcards = [];
570
+ let currentFilter = 'all';
571
+ let masteredCards = JSON.parse(localStorage.getItem('phonicsMastered')) || [];
572
+ let flippedCards = JSON.parse(localStorage.getItem('phonicsFlipped')) || [];
573
+ let thinkingTimers = {};
574
 
575
  // DOM Elements
576
+ const flashcardsGrid = document.getElementById('flashcardsGrid');
577
+ const filterTags = document.getElementById('filterTags');
578
+ const totalCardsEl = document.getElementById('totalCards');
579
+ const flippedCardsEl = document.getElementById('flippedCards');
580
+ const completedCardsEl = document.getElementById('completedCards');
581
+ const remainingCardsEl = document.getElementById('remainingCards');
582
+ const progressBar = document.getElementById('progressBar');
583
+ const flipAllBtn = document.getElementById('flipAllBtn');
584
+ const shuffleBtn = document.getElementById('shuffleBtn');
585
+ const resetBtn = document.getElementById('resetBtn');
586
+ const markAllBtn = document.getElementById('markAllBtn');
587
+
588
+ // Initialize app
589
  function initApp() {
590
+ // Verify we have 100 cards
591
+ totalCardsEl.textContent = phonicsData.length;
592
+
593
+ // Initialize flashcards
594
+ createFlashcards();
595
+ renderFlashcards();
596
+ renderFilterTags();
597
+ updateStats();
598
+
599
+ // Event listeners
600
+ flipAllBtn.addEventListener('click', flipAllBack);
601
+ shuffleBtn.addEventListener('click', shuffleFlashcards);
602
+ resetBtn.addEventListener('click', resetProgress);
603
+ markAllBtn.addEventListener('click', markAllMastered);
604
+ }
605
+
606
+ // Create flashcards from data
607
+ function createFlashcards() {
608
+ flashcards = phonicsData.map((item, index) => {
609
+ return {
610
+ id: index,
611
+ pattern: item.pattern,
612
+ pronunciation: item.pronunciation,
613
+ examples: item.examples,
614
+ category: item.category,
615
+ color: item.color,
616
+ isFlipped: flippedCards.includes(index),
617
+ isMastered: masteredCards.includes(index)
618
+ };
619
  });
 
 
 
 
 
 
 
 
 
620
  }
621
+
622
+ // Render flashcards to grid
623
+ function renderFlashcards() {
624
+ flashcardsGrid.innerHTML = '';
 
625
 
626
+ const filteredCards = currentFilter === 'all'
627
+ ? flashcards
628
+ : flashcards.filter(card => card.category === currentFilter);
629
 
630
+ filteredCards.forEach((card, index) => {
631
+ const flashcard = document.createElement('div');
632
+ flashcard.className = `flashcard ${card.isFlipped ? 'flipped' : ''} ${card.isMastered ? 'completed' : ''}`;
633
+ flashcard.dataset.id = card.id;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
634
 
635
+ // Start thinking timer when card is shown
636
+ if (!thinkingTimers[card.id]) {
637
+ thinkingTimers[card.id] = 0;
 
 
 
 
 
 
 
 
 
 
638
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
639
 
640
+ flashcard.innerHTML = `
641
+ <div class="card-counter">${index + 1}/${filteredCards.length}</div>
642
+ <div class="completed-badge"><i class="fas fa-check-circle"></i> MASTERED</div>
643
+ <div class="flashcard-inner">
644
+ <div class="flashcard-front">
645
+ <div class="category-badge">${card.category}</div>
646
+ <div class="pattern">${card.pattern}</div>
647
+ <div class="hint">Think of 3 words with this pattern...</div>
648
+ <div class="flip-icon"><i class="fas fa-hand-point-up"></i></div>
649
+ <div class="thinking-time">
650
+ Thinking time: <span class="thinking-timer" id="timer-${card.id}">0</span>s
651
+ </div>
652
+ </div>
653
+ <div class="flashcard-back">
654
+ <div class="category-badge">${card.category}</div>
655
+ <div class="pronunciation">"${card.pronunciation}"</div>
656
+ <div class="examples">
657
+ ${card.examples.map(example => `
658
+ <div class="example-word">${example}</div>
659
+ `).join('')}
660
+ </div>
661
+ <div class="hint">Did you think of these?</div>
662
+ <div class="flip-icon"><i class="fas fa-undo"></i></div>
663
+ </div>
664
  </div>
665
  `;
666
 
667
+ // Add click event for flipping
668
+ flashcard.addEventListener('click', (e) => {
669
+ if (!e.target.closest('.btn')) {
670
+ toggleFlashcard(card.id);
671
+ }
672
+ });
673
+
674
+ // Add right-click for marking as mastered
675
+ flashcard.addEventListener('contextmenu', (e) => {
676
+ e.preventDefault();
677
+ toggleMastered(card.id);
678
  });
679
 
680
+ flashcardsGrid.appendChild(flashcard);
681
  });
 
 
 
 
 
 
682
 
683
+ // Start thinking timers for visible cards
684
+ startThinkingTimers(filteredCards);
685
+ }
686
+
687
+ // Start thinking timers for cards
688
+ function startThinkingTimers(cards) {
689
+ cards.forEach(card => {
690
+ const timerElement = document.getElementById(`timer-${card.id}`);
691
+ if (timerElement && !card.isFlipped && !card.isMastered) {
692
+ // Update timer every second
693
+ const timerInterval = setInterval(() => {
694
+ thinkingTimers[card.id]++;
695
+ timerElement.textContent = thinkingTimers[card.id];
696
+
697
+ // If card gets flipped or mastered, stop timer
698
+ const currentCard = flashcards.find(c => c.id === card.id);
699
+ if (currentCard.isFlipped || currentCard.isMastered) {
700
+ clearInterval(timerInterval);
701
+ }
702
+ }, 1000);
703
+ }
704
  });
705
+ }
706
+
707
+ // Toggle flashcard flip state
708
+ function toggleFlashcard(cardId) {
709
+ const card = flashcards.find(c => c.id === cardId);
710
+ card.isFlipped = !card.isFlipped;
711
 
712
+ // Update flipped cards in localStorage
713
+ if (card.isFlipped && !flippedCards.includes(cardId)) {
714
+ flippedCards.push(cardId);
715
+ } else if (!card.isFlipped && flippedCards.includes(cardId)) {
716
+ flippedCards = flippedCards.filter(id => id !== cardId);
717
  }
718
 
719
+ localStorage.setItem('phonicsFlipped', JSON.stringify(flippedCards));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
720
 
721
+ // Update UI
722
+ const cardElement = document.querySelector(`[data-id="${cardId}"]`);
723
+ if (cardElement) {
724
+ cardElement.classList.toggle('flipped');
 
 
 
725
  }
726
 
727
+ updateStats();
728
+ }
729
+
730
+ // Toggle mastered state
731
+ function toggleMastered(cardId) {
732
+ const card = flashcards.find(c => c.id === cardId);
733
+ card.isMastered = !card.isMastered;
734
 
735
+ // Update mastered cards in localStorage
736
+ if (card.isMastered && !masteredCards.includes(cardId)) {
737
+ masteredCards.push(cardId);
738
+ } else if (!card.isMastered && masteredCards.includes(cardId)) {
739
+ masteredCards = masteredCards.filter(id => id !== cardId);
740
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
741
 
742
+ localStorage.setItem('phonicsMastered', JSON.stringify(masteredCards));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
743
 
744
+ // Update UI
745
+ const cardElement = document.querySelector(`[data-id="${cardId}"]`);
746
+ if (cardElement) {
747
+ cardElement.classList.toggle('completed');
 
 
 
 
 
748
  }
749
 
750
+ updateStats();
751
+ }
752
+
753
+ // Flip all cards back to front
754
+ function flipAllBack() {
755
+ flashcards.forEach(card => {
756
+ card.isFlipped = false;
757
  });
758
+ flippedCards = [];
759
+ localStorage.setItem('phonicsFlipped', JSON.stringify([]));
760
+ renderFlashcards();
761
+ updateStats();
762
+ }
763
+
764
+ // Shuffle flashcards
765
+ function shuffleFlashcards() {
766
+ // Fisher-Yates shuffle
767
+ for (let i = flashcards.length - 1; i > 0; i--) {
768
+ const j = Math.floor(Math.random() * (i + 1));
769
+ [flashcards[i], flashcards[j]] = [flashcards[j], flashcards[i]];
770
+ }
771
+ renderFlashcards();
772
  }
773
+
774
+ // Reset all progress
775
+ function resetProgress() {
776
+ if (confirm('Are you sure you want to reset all progress? This will unflip all cards and clear mastered status.')) {
777
+ flashcards.forEach(card => {
778
+ card.isFlipped = false;
779
+ card.isMastered = false;
780
+ });
781
+ flippedCards = [];
782
+ masteredCards = [];
783
+ thinkingTimers = {};
784
+ localStorage.setItem('phonicsFlipped', JSON.stringify([]));
785
+ localStorage.setItem('phonicsMastered', JSON.stringify([]));
786
+ renderFlashcards();
787
+ updateStats();
788
  }
789
+ }
790
+
791
+ // Mark all cards as mastered
792
+ function markAllMastered() {
793
+ if (confirm('Mark all cards as mastered?')) {
794
+ flashcards.forEach(card => {
795
+ card.isMastered = true;
796
+ if (!masteredCards.includes(card.id)) {
797
+ masteredCards.push(card.id);
798
+ }
799
+ });
800
+ localStorage.setItem('phonicsMastered', JSON.stringify(masteredCards));
801
+ renderFlashcards();
802
+ updateStats();
 
 
 
 
 
 
 
803
  }
804
+ }
805
+
806
+ // Render filter tags
807
+ function renderFilterTags() {
808
+ filterTags.innerHTML = '';
809
 
810
+ const categories = ['all', ...new Set(phonicsData.map(item => item.category))];
811
+
812
+ categories.forEach(category => {
813
+ const tag = document.createElement('div');
814
+ tag.className = `tag ${currentFilter === category ? 'active' : ''}`;
815
+ tag.textContent = category === 'all' ? 'All Cards' : category;
816
+ tag.dataset.category = category;
 
 
 
 
 
 
 
 
 
 
 
817
 
818
+ tag.addEventListener('click', () => {
819
+ currentFilter = category;
820
+ renderFilterTags();
821
+ renderFlashcards();
822
  });
823
 
824
+ filterTags.appendChild(tag);
825
  });
826
  }
 
 
 
827
 
828
+ // Update statistics
829
+ function updateStats() {
830
+ const total = flashcards.length;
831
+ const flipped = flashcards.filter(card => card.isFlipped).length;
832
+ const completed = flashcards.filter(card => card.isMastered).length;
833
+ const remaining = total - completed;
834
+
835
+ flippedCardsEl.textContent = flipped;
836
+ completedCardsEl.textContent = completed;
837
+ remainingCardsEl.textContent = remaining;
838
+
839
+ // Update progress bar
840
+ const progress = (completed / total) * 100;
841
+ progressBar.style.width = `${progress}%`;
842
  }
843
+
844
+ // Initialize when page loads
845
+ document.addEventListener('DOMContentLoaded', initApp);
846
  </script>
847
  </body>
848
  </html>