Spaces:
Running
Running
Update index.html
Browse files- index.html +239 -42
index.html
CHANGED
@@ -583,14 +583,179 @@
|
|
583 |
}
|
584 |
}
|
585 |
|
586 |
-
// Common words
|
587 |
-
const
|
588 |
-
|
589 |
-
|
590 |
-
|
591 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
592 |
];
|
593 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
594 |
// Keyboard layout
|
595 |
const KEY_ROWS = [
|
596 |
['q','w','e','r','t','y','u','i','o','p'],
|
@@ -716,56 +881,88 @@
|
|
716 |
});
|
717 |
}
|
718 |
|
719 |
-
|
|
|
720 |
if (tracker.totalObservations < 20) {
|
721 |
-
|
|
|
722 |
}
|
723 |
|
724 |
-
// Calculate focus scores
|
725 |
-
const
|
726 |
.filter(([k, v]) => v.count > 0)
|
727 |
.map(([k, v]) => ({
|
728 |
-
key: k,
|
729 |
focus: v.mean * (v.sd + 0.1),
|
730 |
-
|
731 |
-
|
732 |
-
}))
|
733 |
-
|
734 |
-
|
|
|
|
|
735 |
|
736 |
-
//
|
737 |
-
const
|
738 |
-
if (topKeys.length === 0) return "Great job! Keep practicing to maintain your skills.";
|
739 |
|
740 |
-
//
|
741 |
-
|
742 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
743 |
|
744 |
-
|
745 |
-
|
746 |
-
|
747 |
-
|
748 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
749 |
|
750 |
-
if (
|
751 |
-
|
752 |
-
|
753 |
-
|
754 |
-
const patterns = [
|
755 |
-
targetKey.key + targetKey.key,
|
756 |
-
targetKey.key + "a" + targetKey.key,
|
757 |
-
targetKey.key + "e" + targetKey.key,
|
758 |
-
"a" + targetKey.key + "a"
|
759 |
-
];
|
760 |
-
drill += patterns[Math.floor(Math.random() * patterns.length)] + " ";
|
761 |
}
|
|
|
|
|
|
|
|
|
|
|
762 |
} else {
|
763 |
-
//
|
764 |
-
|
|
|
|
|
|
|
|
|
765 |
}
|
766 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
767 |
|
768 |
-
return
|
769 |
}
|
770 |
|
771 |
function updateExplanation(posteriors){
|
@@ -867,7 +1064,7 @@
|
|
867 |
updateHeatmap(post);
|
868 |
updateExplanation(post);
|
869 |
|
870 |
-
const drillText = generateDrill(post,
|
871 |
drillDiv.textContent = drillText;
|
872 |
|
873 |
updateStats(post);
|
|
|
583 |
}
|
584 |
}
|
585 |
|
586 |
+
// Common words organized by difficulty and letter patterns
|
587 |
+
const WORD_CORPUS = {
|
588 |
+
// High-frequency words (top 100 most common)
|
589 |
+
common: [
|
590 |
+
'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'would', 'her',
|
591 |
+
'was', 'one', 'our', 'out', 'day', 'had', 'has', 'his', 'how', 'man',
|
592 |
+
'its', 'say', 'she', 'which', 'their', 'time', 'will', 'way', 'about',
|
593 |
+
'many', 'then', 'them', 'write', 'like', 'these', 'long', 'make', 'thing',
|
594 |
+
'see', 'him', 'two', 'look', 'more', 'go', 'come', 'number', 'sound',
|
595 |
+
'most', 'people', 'over', 'know', 'water', 'than', 'call', 'first'
|
596 |
+
],
|
597 |
+
// Words with common digraphs
|
598 |
+
digraphs: {
|
599 |
+
'th': ['the', 'that', 'this', 'they', 'there', 'think', 'through', 'three', 'thanks', 'thought'],
|
600 |
+
'ch': ['change', 'check', 'choice', 'choose', 'chair', 'chance', 'charge', 'cheap', 'church', 'chapter'],
|
601 |
+
'sh': ['should', 'show', 'share', 'short', 'shape', 'sharp', 'shift', 'shine', 'shock', 'shoot'],
|
602 |
+
'wh': ['what', 'when', 'where', 'which', 'while', 'white', 'whole', 'whose', 'wheel', 'whether'],
|
603 |
+
'qu': ['quick', 'quite', 'quiet', 'queen', 'question', 'quality', 'quarter', 'square', 'require', 'equal'],
|
604 |
+
'ing': ['thing', 'being', 'doing', 'going', 'making', 'taking', 'coming', 'looking', 'working', 'thinking'],
|
605 |
+
'er': ['other', 'after', 'never', 'every', 'under', 'number', 'perhaps', 'better', 'together', 'remember'],
|
606 |
+
'ed': ['called', 'looked', 'asked', 'needed', 'wanted', 'worked', 'lived', 'turned', 'started', 'seemed']
|
607 |
+
},
|
608 |
+
// Words by difficulty (based on hand movements)
|
609 |
+
patterns: {
|
610 |
+
homeRow: ['had', 'ask', 'dad', 'sad', 'lad', 'fad', 'gas', 'has', 'lag', 'sag'],
|
611 |
+
topRow: ['were', 'your', 'trip', 'quit', 'power', 'write', 'quiet', 'worry', 'pretty', 'twenty'],
|
612 |
+
bottomRow: ['can', 'man', 'been', 'came', 'name', 'mean', 'become', 'common', 'woman', 'human'],
|
613 |
+
mixed: ['their', 'would', 'about', 'there', 'think', 'which', 'people', 'could', 'other', 'after']
|
614 |
+
},
|
615 |
+
// Common programming/tech words
|
616 |
+
technical: [
|
617 |
+
'function', 'variable', 'return', 'class', 'import', 'export', 'const', 'async', 'array', 'object',
|
618 |
+
'string', 'number', 'boolean', 'interface', 'public', 'private', 'static', 'method', 'property'
|
619 |
+
]
|
620 |
+
};
|
621 |
+
|
622 |
+
// Sentence templates for more natural practice
|
623 |
+
const SENTENCE_TEMPLATES = [
|
624 |
+
"The {adjective} {noun} {verb} {preposition} the {noun}.",
|
625 |
+
"{pronoun} {verb} to {verb} the {adjective} {noun}.",
|
626 |
+
"Can you {verb} the {noun} {preposition} the {adjective} {noun}?",
|
627 |
+
"{number} {adjective} {noun}s {verb} {adverb} {preposition} the {noun}.",
|
628 |
+
"The {noun} {verb} {adjective} and {adjective}."
|
629 |
];
|
630 |
|
631 |
+
const WORD_TYPES = {
|
632 |
+
adjective: ['quick', 'brown', 'lazy', 'beautiful', 'small', 'large', 'happy', 'sad', 'fast', 'slow'],
|
633 |
+
noun: ['fox', 'dog', 'cat', 'house', 'tree', 'book', 'computer', 'phone', 'desk', 'chair'],
|
634 |
+
verb: ['jumps', 'runs', 'walks', 'sits', 'stands', 'writes', 'reads', 'types', 'thinks', 'works'],
|
635 |
+
pronoun: ['I', 'you', 'he', 'she', 'we', 'they', 'it'],
|
636 |
+
preposition: ['over', 'under', 'beside', 'through', 'across', 'behind', 'near', 'between'],
|
637 |
+
adverb: ['quickly', 'slowly', 'carefully', 'happily', 'quietly', 'loudly'],
|
638 |
+
number: ['two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten']
|
639 |
+
};
|
640 |
+
|
641 |
+
// Enhanced drill generator
|
642 |
+
function generateDrill(posteriors, length = 50) {
|
643 |
+
if (tracker.totalObservations < 20) {
|
644 |
+
// Start with common words for beginners
|
645 |
+
return "Type these common words: the quick brown fox jumps over the lazy dog. Practice makes perfect!";
|
646 |
+
}
|
647 |
+
|
648 |
+
// Calculate focus scores for each letter
|
649 |
+
const letterScores = Object.entries(posteriors)
|
650 |
+
.filter(([k, v]) => v.count > 0)
|
651 |
+
.map(([k, v]) => ({
|
652 |
+
key: k,
|
653 |
+
focus: v.mean * (v.sd + 0.1),
|
654 |
+
errorRate: v.mean,
|
655 |
+
uncertainty: v.sd
|
656 |
+
}))
|
657 |
+
.sort((a, b) => b.focus - a.focus);
|
658 |
+
|
659 |
+
// Get top problematic letters
|
660 |
+
const problemLetters = letterScores.slice(0, 5).map(s => s.key);
|
661 |
+
|
662 |
+
// Categorize problem areas
|
663 |
+
const problemDigraphs = [];
|
664 |
+
const problemPatterns = [];
|
665 |
+
|
666 |
+
// Check for problematic digraphs
|
667 |
+
for (const [digraph, words] of Object.entries(WORD_CORPUS.digraphs)) {
|
668 |
+
if (digraph.split('').some(letter => problemLetters.includes(letter))) {
|
669 |
+
problemDigraphs.push({ pattern: digraph, words });
|
670 |
+
}
|
671 |
+
}
|
672 |
+
|
673 |
+
// Build adaptive drill
|
674 |
+
let drill = [];
|
675 |
+
let currentLength = 0;
|
676 |
+
|
677 |
+
// Mix different types of practice
|
678 |
+
while (currentLength < length) {
|
679 |
+
const random = Math.random();
|
680 |
+
|
681 |
+
if (random < 0.4 && problemDigraphs.length > 0) {
|
682 |
+
// 40% - Focus on problematic digraphs
|
683 |
+
const digraph = problemDigraphs[Math.floor(Math.random() * problemDigraphs.length)];
|
684 |
+
const word = digraph.words[Math.floor(Math.random() * digraph.words.length)];
|
685 |
+
drill.push(word);
|
686 |
+
currentLength += word.length + 1;
|
687 |
+
} else if (random < 0.7) {
|
688 |
+
// 30% - Words containing problem letters
|
689 |
+
const targetLetter = problemLetters[Math.floor(Math.random() * Math.min(3, problemLetters.length))];
|
690 |
+
const candidates = [
|
691 |
+
...WORD_CORPUS.common,
|
692 |
+
...Object.values(WORD_CORPUS.patterns).flat()
|
693 |
+
].filter(w => w.includes(targetLetter));
|
694 |
+
|
695 |
+
if (candidates.length > 0) {
|
696 |
+
const word = candidates[Math.floor(Math.random() * candidates.length)];
|
697 |
+
drill.push(word);
|
698 |
+
currentLength += word.length + 1;
|
699 |
+
}
|
700 |
+
} else if (random < 0.85) {
|
701 |
+
// 15% - Common words for flow
|
702 |
+
const word = WORD_CORPUS.common[Math.floor(Math.random() * WORD_CORPUS.common.length)];
|
703 |
+
drill.push(word);
|
704 |
+
currentLength += word.length + 1;
|
705 |
+
} else {
|
706 |
+
// 15% - Generate a short sentence
|
707 |
+
if (currentLength + 20 < length) {
|
708 |
+
const sentence = generateSentence(problemLetters);
|
709 |
+
drill.push(sentence);
|
710 |
+
currentLength += sentence.length + 1;
|
711 |
+
}
|
712 |
+
}
|
713 |
+
}
|
714 |
+
|
715 |
+
// Format the drill nicely
|
716 |
+
const drillText = drill.join(' ').trim();
|
717 |
+
|
718 |
+
// Add a note about what we're focusing on
|
719 |
+
const focusNote = problemLetters.length > 0
|
720 |
+
? `Focus areas: ${problemLetters.map(l => l.toUpperCase()).join(', ')} | `
|
721 |
+
: '';
|
722 |
+
|
723 |
+
return focusNote + drillText;
|
724 |
+
}
|
725 |
+
|
726 |
+
// Generate sentences with problem letters
|
727 |
+
function generateSentence(problemLetters) {
|
728 |
+
const template = SENTENCE_TEMPLATES[Math.floor(Math.random() * SENTENCE_TEMPLATES.length)];
|
729 |
+
let sentence = template;
|
730 |
+
|
731 |
+
// Replace placeholders with words containing problem letters when possible
|
732 |
+
for (const [type, words] of Object.entries(WORD_TYPES)) {
|
733 |
+
if (sentence.includes(`{${type}}`)) {
|
734 |
+
const candidates = words.filter(w =>
|
735 |
+
problemLetters.some(letter => w.includes(letter))
|
736 |
+
);
|
737 |
+
const wordList = candidates.length > 0 ? candidates : words;
|
738 |
+
const word = wordList[Math.floor(Math.random() * wordList.length)];
|
739 |
+
sentence = sentence.replace(`{${type}}`, word);
|
740 |
+
}
|
741 |
+
}
|
742 |
+
|
743 |
+
return sentence;
|
744 |
+
}
|
745 |
+
|
746 |
+
// Update the initial target text
|
747 |
+
function getInitialText() {
|
748 |
+
const introTexts = [
|
749 |
+
"Welcome to adaptive typing practice. Start with this sentence to build your profile.",
|
750 |
+
"Type this paragraph to help me understand your typing patterns and create personalized drills.",
|
751 |
+
"Every keystroke teaches me about your typing style. Let's begin with this warm-up text.",
|
752 |
+
"Practice makes perfect. Begin typing to discover your unique strengths and challenges.",
|
753 |
+
"Your personalized typing journey starts here. Type this text to establish your baseline."
|
754 |
+
];
|
755 |
+
|
756 |
+
return introTexts[Math.floor(Math.random() * introTexts.length)];
|
757 |
+
}
|
758 |
+
|
759 |
// Keyboard layout
|
760 |
const KEY_ROWS = [
|
761 |
['q','w','e','r','t','y','u','i','o','p'],
|
|
|
881 |
});
|
882 |
}
|
883 |
|
884 |
+
// Enhanced drill generator
|
885 |
+
function generateDrill(posteriors, length = 80) {
|
886 |
if (tracker.totalObservations < 20) {
|
887 |
+
// Start with common words for beginners
|
888 |
+
return "Type these common words: the quick brown fox jumps over the lazy dog. Practice makes perfect!";
|
889 |
}
|
890 |
|
891 |
+
// Calculate focus scores for each letter
|
892 |
+
const letterScores = Object.entries(posteriors)
|
893 |
.filter(([k, v]) => v.count > 0)
|
894 |
.map(([k, v]) => ({
|
895 |
+
key: k,
|
896 |
focus: v.mean * (v.sd + 0.1),
|
897 |
+
errorRate: v.mean,
|
898 |
+
uncertainty: v.sd
|
899 |
+
}))
|
900 |
+
.sort((a, b) => b.focus - a.focus);
|
901 |
+
|
902 |
+
// Get top problematic letters
|
903 |
+
const problemLetters = letterScores.slice(0, 5).map(s => s.key);
|
904 |
|
905 |
+
// Categorize problem areas
|
906 |
+
const problemDigraphs = [];
|
|
|
907 |
|
908 |
+
// Check for problematic digraphs
|
909 |
+
for (const [digraph, words] of Object.entries(WORD_CORPUS.digraphs)) {
|
910 |
+
if (digraph.split('').some(letter => problemLetters.includes(letter))) {
|
911 |
+
problemDigraphs.push({ pattern: digraph, words });
|
912 |
+
}
|
913 |
+
}
|
914 |
+
|
915 |
+
// Build adaptive drill
|
916 |
+
let drill = [];
|
917 |
+
let currentLength = 0;
|
918 |
|
919 |
+
// Mix different types of practice
|
920 |
+
while (currentLength < length) {
|
921 |
+
const random = Math.random();
|
922 |
+
|
923 |
+
if (random < 0.4 && problemDigraphs.length > 0) {
|
924 |
+
// 40% - Focus on problematic digraphs
|
925 |
+
const digraph = problemDigraphs[Math.floor(Math.random() * problemDigraphs.length)];
|
926 |
+
const word = digraph.words[Math.floor(Math.random() * digraph.words.length)];
|
927 |
+
drill.push(word);
|
928 |
+
currentLength += word.length + 1;
|
929 |
+
} else if (random < 0.7) {
|
930 |
+
// 30% - Words containing problem letters
|
931 |
+
const targetLetter = problemLetters[Math.floor(Math.random() * Math.min(3, problemLetters.length))];
|
932 |
+
const candidates = [
|
933 |
+
...WORD_CORPUS.common,
|
934 |
+
...Object.values(WORD_CORPUS.patterns).flat()
|
935 |
+
].filter(w => w.includes(targetLetter));
|
936 |
|
937 |
+
if (candidates.length > 0) {
|
938 |
+
const word = candidates[Math.floor(Math.random() * candidates.length)];
|
939 |
+
drill.push(word);
|
940 |
+
currentLength += word.length + 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
941 |
}
|
942 |
+
} else if (random < 0.85) {
|
943 |
+
// 15% - Common words for flow
|
944 |
+
const word = WORD_CORPUS.common[Math.floor(Math.random() * WORD_CORPUS.common.length)];
|
945 |
+
drill.push(word);
|
946 |
+
currentLength += word.length + 1;
|
947 |
} else {
|
948 |
+
// 15% - Generate a short sentence
|
949 |
+
if (currentLength + 20 < length) {
|
950 |
+
const sentence = generateSentence(problemLetters);
|
951 |
+
drill.push(sentence);
|
952 |
+
currentLength += sentence.length + 1;
|
953 |
+
}
|
954 |
}
|
955 |
}
|
956 |
+
|
957 |
+
// Format the drill nicely
|
958 |
+
const drillText = drill.join(' ').trim();
|
959 |
+
|
960 |
+
// Add a note about what we're focusing on
|
961 |
+
const focusNote = problemLetters.length > 0
|
962 |
+
? `Focus: ${problemLetters.slice(0,3).map(l => l.toUpperCase()).join(', ')} | `
|
963 |
+
: '';
|
964 |
|
965 |
+
return focusNote + drillText;
|
966 |
}
|
967 |
|
968 |
function updateExplanation(posteriors){
|
|
|
1064 |
updateHeatmap(post);
|
1065 |
updateExplanation(post);
|
1066 |
|
1067 |
+
const drillText = generateDrill(post, 80);
|
1068 |
drillDiv.textContent = drillText;
|
1069 |
|
1070 |
updateStats(post);
|