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);
|