Skip to content

Commit 70d0da2

Browse files
committed
init
0 parents  commit 70d0da2

File tree

6 files changed

+366
-0
lines changed

6 files changed

+366
-0
lines changed

.prettierrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"printWidth": 120,
3+
"tabWidth": 4
4+
}

assets/digitaldream.ttf

28.6 KB
Binary file not shown.

assets/oof.wav

21 KB
Binary file not shown.

assets/wrong.mp3

4.23 KB
Binary file not shown.

assets/yay.wav

317 KB
Binary file not shown.

index.html

Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
<!--
2+
Sounds on creative commons 0 license (public domain):
3+
oof: https://freesound.org/people/fotoshop/sounds/47356/
4+
wrong: https://freesound.org/people/Raclure/sounds/483598/
5+
yay: https://freesound.org/people/Higgs01/sounds/428156/
6+
7+
Digital Dream font by Jakob Fischer (free for personal and commercial use):
8+
https://www.1001fonts.com/digital-dream-font.html
9+
www.pizzadude.dk
10+
-->
11+
<!DOCTYPE html>
12+
<html>
13+
<head>
14+
<title>Time flies!</title>
15+
<style>
16+
@font-face {
17+
font-family: "Digital Dream";
18+
src: url("assets/digitaldream.ttf");
19+
}
20+
21+
html,
22+
body {
23+
padding: 0;
24+
margin: 0;
25+
overflow: hidden;
26+
}
27+
28+
:root {
29+
--default-radius: 4rem;
30+
--default-diameter: calc(var(--default-radius) * 2);
31+
--clock-margin: 0 5rem;
32+
font-size: 14px;
33+
box-sizing: border-box;
34+
}
35+
36+
.row {
37+
padding: 1rem 0;
38+
overflow: hidden;
39+
height: var(--default-diameter);
40+
/* border-bottom: 1px solid red; */
41+
}
42+
43+
.row:first-child {
44+
/* border-top: 1px solid red; */
45+
}
46+
47+
.tracks {
48+
display: flex;
49+
flex-direction: column;
50+
height: 100vh;
51+
justify-content: center;
52+
}
53+
54+
.track {
55+
height: 100%;
56+
display: flex;
57+
width: max-content;
58+
}
59+
60+
.track > div {
61+
flex: 1 0 var(--default-diameter);
62+
}
63+
64+
.outer-circle {
65+
--diameter: calc(var(--radius) * 2);
66+
--pointer-width: 0.3rem;
67+
--hover-shadow-color: lime;
68+
width: var(--diameter);
69+
height: var(--diameter);
70+
border-radius: 50%;
71+
border: 0.3rem solid black;
72+
position: relative;
73+
/* margin: 0 12rem; */
74+
margin: var(--clock-margin);
75+
}
76+
77+
.outer-circle:hover,
78+
.outer-circle:hover > * {
79+
box-shadow: 0px 0px 5px 2px var(--hover-shadow-color);
80+
}
81+
82+
.outer-circle:hover {
83+
cursor: pointer;
84+
}
85+
86+
.long-pointer {
87+
height: calc(var(--radius) * 0.9);
88+
width: 0.2rem;
89+
background: black;
90+
position: absolute;
91+
bottom: 50%;
92+
left: 50%;
93+
transform: translateX(-50%) rotateZ(calc(var(--minutes) * 30deg));
94+
transform-origin: bottom left;
95+
}
96+
97+
.short-pointer {
98+
height: calc(var(--radius) * 0.7);
99+
width: 0.35rem;
100+
background: black;
101+
position: absolute;
102+
bottom: 50%;
103+
left: 50%;
104+
transform: translateX(-50%) rotateZ(calc(var(--hour) * 30deg));
105+
transform-origin: bottom left;
106+
}
107+
108+
.top-bar {
109+
display: flex;
110+
justify-content: space-around;
111+
align-items: center;
112+
font-family: "Digital Dream";
113+
}
114+
115+
.top-bar > * {
116+
padding: 1rem;
117+
}
118+
119+
.time {
120+
font-size: 3rem;
121+
}
122+
123+
.points {
124+
font-size: 1.5rem;
125+
}
126+
127+
.hp {
128+
font-size: 2rem;
129+
}
130+
131+
.empty {
132+
width: var(--default-diameter);
133+
margin: var(--clock-margin);
134+
}
135+
136+
#looser {
137+
position: fixed;
138+
top: 50%;
139+
left: 50%;
140+
transform: translate(-50%, -50%);
141+
font-family: Verdana, Geneva, Tahoma, sans-serif;
142+
font-size: 2.5rem;
143+
background: white;
144+
padding: 2rem;
145+
border: 1px solid black;
146+
text-transform: uppercase;
147+
display: none;
148+
}
149+
150+
#looser button {
151+
background: lime;
152+
border: 1px solid black;
153+
margin: 0 auto;
154+
display: block;
155+
padding: 0.5rem;
156+
font-size: 1.5rem;
157+
cursor: pointer;
158+
margin-top: 2rem;
159+
}
160+
</style>
161+
</head>
162+
<body>
163+
<div class="top-bar">
164+
<div class="hp">❤️❤️❤️</div>
165+
<div class="time">3:15</div>
166+
<div class="points"><span class="value">0</span> points</div>
167+
</div>
168+
<div class="tracks">
169+
<div class="row">
170+
<div class="track"></div>
171+
</div>
172+
<div class="row">
173+
<div class="track"></div>
174+
</div>
175+
<div class="row">
176+
<div class="track"></div>
177+
</div>
178+
<div class="row">
179+
<div class="track"></div>
180+
</div>
181+
<div class="row">
182+
<div class="track"></div>
183+
</div>
184+
<div class="row">
185+
<div class="track"></div>
186+
</div>
187+
</div>
188+
189+
<div id="looser">
190+
<div>You lost 😥</div>
191+
<div id="try-again-wrapper"><button onclick="tryAgain()">Try again</button></div>
192+
</div>
193+
194+
<template id="t-clock">
195+
<div class="outer-circle" style="--radius: 4rem; --hour: 8; --minutes: 15" onclick="handleClockClick(this)">
196+
<div class="long-pointer"></div>
197+
<div class="short-pointer"></div>
198+
</div>
199+
</template>
200+
201+
<audio id="audio-yay" src="assets/yay.wav"></audio>
202+
<audio id="audio-wrong" src="assets/wrong.mp3"></audio>
203+
<audio id="audio-oof" src="assets/oof.wav"></audio>
204+
205+
<script>
206+
function randomInt(to) {
207+
return Math.floor(Math.random() * to);
208+
}
209+
210+
const yaySfx = document.querySelector("#audio-yay");
211+
const wrongSfx = document.querySelector("#audio-wrong");
212+
const oofSfx = document.querySelector("#audio-oof");
213+
const hpElement = document.querySelector(".hp");
214+
const pointsElement = document.querySelector(".points .value");
215+
const looserElement = document.querySelector("#looser");
216+
const tracks = document.querySelectorAll(".row .track");
217+
const rowsWrapper = document.querySelector(".tracks");
218+
219+
const MINUTES_EASY = [0, 15, 30, 45];
220+
// const MINUTES_HARD = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55];
221+
// const MINUTES_IMPOSSIBLE = Array(60)
222+
// .fill()
223+
// .map((_, i) => i);
224+
const TRACKS_NUM = 6;
225+
226+
function instantiateClock(hour, minutes) {
227+
const template = document.querySelector("#t-clock");
228+
const instance = template.content.cloneNode(true);
229+
const clock = instance.children[0];
230+
clock.style.setProperty("--hour", hour);
231+
clock.style.setProperty("--minutes", minutes);
232+
return clock;
233+
}
234+
235+
const TRACK_LENGTH = 20;
236+
const matrixRows = Array(TRACKS_NUM)
237+
.fill()
238+
.map(() => Array(TRACK_LENGTH).fill(0));
239+
240+
const clockCellsSet = new Set();
241+
for (let i = 0; i < TRACK_LENGTH; i++) {
242+
const row = Math.floor(Math.random() * TRACKS_NUM);
243+
matrixRows[row][i] = 1;
244+
clockCellsSet.add(`${row},${i}`);
245+
}
246+
247+
// omg
248+
let goalCells = new Set(clockCellsSet);
249+
for (let i = goalCells.size; i > 3; i--) {
250+
goalCells.delete([...goalCells.values()][randomInt(goalCells.size)]);
251+
}
252+
253+
for (const g of goalCells) {
254+
const [row, col] = g.split(",");
255+
matrixRows[Number(row)][Number(col)] = 2;
256+
}
257+
258+
const empty = document.createElement("div");
259+
empty.className = "empty";
260+
261+
const GOAL_H = 3;
262+
const GOAL_M = 15;
263+
264+
let hp = 3;
265+
function decreaseHP() {
266+
oofSfx.play();
267+
hp--;
268+
hpElement.innerHTML = Array(hp).fill("❤️").join("");
269+
if (hp === 0) {
270+
looserElement.style.display = "block";
271+
}
272+
}
273+
274+
let points = 0;
275+
function increasePoints() {
276+
yaySfx.play();
277+
points++;
278+
pointsElement.innerHTML = points;
279+
}
280+
281+
matrixRows.forEach((row, idx) => {
282+
for (const col of row) {
283+
if (col === 1) {
284+
const hour = Math.floor(Math.random() * 12) + 1;
285+
// const hour = randomInt(GOAL_H) + randomInt(12 - (GOAL_H + 1) - GOAL_H + 1) + GOAL_H + 1;
286+
const minutesIdx = Math.floor(Math.random() * MINUTES_EASY.length);
287+
const clock = instantiateClock(hour, MINUTES_EASY[minutesIdx]);
288+
tracks[idx].appendChild(clock);
289+
} else if (col === 2) {
290+
const clock = instantiateClock(3, 15);
291+
// I had some problems with this observer, so I went with the setInterval method :/
292+
// const intObs = new IntersectionObserver(
293+
// (entries) => {
294+
// for (const e of entries) {
295+
// if (e.boundingClientRect.x < 0 && !e.isIntersecting) console.log("NAY");
296+
// }
297+
// },
298+
// {
299+
// root: tracks[idx].parentElement,
300+
// }
301+
// );
302+
// intObs.observe(clock);
303+
tracks[idx].appendChild(clock);
304+
305+
const intervId = setInterval(() => {
306+
const bounds = clock.getBoundingClientRect();
307+
if (bounds.right < 0) {
308+
clearInterval(intervId);
309+
decreaseHP();
310+
}
311+
}, 16);
312+
} else {
313+
tracks[idx].appendChild(empty.cloneNode());
314+
}
315+
}
316+
});
317+
318+
for (const t of tracks) {
319+
const initial = `translateX(calc(100vw + ${Math.random()} * 10rem))`;
320+
t.style.transform = initial;
321+
322+
const bounds = t.getClientRects();
323+
const anim = [
324+
{ transform: initial },
325+
{ transform: `translateX(${-Math.ceil(t.getBoundingClientRect().width)}px)` },
326+
];
327+
328+
const options = {
329+
duration: 25000 + Math.random() * 10000 - 5000,
330+
iterations: 1,
331+
delay: Math.random() * 5000,
332+
};
333+
334+
const animation = t.animate(anim, options);
335+
animation.addEventListener("finish", (e) => {
336+
// TODO: reinstantiate clocks in this track and run the animation again with randomized duration and delay
337+
// console.log(e);
338+
});
339+
}
340+
341+
function handleClockClick(instance) {
342+
const hour = instance.style.getPropertyValue("--hour");
343+
const minutes = instance.style.getPropertyValue("--minutes");
344+
if (hour == GOAL_H && minutes == GOAL_M) {
345+
increasePoints();
346+
// TODO: add animation on the clicked clock
347+
instance.parentNode.replaceChild(empty.cloneNode(), instance);
348+
} else {
349+
wrongSfx.play();
350+
instance.style.setProperty("--hover-shadow-color", "red");
351+
instance.addEventListener(
352+
"mouseleave",
353+
() => {
354+
instance.style.setProperty("--hover-shadow-color", "lime");
355+
},
356+
{ once: true }
357+
);
358+
}
359+
}
360+
</script>
361+
</body>
362+
</html>

0 commit comments

Comments
 (0)