let wordsList = []; let currentWordIndex = 0; let currentWordElementIndex = 0; let inputHistory = []; let correctedHistory = []; let currentCorrected = ""; let currentInput = ""; let time = 0; let timer = null; let testActive = false; let testStart, testEnd; let wpmHistory = []; let rawHistory = []; let restartCount = 0; let incompleteTestSeconds = 0; let currentTestLine = 0; let pageTransition = false; let lineTransition = false; let keypressPerSecond = []; let currentKeypress = { count: 0, words: [], }; let errorsPerSecond = []; let currentError = { count: 0, words: [], }; let resultVisible = false; let activeWordTopBeforeJump = 0; let activeWordTop = 0; let activeWordJumped = false; let sameWordset = false; let quotes = []; let focusState = false; let activeFunBox = "none"; let manualRestart = false; let bailout = false; let notSignedInLastResult = null; let caretAnimating = true; let themeColors = { bg: "#323437", main: "#e2b714", caret: "#e2b714", sub: "#646669", text: "#d1d0c5", error: "#ca4754", errorExtra: "#7e2a33", colorfulError: "#ca4754", colorfulErrorExtra: "#7e2a33", }; let accuracyStats = { correct: 0, incorrect: 0, }; let keypressStats = { spacing: { current: -1, array: [], }, duration: { current: -1, array: [], }, }; let errorSound = new Audio("../sound/error.wav"); let clickSounds = { "1": [ { sounds: [ new Audio("../sound/click1/click1_1.wav"), new Audio("../sound/click1/click1_1.wav"), ], counter: 0, }, { sounds: [ new Audio("../sound/click1/click1_2.wav"), new Audio("../sound/click1/click1_2.wav"), ], counter: 0, }, { sounds: [ new Audio("../sound/click1/click1_3.wav"), new Audio("../sound/click1/click1_3.wav"), ], counter: 0, }, ], "2": [ { sounds: [ new Audio("../sound/click2/click2_1.wav"), new Audio("../sound/click2/click2_1.wav"), ], counter: 0, }, { sounds: [ new Audio("../sound/click2/click2_2.wav"), new Audio("../sound/click2/click2_2.wav"), ], counter: 0, }, { sounds: [ new Audio("../sound/click2/click2_3.wav"), new Audio("../sound/click2/click2_3.wav"), ], counter: 0, }, ], "3": [ { sounds: [ new Audio("../sound/click3/click3_1.wav"), new Audio("../sound/click3/click3_1.wav"), ], counter: 0, }, { sounds: [ new Audio("../sound/click3/click3_2.wav"), new Audio("../sound/click3/click3_2.wav"), ], counter: 0, }, { sounds: [ new Audio("../sound/click3/click3_3.wav"), new Audio("../sound/click3/click3_3.wav"), ], counter: 0, }, ], "4": [ { sounds: [ new Audio("../sound/click4/click4_1.wav"), new Audio("../sound/click4/click4_1.wav"), ], counter: 0, }, { sounds: [ new Audio("../sound/click4/click4_2.wav"), new Audio("../sound/click4/click4_2.wav"), ], counter: 0, }, { sounds: [ new Audio("../sound/click4/click4_3.wav"), new Audio("../sound/click4/click4_3.wav"), ], counter: 0, }, { sounds: [ new Audio("../sound/click4/click4_4.wav"), new Audio("../sound/click4/click4_4.wav"), ], counter: 0, }, { sounds: [ new Audio("../sound/click4/click4_5.wav"), new Audio("../sound/click4/click4_5.wav"), ], counter: 0, }, { sounds: [ new Audio("../sound/click4/click4_6.wav"), new Audio("../sound/click4/click4_6.wav"), ], counter: 0, }, ], }; let customText = "The quick brown fox jumps over the lazy dog".split(" "); let customTextIsRandom = false; let customTextWordCount = 1; let randomQuote = null; const testCompleted = firebase.functions().httpsCallable("testCompleted"); const addTag = firebase.functions().httpsCallable("addTag"); const editTag = firebase.functions().httpsCallable("editTag"); const removeTag = firebase.functions().httpsCallable("removeTag"); const updateResultTags = firebase.functions().httpsCallable("updateResultTags"); const saveConfig = firebase.functions().httpsCallable("saveConfig"); const generatePairingCode = firebase .functions() .httpsCallable("generatePairingCode"); function refreshThemeColorObject() { let st = getComputedStyle(document.body); themeColors.bg = st.getPropertyValue("--bg-color").replace(" ", ""); themeColors.main = st.getPropertyValue("--main-color").replace(" ", ""); themeColors.caret = st.getPropertyValue("--caret-color").replace(" ", ""); themeColors.sub = st.getPropertyValue("--sub-color").replace(" ", ""); themeColors.text = st.getPropertyValue("--text-color").replace(" ", ""); themeColors.error = st.getPropertyValue("--error-color").replace(" ", ""); themeColors.errorExtra = st .getPropertyValue("--error-extra-color") .replace(" ", ""); themeColors.colorfulError = st .getPropertyValue("--colorful-error-color") .replace(" ", ""); themeColors.colorfulErrorExtra = st .getPropertyValue("--colorful-error-extra-color") .replace(" ", ""); } function copyResultToClipboard() { if (navigator.userAgent.toLowerCase().indexOf("firefox") > -1) { showNotification("Sorry, this feature is not supported in Firefox", 4000); } else { $(".pageTest .ssWatermark").removeClass("hidden"); $(".pageTest .buttons").addClass("hidden"); let src = $("#middle"); var sourceX = src.position().left; /*X position from div#target*/ var sourceY = src.position().top; /*Y position from div#target*/ var sourceWidth = src.width(); /*clientWidth/offsetWidth from div#target*/ var sourceHeight = src.height(); /*clientHeight/offsetHeight from div#target*/ $(".notification").addClass("hidden"); try { html2canvas(document.body, { backgroundColor: themeColors.bg, height: sourceHeight + 50, width: sourceWidth + 50, x: sourceX - 25, y: sourceY - 25, }).then(function (canvas) { // document.body.appendChild(canvas); canvas.toBlob(function (blob) { navigator.clipboard .write([ new ClipboardItem( Object.defineProperty({}, blob.type, { value: blob, enumerable: true, }) ), ]) .then((f) => { $(".notification").removeClass("hidden"); showNotification("Copied to clipboard", 1000); $(".pageTest .ssWatermark").addClass("hidden"); $(".pageTest .buttons").removeClass("hidden"); }) .catch((f) => { $(".notification").removeClass("hidden"); showNotification("Error saving image to clipboard", 2000); $(".pageTest .ssWatermark").addClass("hidden"); $(".pageTest .buttons").removeClass("hidden"); }); }); }); } catch (e) { $(".notification").removeClass("hidden"); showNotification("Error creating image", 2000); $(".pageTest .ssWatermark").addClass("hidden"); $(".pageTest .buttons").removeClass("hidden"); } } } function activateFunbox(funbox, mode) { if (testActive || resultVisible) { showNotification( "You can only change the funbox before starting a test.", 4000 ); return false; } $("#funBoxTheme").attr("href", ``); if (funbox === "none") { activeFunBox = "none"; } if (mode === "style") { if (funbox != undefined) { $("#funBoxTheme").attr("href", `funbox/${funbox}.css`); activeFunBox = funbox; } if (funbox === "simon_says") { changeKeymapMode("next"); settingsGroups.keymapMode.updateButton(); restartTest(); } } else if (mode === "script") { if (funbox === "tts") { $("#funBoxTheme").attr("href", `funbox/simon_says.css`); config.keymapMode = "off"; settingsGroups.keymapMode.updateButton(); restartTest(); } else if (funbox === "layoutfluid") { config.keymapMode = "on"; changeKeymapMode("next"); settingsGroups.keymapMode.updateButton(); changeLayout("qwerty"); settingsGroups.layout.updateButton(); changeKeymapLayout("qwerty"); settingsGroups.keymapLayout.updateButton(); restartTest(); } activeFunBox = funbox; } updateTestModesNotice(); return true; } function toggleScriptFunbox(...params) { if (activeFunBox === "tts") { var msg = new SpeechSynthesisUtterance(); // var voices = window.speechSynthesis.getVoices(); // msg.voice = voices[0]; // msg.volume = 1; // From 0 to 1 // msg.rate = 1; // From 0.1 to 10 msg.text = params[0]; msg.lang = "en-US"; window.speechSynthesis.cancel(); window.speechSynthesis.speak(msg); } } function verifyUsername() { // test = firebase.functions().httpsCallable('moveResults') // test2 = firebase.functions().httpsCallable('getNames') // test3 = firebase.functions().httpsCallable('checkNameAvailability') const check = firebase.functions().httpsCallable("checkIfNeedsToChangeName"); check({ uid: firebase.auth().currentUser.uid }).then((data) => { if (data.data === 1) { $(".nameChangeMessage").slideDown(); } else if (data.data === 2) { $(".nameChangeMessage").slideDown(); } }); $(".nameChangeMessage").click((e) => { alert(`Im currently preparing the system to be ready for leaderboards and other awesome features - it looks like you need to change your display name. It either contains special characters, or your display name is the same as someone elses and your account was made later. Sorry for this inconvenience. `); let newName = prompt( "Please provide a new username - you can use lowercase and uppercase characters, numbers and one of these special characters ( . _ - ). The new name cannot be longer than 12 characters.", firebase.auth().currentUser.displayName ); if (newName) { cn = firebase.functions().httpsCallable("changeName"); cn({ uid: firebase.auth().currentUser.uid, name: newName }).then((d) => { if (d.data === 1) { //all good alert("Thanks! All good."); location.reload(); $(".nameChangeMessage").slideUp(); } else if (d.data === 0) { //invalid or unavailable alert("Name invalid or taken. Try again."); } else if (d.data === -1) { //error alert("Unknown error. Contact Miodec on Discord."); } }); } }); } function getuid() { console.error("Only share this uid with Miodec and nobody else!"); console.log(firebase.auth().currentUser.uid); console.error("Only share this uid with Miodec and nobody else!"); } function setFocus(foc) { if (foc && !focusState) { focusState = true; stopCaretAnimation(); $("#top").addClass("focus"); $("#bottom").addClass("focus"); $("body").css("cursor", "none"); $("#middle").addClass("focus"); } else if (!foc && focusState) { focusState = false; if (testActive) { stopCaretAnimation(); } else { startCaretAnimation(); } $("#top").removeClass("focus"); $("#bottom").removeClass("focus"); $("body").css("cursor", "default"); $("#middle").removeClass("focus"); } } function initWords() { testActive = false; wordsList = []; currentWordIndex = 0; currentWordElementIndex = 0; accuracyStats = { correct: 0, incorrect: 0, }; inputHistory = []; correctedHistory = []; currentCorrected = ""; currentInput = ""; let language = words[config.language]; if (language === undefined && config.language === "english_10k") { showBackgroundLoader(); $.ajax({ url: "js/english_10k.json", async: false, success: function (data) { hideBackgroundLoader(); words["english_10k"] = { leftToRight: true, words: data }; language = words[config.language]; }, }); } if (config.mode === "quote" && quotes.length == 0) { showBackgroundLoader(); $.ajax({ url: "js/english_quotes.json", async: false, success: function (data) { hideBackgroundLoader(); quotes = data; }, }); } if (!language) { config.language = "english"; language = words[config.language]; } if ( config.mode == "time" || config.mode == "words" || config.mode == "custom" ) { // let wordsBound = config.mode == "time" ? 60 : config.words; let wordsBound = 60; if (config.showAllLines) { if (config.mode === "custom") { if (customTextIsRandom) { wordsBound = customTextWordCount; } else { wordsBound = customText.length; } } else if (config.mode != "time") { wordsBound = config.words; } } else { if (config.mode === "words" && config.words < wordsBound) { wordsBound = config.words; } if ( config.mode == "custom" && customTextIsRandom && customTextWordCount < wordsBound ) { wordsBound = customTextWordCount; } if ( config.mode == "custom" && !customTextIsRandom && customText.length < wordsBound ) { wordsBound = customText.length; } } if (activeFunBox === "plus_one") { wordsBound = 2; } let wordset = language.words; if (config.mode == "custom") { wordset = customText; } for (let i = 0; i < wordsBound; i++) { randomWord = wordset[Math.floor(Math.random() * wordset.length)]; previousWord = wordsList[i - 1]; previousWord2 = wordsList[i - 2]; if (config.mode == "custom" && wordset.length < 3 && customTextIsRandom) { randomWord = wordset[Math.floor(Math.random() * wordset.length)]; } else if (config.mode == "custom" && !customTextIsRandom) { randomWord = customText[i]; } else { while ( randomWord == previousWord || randomWord == previousWord2 || (!config.punctuation && randomWord == "I") || randomWord.indexOf(" ") > -1 ) { randomWord = wordset[Math.floor(Math.random() * wordset.length)]; } } if (activeFunBox === "rAnDoMcAsE") { let randomcaseword = ""; for (let i = 0; i < randomWord.length; i++) { if (i % 2 != 0) { randomcaseword += randomWord[i].toUpperCase(); } else { randomcaseword += randomWord[i]; } } randomWord = randomcaseword; } else if (activeFunBox === "gibberish") { randomWord = getGibberish(); } else if (activeFunBox === "58008") { setToggleSettings(false); randomWord = getNumbers(7); } else if (activeFunBox === "specials") { setToggleSettings(false); randomWord = getSpecials(); } else if (activeFunBox === "ascii") { setToggleSettings(false); randomWord = getASCII(); } if (config.punctuation && config.mode != "custom") { randomWord = punctuateWord(previousWord, randomWord, i, wordsBound); } if (config.numbers && config.mode != "custom") { if (Math.random() < 0.1) { randomWord = getNumbers(4); } } wordsList.push(randomWord); } } else if (config.mode == "quote") { randomQuote = quotes[Math.floor(Math.random() * quotes.length)]; let w = randomQuote.text.trim().split(" "); for (let i = 0; i < w.length; i++) { wordsList.push(w[i]); } } //handle right-to-left languages if (language.leftToRight) { arrangeCharactersLeftToRight(); } else { arrangeCharactersRightToLeft(); } showWords(); } function arrangeCharactersRightToLeft() { $("#words").addClass("rightToLeftTest"); } function arrangeCharactersLeftToRight() { $("#words").removeClass("rightToLeftTest"); } function setToggleSettings(state) { setPunctuation(state); setNumbers(state); } function emulateLayout(event) { function emulatedLayoutShouldShiftKey(event, newKeyPreview) { if (config.capsLockBackspace) return event.shiftKey; const isCapsLockHeld = event.originalEvent.getModifierState("CapsLock"); if (isCapsLockHeld) return isASCIILetter(newKeyPreview) !== event.shiftKey; return event.shiftKey; } function replaceEventKey(event, keyCode) { const newKey = String.fromCharCode(keyCode); event.keyCode = keyCode; event.charCode = keyCode; event.which = keyCode; event.key = newKey; event.code = "Key" + newKey.toUpperCase(); } if (event.key === " " || event.key === "Enter") return event; if (config.layout === "default") { //override the caps lock modifier for the default layout if needed if (config.capsLockBackspace && isASCIILetter(event.key)) { replaceEventKey( event, event.shiftKey ? event.key.toUpperCase().charCodeAt(0) : event.key.toLowerCase().charCodeAt(0) ); } return event; } const qwertyMasterLayout = { Backquote: "`~", Digit1: "1!", Digit2: "2@", Digit3: "3#", Digit4: "4$", Digit5: "5%", Digit6: "6^", Digit7: "7&", Digit8: "8*", Digit9: "9(", Digit0: "0)", Minus: "-_", Equal: "=+", KeyQ: "qQ", KeyW: "wW", KeyE: "eE", KeyR: "rR", KeyT: "tT", KeyY: "yY", KeyU: "uU", KeyI: "iI", KeyO: "oO", KeyP: "pP", BracketLeft: "[{", BracketRight: "]}", KeyA: "aA", KeyS: "sS", KeyD: "dD", KeyF: "fF", KeyG: "gG", KeyH: "hH", KeyJ: "jJ", KeyK: "kK", KeyL: "lL", Semicolon: ";:", Quote: "'\"", Backslash: "\\|", KeyZ: "zZ", KeyX: "xX", KeyC: "cC", KeyV: "vV", KeyB: "bB", KeyN: "nN", KeyM: "mM", Comma: ",<", Period: ".>", Slash: "/?", Space: " ", }; const layoutMap = layouts[config.layout]; const qwertyMap = layouts["qwerty"]; const qwertyKey = qwertyMasterLayout[event.code]; let mapIndex; for (let i = 0; i < qwertyMap.length; i++) { const key = qwertyMap[i]; const keyIndex = key.indexOf(qwertyKey); if (keyIndex != -1) { mapIndex = i; } } const newKeyPreview = layoutMap[mapIndex][0]; const shift = emulatedLayoutShouldShiftKey(event, newKeyPreview) ? 1 : 0; const newKey = layoutMap[mapIndex][shift]; replaceEventKey(event, newKey.charCodeAt(0)); return event; } function punctuateWord(previousWord, currentWord, index, maxindex) { let word = currentWord; if ( index == 0 || getLastChar(previousWord) == "." || getLastChar(previousWord) == "?" || getLastChar(previousWord) == "!" ) { //always capitalise the first word or if there was a dot word = capitalizeFirstLetter(word); } else if ( //10% chance to end a sentence (Math.random() < 0.1 && getLastChar(previousWord) != "." && index != maxindex - 2) || index == maxindex - 1 ) { let rand = Math.random(); if (rand <= 0.8) { word += "."; } else if (rand > 0.8 && rand < 0.9) { word += "?"; } else { word += "!"; } } else if ( Math.random() < 0.01 && getLastChar(previousWord) != "," && getLastChar(previousWord) != "." ) { //1% chance to add quotes word = `"${word}"`; } else if (Math.random() < 0.01) { //1% chance to add a colon word = word + ":"; } else if ( Math.random() < 0.01 && getLastChar(previousWord) != "," && getLastChar(previousWord) != "." && previousWord != "-" ) { //1% chance to add a dash word = "-"; } else if (Math.random() < 0.2 && getLastChar(previousWord) != ",") { //2% chance to add a comma word += ","; } return word; } function addWord() { let bound = 60; if (activeFunBox === "plus_one") bound = 1; if ( !config.showAllLines && (wordsList.length - inputHistory.length > bound || (config.mode === "words" && wordsList.length >= config.words) || (config.mode === "custom" && customTextIsRandom && wordsList.length >= customTextWordCount) || (config.mode === "custom" && !customTextIsRandom && wordsList.length >= customText.length)) ) return; const language = config.mode !== "custom" ? words[config.language] : { //borrow the direction of the current language leftToRight: words[config.language].leftToRight, words: customText }; const wordset = language.words; let randomWord = wordset[Math.floor(Math.random() * wordset.length)]; previousWord = wordsList[wordsList.length - 1]; previousWordStripped = previousWord.replace(/[.?!":\-,]/g, "").toLowerCase(); previousWord2Stripped = wordsList[wordsList.length - 2] .replace(/[.?!":\-,]/g, "") .toLowerCase(); if (config.mode === "custom" && customTextIsRandom && wordset.length < 3) { randomWord = wordset[Math.floor(Math.random() * wordset.length)]; } else if (config.mode == "custom" && !customTextIsRandom) { randomWord = customText[wordsList.length]; } else { while ( previousWordStripped == randomWord || previousWord2Stripped == randomWord || randomWord.indexOf(" ") > -1 || (!config.punctuation && randomWord == "I") ) { randomWord = wordset[Math.floor(Math.random() * wordset.length)]; } } if (activeFunBox === "rAnDoMcAsE") { let randomcaseword = ""; for (let i = 0; i < randomWord.length; i++) { if (i % 2 != 0) { randomcaseword += randomWord[i].toUpperCase(); } else { randomcaseword += randomWord[i]; } } randomWord = randomcaseword; } else if (activeFunBox === "gibberish") { randomWord = getGibberish(); } else if (activeFunBox === "58008") { randomWord = getNumbers(7); } else if (activeFunBox === "specials") { randomWord = getSpecials(); } else if (activeFunBox === "ascii") { randomWord = getASCII(); } if (config.punctuation && config.mode != "custom") { randomWord = punctuateWord(previousWord, randomWord, wordsList.length, 0); } if (config.numbers && config.mode != "custom") { if (Math.random() < 0.1) { randomWord = getNumbers(4); } } wordsList.push(randomWord); let w = "