├── README.md
├── index.html
├── media
├── cactus.png
├── favicon.ico
├── replit.png
├── resp.png
├── ruby.png
└── slide.wav
├── script.js
└── style.css
/README.md:
--------------------------------------------------------------------------------
1 | # Slidey
2 |
3 | Recreation of the classic picture sliding puzzle thingy.
4 |
5 | ***
6 |
7 | ## How To Play
8 |
9 | #### Setting up
10 | - Choose an image from the 4 provided or upload your own
11 | - Press play
12 | - The image will be shuffled
13 | - One random tile will be removed
14 | - The timer will begin
15 |
16 | #### Playing
17 | - Click on the tile that you wish to move into the unoccupied square
18 | - You may only move a tile directly adjacent to the unoccupied square
19 |
20 | #### Winning
21 | - Rearrange all the tiles so that they match the original image
22 |
23 | ***
24 |
25 | ## Features
26 |
27 | - 3 grid sizes: 3x3, 4x4, 5x5
28 | - Multiple images to use
29 | - Upload your own image
30 | - Sliding animation
31 | - Sliding sound
32 | - Ability to enable/disable the animations and sounds
33 | - Best score and time for each mode are remembered
34 | - Fully responsive
35 | - Fun!
36 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Slidey
16 |
17 |
18 |
19 |
20 |
21 |
22 | Slidey
23 | Slide the tiles to correct the picture!
24 |
25 | Best Score: NA
Best Time: NA
35 |
36 |
37 |
Choose an image
38 |
39 |
40 |
41 |
42 |
43 |
44 |
Upload Image
45 |
46 |
47 |
48 |
49 |
50 |
87 |
88 |
120 |
121 | Play
122 |
123 |
124 |
125 |
Choose an image
126 |
127 |
128 |
129 |
130 |
131 |
Upload Image
132 |
133 |
134 |
149 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
--------------------------------------------------------------------------------
/media/cactus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Coding-Cactus/slidey/4c96674f3545179307e080608c5b97e0121748c8/media/cactus.png
--------------------------------------------------------------------------------
/media/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Coding-Cactus/slidey/4c96674f3545179307e080608c5b97e0121748c8/media/favicon.ico
--------------------------------------------------------------------------------
/media/replit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Coding-Cactus/slidey/4c96674f3545179307e080608c5b97e0121748c8/media/replit.png
--------------------------------------------------------------------------------
/media/resp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Coding-Cactus/slidey/4c96674f3545179307e080608c5b97e0121748c8/media/resp.png
--------------------------------------------------------------------------------
/media/ruby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Coding-Cactus/slidey/4c96674f3545179307e080608c5b97e0121748c8/media/ruby.png
--------------------------------------------------------------------------------
/media/slide.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Coding-Cactus/slidey/4c96674f3545179307e080608c5b97e0121748c8/media/slide.wav
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 | window.onload = () => {
2 | let bestScore = localStorage.getItem("bestScore-x3");
3 | bestScore = bestScore === null ? "NA" : bestScore;
4 | document.getElementById("bestScore").innerHTML = bestScore;
5 |
6 | let bestTime = localStorage.getItem("bestTime-x3");
7 | bestTime = bestTime === null ? "NA" : displayTime(bestTime);
8 | document.getElementById("bestTime").innerHTML = bestTime;
9 |
10 | let soundOn = true;
11 | let animationOn = true;
12 | let backgroundOn = true;
13 |
14 | function soundToggle() {
15 | soundOn = !soundOn;
16 | document.getElementById("sound").checked = soundOn;
17 | document.getElementById("sound2").checked = soundOn;
18 | }
19 |
20 | function animationToggle() {
21 | animationOn = !animationOn;
22 | document.getElementById("animation").checked = animationOn;
23 | document.getElementById("animation2").checked = animationOn;
24 | }
25 |
26 | function backgroundToggle() {
27 | backgroundOn = !backgroundOn;
28 | document.getElementById("background-toggle").checked = backgroundOn;
29 | document.getElementById("background-toggle2").checked = backgroundOn;
30 | if (backgroundOn) {
31 | background();
32 | } else {
33 | clearInterval(bgInterval);
34 | document.getElementById("background").innerHTML = "";
35 | }
36 |
37 | }
38 |
39 | document.getElementById("sound").addEventListener("change", soundToggle);
40 | document.getElementById("sound2").addEventListener("change", soundToggle);
41 | document.getElementById("animation").addEventListener("change", animationToggle);
42 | document.getElementById("animation2").addEventListener("change", animationToggle);
43 | document.getElementById("background-toggle").addEventListener("change", backgroundToggle);
44 | document.getElementById("background-toggle2").addEventListener("change", backgroundToggle);
45 |
46 |
47 | function radioChange(e) {
48 | const id = e.target.id;
49 | if (id.includes("mobile-")) {
50 | document.getElementById(id.replace("mobile-", "")).checked = true;
51 | } else {
52 | document.getElementById("mobile-"+id).checked = true;
53 | }
54 |
55 | const mode = id.replace("mobile-", "").substr(1);
56 | document.getElementById("board").className = mode;
57 |
58 | let bestScore = localStorage.getItem("bestScore-"+mode);
59 | bestScore = bestScore === null ? "NA" : bestScore;
60 | document.getElementById("bestScore").innerHTML = bestScore;
61 |
62 | let bestTime = localStorage.getItem("bestTime-"+mode);
63 | bestTime = bestTime === null ? "NA" : displayTime(bestTime);
64 | document.getElementById("bestTime").innerHTML = bestTime;
65 |
66 | clearInterval(interval);
67 | resetBoard();
68 | }
69 |
70 | document.getElementById("3x3").addEventListener("change", radioChange);
71 | document.getElementById("4x4").addEventListener("change", radioChange);
72 | document.getElementById("5x5").addEventListener("change", radioChange);
73 | document.getElementById("mobile-3x3").addEventListener("change", radioChange);
74 | document.getElementById("mobile-4x4").addEventListener("change", radioChange);
75 | document.getElementById("mobile-5x5").addEventListener("change", radioChange);
76 |
77 |
78 | function displayTime(d) {
79 | let s = String(d % 60);
80 | let m = String(Math.floor(d / 60));
81 |
82 | if (s.length < 2) {s = "0" + s}
83 |
84 | return m + ":" + s;
85 | }
86 |
87 | function resetBoard() {
88 | const old_element = document.getElementById("board");
89 | const new_element = old_element.cloneNode(true);
90 | old_element.parentNode.replaceChild(new_element, old_element);
91 | new_element.className = new_element.className.replace(" won", "");
92 |
93 | let boardTiles = document.getElementById("board").children;
94 | for (let row = 0; row < boardTiles.length; row++) {
95 | for (let col = 0; col < boardTiles[row].children.length; col++) {
96 | boardTiles[row].children[col].className = "row" + (row+1) + " col" + (col+1) + " tile";
97 | }
98 | }
99 |
100 | document.getElementById("moves").innerHTML = "0";
101 | document.getElementById("time").innerHTML = "0:00";
102 | document.getElementById("start").innerHTML = "Play";
103 | }
104 |
105 | function shuffleBoard(blank, mode) {
106 | let tiles = [];
107 | for (let row = 1; row <= mode; row++) {
108 | for (let col = 1; col <= mode; col++) {
109 | tiles.push("row" + row + " col" + col);
110 | }
111 | }
112 | tiles[blank] = "blank";
113 |
114 | for (let i = 0; i < 10000; i++) {
115 | let moved = false;
116 | while (!moved) {
117 | let num = Math.floor(Math.random() * 4);
118 | if (num === 0) {
119 | const moveIndex = tiles.indexOf("blank") - mode;
120 | if (moveIndex > -1) {
121 | const tmp = tiles[moveIndex];
122 | tiles[moveIndex] = "blank";
123 | tiles[moveIndex+mode] = tmp;
124 | moved = true;
125 | }
126 | } else if (num === 1) {
127 | const moveIndex = tiles.indexOf("blank") + 1;
128 | if (moveIndex % mode !== 0 && moveIndex < mode**2) {
129 | const tmp = tiles[moveIndex];
130 | tiles[moveIndex] = "blank";
131 | tiles[moveIndex-1] = tmp;
132 | moved = true;
133 | }
134 | } else if (num === 2) {
135 | const moveIndex = tiles.indexOf("blank") + mode;
136 | if (moveIndex < mode**2) {
137 | const tmp = tiles[moveIndex];
138 | tiles[moveIndex] = "blank";
139 | tiles[moveIndex-mode] = tmp;
140 | moved = true;
141 | }
142 | } else if (num === 3) {
143 | const moveIndex = tiles.indexOf("blank") - 1;
144 | if ((moveIndex + 1) % mode !== 0 && moveIndex > 0) {
145 | const tmp = tiles[moveIndex];
146 | tiles[moveIndex] = "blank";
147 | tiles[moveIndex+1] = tmp;
148 | moved = true;
149 | }
150 | }
151 | }
152 | }
153 |
154 | for (let row = 0; row < mode; row++) {
155 | for (let col=0; col < mode; col++) {
156 | document.getElementById("board").children[row].children[col].className = tiles[row*mode+col] + " tile";
157 | }
158 | }
159 | }
160 |
161 | function checkWin(blank, moves, timeStart, interval, mode) {
162 | let correctTiles = [];
163 | for (let row = 1; row <= mode; row++) {
164 | for (let col = 1; col <= mode; col++) {
165 | correctTiles.push("row" + row + " col" + col);
166 | }
167 | }
168 | correctTiles[blank] = "blank";
169 |
170 | const boardTiles = document.getElementById("board").children;
171 | for (let row = 0; row < mode; row++) {
172 | for (let col = 0; col < mode; col++) {
173 | if (boardTiles[row].children[col].className.replace(" tile", "") !== correctTiles[row*mode+col]) {
174 | return;
175 | }
176 | }
177 | }
178 |
179 | clearInterval(interval);
180 | document.getElementById("board").className += " won";
181 |
182 | if (localStorage.getItem("bestScore-x"+mode) === null || moves < Number(localStorage.getItem("bestScore-x"+mode))) {
183 | localStorage.setItem("bestScore-x"+mode, moves);
184 | document.getElementById("bestScore").innerHTML = moves;
185 | }
186 |
187 | let time = Math.floor((new Date() - timeStart)/1000);
188 | if (localStorage.getItem("bestTime-x"+mode) === null || time < Number(localStorage.getItem("bestTime-x"+mode))) {
189 | localStorage.setItem("bestTime-x"+mode, time);
190 | document.getElementById("bestTime").innerHTML = displayTime(time);
191 | }
192 | }
193 |
194 | let interval = 0;
195 | document.getElementById("start").addEventListener("click", () => {
196 | const old_element = document.getElementById("board");
197 | const new_element = old_element.cloneNode(true);
198 | old_element.parentNode.replaceChild(new_element, old_element);
199 |
200 | new_element.className = new_element.className.replace(" won", "");
201 | const mode = Number(new_element.className.substr(1));
202 | let moves = 0;
203 | document.getElementById("moves").innerHTML = moves;
204 | let timeStart = new Date().getTime();
205 | document.getElementById("time").innerHTML = "0:00";
206 |
207 | const blank = Math.floor(Math.random() * mode**2);
208 | document.getElementById("start").innerHTML = "Restart";
209 | shuffleBoard(blank, mode);
210 |
211 | clearInterval(interval);
212 | interval = setInterval(() => {
213 | document.getElementById("time").innerHTML = displayTime(Math.floor((new Date().getTime() - timeStart)/1000));
214 | }, 250);
215 |
216 | const sound = new Howl({
217 | src: ['media/slide.wav']
218 | });
219 |
220 | document.querySelectorAll(".tile").forEach( tile => {
221 | tile.addEventListener("click", (e) => {
222 | const row = Number(tile.parentElement.id.replace("row", ""));
223 | let col;
224 | for (let c = 0; c < mode; c++) {
225 | if (document.getElementById("row"+row).children[c] == e.target) {
226 | col = c;
227 | break;
228 | }
229 | }
230 |
231 | if (row < mode) {
232 | if (document.getElementById("row"+String(row + 1)).children[col].className.includes("blank")) {
233 | if (document.getElementById("sound").checked) {
234 | sound.play();
235 | }
236 | let time = 0;
237 | if (document.getElementById("animation").checked) {
238 | time = 150;
239 | tile.className += " down";
240 | }
241 | setTimeout(() => {
242 | document.getElementById("row"+String(row + 1)).children[col].className = tile.className.replace(" down", "");
243 | tile.className = "blank tile";
244 | moves++;
245 | document.getElementById("moves").innerHTML = moves;
246 | checkWin(blank, moves, timeStart, interval, mode);
247 | return;
248 | }, time);
249 | }
250 | } if (row > 1) {
251 | if (document.getElementById("row"+String(row - 1)).children[col].className.includes("blank")) {
252 | if (document.getElementById("sound").checked) {
253 | sound.play();
254 | }
255 | let time = 0;
256 | if (document.getElementById("animation").checked) {
257 | time = 150;
258 | tile.className += " up";
259 | }
260 | setTimeout(() => {
261 | document.getElementById("row"+String(row - 1)).children[col].className = tile.className.replace(" up", "");
262 | tile.className = "blank tile";
263 | moves++;
264 | document.getElementById("moves").innerHTML = moves;
265 | checkWin(blank, moves, timeStart, interval, mode);
266 | return;
267 | }, time);
268 | }
269 | } if (col < mode-1) {
270 | if (document.getElementById("row"+String(row)).children[col+1].className.includes("blank")) {
271 | if (document.getElementById("sound").checked) {
272 | sound.play();
273 | }
274 | let time = 0;
275 | if (document.getElementById("animation").checked) {
276 | time = 150;
277 | tile.className += " right";
278 | }
279 | setTimeout(() => {
280 | document.getElementById("row"+String(row)).children[col+1].className = tile.className.replace(" right", "");
281 | tile.className = "blank tile";
282 | moves++;
283 | document.getElementById("moves").innerHTML = moves;
284 | checkWin(blank, moves, timeStart, interval, mode);
285 | return;
286 | }, time);
287 | }
288 | } if (col > 0) {
289 | if (document.getElementById("row"+String(row)).children[col-1].className.includes("blank")) {
290 | if (document.getElementById("sound").checked) {
291 | sound.play();
292 | }
293 | let time = 0;
294 | if (document.getElementById("animation").checked) {
295 | time = 150;
296 | tile.className += " left";
297 | }
298 | setTimeout(() => {
299 | document.getElementById("row"+String(row)).children[col-1].className = tile.className.replace(" left", "");
300 | tile.className = "blank tile";
301 | moves++;
302 | document.getElementById("moves").innerHTML = moves;
303 | checkWin(blank, moves, timeStart, interval, mode);
304 | return;
305 | }, time);
306 | }
307 | }
308 | });
309 | });
310 | });
311 |
312 | document.querySelectorAll("#images button").forEach(button => {
313 | button.addEventListener("click", () =>{
314 | document.querySelectorAll(".tile").forEach(tile => {
315 | tile.style.backgroundImage = "url("+button.getAttribute("data-src")+")";
316 | });
317 | clearInterval(interval);
318 | resetBoard();
319 | });
320 | });
321 | document.querySelectorAll("#mobile-images button").forEach(button => {
322 | button.addEventListener("click", () =>{
323 | document.querySelectorAll(".tile").forEach(tile => {
324 | tile.style.backgroundImage = "url("+button.getAttribute("data-src")+")";
325 | });
326 | clearInterval(interval);
327 | resetBoard();
328 | });
329 | });
330 |
331 | document.getElementById("img-input").onchange = function (e) {
332 | const files = e.target.files;
333 | var fr = new FileReader();
334 | fr.onload = function () {
335 | document.querySelectorAll(".tile").forEach(tile => {
336 | tile.style.backgroundImage = "url("+fr.result+")";
337 | });
338 | }
339 | fr.readAsDataURL(files[0]);
340 | clearInterval(interval);
341 | resetBoard();
342 | e.preventDefault();
343 | }
344 | document.getElementById("mobile-img-input").onchange = function (e) {
345 | const files = e.target.files;
346 | var fr = new FileReader();
347 | fr.onload = function () {
348 | document.querySelectorAll(".tile").forEach(tile => {
349 | tile.style.backgroundImage = "url("+fr.result+")";
350 | });
351 | }
352 | fr.readAsDataURL(files[0]);
353 | clearInterval(interval);
354 | resetBoard();
355 | e.preventDefault();
356 | }
357 |
358 |
359 |
360 | let bgInterval;
361 | function background() {
362 | if (backgroundOn) {
363 | document.getElementById("background").innerHTML = "";
364 | clearInterval(bgInterval);
365 |
366 | const cols = Math.ceil(window.innerWidth / 175);
367 | const rows = Math.ceil(window.innerHeight / 175);
368 | const randBlank = Math.floor(Math.random() * (cols*rows));
369 |
370 | const colours = ["#cc0000bb", "#00cc00bb", "#0000ccbb", "#cccc00bb", "#cc00ccbb", "#00ccccbb"]
371 | for (let r = 1; r <= rows; r++) {
372 | let row = document.createElement("div");
373 | row.id = "bg-row"+r;
374 | row.className = "bg-row"
375 | document.getElementById("background").appendChild(row);
376 | for (let c = 1; c <= cols; c++) {
377 | let col = document.createElement("div");
378 | document.getElementById("bg-row"+r).appendChild(col);
379 | }
380 | }
381 |
382 | for (let r = 1; r <= rows; r++) {
383 | for (let c = 1; c <= cols; c++) {
384 | col = document.getElementById("bg-row"+r).children[c-1];
385 | if ((r-1)*cols + c !== randBlank) {
386 | col.className = "bg-tile bg-col" + c;
387 | colour1 = colours[Math.floor(Math.random() * colours.length)];
388 | colour2 = colours[Math.floor(Math.random() * colours.length)];
389 | while (colour1 == colour2) {
390 | colour1 = colours[Math.floor(Math.random() * colours.length)];
391 | colour2 = colours[Math.floor(Math.random() * colours.length)];
392 | }
393 | col.style.background = "linear-gradient("+Math.floor(Math.random() * 360)+"deg,"+colour1+","+colour2+")";
394 | } else {
395 | col.className = "bg-tile bg-blank bg-col"+c;
396 | }
397 | }
398 | }
399 |
400 | let last_direction;
401 | bgInterval = setInterval(() => {
402 | const blank = document.querySelector(".bg-blank");
403 | const blankRow = Number(blank.parentElement.id.replace("bg-row", ""));
404 | const blankCol = Number(blank.className.replace("bg-tile bg-blank bg-col", ""));
405 | let moved = false;
406 | while (!moved) {
407 | let direction = Math.floor(Math.random() * 4);
408 | while (direction === 3-last_direction) {
409 | direction = Math.floor(Math.random() * 4);
410 | }
411 | if (direction === 0 && blankRow < rows) {
412 | moved = true;
413 | let tile = document.getElementById("bg-row"+String(blankRow + 1)).children[blankCol-1];
414 | tile.className += " up";
415 | setTimeout(() => {
416 | blank.style.background = tile.style.background;
417 | blank.className = "bg-tile bg-col"+blankCol;
418 | tile.className = "bg-tile bg-blank bg-col"+blankCol;
419 | tile.background = "";
420 | last_direction = direction;
421 | }, 500);
422 | } else if (direction === 1 && blankCol < cols) {
423 | moved = true;
424 | let tile = document.getElementById("bg-row"+blankRow).children[blankCol];
425 | tile.className += " left";
426 | setTimeout(() => {
427 | blank.style.background = tile.style.background;
428 | blank.className = "bg-tile bg-col"+blankCol;
429 | tile.className = "bg-tile bg-blank bg-col"+(blankCol+1);
430 | tile.background = "";
431 | last_direction = direction;
432 | }, 500);
433 | } else if (direction === 2 && blankCol > 1) {
434 | moved = true;
435 | let tile = document.getElementById("bg-row"+blankRow).children[blankCol-2];
436 | tile.className += " right";
437 | setTimeout(() => {
438 | blank.style.background = tile.style.background;
439 | blank.className = "bg-tile bg-col"+blankCol;
440 | tile.className = "bg-tile bg-blank bg-col"+(blankCol-1);
441 | tile.background = "";
442 | last_direction = direction;
443 | }, 500);
444 | } else if (direction === 3 && blankRow > 1) {
445 | moved = true;
446 | let tile = document.getElementById("bg-row"+String(blankRow - 1)).children[blankCol-1];
447 | tile.className += " down";
448 | setTimeout(() => {
449 | blank.style.background = tile.style.background;
450 | blank.className = "bg-tile bg-col"+blankCol;
451 | tile.className = "bg-tile bg-blank bg-col"+blankCol;
452 | tile.background = "";
453 | last_direction = direction;
454 | }, 500);
455 | }
456 | }
457 | }, 1000);
458 | }
459 | }
460 | background();
461 | window.addEventListener("resize", background);
462 | }
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=KoHo&display=swap");
2 |
3 | * {
4 | margin: 0;
5 | padding: 0;
6 | box-sizing: border-box;
7 | font-family: "KoHo", sans-serif;
8 | }
9 |
10 | h1 { font-size: 5vh; }
11 | h5 { font-size: 2vh; }
12 | h1, h5 { text-align: center; }
13 |
14 | h1, h5, #scores, #mobile-options h4, #mobile-options label {
15 | color: #fff;
16 | text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
17 | }
18 | #mobile-options label { text-shadow: -0.5px -0.5px 0 #000, 0.5px -0.5px 0 #000, -0.5px 0.5px 0 #000, 0.5px 0.5px 0 #000; }
19 | #mobile-options .img-label { font-size: 18px; }
20 |
21 | main {
22 | width: max-content;
23 | margin: auto;
24 | --size: 65vh;
25 | padding-top: 20px;
26 | }
27 |
28 | #board {
29 | --padding: 5px;
30 | --spacing: 2.5px;
31 | vertical-align: middle;
32 | height: var(--size);
33 | width: var(--size);
34 | padding: var(--padding);
35 | margin: 5px auto;
36 | background-color: rgba(200, 200, 200, 0.85);
37 | border-radius: var(--padding);
38 | display: inline-block;
39 | }
40 |
41 | .x3 { --mode: 3; }
42 | .x4 { --mode: 4; }
43 | .x5 { --mode: 5; }
44 |
45 | .row {
46 | width: 100%;
47 | height: calc(100% / var(--mode) - var(--spacing) * (1 - 1 / var(--mode)));
48 | }
49 |
50 | .tile {
51 | height: 100%;
52 | width: calc(100% / var(--mode) - var(--spacing) * (1 - 1 / var(--mode)));
53 | display: inline-block;
54 | background-color: white;
55 | background-image: url(/media/replit.png);
56 | background-size: calc(var(--size) - 2 * var(--padding));
57 | background-repeat: no-repeat;
58 | transition: transform 150ms;
59 | }
60 |
61 | .row:not(:first-child) { margin-top: var(--spacing); }
62 | .row .tile:not(:first-child) { margin-left: var(--spacing); }
63 |
64 | .row .col1 { background-position-x: 0; }
65 | .row .col2 { background-position-x: calc((var(--size) / var(--mode) - var(--spacing)) * -1) }
66 | .row .col3 { background-position-x: calc((var(--size) / (var(--mode) / 2) - 2 * var(--spacing)) * -1); }
67 | .row .col4 { background-position-x: calc((var(--size) / (var(--mode) / 3) - 3 * var(--spacing)) * -1); }
68 | .row .col5 { background-position-x: calc((var(--size) / (var(--mode) / 4) - 4 * var(--spacing)) * -1); }
69 |
70 | .row1.tile { background-position-y: 0; }
71 | .row2.tile { background-position-y: calc((var(--size) / var(--mode) - var(--spacing)) * -1); }
72 | .row3.tile { background-position-y: calc((var(--size) / (var(--mode) / 2) - 2 * var(--spacing)) * -1); }
73 | .row4.tile { background-position-y: calc((var(--size) / (var(--mode) / 3) - 3 * var(--spacing)) * -1); }
74 | .row5.tile { background-position-y: calc((var(--size) / (var(--mode) / 4) - 4 * var(--spacing)) * -1); }
75 |
76 | .tile.right { transform: translateX(calc(var(--size) / var(--mode) - var(--spacing))); }
77 | .tile.left { transform: translateX(calc(var(--size) / var(--mode) * -1 + var(--spacing))); }
78 | .tile.up { transform: translateY(calc(var(--size) / var(--mode) * -1 + var(--spacing))); }
79 | .tile.down { transform: translateY(calc(var(--size) / var(--mode) - var(--spacing))); }
80 | .tile.right, .tile.left, .tile.down, .tile.up { pointer-events: none; }
81 |
82 | .x3 .row .col4, .x3 .row .col5, .x3 #row4, .x3 #row5, .x4 .row .col5, .x4 #row5 { display: none !important; }
83 |
84 | .blank {
85 | background-image: none !important;
86 | background-color: transparent !important;
87 | }
88 |
89 | .won {
90 | pointer-events: none;
91 | background-color: rgba(0, 225, 0, 0.85) !important;
92 | }
93 |
94 | #image-select, #settings {
95 | --padding: 10px;
96 | font-size: calc(var(--size) / 35);
97 | display: inline-block;
98 | width: calc((var(--size) / 3 + (var(--padding)) * 2) / 1.25);
99 | height: var(--size);
100 | vertical-align: middle;
101 | background-color: rgba(200, 200, 200, 0.85);
102 | padding: var(--padding);
103 | border-radius: calc(var(--padding) / 2);
104 | position: relative;
105 | }
106 |
107 | #images {
108 | margin: auto;
109 | width: max-content;
110 | }
111 |
112 | #images > button, #images label {
113 | --width: calc((var(--size) / 3.6 - (var(--padding) * 2)) / 1.25);
114 | }
115 |
116 | #images > button, #mobile-images > button {
117 | width: var(--width);
118 | height: var(--width);
119 | cursor: pointer;
120 | display: block;
121 | margin: 10px auto;
122 | border: 5px solid #16aaec;
123 | border-radius: 22.5%;
124 | transition: border-color 0.3s;
125 | }
126 |
127 | #images > button:hover, #mobile-images > button:hover {
128 | border-color: #45cc01;
129 | }
130 |
131 | #images img, #mobile-images img {
132 | width: 100%;
133 | height: 100%;
134 | object-fit: cover;
135 | border-radius: 22.5%;
136 | }
137 |
138 | label {
139 | cursor: pointer;
140 | vertical-align: middle;
141 | user-select: none;
142 | }
143 |
144 | .img-label {
145 | position: absolute;
146 | bottom: 10px;
147 | display: block;
148 | width: var(--width);
149 | text-align: center;
150 | background-color: #16aaec;
151 | color: #fff;
152 | padding: 5px;
153 | border-radius: 5px;
154 | font-weight: 600;
155 | font-size: calc(var(--size) / 40);
156 | transition: background-color 0.3s;
157 | }
158 |
159 | .img-label:hover {
160 | background-color: #45cc01;
161 | }
162 |
163 | #img-input, #mobile-img-input {
164 | display: none;
165 | }
166 |
167 | #preferences {
168 | margin-top: 20px;
169 | }
170 |
171 | #preferences h4, #mode h4 {
172 | margin-bottom: 10px;
173 | }
174 |
175 | input, select, button, textarea {
176 | -webkit-appearance: none;
177 | }
178 |
179 | input[type="checkbox"], input[type="radio"] {
180 | display: inline-block;
181 | position: relative;
182 | width: calc(var(--size) / 35);
183 | height: calc(var(--size) / 35);
184 | font-size: calc(var(--size) / 35);
185 | margin: 0;
186 | margin-right: 3px;
187 | vertical-align: middle;
188 | border-radius: 25%;
189 | background-color: #ddd;
190 | cursor: pointer;
191 | }
192 |
193 | input[type="checkbox"]:checked, input[type="radio"]:checked {
194 | background-color: #16aaec;
195 | }
196 |
197 | input[type="checkbox"]:checked::before, input[type="radio"]:checked::before {
198 | position: absolute;
199 | display: block;
200 | content: "✔";
201 | color: #fff;
202 | top: 45%;
203 | left: 50%;
204 | transform: translateX(-50%) translateY(-50%);
205 | }
206 |
207 | input[type="radio"]:checked::before {
208 | content: "•";
209 | font-size: 150%;
210 | top: 36%;
211 | }
212 |
213 | input[type="radio"] {
214 | border-radius: 50%;
215 | }
216 |
217 | #start {
218 | display: block;
219 | width: var(--size);
220 | font-size: calc(var(--size) / 12.5);
221 | padding-bottom: 5px;
222 | background-color: #16aaec;
223 | color: #fff;
224 | border-radius: 30px;
225 | cursor: pointer;
226 | font-weight: 600;
227 | transition: background-color 0.3s;
228 | border: none;
229 | margin: auto;
230 | }
231 |
232 | #start:hover {
233 | background-color: #45cc01;
234 | }
235 |
236 | #scores {
237 | width: var(--size);
238 | margin: auto;
239 | margin-top: 20px;
240 | }
241 |
242 | #current, #best {
243 | width: 50%;
244 | display: inline-block;
245 | padding: 10px;
246 | }
247 |
248 | #current { border-right: 2.5px solid #16aaec; }
249 | #best { border-left: 2.5px solid #16aaec; }
250 |
251 | #current p {
252 | text-align: right;
253 | }
254 |
255 | #best p {
256 | text-align: left;
257 | }
258 |
259 | #mobile-options { display: none; }
260 |
261 | @media screen and (max-width: 1000px) {
262 | main { --size: 60vh; }
263 | .img-label { font-size: calc(var(--size) / 45)}
264 | }
265 |
266 | @media screen and (max-width: 600px) {
267 | h1 { font-size: 30px; }
268 | h5 { font-size: 10px; }
269 |
270 | #mobile-options { display: block; }
271 | #image-select, #settings { display: none; }
272 |
273 | main {
274 | --size: 95vw;
275 | padding-bottom: calc((100vh - var(--size)) / 2 - 75px);
276 | }
277 |
278 | #scores { width: 100%; }
279 |
280 | #board {
281 | display: block;
282 | margin-left: auto;
283 | margin-right: auto;
284 | }
285 |
286 | #mobile-images > button {
287 | width: 22.5vw;
288 | height: 22.5vw;
289 | display: inline-block;
290 | margin-right: 1vw;
291 | }
292 |
293 | #mobile-images { margin: auto; }
294 |
295 | .img-label {
296 | display: block;
297 | position: static;
298 | width: var(--size);
299 | bottom: 0;
300 | }
301 |
302 | #mobile-options {
303 | font-size: 20px;
304 | }
305 |
306 | input[type="checkbox"], input[type="radio"] {
307 | width: 15px;
308 | height: 15px;
309 | }
310 | }
311 |
312 |
313 |
314 | #background {
315 | position: fixed;
316 | height: 100vh;
317 | width: 100vw;
318 | z-index: -100;
319 | overflow: hidden;
320 | }
321 |
322 | .bg-row {
323 | height: 175px;
324 | width: 100vw;
325 | white-space: nowrap;
326 | }
327 |
328 | .bg-tile {
329 | width: 175px;
330 | height: 100%;
331 | display: inline-block;
332 | transition: transform 500ms;
333 | }
334 |
335 | .bg-blank {
336 | background: none !important;
337 | }
338 |
339 | .bg-tile.up { transform: translateY(-175px); }
340 | .bg-tile.down { transform: translateY(175px); }
341 | .bg-tile.left { transform: translateX(-175px); }
342 | .bg-tile.right { transform: translateX(175px); }
--------------------------------------------------------------------------------