<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>ImoType</title>
<link rel="stylesheet" href="style.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="caps-lock-warning" style="display: none;">
<p>Caps Lock is on.</p>
</div>
<div class="wrapper">
<input type="text" class="input-field">
<div class="content-box">
<div class="typing-text">
<p></p>
</div>
<div class="content">
<ul class="result-detail">
<li class="time">
<p>Time:</p>
<span><b>60s</b></span>
</li>
<li class="mistake">
<p>Mistakes:</p>
<span>0</span>
</li>
<li class="WPM">
<p>WPM:</p>
<span>60</span>
</li>
<li class="CPM">
<p>CPM:</p>
<span>60</span>
</li>
</ul>
<div class="spotify-play">
<span></span>
</div>
<button>Try Again</button>
</div>
</div>
</div>
</div>
<script src="js/paragraph.js"></script>
<script src="js/script.js"></script>
</body>
</html>
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@1000&family=Zen+Kaku+Gothic+Antique&display=swap');
* {
box-sizing: border-box;
font-family: 'Zen Kaku Gothic Antique', sans-serif;
}
h1 {
font-family: 'Nunito', sans-serif;
}
body {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
min-height: 100vh;
color: #fcf7ec;
background: #0e0e0e;
cursor: url('images/whiterabbitcursor.gif'),auto;
}
.wrapper {
width: 1000px;
background: #1a1a1a;
padding: 20px;
border-radius: 20px;
}
.wrapper .input-field{
z-index:-999;
opacity: 0;
position: absolute;
}
.wrapper .content-box {
padding: 15px 20px 0;
display: flex;
}
.content-box .typing-text {
display: flex;
max-height: 255px;
padding-right:10px;
overflow-y: auto;
}
.typing-text::scrollbar {
width: 0;
}
.typing-text {
color: #ffffff;
font-size: 21px;
letter-spacing: 1px;
text-align: justify;
}
.typing-text p span.incorrect{
color:red;
background-color: rgb(236, 205, 194);
border-radius: 3px;
}
.typing-text p span.active {
color: #b6b6b6;
position: relative;
}
.typing-text p span.active::before{
content:"";
position: absolute;
left: 0;
bottom: 0;
height:2px;
width: 100%;
background: #a02828;
animation: blink 4s ease-in-out infinite;
}
@keyframes blink
{
50%{
opacity: 1;
}
}
.content-box .content {
display: flex;
flex-direction: column;
align-content: center;
margin-top: 17px;
margin-left: 10px;
min-width:250px;
align-items: center;
}
.content .result-detail {
justify-content: space-between;
}
.result-detail li {
height: 22px;
display: flex;
list-style: none;
align-items: center;
left:-10px;
position: relative;
}
.result-detail li p {
font-size: 19px;
}
.result-detail li span {
display: block;
list-style: none;
font-size: 20px;
margin-left: 10px;
}
.result-detail li b{
font-weight: 700;
}
.content button {
border-radius: 10px;
border:none;
outline:none;
width:105px;
background:#000000;
height:35px;
font-size: 15px;
color:#ffffff;
font-weight: 1000;
transition: transform 0.3s ease;
}
.content button:active {
transform: scale(0.95);
}
.content button:hover {
cursor: pointer;
background:#383838;
}
const paragraphs = [
"Keep on walkin' Ain't no stoppin' in this dirty, filthy, rotten Nasty little world we call our home They get blickies poppin' Ain't no option for my partners So they resort to scams and robbin' Take away stress, we ganja-coppin' Blow it all out, it's all forgotten Walkin' with my back against the sun I've been runnin' all my life, that's way before my life begun Since my birth and seconds on Earth, I been the first one to confront All of these cycles that get recycled, makin' it stifle while I stunt Roll me a blunt so I forget it But it make the details look so vivid ",
"It's tomato or tomato, either way, the boy the greatest Play it, I won't say it no more I was just fucked up, I was just down, down bad I had to tighten the fuck up, but I'm here for the crown Board of Education vs. Brown I was bored of education, left the town Fuck a résumé and fuck a cap and gown Fuck a background check, back 'round when I get the check",
"She throw it back, hands on her knees I got the racks, so bust the cheeks She love to dance, but not for free So name the price, I'll pay the fee You know that ass want it, I put this cash on it The way you bounce that ass, you make them bitches mad, don't you? You want somebody to come and take care your niece, huh? 'Cause you a bad bitch and you don't fuck with fleas, huh?",
"Yellow diamonds in the watch, this shit cost a lot Never send a bitch your dot, that's how you get shot I DM in vanish mode, I do that shit a lot Took her panties off and this bitch thicker than the plot All my exes ain't nothin', them hoes busted If my opps ain't rappin', they ass duckin' You ain't ready to pull the trigger, don't clutch it I know you on your period baby, can you suck it?",
"These bitches love Sosa, O end or no end Fuckin' with them O boys, you gon' get fucked over 'Raris and Rovers, these hoes love Chief Sosa Hit him with that Cobra, now that boy slumped over They do it all for Sosa, you boys ain't making no noise Y'all know I'm a grown boy, your clique full of broke boys God, y'all some broke boys, God, y'all some broke boys We GBE dope boys, we got lots of dough, boy",
"I got a Draco in my bookbag, lil' bitch Yeah, woo That motherfucker got a kickback on it Yeah, woo That motherfucker got a kickback on it Yeah, woo And I put this on Onyx, I'ma leave my opp in the cement Lay that bitch down in the yard like a picnic He even ain't got no gun, how the fuck he flexin'? We gon' push up, bitch, lil' bitch We gon' push up, bitch, lil' bitch Pull up And I'm takin' shit",
"This ain't what you want, This ain't what you want Ha! 1600 block, I just wanna rock shake it down I just wanna, ah, ah, ah, ah, ah, ah, ah I just wanna rock, body-ody, yeah shake it down Shawty got that body-ody, ah, ah, ah shake it down Hit her once, no ties shake it-shake it How the fuck you gon' kill my vibe? Shake it down Stand on my money, don't know my size shake it-shake it Pick them sides, and you better choose wisely shake it-shake it down-down That's my high, one, two, three, four, throw up the five shake it-shake it"
];
const spotify = [
"<iframe style='border-radius:12px' src='https://open.spotify.com/embed/track/1q8DwZtQen5fvyB7cKbShC?utm_source=generator' width='100%' height='152' frameBorder='0' allowfullscreen='' allow='autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture' loading='lazy'></iframe>",
"<iframe style='border-radius:12px' src='https://open.spotify.com/embed/track/36J0iaPDGbYGsHvYpaYs3k?utm_source=generator' width='100%' height='152' frameBorder='0' allowfullscreen='' allow='autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture' loading='lazy'></iframe>",
"<iframe style='border-radius:12px' src='https://open.spotify.com/embed/track/27F7BWz4WuC8GuSIcFbx1a?utm_source=generator' width='100%' height='152' frameBorder='0' allowfullscreen='' allow='autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture' loading='lazy'></iframe>",
"<iframe style='border-radius:12px' src='https://open.spotify.com/embed/track/1bDbXMyjaUIooNwFE9wn0N?utm_source=generator' width='100%' height='152' frameBorder='0' allowfullscreen='' allow='autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture' loading='lazy'></iframe>",
"<iframe style='border-radius:12px' src='https://open.spotify.com/embed/track/01Lr5YepbgjXAWR9iOEyH1?utm_source=generator' width='100%' height='152' frameBorder='0' allowfullscreen='' allow='autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture' loading='lazy'></iframe>",
"<iframe style='border-radius:12px' src='https://open.spotify.com/embed/track/3dl8bSF08LQfCf4T6CCksf?utm_source=generator' width='100%' height='152' frameBorder='0' allowfullscreen='' allow='autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture' loading='lazy'></iframe>",
"<iframe style='border-radius:12px' src='https://open.spotify.com/embed/track/4FyesJzVpA39hbYvcseO2d?utm_source=generator' width='100%' height='152' frameBorder='0' allowfullscreen='' allow='autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture' loading='lazy'></iframe>",
];
const typingText = document.querySelector(".typing-text p");
inpField = document.querySelector(".wrapper .input-field");
mistakeTag = document.querySelector(".mistake span");
wpmTag = document.querySelector(".WPM span");
cpmTag = document.querySelector(".CPM span");
resetBtn = document.querySelector("button");
timeTag = document.querySelector(".time span b");
spotifyTag = document.querySelector(".spotify-play span");
warningMessage = document.getElementById("caps-lock-warning");
let timer,
maxTime = 60,
timeLeft = maxTime,
charIndex = (mistakes = isTyping = 0);
//function for selecting random paragraph
function randomParagraph() {
//select a random number thats always less than the length of the paragraph array
let randIndex = Math.floor(Math.random() * paragraphs.length);
typingText.innerHTML = "";
spotifyTag.innerHTML = spotify[randIndex];
//selecting a random pragraph and then splitting each individual character
//add each character into a span tag, which should then be inside a p tag
paragraphs[randIndex].split("").forEach((span) => {
let spanTag = `<span>${span}</span>`;
typingText.innerHTML += spanTag;
});
//focus on the input field either when key pressed or click
document.addEventListener("keydown", () => inpField.focus());
typingText.addEventListener("click", () => inpField.focus());
}
//initiate typing function
function initTyping() {
//find charcters in the span tag
// typedchar variable is set to typed characters split in the input box
const characters = typingText.querySelectorAll("span");
let typedChar = inpField.value.split("")[charIndex];
if (charIndex < characters.length - 1 && timeLeft > 0) {
if (!isTyping) {
//if not typing then start timer, otherwise don't restart
timer = setInterval(initTimer, 1000);
isTyping = true;
}
//if the user hasn't entered a charcter, or if they press backspace
if (typedChar == null) {
//go back one index and decrement charINdex
charIndex--;
//decrement mistakes only if the charIndex span has incorrect class
if (characters[charIndex].classList.contains("incorrect")) {
mistakes--;
}
//remove charindex span correct or incorrect
characters[charIndex].classList.remove("correct", "incorrect");
} else {
//if correct character is typed then assign correct to the span
if (characters[charIndex].innerText == typedChar) {
characters[charIndex].classList.add("correct");
} else {
//if incorrect character is typed then assign incorrect to the span and increase mistakes
mistakes++;
characters[charIndex].classList.add("incorrect");
}
//increment char index by one
charIndex++;
}
//remove the previous active character and add the new charindex as active
characters.forEach((span) => span.classList.remove("active"));
characters[charIndex].classList.add("active");
const elapsedSeconds = maxTime - timeLeft;
//calculate wpm
const numCompleteWords = typingText.innerText
.slice(0, charIndex)
.trim()
.split(" ").length;
let wpm = Math.round(numCompleteWords / (elapsedSeconds / 60));
// set wpm to 0 if the wpm is less than 0, null or infinity
wpm = wpm < 0 || !wpm || wpm === Infinity ? 0 : wpm;
//check if caps is on
inpField.addEventListener("keyup", function (event) {
const capsLockOn = event.getModifierState("CapsLock");
if (capsLockOn) {
warningMessage.style.display = "block";
} else {
warningMessage.style.display = "none";
}
});
//update text in result detail parameters
mistakeTag.innerText = mistakes;
cpmTag.innerText = charIndex - mistakes;
wpmTag.innerText = wpm;
} else {
inpField.value = "";
clearInterval(timer);
}
}
//function for starting the timer
function initTimer() {
//if time left is greater than timer then decrement timer
if (timeLeft > 0) {
timeLeft--;
timeTag.innerText = timeLeft;
} else {
clearInterval(timer);
}
}
function resetGame() {
randomParagraph();
inpField.value = "";
timeLeft = maxTime;
charIndex = mistakes = isTyping = 0;
mistakeTag.innerText = mistakes;
cpmTag.innerText = 0;
wpmTag.innerText = 0;
}
randomParagraph();
inpField.addEventListener("input", initTyping);
resetBtn.addEventListener("click", resetGame);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.