// ==UserScript==
// @name Nitro Monkey | NT Theme
// @namespace https://greasyfork.org/users/1331131-tensorflow-dvorak
// @version 2024-10-27
// @description Custom Nitro Type Theme w/ Font-Size, Height Sliders, and Cursor Customization
// @author TensorFlow - Dvorak
// @match *://*.nitrotype.com/race
// @match *://*.nitrotype.com/race/*
// @require https://update.greasyfork.org/scripts/501960/1418069/findReact.js
// @license MIT
// @grant none
// ==/UserScript==
(function () {
const dynamicStyle = document.createElement("style");
document.head.appendChild(dynamicStyle);
let currentCursorType = localStorage.getItem("cursorType") || "line";
let currentCursorSpeed = localStorage.getItem("cursorSpeed") || "medium";
function updateStyles() {
dynamicStyle.innerHTML = `
${generateCursorStyle(currentCursorType, currentCursorSpeed)}
${generateFontSizeStyle(localStorage.getItem("dashFontSize") || "40")}
`;
}
function generateCursorStyle(cursorType, cursorSpeed) {
let cursorSize = "2px";
let cursorHeight = "1.2em";
let animationDuration = "1s";
let cursorTopOffset = "0.2em";
let cursorColor = "#00FF7F";
let cursorTransform = "";
if (cursorType === "block") {
cursorSize = "0.7em";
cursorHeight = "1.1em";
cursorTopOffset = "0";
cursorColor = "#0075ff42";
cursorTransform = "translateY(0.3em)";
} else if (cursorType === "line") {
cursorSize = "2px";
cursorHeight = "1.2em";
cursorColor = "#0075ff";
} else {
cursorSize = "0";
}
if (cursorSpeed === "slow") {
animationDuration = "1.5s";
} else if (cursorSpeed === "fast") {
animationDuration = "0.5s";
}
return `
.dash-letter {
color: #acaaff;
}
.dash-letter.is-waiting {
position: relative;
color: #acaaff;
background-color: #1c99f400;
}
.dash-letter.is-waiting::after {
content: '';
display: inline-block;
width: ${cursorSize};
height: ${cursorHeight};
background-color: ${cursorColor};
animation: blink ${animationDuration} step-end infinite;
position: absolute;
top: ${cursorTopOffset};
left: 0;
transform: ${cursorTransform};
}
.dash-letter.is-incorrect {
color: red;
background: #ffffff00;
position: relative;
}
.dash-letter.is-incorrect::after {
content: '';
display: inline-block;
width: ${cursorSize};
height: ${cursorHeight};
background-color: rgba(255, 0, 0, 0.5);
animation: blink ${animationDuration} step-end infinite;
position: absolute;
top: ${cursorTopOffset};
left: 0;
transform: ${cursorTransform};
}
@keyframes blink {
50% { opacity: 0; }
}
`;
}
function generateFontSizeStyle(fontSize) {
return `
#root {
background-color: #060516;
}
.dash-copy {
font-size: ${fontSize}px !important;
}
.dash-copyContainer {
background: linear-gradient(to bottom, rgba(6, 5, 22, 0.9) 65%, rgba(6, 5, 22, 0.87) 70%, #060516 100%);
flex: 1;
overflow: hidden;
padding:0px;
padding-left:10px;
border-radius: 0px;
box-shadow: none;
width: 100%;
display: flex;
}
.dash-side, .dash-actions, .dash-nitros {
display: none;
}
.dash:before {
height: min-content;
}
.structure-footer {
display: flex;
padding-top: 2rem;
}
.race-results {
background-color: #060516;
}
.raceResults--default {
background: #060516;
}
.raceResults-rewards {
background: #0c0b18;
}
.raceResults-dailyChallenges {
background: #0c0b18;
}
.g-b--7of12 {
background: #060516;
}
.footer-nav {
background: #0c0b18;
}
.nav-list {
background: #0c0b18;
}
.nav {
background: #0c0b18;
border-bottom: 1px solid #14141b;
}
.btn--primary {
background: #403dae;
}
.btn--primary:hover {
background: #8a1bdf;
}
.btn--secondary {
background: #5b048a;
}
.btn--secondary:hover {
background: #8d11d0;
}
.gridTable--raceResults .gridTable-cell {
background: #0c0b18;
}
.gridTable-cell {
background: #0c0b18;
}
.dashShield-layer {
display: none;
}
.dash-center {
padding: 0px;
background: #06051687;
}
.nt-stats-right-section {
background: #060516;
}
.nt-stats-daily-challenges {
background: #060516;
}
.nt-stats-body {
background: #0c0b18;
}
.experiment {
background: #0c0b18 !important;
color: #b7b5f7 !important;
}
`;
}
const dashElement = document.querySelector(".dash");
const container = document.querySelector(".structure-content div");
if (dashElement) {
const displayContainer = document.createElement("div");
displayContainer.classList.add('nitro-monkey__settings-container')
displayContainer.style.display = "flex";
displayContainer.style.marginTop = "4rem";
displayContainer.style.fontSize = "20px";
displayContainer.style.flexWrap = "wrap";
displayContainer.style.color = "#6864f6";
displayContainer.style.background = "rgba(6, 5, 22, 0.8)";
displayContainer.style.borderRadius = "5px";
displayContainer.style.borderRadius = "5px";
displayContainer.style.borderBottom = "2px solid #0c0b18";
displayContainer.style.justifyContent = "space-between";
displayContainer.innerHTML = `
<span id="targetWPMValue" style="display: none">Target WPM: 100</span>
<div>
<label for="targetWPM" style="margin-right: 0px;">Target WPM:</label>
<button id="decreaseWPM" class= "animate--iconSlam btn btn--fw btn--gloss btn--primary dhf" style="margin-right: 5px; margin-left: 5px; width: 1.3rem; height:.5rem;">-</button>
<input style="background: #060608; border-color: #0c0b18" type="number" id="targetWPM" min="10" max="300" value="100" readonly>
<button id="increaseWPM" class="animate--iconSlam btn btn--fw btn--gloss btn--primary dhf" style="margin-left: 5px; margin-left: 5px; height: .5rem; width: 1.3rem;">+</button>
</div>
<div>
<label for="cursorType" style="margin-right: 10px;">Cursor Type:</label>
<button class= "animate--iconSlam btn btn--fw btn--gloss btn--primary dhf" style="height: 2.5rem; width: 4rem;" id="cursorTypeButton">${
currentCursorType.charAt(0).toUpperCase() + currentCursorType.slice(1)
}</button>
</div>
<div>
<label for="cursorSpeed" style="margin-right: 10px;">Cursor Speed:</label>
<button class= "animate--iconSlam btn btn--fw btn--gloss btn--primary dhf" style="height: 2.5rem; width: 4rem;" id="cursorSpeedButton">${
currentCursorSpeed.charAt(0).toUpperCase() +
currentCursorSpeed.slice(1)
}</button>
</div>
`;
container.appendChild(displayContainer);
const wpmDisplay = displayContainer.querySelector("div:nth-child(1)");
const targetWPMValueDisplay =
displayContainer.querySelector("#targetWPMValue");
const accuracyDisplay = displayContainer.querySelector("div:nth-child(3)");
const targetWPMInput = displayContainer.querySelector("#targetWPM");
const increaseWPMButton = displayContainer.querySelector("#increaseWPM");
const decreaseWPMButton = displayContainer.querySelector("#decreaseWPM");
const cursorTypeButton =
displayContainer.querySelector("#cursorTypeButton");
const cursorSpeedButton =
displayContainer.querySelector("#cursorSpeedButton");
const savedTargetWPM = localStorage.getItem("targetWPM") || "100";
targetWPMInput.value = savedTargetWPM;
targetWPMValueDisplay.textContent = `Target WPM: ${savedTargetWPM}`;
function updateTargetWPM(value) {
const targetWPM = Math.max(50, Math.min(200, parseInt(value, 10)));
targetWPMInput.value = targetWPM;
targetWPMValueDisplay.textContent = `Target WPM: ${targetWPM}`;
localStorage.setItem("targetWPM", targetWPM);
}
increaseWPMButton.addEventListener("click", function () {
updateTargetWPM(parseInt(targetWPMInput.value, 10) + 5);
});
decreaseWPMButton.addEventListener("click", function () {
updateTargetWPM(parseInt(targetWPMInput.value, 10) - 5);
});
const cursorTypes = ["none", "block", "line"];
const cursorSpeeds = ["slow", "medium", "fast"];
cursorTypeButton.addEventListener("click", function () {
let currentIndex = cursorTypes.indexOf(currentCursorType);
currentCursorType = cursorTypes[(currentIndex + 1) % cursorTypes.length];
cursorTypeButton.textContent =
currentCursorType.charAt(0).toUpperCase() + currentCursorType.slice(1);
localStorage.setItem("cursorType", currentCursorType);
updateStyles();
});
cursorSpeedButton.addEventListener("click", function () {
let currentIndex = cursorSpeeds.indexOf(currentCursorSpeed);
currentCursorSpeed =
cursorSpeeds[(currentIndex + 1) % cursorSpeeds.length];
cursorSpeedButton.textContent =
currentCursorSpeed.charAt(0).toUpperCase() +
currentCursorSpeed.slice(1);
localStorage.setItem("cursorSpeed", currentCursorSpeed);
updateStyles();
});
// Height slider
const heightLabel = document.createElement("label");
heightLabel.textContent = "Adjust Height:";
heightLabel.style.color = "#5d5aec";
heightLabel.style.display = "block";
heightLabel.style.marginTop = "10px";
const heightSlider = document.createElement("input");
heightSlider.type = "range";
heightSlider.min = "100";
heightSlider.max = "1000";
const savedHeight = localStorage.getItem("dashHeight") || "500";
dashElement.style.height = `${savedHeight}px`;
heightSlider.value = savedHeight;
heightSlider.style.width = "100%";
heightSlider.style.marginTop = "5px";
heightSlider.style.cursor = "pointer";
container.appendChild(heightLabel);
container.appendChild(heightSlider);
heightSlider.addEventListener("input", function () {
const heightValue = heightSlider.value;
dashElement.style.height = `${heightValue}px`;
localStorage.setItem("dashHeight", heightValue);
});
// Font size slider
const fontSizeLabel = document.createElement("label");
fontSizeLabel.textContent = "Adjust Font Size:";
fontSizeLabel.style.color = "#5d5aec";
fontSizeLabel.style.display = "block";
fontSizeLabel.style.marginTop = "10px";
const fontSizeSlider = document.createElement("input");
fontSizeSlider.type = "range";
fontSizeSlider.min = "20";
fontSizeSlider.max = "80";
fontSizeSlider.value = localStorage.getItem("dashFontSize") || "40";
fontSizeSlider.style.width = "100%";
fontSizeSlider.style.marginTop = "5px";
fontSizeSlider.style.cursor = "pointer";
container.appendChild(fontSizeLabel);
container.appendChild(fontSizeSlider);
fontSizeSlider.addEventListener("input", function () {
const newFontSize = fontSizeSlider.value;
localStorage.setItem("dashFontSize", newFontSize);
updateStyles();
});
updateStyles();
}
setInterval(() => {
const experimentDiv = document.querySelector(".experiment");
const lastSlider = container.querySelector(
'input[type="range"]:last-of-type'
);
if (experimentDiv && lastSlider) {
const experimentParent = experimentDiv.parentElement;
if (experimentParent !== lastSlider.parentElement) {
lastSlider.parentElement.appendChild(experimentDiv);
}
}
}, 500);
// Retain scroll position
window.addEventListener("beforeunload", () => {
localStorage.setItem("scrollPosition", window.scrollY);
});
window.addEventListener("load", () => {
setTimeout(() => {
const scrollPosition = localStorage.getItem("scrollPosition");
if (scrollPosition) {
window.scrollTo(0, parseInt(scrollPosition, 10));
}
}, 1000);
});
// Fix stickers?
setInterval(() => {
const raceChatElement = document.querySelector(".raceChat");
const heightValue = localStorage.getItem("dashHeight") || "500";
if (raceChatElement) {
raceChatElement.style.bottom = `calc(${parseInt(heightValue)}px + 2.5rem)`;
//raceChatElement.style.bottom = `${parseInt(heightValue)}px`;
}
}, 500);
})();
(function () {
let startTime = null;
let intervalId = null;
let peakWPM = 0;
let skippedChars = 0;
let totalIncorrectTypedCharacters = 0;
const trackedIncorrectLetters = new Set();
let totalCharactersInRace = 0;
let errorsAllowed = 0;
function addWPMDrawer() {
const dashElement = document.querySelector(".dash");
if (dashElement) {
const displayContainer = document.createElement("div");
displayContainer.classList.add("nitro-monkey__wpm-container");
displayContainer.style.display = "flex";
displayContainer.style.gap = "2rem";
displayContainer.style.fontSize = "25px";
displayContainer.style.width = "100%";
displayContainer.style.color = "#6864f6";
displayContainer.style.height = "2.5rem";
displayContainer.style.background = "#060516";
displayContainer.style.justifyContent = "space-between";
displayContainer.style.borderBottom = "2px solid #0c0b18";
displayContainer.innerHTML = `
<div>WPM: <span id="wpmValue">0</span></div>
<div>Accuracy: <span id="accuracyValue">100%</span></div>
<div>Peak WPM: <span id="peakWpmValue">0</span></div>
<div>Errors Allowed: <span id="errorsAllowedValue">0</span></div>
`;
dashElement.parentNode.insertBefore(displayContainer, dashElement);
}
}
function calculateWPM(totalCharacters, timeInSeconds) {
const wordsTyped = totalCharacters / 5;
const WPM = (wordsTyped * 60) / timeInSeconds;
return WPM;
}
function calculateErrorsAllowed(totalCharacters) {
return Math.floor(0.04 * totalCharacters);
}
function getCorrectlyTypedCharacterCount() {
let correctLetters = document.querySelectorAll(
".dash-letter.is-correct.is-typed"
)?.length;
const skippedWord = Array.from(
document.querySelectorAll(".dash-letter.is-correct.is-typed")
);
const nitorUsed = document.querySelector(".dash-nitro.is-used");
if (nitorUsed && skippedWord) {
correctLetters =
correctLetters - skippedWord.pop().parentNode.children.length;
if (skippedChars === 0) {
skippedChars = skippedWord.pop().parentNode.children.length;
}
}
return correctLetters;
}
function detectMistakes() {
const incorrectLetters = document.querySelectorAll(
".dash-letter.is-incorrect"
);
incorrectLetters.forEach((letter) => {
if (!trackedIncorrectLetters.has(letter)) {
totalIncorrectTypedCharacters += 1;
trackedIncorrectLetters.add(letter);
errorsAllowed = Math.max(0, errorsAllowed - 1);
document.getElementById("errorsAllowedValue").textContent =
errorsAllowed;
}
});
}
function getTotalTypedCharacterCount() {
const typedLetters = document.querySelectorAll(".dash-letter.is-typed");
return typedLetters.length - skippedChars;
}
function getTotalCharacters() {
const totalLetters = document.querySelectorAll(".dash-letter").length;
return totalLetters - 1;
}
function calculateAccuracy(correctCharacters, incorrectCharacters) {
const totalTyped = correctCharacters + incorrectCharacters;
return (correctCharacters / totalTyped) * 100;
}
const getTypedCharacterCount = () => {
return document.querySelectorAll(".dash-letter.is-correct.is-typed")
?.length;
};
function updateWPM() {
if (!startTime) return;
const currentTime = new Date();
const elapsedTime = (currentTime - startTime) / 1000;
const typedCharacters = getTypedCharacterCount();
const correctlyTypedCharacters = getCorrectlyTypedCharacterCount();
detectMistakes();
const wpm = calculateWPM(correctlyTypedCharacters, elapsedTime);
document.getElementById("wpmValue").textContent = Math.round(wpm);
if (wpm > peakWPM) {
peakWPM = wpm;
document.getElementById("peakWpmValue").textContent = Math.round(peakWPM);
}
const accuracy = calculateAccuracy(
correctlyTypedCharacters,
totalIncorrectTypedCharacters
);
document.getElementById("accuracyValue").textContent = `${accuracy.toFixed(
2
)}%`;
if (typedCharacters >= totalCharactersInRace) {
createPostRaceInfo();
stopWPMTimer();
}
fetchStats();
}
function createPostRaceInfo() {
const wpmContainer = document.querySelector(".nitro-monkey__wpm-container");
if (wpmContainer) {
wpmContainer.style.bottom = "-3rem";
wpmContainer.style.position = "absolute";
}
}
function startWPMTimer() {
if (!startTime) {
startTime = new Date();
peakWPM = 0;
totalIncorrectTypedCharacters = 0;
trackedIncorrectLetters.clear();
totalCharactersInRace = getTotalCharacters();
errorsAllowed = calculateErrorsAllowed(totalCharactersInRace);
document.getElementById("errorsAllowedValue").textContent = errorsAllowed;
intervalId = setInterval(updateWPM, 100);
}
}
function stopWPMTimer() {
if (intervalId) {
clearInterval(intervalId);
}
}
function getRaceServer() {
const raceContainer = document.getElementById("raceContainer");
if (raceContainer) {
const raceObj = findReact(raceContainer);
return raceObj ? raceObj.server : null;
}
return null;
}
const fetchStats = () => {
const wpmElement = document.querySelector("#wpmValue");
const accuracyElement = document.querySelector("#accuracyValue");
const targetWPM = parseInt(localStorage.getItem("targetWPM"), 10) || 100;
const green = "#00FF7F";
const yellow = "#FFD700";
const red = "red";
if (wpmElement) {
const wpmValue = parseInt(wpmElement.textContent, 10) || 0;
if (wpmValue >= targetWPM - 5 && wpmValue <= targetWPM + 5) {
wpmElement.style.color = green;
} else if (wpmValue < targetWPM - 10) {
wpmElement.style.color = red;
} else if (wpmValue < targetWPM - 5) {
wpmElement.style.color = yellow;
} else if (wpmValue > targetWPM + 5) {
wpmElement.style.color = "blue";
}
}
if (accuracyElement) {
const accuracyValue = parseFloat(accuracyElement.textContent) || 0;
if (accuracyValue < 94) {
accuracyElement.style.color = red;
} else if (accuracyValue >= 94 && accuracyValue < 96) {
accuracyElement.style.color = yellow;
} else {
accuracyElement.style.color = green;
}
}
};
function monitorServerEvents() {
const server = getRaceServer();
if (!server) {
setTimeout(monitorServerEvents, 1000);
return;
}
server.on("status", (e) => {
if (e.status === "racing") {
startWPMTimer();
} else if (e.status === "complete") {
stopWPMTimer();
}
});
}
function initializeScript() {
addWPMDrawer();
monitorServerEvents();
}
window.addEventListener("load", () => {
setTimeout(() => {
initializeScript();
}, 1000);
});
})();