Nitro Monkey | NT Theme

Custom Nitro Type Theme w/ Font-Size, Height Sliders, and Cursor Customization

当前为 2024-10-27 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
  });
})();