AptlyDigital commited on
Commit
a041102
·
verified ·
1 Parent(s): 9fcc717

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +525 -1
index.html CHANGED
@@ -570,4 +570,528 @@
570
 
571
  // Vowel Teams (30 patterns)
572
  { pattern: "ai", pronunciation: "ay", category: "Vowels", words: ["rain", "train", "wait", "mail", "pain", "brain", "chain", "sail", "tail", "frail"] },
573
- { pattern: "ay", pronunciation: "ay", catego
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570
 
571
  // Vowel Teams (30 patterns)
572
  { pattern: "ai", pronunciation: "ay", category: "Vowels", words: ["rain", "train", "wait", "mail", "pain", "brain", "chain", "sail", "tail", "frail"] },
573
+ { pattern: "ay", pronunciation: "ay", category: "Vowels", words: ["day", "play", "say", "way", "may", "pay", "stay", "gray", "clay", "spray"] },
574
+ { pattern: "ee", pronunciation: "ee", category: "Vowels", words: ["tree", "see", "sleep", "bee", "feet", "green", "sheep", "three", "queen", "sweet"] },
575
+ { pattern: "ea", pronunciation: "ee", category: "Vowels", words: ["eat", "sea", "team", "leaf", "meal", "seat", "beach", "peach", "teach", "cheap"] },
576
+ { pattern: "ie", pronunciation: "ee", category: "Vowels", words: ["piece", "chief", "field", "believe", "grief", "relief", "thief", "yield", "shield", "niece"] },
577
+ { pattern: "y", pronunciation: "ee", category: "Vowels", words: ["happy", "baby", "funny", "sunny", "candy", "city", "puppy", "lady", "penny", "cherry"] },
578
+ { pattern: "y", pronunciation: "eye", category: "Vowels", words: ["cry", "fly", "my", "dry", "sky", "try", "why", "shy", "spy", "ply"] },
579
+ { pattern: "igh", pronunciation: "eye", category: "Vowels", words: ["night", "light", "high", "right", "fight", "bright", "flight", "sight", "tight", "might"] },
580
+ { pattern: "oa", pronunciation: "oh", category: "Vowels", words: ["boat", "coat", "road", "goat", "soap", "toast", "float", "throat", "coach", "roast"] },
581
+ { pattern: "oe", pronunciation: "oh", category: "Vowels", words: ["toe", "hoe", "foe", "doe", "woe", "aloe", "canoe", "oboe", "roe", "shoe"] },
582
+ { pattern: "ow", pronunciation: "oh", category: "Vowels", words: ["snow", "grow", "show", "bow", "row", "blow", "flow", "slow", "throw", "arrow"] },
583
+ { pattern: "ou", pronunciation: "ow", category: "Vowels", words: ["out", "cloud", "found", "house", "mouse", "shout", "proud", "round", "sound", "count"] },
584
+ { pattern: "ow", pronunciation: "ow", category: "Vowels", words: ["cow", "town", "down", "how", "now", "brown", "crowd", "flower", "power", "tower"] },
585
+ { pattern: "oi", pronunciation: "oy", category: "Vowels", words: ["coin", "boil", "soil", "oil", "noise", "voice", "choice", "spoil", "join", "point"] },
586
+ { pattern: "oy", pronunciation: "oy", category: "Vowels", words: ["toy", "boy", "enjoy", "joy", "soy", "annoy", "destroy", "employ", "loyal", "royal"] },
587
+ { pattern: "oo", pronunciation: "oo (long)", category: "Vowels", words: ["moon", "spoon", "tooth", "food", "room", "boot", "root", "pool", "school", "cool"] },
588
+ { pattern: "oo", pronunciation: "oo (short)", category: "Vowels", words: ["book", "look", "foot", "good", "hood", "wood", "stood", "cook", "hook", "shook"] },
589
+ { pattern: "ew", pronunciation: "yoo", category: "Vowels", words: ["few", "dew", "new", "chew", "news", "jewel", "nephew", "stew", "view", "review"] },
590
+ { pattern: "ue", pronunciation: "yoo", category: "Vowels", words: ["blue", "true", "clue", "glue", "value", "rescue", "argue", "venue", "statue", "virtue"] },
591
+ { pattern: "au", pronunciation: "aw", category: "Vowels", words: ["haul", "autumn", "launch", "cause", "pause", "audio", "author", "sauce", "haunt", "fault"] },
592
+ { pattern: "aw", pronunciation: "aw", category: "Vowels", words: ["saw", "draw", "claw", "law", "paw", "straw", "jaw", "lawn", "dawn", "yawn"] },
593
+ { pattern: "ei", pronunciation: "ay", category: "Vowels", words: ["veil", "eight", "weigh", "neighbor", "reign", "freight", "vein", "beige", "sleigh", "rein"] },
594
+ { pattern: "ey", pronunciation: "ay", category: "Vowels", words: ["they", "prey", "grey", "convey", "survey", "obey", "disobey", "hey", "whey", "abeyance"] },
595
+ { pattern: "ei", pronunciation: "ee", category: "Vowels", words: ["receive", "ceiling", "deceive", "perceive", "conceive", "receipt", "seize", "leisure", "weird", "either"] },
596
+ { pattern: "ey", pronunciation: "ee", category: "Vowels", words: ["key", "monkey", "honey", "money", "donkey", "turkey", "valley", "journey", "chimney", "kidney"] },
597
+ { pattern: "ie", pronunciation: "eye", category: "Vowels", words: ["pie", "tie", "die", "lie", "fried", "cried", "tried", "dried", "spied", "supplied"] },
598
+ { pattern: "ea", pronunciation: "eh", category: "Vowels", words: ["bread", "head", "ready", "heavy", "weather", "feather", "leather", "sweater", "treasure", "pleasure"] },
599
+ { pattern: "ou", pronunciation: "oo", category: "Vowels", words: ["soup", "group", "youth", "coupon", "routine", "souvenir", "you", "through", "wound", "route"] },
600
+ { pattern: "ui", pronunciation: "oo", category: "Vowels", words: ["fruit", "juice", "suit", "bruise", "cruise", "suitcase", "recruit", "biscuit", "circuit", "pursuit"] },
601
+ { pattern: "ew", pronunciation: "oo", category: "Vowels", words: ["blew", "grew", "flew", "crew", "chew", "drew", "threw", "screw", "brew", "jewel"] },
602
+
603
+ // Word Endings (15 patterns)
604
+ { pattern: "tion", pronunciation: "shun", category: "Endings", words: ["action", "station", "nation", "education", "vacation", "celebration", "information", "communication", "population", "attention"] },
605
+ { pattern: "sion", pronunciation: "zhun", category: "Endings", words: ["vision", "decision", "confusion", "television", "division", "conclusion", "explosion", "invasion", "occasion", "persuasion"] },
606
+ { pattern: "sion", pronunciation: "shun", category: "Endings", words: ["mission", "passion", "session", "expression", "discussion", "permission", "profession", "impression", "depression", "compression"] },
607
+ { pattern: "cian", pronunciation: "shun", category: "Endings", words: ["musician", "magician", "physician", "politician", "electrician", "mathematician", "technician", "beautician", "dietician", "statistician"] },
608
+ { pattern: "ture", pronunciation: "chur", category: "Endings", words: ["picture", "future", "nature", "creature", "adventure", "furniture", "temperature", "signature", "mixture", "literature"] },
609
+ { pattern: "le", pronunciation: "ul", category: "Endings", words: ["table", "candle", "little", "apple", "bubble", "puzzle", "turtle", "castle", "bottle", "middle"] },
610
+ { pattern: "ous", pronunciation: "us", category: "Endings", words: ["famous", "nervous", "dangerous", "enormous", "generous", "humorous", "mysterious", "numerous", "precious", "serious"] },
611
+ { pattern: "cious", pronunciation: "shus", category: "Endings", words: ["delicious", "suspicious", "precious", "conscious", "vicious", "gracious", "spacious", "judicious", "malicious", "auspicious"] },
612
+ { pattern: "tious", pronunciation: "shus", category: "Endings", words: ["ambitious", "cautious", "nutritious", "fictitious", "infectious", "superstitious", "contentious", "ostentatious", "pretentious", "conscientious"] },
613
+ { pattern: "cial", pronunciation: "shul", category: "Endings", words: ["special", "social", "official", "commercial", "financial", "racial", "crucial", "judicial", "beneficial", "superficial"] },
614
+ { pattern: "tial", pronunciation: "shul", category: "Endings", words: ["partial", "essential", "initial", "potential", "residential", "confidential", "substantial", "sequential", "influential", "presidential"] },
615
+ { pattern: "age", pronunciation: "ij", category: "Endings", words: ["cage", "page", "stage", "message", "village", "damage", "manage", "package", "passage", "voyage"] },
616
+ { pattern: "age", pronunciation: "azh", category: "Endings", words: ["garage", "massage", "mirage", "corsage", "camouflage", "espionage", "collage", "montage", "sabotage", "badinage"] },
617
+ { pattern: "ine", pronunciation: "in", category: "Endings", words: ["engine", "imagine", "medicine", "discipline", "determine", "famine", "routine", "doctrine", "genuine", "vaccine"] },
618
+ { pattern: "ine", pronunciation: "ine", category: "Endings", words: ["pine", "fine", "line", "mine", "nine", "vine", "wine", "shine", "whine", "divine"] }
619
+ ];
620
+
621
+ // Game state
622
+ let currentPattern = null;
623
+ let currentWord = "";
624
+ let selectedOption = null;
625
+ let score = 0;
626
+ let streak = 0;
627
+ let correctCount = 0;
628
+ let totalQuestions = 0;
629
+ let patternsMastered = new Set();
630
+ let timer = 30;
631
+ let timerInterval = null;
632
+ let gameActive = false;
633
+ let wordHistory = [];
634
+ let currentCategory = "all";
635
+
636
+ // DOM Elements
637
+ const wordDisplay = document.getElementById('wordDisplay');
638
+ const wordHint = document.getElementById('wordHint');
639
+ const patternOptions = document.getElementById('patternOptions');
640
+ const feedback = document.getElementById('feedback');
641
+ const submitBtn = document.getElementById('submitBtn');
642
+ const nextBtn = document.getElementById('nextBtn');
643
+ const hintBtn = document.getElementById('hintBtn');
644
+ const resetBtn = document.getElementById('resetBtn');
645
+ const clearHistoryBtn = document.getElementById('clearHistoryBtn');
646
+ const exportBtn = document.getElementById('exportBtn');
647
+ const scoreEl = document.getElementById('score');
648
+ const streakEl = document.getElementById('streak');
649
+ const correctCountEl = document.getElementById('correctCount');
650
+ const totalQuestionsEl = document.getElementById('totalQuestions');
651
+ const accuracyEl = document.getElementById('accuracy');
652
+ const progressFill = document.getElementById('progressFill');
653
+ const progressText = document.getElementById('progressText');
654
+ const historyList = document.getElementById('historyList');
655
+ const historyCount = document.getElementById('historyCount');
656
+ const timerEl = document.getElementById('timer');
657
+ const completedWord = document.getElementById('completedWord');
658
+ const patternCategories = document.getElementById('patternCategories');
659
+
660
+ // Initialize game
661
+ function initGame() {
662
+ loadGameState();
663
+ setupEventListeners();
664
+ renderCategoryFilters();
665
+ generateNewWord();
666
+ updateStats();
667
+ updateHistory();
668
+ }
669
+
670
+ // Load game state from localStorage
671
+ function loadGameState() {
672
+ const savedState = JSON.parse(localStorage.getItem('wordBuilderState'));
673
+ if (savedState) {
674
+ score = savedState.score || 0;
675
+ streak = savedState.streak || 0;
676
+ correctCount = savedState.correctCount || 0;
677
+ totalQuestions = savedState.totalQuestions || 0;
678
+ patternsMastered = new Set(savedState.patternsMastered || []);
679
+ wordHistory = savedState.wordHistory || [];
680
+ currentCategory = savedState.currentCategory || "all";
681
+ }
682
+ }
683
+
684
+ // Save game state to localStorage
685
+ function saveGameState() {
686
+ const state = {
687
+ score,
688
+ streak,
689
+ correctCount,
690
+ totalQuestions,
691
+ patternsMastered: Array.from(patternsMastered),
692
+ wordHistory,
693
+ currentCategory,
694
+ timestamp: Date.now()
695
+ };
696
+ localStorage.setItem('wordBuilderState', JSON.stringify(state));
697
+ }
698
+
699
+ // Render category filters
700
+ function renderCategoryFilters() {
701
+ const categories = ['all', 'Consonant', 'Vowels', 'Endings'];
702
+
703
+ patternCategories.innerHTML = '';
704
+ categories.forEach(category => {
705
+ const count = category === 'all'
706
+ ? wordBuilderData.length
707
+ : wordBuilderData.filter(p => p.category === category).length;
708
+
709
+ const tag = document.createElement('div');
710
+ tag.className = `category-tag ${currentCategory === category ? 'active' : ''}`;
711
+ tag.textContent = `${category} (${count})`;
712
+ tag.dataset.category = category;
713
+
714
+ tag.addEventListener('click', () => {
715
+ currentCategory = category;
716
+ renderCategoryFilters();
717
+ generateNewWord();
718
+ saveGameState();
719
+ });
720
+
721
+ patternCategories.appendChild(tag);
722
+ });
723
+ }
724
+
725
+ // Setup event listeners
726
+ function setupEventListeners() {
727
+ submitBtn.addEventListener('click', checkAnswer);
728
+ nextBtn.addEventListener('click', generateNewWord);
729
+ hintBtn.addEventListener('click', showHint);
730
+ resetBtn.addEventListener('click', resetGame);
731
+ clearHistoryBtn.addEventListener('click', clearHistory);
732
+ exportBtn.addEventListener('click', exportHistory);
733
+
734
+ // Keyboard shortcuts
735
+ document.addEventListener('keydown', (e) => {
736
+ if (e.key >= '1' && e.key <= '8') {
737
+ const index = parseInt(e.key) - 1;
738
+ const options = document.querySelectorAll('.pattern-option');
739
+ if (options[index]) {
740
+ options[index].click();
741
+ }
742
+ } else if (e.key === 'Enter') {
743
+ if (gameActive) {
744
+ checkAnswer();
745
+ } else {
746
+ generateNewWord();
747
+ }
748
+ } else if (e.key === ' ') {
749
+ e.preventDefault();
750
+ if (!gameActive) {
751
+ generateNewWord();
752
+ }
753
+ }
754
+ });
755
+ }
756
+
757
+ // Generate a new word with continuous blank line
758
+ function generateNewWord() {
759
+ // Stop any existing timer
760
+ if (timerInterval) clearInterval(timerInterval);
761
+
762
+ // Reset UI
763
+ feedback.style.display = 'none';
764
+ selectedOption = null;
765
+ completedWord.textContent = '';
766
+
767
+ // Filter patterns by category
768
+ let filteredPatterns = wordBuilderData;
769
+ if (currentCategory !== 'all') {
770
+ filteredPatterns = wordBuilderData.filter(p => p.category === currentCategory);
771
+ }
772
+
773
+ // Select random pattern
774
+ const randomIndex = Math.floor(Math.random() * filteredPatterns.length);
775
+ currentPattern = filteredPatterns[randomIndex];
776
+
777
+ // Select random word from pattern
778
+ const wordIndex = Math.floor(Math.random() * currentPattern.words.length);
779
+ currentWord = currentPattern.words[wordIndex];
780
+
781
+ // Find pattern in word (case insensitive)
782
+ const patternIndex = currentWord.toLowerCase().indexOf(currentPattern.pattern.toLowerCase());
783
+
784
+ if (patternIndex !== -1) {
785
+ const before = currentWord.substring(0, patternIndex);
786
+ const after = currentWord.substring(patternIndex + currentPattern.pattern.length);
787
+
788
+ // Display with continuous blank line
789
+ wordDisplay.innerHTML = `
790
+ ${before ? `<span class="word-part">${before}</span>` : ''}
791
+ <div class="continuous-blank">
792
+ <div class="blank-line"></div>
793
+ </div>
794
+ ${after ? `<span class="word-part">${after}</span>` : ''}
795
+ `;
796
+
797
+ // Update hint
798
+ wordHint.textContent = `Category: ${currentPattern.category} | Sound: "${currentPattern.pronunciation}"`;
799
+ } else {
800
+ // Fallback: show entire word as blank (shouldn't happen)
801
+ wordDisplay.innerHTML = `
802
+ <div class="continuous-blank" style="min-width: 250px;">
803
+ <div class="blank-line"></div>
804
+ </div>
805
+ `;
806
+ wordHint.textContent = "Find the phonics pattern that completes this word";
807
+ }
808
+
809
+ // Generate options
810
+ generateOptions();
811
+
812
+ // Start timer
813
+ timer = 30;
814
+ timerEl.textContent = `Time: ${timer}s`;
815
+ timerEl.style.color = "#ffd166";
816
+ gameActive = true;
817
+
818
+ timerInterval = setInterval(() => {
819
+ timer--;
820
+ timerEl.textContent = `Time: ${timer}s`;
821
+
822
+ if (timer <= 0) {
823
+ clearInterval(timerInterval);
824
+ gameActive = false;
825
+ feedback.textContent = "Time's up! The answer was: " + currentPattern.pattern;
826
+ feedback.className = "feedback incorrect";
827
+ feedback.style.display = "block";
828
+ addToHistory(false, false);
829
+ }
830
+
831
+ if (timer <= 10) {
832
+ timerEl.style.color = "#ff416c";
833
+ } else if (timer <= 20) {
834
+ timerEl.style.color = "#ffb347";
835
+ }
836
+ }, 1000);
837
+
838
+ // Update UI
839
+ patternOptions.querySelectorAll('.pattern-option').forEach(opt => {
840
+ opt.classList.remove('selected', 'wrong');
841
+ });
842
+
843
+ submitBtn.disabled = false;
844
+ submitBtn.innerHTML = '<i class="fas fa-check-circle"></i> Submit Answer';
845
+ }
846
+
847
+ // Generate pattern options
848
+ function generateOptions() {
849
+ // Clear existing options
850
+ patternOptions.innerHTML = '';
851
+ const options = [currentPattern.pattern];
852
+
853
+ // Get other patterns for wrong options
854
+ let allPatterns = [...new Set(wordBuilderData.map(p => p.pattern))];
855
+
856
+ // Filter by category if needed
857
+ if (currentCategory !== 'all') {
858
+ allPatterns = [...new Set(wordBuilderData
859
+ .filter(p => p.category === currentCategory)
860
+ .map(p => p.pattern))];
861
+ }
862
+
863
+ const wrongPatterns = allPatterns.filter(p => p !== currentPattern.pattern);
864
+
865
+ // Shuffle and select 7 wrong options (8 total)
866
+ shuffleArray(wrongPatterns);
867
+ for (let i = 0; i < 7; i++) {
868
+ if (wrongPatterns[i]) {
869
+ options.push(wrongPatterns[i]);
870
+ }
871
+ }
872
+
873
+ // Shuffle options
874
+ shuffleArray(options);
875
+
876
+ // Create option buttons
877
+ options.forEach((pattern, index) => {
878
+ const option = document.createElement('div');
879
+ option.className = 'pattern-option';
880
+ option.textContent = pattern;
881
+ option.dataset.pattern = pattern;
882
+ option.title = `Option ${index + 1} (Press ${index + 1})`;
883
+
884
+ option.addEventListener('click', () => {
885
+ if (!gameActive) return;
886
+
887
+ // Deselect all options
888
+ patternOptions.querySelectorAll('.pattern-option').forEach(opt => {
889
+ opt.classList.remove('selected');
890
+ });
891
+
892
+ // Select clicked option
893
+ option.classList.add('selected');
894
+ selectedOption = pattern;
895
+ });
896
+
897
+ patternOptions.appendChild(option);
898
+ });
899
+ }
900
+
901
+ // Check answer
902
+ function checkAnswer() {
903
+ if (!selectedOption || !gameActive) return;
904
+
905
+ clearInterval(timerInterval);
906
+ gameActive = false;
907
+
908
+ const isCorrect = selectedOption === currentPattern.pattern;
909
+ const timeBonus = Math.floor(timer / 5) * 10;
910
+
911
+ // Update score and streak
912
+ if (isCorrect) {
913
+ score += 10 + timeBonus;
914
+ streak++;
915
+ correctCount++;
916
+ patternsMastered.add(currentPattern.pattern);
917
+
918
+ feedback.textContent = `Correct! +${10 + timeBonus} points (${timeBonus} time bonus)`;
919
+ feedback.className = "feedback correct";
920
+ completedWord.textContent = `Complete word: ${currentWord}`;
921
+ } else {
922
+ streak = 0;
923
+
924
+ feedback.textContent = `Incorrect. The answer was: ${currentPattern.pattern}`;
925
+ feedback.className = "feedback incorrect";
926
+
927
+ // Highlight correct option
928
+ patternOptions.querySelectorAll('.pattern-option').forEach(opt => {
929
+ if (opt.dataset.pattern === currentPattern.pattern) {
930
+ opt.classList.add('selected');
931
+ } else if (opt.dataset.pattern === selectedOption) {
932
+ opt.classList.add('wrong');
933
+ }
934
+ });
935
+ }
936
+
937
+ totalQuestions++;
938
+
939
+ // Add to history
940
+ addToHistory(isCorrect, true);
941
+
942
+ // Update stats and save
943
+ updateStats();
944
+ saveGameState();
945
+
946
+ // Disable submit button
947
+ submitBtn.disabled = true;
948
+ submitBtn.innerHTML = '<i class="fas fa-check"></i> Answered';
949
+ }
950
+
951
+ // Show hint
952
+ function showHint() {
953
+ // Already showing hint in wordHint
954
+ // Could add more specific hint here
955
+ if (currentPattern) {
956
+ const examples = currentPattern.words.slice(0, 3).join(", ");
957
+ alert(`Hint: The pattern "${currentPattern.pattern}" makes the sound "${currentPattern.pronunciation}"\n\nExamples: ${examples}`);
958
+ }
959
+ }
960
+
961
+ // Reset game
962
+ function resetGame() {
963
+ if (confirm("Start a new game? Your progress will be saved, but current streak will reset.")) {
964
+ streak = 0;
965
+ generateNewWord();
966
+ updateStats();
967
+ saveGameState();
968
+ }
969
+ }
970
+
971
+ // Add word to history
972
+ function addToHistory(isCorrect, attempted) {
973
+ const historyItem = {
974
+ word: currentWord,
975
+ pattern: currentPattern.pattern,
976
+ correct: isCorrect,
977
+ attempted: attempted, // false for timeout
978
+ timestamp: new Date().toISOString(),
979
+ time: new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}),
980
+ date: new Date().toLocaleDateString(),
981
+ category: currentPattern.category
982
+ };
983
+
984
+ wordHistory.unshift(historyItem);
985
+ // Keep only last 100 items
986
+ if (wordHistory.length > 100) {
987
+ wordHistory = wordHistory.slice(0, 100);
988
+ }
989
+ updateHistory();
990
+ }
991
+
992
+ // Update history display
993
+ function updateHistory() {
994
+ historyList.innerHTML = '';
995
+
996
+ if (wordHistory.length === 0) {
997
+ historyList.innerHTML = '<div style="text-align: center; padding: 20px; opacity: 0.7;">No attempts yet</div>';
998
+ historyCount.textContent = '0';
999
+ return;
1000
+ }
1001
+
1002
+ wordHistory.forEach(item => {
1003
+ const div = document.createElement('div');
1004
+ div.className = `history-item ${item.correct ? 'correct' : 'incorrect'}`;
1005
+
1006
+ let statusIcon = item.correct ? '✓' : '✗';
1007
+ let statusText = item.correct ? 'Correct' : 'Incorrect';
1008
+ if (!item.attempted) {
1009
+ statusIcon = '⏰';
1010
+ statusText = 'Timeout';
1011
+ }
1012
+
1013
+ div.innerHTML = `
1014
+ <div class="history-word" title="${item.word}">${item.word}</div>
1015
+ <div class="history-pattern">${item.pattern}</div>
1016
+ <div class="history-result">
1017
+ <span>${statusIcon}</span>
1018
+ <span>${item.time}</span>
1019
+ </div>
1020
+ `;
1021
+ div.title = `${item.word} - ${item.pattern} - ${item.category} - ${statusText} at ${item.time}`;
1022
+
1023
+ historyList.appendChild(div);
1024
+ });
1025
+
1026
+ historyCount.textContent = wordHistory.length;
1027
+ }
1028
+
1029
+ // Clear history
1030
+ function clearHistory() {
1031
+ if (confirm("Clear all attempt history? This cannot be undone.")) {
1032
+ wordHistory = [];
1033
+ updateHistory();
1034
+ saveGameState();
1035
+ }
1036
+ }
1037
+
1038
+ // Export history
1039
+ function exportHistory() {
1040
+ if (wordHistory.length === 0) {
1041
+ alert("No history to export.");
1042
+ return;
1043
+ }
1044
+
1045
+ let csvContent = "Word,Pattern,Category,Result,Date,Time\n";
1046
+
1047
+ wordHistory.forEach(item => {
1048
+ const result = item.correct ? 'Correct' : (item.attempted ? 'Incorrect' : 'Timeout');
1049
+ csvContent += `"${item.word}","${item.pattern}","${item.category}",${result},"${item.date}","${item.time}"\n`;
1050
+ });
1051
+
1052
+ const blob = new Blob([csvContent], { type: 'text/csv' });
1053
+ const url = window.URL.createObjectURL(blob);
1054
+ const a = document.createElement('a');
1055
+ a.href = url;
1056
+ a.download = `phonics-history-${new Date().toISOString().slice(0,10)}.csv`;
1057
+ document.body.appendChild(a);
1058
+ a.click();
1059
+ document.body.removeChild(a);
1060
+ window.URL.revokeObjectURL(url);
1061
+
1062
+ alert(`Exported ${wordHistory.length} attempts to CSV file.`);
1063
+ }
1064
+
1065
+ // Update all stats
1066
+ function updateStats() {
1067
+ scoreEl.textContent = score;
1068
+ streakEl.textContent = streak;
1069
+ correctCountEl.textContent = correctCount;
1070
+ totalQuestionsEl.textContent = totalQuestions;
1071
+
1072
+ const accuracy = totalQuestions > 0 ? Math.round((correctCount / totalQuestions) * 100) : 0;
1073
+ accuracyEl.textContent = `${accuracy}%`;
1074
+
1075
+ const totalPatterns = currentCategory === 'all'
1076
+ ? wordBuilderData.length
1077
+ : wordBuilderData.filter(p => p.category === currentCategory).length;
1078
+
1079
+ const progress = totalPatterns > 0 ? (patternsMastered.size / totalPatterns) * 100 : 0;
1080
+ progressFill.style.width = `${progress}%`;
1081
+ progressText.textContent = `${patternsMastered.size}/${totalPatterns} patterns (${Math.round(progress)}%)`;
1082
+ }
1083
+
1084
+ // Utility: Shuffle array
1085
+ function shuffleArray(array) {
1086
+ for (let i = array.length - 1; i > 0; i--) {
1087
+ const j = Math.floor(Math.random() * (i + 1));
1088
+ [array[i], array[j]] = [array[j], array[i]];
1089
+ }
1090
+ return array;
1091
+ }
1092
+
1093
+ // Initialize when page loads
1094
+ document.addEventListener('DOMContentLoaded', initGame);
1095
+ </script>
1096
+ </body>
1097
+ </html>