├── img └── share.png ├── index.html ├── readme.md ├── script.js └── style.css /img/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tholman/checkboxrace/7d21a75ec789d1d04d884e5b01c1d15610e34d23/img/share.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Checkbox Race! 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 20 | 21 | 22 | 23 | 24 | 28 | 32 | 33 | 34 | 38 | 39 | 40 | 41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 151 | 156 | 157 | 158 | 159 |
160 | NICE! 161 |

Your Time was

162 | 163 |
164 |
165 | 166 |
167 |
168 | 000/100
169 | 00:00 170 |
171 | › 172 |
173 |
174 | Check the first box and we're off to the races!

↯ 175 |
176 | 177 |
178 | 🍅 179 | 180 |
181 | 182 |
183 |
184 | 🕺 The Useless Web 185 |
186 |
187 | ☑️ Checkbox Toys 188 |
189 | 190 | 191 |
192 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Checkbox Race 2 | 3 | A point-and-click racing minigame. Click the checkboxes as fast as you can to win! 4 | 5 | Screen Shot 2021-12-27 at 2 24 32 PM 6 | 7 | Screen Shot 2021-12-27 at 2 25 00 PM 8 | 9 | Screen Shot 2021-12-27 at 2 25 21 PM 10 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | const allCheckboxes = document.querySelectorAll("input"); 3 | const checkboxWrapper = document.querySelector(".checkboxes"); 4 | const scoreBoard = document.querySelector(".score"); 5 | const currentScore = document.querySelector(".current"); 6 | const tips = document.querySelector(".tips"); 7 | const timer = document.querySelector(".timer"); 8 | const flagPiece = document.querySelector(".flagPiece"); 9 | const finalTime = document.querySelector(".finalTime"); 10 | const resetButton = document.querySelector(".resetButton"); 11 | const endBoard = document.querySelector(".end"); 12 | const boostWords = [ 13 | "Speed!", 14 | "Nice!", 15 | "Fast!", 16 | "Power!", 17 | "Great!", 18 | "Awesome!", 19 | "Amazing!", 20 | "Super!", 21 | ]; 22 | 23 | resetButton.addEventListener("click", reset); 24 | 25 | setTimeout(() => { 26 | window.scrollTo(0, 0); 27 | }, 50); 28 | 29 | let animationFrame; 30 | let startTime; 31 | 32 | let currentIndex = 0; 33 | 34 | function startTimer() { 35 | startTime = Date.now(); 36 | animationFrame = window.requestAnimationFrame(tick); 37 | } 38 | 39 | function msToTime(duration) { 40 | var milliseconds = parseInt((duration % 1000) / 10) 41 | .toString() 42 | .padStart(2, "0"), 43 | seconds = Math.floor((duration / 1000) % 60), 44 | minutes = Math.floor((duration / (1000 * 60)) % 60), 45 | minutes = minutes < 10 ? "0" + minutes : minutes; 46 | seconds = seconds < 10 ? "0" + seconds : seconds; 47 | 48 | return minutes + ":" + seconds + ":" + milliseconds; 49 | } 50 | 51 | function tick() { 52 | var delta = Date.now() - startTime; 53 | timer.innerHTML = msToTime(delta); 54 | 55 | animationFrame = window.requestAnimationFrame(tick); 56 | } 57 | 58 | function randomPosOrNeg(number) { 59 | const posOrNeg = Math.random() < 0.5 ? -1 : 1; 60 | return Math.min(Math.random() * number, window.innerHeight - 10) * posOrNeg; 61 | } 62 | 63 | function reset() { 64 | allCheckboxes.forEach((checkbox, index) => { 65 | checkbox.style.transform = "none"; 66 | if (index !== 0) { 67 | checkbox.disabled = true; 68 | } 69 | checkbox.checked = false; 70 | }); 71 | 72 | currentIndex = 0; 73 | checkboxWrapper.style.transform = `translateX(${-20 * currentIndex}px)`; 74 | currentScore.innerHTML = 0; 75 | tips.classList.remove("hide"); 76 | startTime = null; 77 | scoreBoard.classList.remove("show"); 78 | timer.innerHTML = "00:00:00"; 79 | flagPiece.style.fill = "red"; 80 | endBoard.classList.remove("show"); 81 | } 82 | 83 | function addBoost(element) { 84 | let verticalMovement = new DOMMatrixReadOnly( 85 | window.getComputedStyle(element).transform 86 | ).f; 87 | 88 | const boostElement = document.createElement("div"); 89 | boostElement.classList.add("boost"); 90 | boostElement.style.top = `${ 91 | checkboxWrapper.clientHeight / 2 + verticalMovement - 60 92 | }px`; 93 | boostElement.style.left = `${element.offsetLeft}px`; 94 | boostElement.innerHTML = 95 | boostWords[Math.floor(Math.random() * boostWords.length)]; 96 | checkboxWrapper.appendChild(boostElement); 97 | } 98 | 99 | document.body.addEventListener("click", () => { 100 | if (currentIndex === 0 || currentIndex === allCheckboxes.length) return; 101 | 102 | allCheckboxes[currentIndex].disabled = true; 103 | allCheckboxes[currentIndex - 1].checked = false; 104 | allCheckboxes[currentIndex - 1].disabled = false; 105 | currentIndex--; 106 | currentScore.innerText = currentIndex.toString().padStart(3, "0"); 107 | checkboxWrapper.style.transform = `translateX(${-20 * currentIndex}px)`; 108 | }); 109 | 110 | allCheckboxes.forEach((checkbox, index) => { 111 | checkbox.addEventListener("click", (event) => { 112 | if (!startTime) { 113 | startTimer(); 114 | } 115 | 116 | if (index === currentIndex) { 117 | if (currentIndex === 0) { 118 | tips.classList.add("hide"); 119 | scoreBoard.classList.add("show"); 120 | } 121 | 122 | event.stopPropagation(); 123 | 124 | if (Math.random() > 0.6) addBoost(checkbox); 125 | 126 | currentIndex++; 127 | currentScore.innerText = currentIndex.toString().padStart(3, "0"); 128 | 129 | if (currentIndex === allCheckboxes.length) { 130 | flagPiece.style.fill = "#00c800"; 131 | cancelAnimationFrame(animationFrame); 132 | scoreBoard.classList.remove("show"); 133 | 134 | var delta = Date.now() - startTime; 135 | finalTime.innerHTML = msToTime(delta); 136 | endBoard.classList.add("show"); 137 | return; 138 | } 139 | 140 | allCheckboxes[currentIndex].disabled = false; 141 | checkboxWrapper.style.transform = `translateX(${-20 * currentIndex}px)`; 142 | 143 | allCheckboxes[ 144 | currentIndex 145 | ].style.transform = `translateY(${randomPosOrNeg(5 + currentIndex)}px)`; 146 | } else if (currentIndex === allCheckboxes.length) { 147 | if (currentIndex === allCheckboxes.length) { 148 | event.stopPropagation(); 149 | event.preventDefault(); 150 | } 151 | } 152 | }); 153 | }); 154 | })(); 155 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, 6 | body { 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | body { 12 | height: 100vh; 13 | display: flex; 14 | align-items: center; 15 | overflow: hidden; 16 | } 17 | 18 | input { 19 | transition: transform 100ms ease; 20 | cursor: pointer; 21 | } 22 | 23 | .toms { 24 | text-decoration: none; 25 | font-size: 20px; 26 | } 27 | 28 | .toms { 29 | display: inline-flex; 30 | align-items: center; 31 | justify-content: center; 32 | width: 33px; 33 | height: 33px; 34 | background-repeat: no-repeat; 35 | background-color: rgba(0, 0, 0, 0.07); 36 | border-radius: 50%; 37 | background-size: 20px 20px; 38 | background-position: center; 39 | vertical-align: top; 40 | } 41 | 42 | .checkboxes { 43 | height: 600px; 44 | display: flex; 45 | align-items: center; 46 | padding-left: calc(50vw - 10px); 47 | padding-right: calc(60vw); 48 | transition: transform 100ms ease; 49 | border-top: 3px dashed #f5f5f5; 50 | border-bottom: 3px dashed #f5f5f5; 51 | } 52 | 53 | .inner { 54 | padding-right: 30px; 55 | } 56 | 57 | .central { 58 | font-family: monospace; 59 | position: fixed; 60 | transform: translateX(-50%); 61 | pointer-events: none; 62 | left: 50%; 63 | font-weight: bold; 64 | display: flex; 65 | justify-content: center; 66 | align-items: center; 67 | transition: opacity 150ms ease-out; 68 | } 69 | 70 | .score { 71 | left: 0; 72 | padding: 30px; 73 | padding-right: 60px; 74 | height: 100vh; 75 | background: linear-gradient( 76 | 90deg, 77 | rgba(255, 255, 255, 1) 0%, 78 | rgba(255, 255, 255, 1) 80%, 79 | rgba(0, 0, 0, 0) 100% 80 | ); 81 | transform: none; 82 | text-align: center; 83 | font-size: 16px; 84 | line-height: 25px; 85 | color: white; 86 | } 87 | 88 | .score.show { 89 | color: black; 90 | } 91 | 92 | .tips { 93 | max-width: 150px; 94 | text-align: center; 95 | font-weight: bold; 96 | transform: translateY(-85%) translateX(-50%); 97 | } 98 | 99 | .flag { 100 | width: 25px; 101 | height: 25px; 102 | } 103 | 104 | .end { 105 | padding: 20px; 106 | background: #eee; 107 | margin-left: 20px; 108 | position: relative; 109 | font-family: monospace; 110 | width: 220px; 111 | text-align: center; 112 | display: flex; 113 | flex-direction: column; 114 | gap: 10px; 115 | transition: opacity 150ms ease-out; 116 | opacity: 0; 117 | } 118 | 119 | p { 120 | margin: 0; 121 | } 122 | 123 | .finalTime { 124 | font-weight: bold; 125 | } 126 | 127 | .nice { 128 | font-size: 18px; 129 | } 130 | 131 | .end:before { 132 | position: absolute; 133 | content: ""; 134 | left: -20px; 135 | top: 50%; 136 | margin-top: -10px; 137 | border-left: 10px solid transparent; 138 | border-right: 10px solid #eee; 139 | border-top: 10px solid transparent; 140 | border-bottom: 10px solid transparent; 141 | width: 20px; 142 | height: 20px; 143 | box-sizing: border-box; 144 | } 145 | 146 | .boost { 147 | position: absolute; 148 | pointer-events: none; 149 | animation: slideUp 800ms cubic-bezier(0.25, 1, 0.5, 1); 150 | opacity: 0; 151 | font-family: monospace; 152 | font-size: 15px; 153 | font-weight: bold; 154 | } 155 | 156 | .sitter { 157 | position: fixed; 158 | bottom: 15px; 159 | font-family: monospace; 160 | font-size: 11px; 161 | font-weight: bold; 162 | text-transform: uppercase; 163 | } 164 | 165 | .watch { 166 | background: rgba(0, 0, 0, 0.07); 167 | padding: 10px 15px; 168 | border-radius: 22px; 169 | display: inline-block; 170 | margin-right: 5px; 171 | } 172 | 173 | .twitter { 174 | background: url("data:image/png;base64,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"); 175 | width: 33px; 176 | height: 33px; 177 | display: inline-block; 178 | background-repeat: no-repeat; 179 | background-color: rgba(0, 0, 0, 0.07); 180 | border-radius: 50%; 181 | background-size: 20px 20px; 182 | background-position: center; 183 | vertical-align: top; 184 | } 185 | 186 | .left { 187 | left: 15px; 188 | } 189 | 190 | .right { 191 | right: 15px; 192 | } 193 | 194 | @keyframes slideUp { 195 | 0% { 196 | opacity: 1; 197 | transform: translateY(0) translateX(-50%); 198 | } 199 | 100% { 200 | opacity: 0; 201 | transform: translateY(-100px) translateX(-50%); 202 | } 203 | } 204 | 205 | .resetButton { 206 | width: 120px; 207 | margin: auto; 208 | margin-top: 2px; 209 | } 210 | 211 | .hide { 212 | opacity: 0; 213 | } 214 | 215 | .show { 216 | opacity: 1; 217 | } 218 | --------------------------------------------------------------------------------