155 Commits

Author SHA1 Message Date
36112f1ec8 less drop-shadows 2026-03-04 00:30:50 +01:00
b28b44507b white border 2026-03-03 14:52:31 +01:00
24b0e72eab more bounce 2026-03-03 14:51:59 +01:00
ab105bf485 settings reorder 2026-03-01 21:29:21 +01:00
ea8dbff564 pseudo 3d 2026-03-01 21:29:10 +01:00
9a2989616a top side 2026-03-01 15:32:02 +01:00
d6006e657f ghost 2026-03-01 11:48:18 +01:00
ce758c1e92 update cleared line animation 2026-02-28 10:31:24 +01:00
8a0590f1b3 hsl 2026-02-23 23:21:11 +01:00
70caed8fb8 ridge 2026-02-23 18:15:31 +01:00
b6eeae15b9 pop is the new classic 2026-02-23 17:11:34 +01:00
45c0e090e5 less brilliant 2026-02-21 11:52:26 +01:00
73ec2015ba more more brilliant 2026-02-21 02:23:48 +01:00
91e1ea9d3a more brilliant 2026-02-20 21:02:52 +01:00
798ac21372 pause on fullscreen exit 2026-02-20 09:05:23 +01:00
2820c42ba8 cover 2026-02-16 00:20:54 +01:00
8e043b9e8c background position 2026-02-16 00:18:31 +01:00
136ea4156f fix hold glich; prefered theme pop 2026-02-16 00:07:32 +01:00
c01c6dcf58 new theme 2026-02-01 16:30:01 +01:00
25826565af bootstrap checkbox 2026-01-27 11:04:09 +01:00
56b906723c fullscreen checkbox 2026-01-27 08:56:28 +01:00
20b96da34b fix border 2026-01-23 08:22:58 +01:00
bd195f7da6 softer blink 2026-01-23 08:21:15 +01:00
06b444c37e more transparency 2026-01-23 00:56:55 +01:00
71945a7ade clearPiece 2026-01-23 00:54:17 +01:00
0956e3d6e0 don't close game over modal 2026-01-21 20:45:44 +01:00
cc4c477a10 classic locked-animation box-shadow 2026-01-21 20:36:47 +01:00
e196d931ca speed up css 2026-01-18 05:29:07 +01:00
a4117b782a locked-animation 2026-01-16 21:16:05 +01:00
3e3fd6d7f4 tweaks 2026-01-16 03:38:58 +01:00
50a4536994 100px 2026-01-16 03:27:09 +01:00
a4e210526f 3D cleared line animation 2026-01-16 03:23:26 +01:00
cb49d42266 3D cleared line animation 2026-01-16 02:31:13 +01:00
90b1251ebf restart hard dropped animation 2026-01-16 01:31:15 +01:00
26a4d113b5 no cleared line animation 2026-01-15 17:55:24 +01:00
b3d012f489 3d trail 2026-01-15 16:51:02 +01:00
021d67b877 less grab 2026-01-15 16:15:51 +01:00
3af40de841 scroll backward 2026-01-15 13:39:47 +01:00
2d700f1927 fix border translation 2026-01-12 08:45:19 +01:00
280ff0ef9f more light 2026-01-12 01:21:44 +01:00
8e5b45bf6f fix border translation 2026-01-12 01:19:21 +01:00
18977dfd1b more light 2026-01-12 00:23:34 +01:00
334e0e0178 tweaks 2026-01-11 22:38:06 +01:00
02a24ec1f2 border 2026-01-11 22:26:58 +01:00
0cb9fd4c27 more shadows 2026-01-11 18:07:00 +01:00
e21f9c7dfa text-shadow 2026-01-11 17:56:25 +01:00
baf5672de8 hold and next light 2026-01-11 00:55:54 +01:00
9ed62d1e79 hold and next light 2026-01-11 00:51:01 +01:00
2a5ce8faab more text-shadow 2026-01-10 03:49:30 +01:00
4f7d44de4b text-shadow 2026-01-10 03:28:11 +01:00
44e048624a top light 2026-01-10 02:30:12 +01:00
d7fa6c4fe5 Merge branch 'master' of https://git.malingrey.fr/adrien/quatuor 2026-01-10 02:18:05 +01:00
b5ece6f892 stats 2026-01-10 02:15:57 +01:00
181b6d28f6 stats 2026-01-10 02:01:10 +01:00
616b364d40 light! 2026-01-09 21:38:04 +01:00
e17517e2eb left always bright, right always dark 2026-01-09 08:15:50 +01:00
e4c0ba5719 floating ghost 2026-01-09 02:32:03 +01:00
a1028cb054 ghost desaturate 2026-01-09 02:28:41 +01:00
1babf41efe beautifuller 2026-01-09 02:17:45 +01:00
8d371a52ec 3D light 2026-01-09 01:45:22 +01:00
743e23d729 nearer 2026-01-08 22:43:50 +01:00
46ca9f92d3 change side color from orientation 2026-01-08 18:49:41 +01:00
6d0b93dfdb left face from right neighbour 2026-01-08 18:35:58 +01:00
97ca770641 left face on first then right face 2026-01-08 18:34:44 +01:00
3093d880aa border-radius: 2px; 2026-01-07 02:31:33 +01:00
fe77be0381 border-radius 2026-01-07 02:29:09 +01:00
a7fae60a1c 4 faced cubes 2026-01-07 02:18:31 +01:00
7927038fb2 universal? double backdrop-filters 2026-01-06 21:02:03 +01:00
bb74fbef45 Revert "universal? double backdrop-filters"
This reverts commit 994a7a7f04.
2026-01-06 17:26:45 +01:00
994a7a7f04 universal? double backdrop-filters 2026-01-06 11:57:25 +01:00
0c186ccd59 backdrop-filter bug 2026-01-06 09:14:37 +01:00
44d68d79cc grab cursor on all sceneDiv 2026-01-06 02:16:56 +01:00
52a7f37eb0 replace synthwave by new wave style 2026-01-06 02:12:32 +01:00
1f78b2dc10 bout à bout 2026-01-06 01:53:51 +01:00
491f5c021a corrections 2026-01-03 13:06:28 +01:00
c4d9621551 corrections 2026-01-03 05:17:20 +01:00
3d8bc0ce11 Merge branch '3729a3762aa6b1aa08a682aea48fb37ab5fe9bd5' 2026-01-03 05:06:08 +01:00
3f665d115a rename styles 2026-01-03 04:52:13 +01:00
d2d4a8e737 classic ghost 2026-01-03 04:42:32 +01:00
fb4755c870 Merge remote-tracking branch 'refs/remotes/malingrey.fr/master' 2026-01-03 03:05:04 +01:00
3729a3762a pop2 style 2026-01-03 02:55:03 +01:00
4b2cd0e5d2 change minimal ghost piece 2025-08-28 10:40:19 +02:00
08763c501e change classic ghost piece 2025-08-28 10:32:48 +02:00
bc5de2448d less blink 2025-08-28 02:32:22 +02:00
67a0f06d03 trail on soft drop 2025-08-28 02:30:17 +02:00
2f1ec2a6f7 white border 2025-08-28 02:17:10 +02:00
26aeb8f9a2 white border 2025-08-28 02:15:48 +02:00
051fc223e9 Add README 2025-05-21 10:22:42 +02:00
9feaa3c098 meta 2025-05-20 17:01:49 +02:00
3175e7b7ad mouse event only on sceneDiv 2025-04-24 20:41:33 +02:00
4281bf1735 grab 2025-04-21 05:03:50 +02:00
ff37cb7823 jpg background 2025-04-14 08:40:52 +02:00
2a8df78d22 background 2025-04-14 08:30:40 +02:00
344575bdc4 center background 2025-04-13 23:28:06 +02:00
2453b2f6b6 background 2025-04-13 17:19:29 +02:00
25a98bf42d little tweaks 2025-04-10 00:52:43 +02:00
022512a5e6 perspective 2025-04-09 08:29:19 +02:00
d56af40362 clear animation 2025-04-09 01:14:39 +02:00
f33723a786 ghost border 2025-04-09 00:57:53 +02:00
df95139650 ghost 2025-04-09 00:51:34 +02:00
d3b527570c radial gradient 2025-04-08 03:42:07 +02:00
a1722a700d floatting text 2025-04-08 03:30:01 +02:00
6cc8a9e645 fix zoom 2025-04-08 00:46:47 +02:00
9023252822 remove log 2025-04-08 00:29:00 +02:00
9bf3c0de0c zoom on wheel 2025-04-07 23:51:13 +02:00
09f4785ef4 3D 2025-04-07 23:37:24 +02:00
bf9554d917 no need block 2025-04-07 09:36:51 +02:00
c33d80facb add binaural (3D) style 2025-04-07 09:30:07 +02:00
9282f94956 add binaural (3D) style 2025-04-07 09:01:14 +02:00
fda289dc9c opéra 2024-10-25 00:10:47 +02:00
b6e58b41aa opera border 2024-10-21 09:05:13 +02:00
bd5c7dad3b remove wall sound 2024-10-21 08:57:13 +02:00
7faae294dc new theme 2024-10-21 01:09:18 +02:00
3b59534f90 colored ghost 2024-09-28 17:32:07 +02:00
ec08747066 more glowing 2024-08-18 02:49:01 +02:00
db8a9a3f74 pop colors 2024-08-17 13:01:48 +02:00
0ac36444c4 disabled pop 2024-08-08 01:35:57 +02:00
5d451db8f9 disable first held piece 2024-08-03 00:07:23 +02:00
ab023ec982 show disabled held piece 2024-08-02 23:24:39 +02:00
d75696fbc3 update pop theme 2024-05-28 21:26:40 +02:00
f97b5f0cf9 fix pop buffer zone 2024-05-18 04:54:47 +02:00
ffefb77f3f serviceWorker in html 2024-05-12 11:31:31 +02:00
21929261e4 shadow 2024-04-29 17:51:17 +02:00
58389623cc move service worker 2024-04-29 17:51:04 +02:00
04f6eaf5dc fix action timout on key up 2023-12-23 03:02:47 +01:00
d56a8a6b06 stripe line clear animation 2023-12-23 03:02:17 +01:00
e1992f4c1e rgba 2023-12-23 03:02:01 +01:00
27d7a0689d rgba 2023-12-23 03:01:43 +01:00
df62a40c2a update themes 2023-12-19 00:43:27 +01:00
2105296238 not mino border 2023-12-18 01:32:42 +01:00
05862b8587 remove double lines 2023-12-18 01:29:01 +01:00
6c1291833d new Pop theme 2023-12-18 01:24:57 +01:00
095e3f8dbe modified: css/pop.css 2023-12-18 00:52:15 +01:00
82929b6f3b fix I color 2023-12-18 00:26:11 +01:00
29661421a5 fix input onblur 2023-12-14 00:39:15 +01:00
d3a6f5d6d6 check for key duplicates 2023-12-13 23:14:28 +01:00
8af1801527 sometime wall sound reset 2023-12-11 23:43:56 +01:00
6d95acddd2 fix autorepeat and volume save 2023-12-11 23:14:26 +01:00
6062f6895b aliasing 2023-12-08 22:00:18 +01:00
b726e95bed center pieces in newt and hold tables 2023-12-08 21:21:20 +01:00
228fc56916 fix settings load 2023-12-08 12:12:15 +01:00
f39d677b62 align hold table and stats 2023-12-08 08:52:39 +01:00
fc21a1b12b change stylesheet for chrome & firefox 2023-12-08 02:13:33 +01:00
072661ff5a ghost 2023-12-08 00:54:46 +01:00
f93f3c71af format 2023-12-08 00:50:56 +01:00
ed4d9b82c9 improve electro style 2023-12-08 00:05:17 +01:00
b17db2ffd4 prevent capslock 2023-12-07 23:36:06 +01:00
9f8c38e6bf update electro style 2023-12-07 22:41:28 +01:00
262a26940c this.form.querySelectorAll("[name]") 2023-12-07 22:36:00 +01:00
7996e4a7ae fix hard drop animation 2023-12-07 21:44:30 +01:00
a0af16d0dd add transparency 2023-12-07 21:44:21 +01:00
9547e585ca format 2023-12-07 21:44:03 +01:00
4d0d51e20d move KEY_NAMES to interface.js 2023-12-07 21:43:50 +01:00
e7246c0a5d fix styleSheet change on chrome 2023-12-07 21:29:48 +01:00
50502be1d1 little changes 2023-12-07 21:29:29 +01:00
18 changed files with 1128 additions and 384 deletions

5
README.md Normal file
View File

@@ -0,0 +1,5 @@
# Quatuor
Falling blocks
![screenshot](https://git.malingrey.fr/adrien/quatuor/raw/branch/master/thumbnail.png)

View File

@@ -1,118 +1,119 @@
.minoes-table {
filter:
drop-shadow(-1px -1px 0 white)
drop-shadow( 1px 1px 0 white);
}
.minoes-table tr {
z-index: calc(100 - var(--row));
position: sticky;
}
tr.matrix td:not(.mino) {
border-left: none;
border-bottom: none;
border-right: 1px solid #30303003;
border-top: 1px solid #30303003;
}
.mino { .mino {
background: radial-gradient( --color: hsl(var(--hue), var(--saturation), 40%);
ellipse 140% 66% at 122% 88%, --light: hsl(var(--hue), calc(0.66 * var(--saturation)), 84%);
var(--background-color) 100%, --top: hsl(var(--hue), calc(0.6 * var(--saturation)), 68%);
var(--frontier-color) 105%, background-color: var(--color);
var(--light-color) 130% background-image:
radial-gradient(
ellipse 22% 8% at 25% 22%,
#ffffff66,
#ffffff33 40%,
transparent 70%
),
radial-gradient(
ellipse 140% 85% at 50% -15%,
var(--light) 0%,
#ffffff77 40%,
#00000005 55%
),
radial-gradient(
ellipse 120% 220% at 50% 140%,
var(--light) 0%,
var(--color) 55%,
#00000066 95%
); );
border: 4px solid; border: 4px ridge var(--color);
padding: 0; border-top-color: var(--light);
opacity: 100%; border-radius: 3px;
border-radius: 1px; box-shadow:
inset 2px 0 4px rgba(0,0,0,.06),
inset -2px 0 4px rgba(0,0,0,.12),
0 -4px 0 var(--top);
filter: saturate(1.1) contrast(1.05);
} }
.I.mino { .I {
--background-color: #009FdA; --hue: 193;
--frontier-color: #43e7fd; --saturation: 100%;
--light-color: #afeff9;
border-top-color: #7cf2fd;
border-left-color: #2ed5e5;
border-right-color: #00b8ca;
border-bottom-color: #00a4b0;
} }
.J.mino { .J {
--background-color: #2E00FB; --hue: 215;
--frontier-color: #7054fb; --saturation: 100%;
--light-color: #b8b4ff;
border-top-color: #4985fd;
border-left-color: #2f36ea;
border-right-color: #0006ca;
border-bottom-color: #00009d;
} }
.L.mino { .L {
--background-color: #FF7900; --hue: 25;
--frontier-color: #fe9551; --saturation: 100%;
--light-color: #fdd0b7;
border-top-color: #fd9f6b;
border-left-color: #e76d28;
border-right-color: #e74f00;
border-bottom-color: #c54800;
} }
.O.mino { .O {
--background-color: #FeCB00; --hue: 42;
--frontier-color: #fce15c; --saturation: 100%;
--light-color: #ffedac;;
border-top-color: #ffe364;
border-left-color: #e7ba23;
border-right-color: #e3a707;
border-bottom-color: #ca9501;
} }
.S.mino { .S {
--background-color: #67EE12; --hue: 95;
--frontier-color: #93f85a; --saturation: 100%;
--light-color: #C8FBA8;
border-top-color: #a4fc6d;
border-left-color: #5ee82b;
border-right-color: #35db00;
border-bottom-color: #1cbc02;
} }
.T.mino { .T {
--background-color: #B000FE; --hue: 300;
--frontier-color: #c541fc; --saturation: 56%;
--light-color: #edb2ff;
border-top-color: #d380ff;
border-left-color: #b42deb;
border-right-color: #8000cd;
border-bottom-color: #6e019a;
} }
.Z.mino { .Z {
--background-color: #ed2939; --hue: 357;
--frontier-color: #fe6483; --saturation: 84%;
--light-color: #ffb8c5;
border-top-color: #fd718d;
border-left-color: #e62250;
border-right-color: #e20332;
border-bottom-color: #ad1936;
} }
.ghost.mino { .ghost {
background: rgba(0, 0, 0, 10%) !important; border: 3px solid #fff1;
border: 3px solid rgba(128, 128, 128, 25%) !important; padding: 2px;
box-shadow: -2px -2px 6px rgba(128, 128, 128, 25%), background-color: #fff1;
-2px 2px 6px rgba(128, 128, 128, 25%), background-clip: content-box;
2px -2px 6px rgba(128, 128, 128, 25%), background-image: none;
2px 2px 6px rgba(128, 128, 128, 25%); box-shadow: none;
} }
.moving.mino { @keyframes trail-animation {
filter: saturate(80%) brightness(150%);
}
.locking.mino {
filter: saturate(50%) brightness(200%);
box-shadow: -1px -1px 4px rgba(128, 128, 128, 25%),
-1px 1px 4px rgba(128, 128, 128, 25%),
1px -1px 4px rgba(128, 128, 128, 25%),
1px 1px 4px rgba(128, 128, 128, 25%);
}
@keyframes locked-animation {
from { from {
filter: saturate(50%) brightness(400%); background-color: #ceffff10;
box-shadow: -1px -1px 4px rgba(255, 255, 255, 25%), }
-1px 1px 4px rgba(255, 255, 255, 25%), to {
1px -1px 4px rgba(255, 255, 255, 25%), background-color: transparent;
1px 1px 4px rgba(255, 255, 255, 25%);
} }
} }
.locked.mino { @keyframes cleared-line-animation {
animation: locked-animation; from {
animation-duration: 0.2s; background-color: #fff6;
filter: saturate(50%) brightness(300%);
box-shadow: 0 0 0 #adb5bd66, 0 0 0 #adb5bd66;
}
60% {
box-shadow: -60px 0 2px #adb5bd33, 60px 0 2px #adb5bd33;
}
to {
background-color: transparent;
box-shadow: -100px 0 5px transparent, 100px 0 5px transparent;
}
} }

View File

@@ -1,6 +1,8 @@
:root { :root {
--cell-side: 24px; --cell-side: 25px;
--rX: -15;
--rY: 0;
--tZ: 25px;
} }
body { body {
@@ -8,18 +10,46 @@ body {
} }
@supports (backdrop-filter: blur()) { @supports (backdrop-filter: blur()) {
.modal::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
backdrop-filter: blur(2px);
}
.modal-content { .modal-content {
background-color: rgba(33, 37, 41, 30%); background-color: #212529b3;
backdrop-filter: blur(15px); backdrop-filter: blur(10px);
} }
} }
.card { .card {
background-color: rgb(37, 41, 45); background-color: #25292d;
}
#matrixCard {
background-image: radial-gradient(#222, #25292d)
}
.card-header {
text-shadow: 0 0 2px black;
} }
.modal-title { .modal-title {
text-shadow: 0 0 8px var(--bs-light); text-shadow: 0 0 8px var(--bs-light);
font-weight: 600;
}
#statsTable td,
#statsModal td {
text-align: right;
}
td#timeCell {
min-width: 10ch;
} }
.minoes-table { .minoes-table {
@@ -35,19 +65,12 @@ body {
margin-top: calc(-1 * var(--buffer-zone-rows) * var(--cell-side)); margin-top: calc(-1 * var(--buffer-zone-rows) * var(--cell-side));
} }
@keyframes hard-dropped-table-animation { @keyframes hard-dropped-table-animation {
from {
transform: translateY(0);
}
25% { 25% {
transform: translateY(2px); transform: translateY(3px);
}
to {
transform: translateY(0);
} }
} }
#matrixTable.hard-dropped-table-animation { .hard-dropped-table-animation {
animation: hard-dropped-table-animation .2s; animation: hard-dropped-table-animation .2s;
} }
@@ -66,30 +89,45 @@ td {
overflow: hidden; overflow: hidden;
width: var(--cell-side); width: var(--cell-side);
height: var(--cell-side); height: var(--cell-side);
box-sizing: border-box;
} }
@keyframes trail-animation { @keyframes trail-animation {
from { from {
background-color: rgb(206, 255, 255, 25%); background-color: #ceffff40;
filter: saturate(50%) brightness(300%);
} }
to { to {
background-color: transparent; background-color: transparent;
} }
} }
@keyframes locked-animation {
from {
filter: saturate(50%) brightness(400%);
}
}
.locked.mino {
animation: locked-animation;
animation-duration: 0.2s;
}
td.trail-animation { td.trail-animation {
animation: trail-animation ease-out .3s; animation: trail-animation ease-out .3s;
} }
@keyframes cleared-line-animation { @keyframes cleared-line-animation {
from { from {
background-color: rgb(206, 255, 255, 40%); background-color: white;
filter: saturate(50%) brightness(300%); filter: saturate(50%) brightness(300%);
box-shadow: -200px 0 5px white, 200px 0 5px white; box-shadow: 0 0 0 #adb5bd, 0 0 0 #adb5bd;
}
60% {
box-shadow: -60px 0 2px #adb5bd66, 60px 0 2px #adb5bd66;
} }
to { to {
background-color: transparent; background-color: transparent;
box-shadow: -100px 0 5px transparent, 100px 0 5px transparent;
} }
} }
@@ -97,13 +135,26 @@ tr.cleared-line-animation {
animation: cleared-line-animation ease-out .3s; animation: cleared-line-animation ease-out .3s;
} }
#holdTable .J,
#holdTable .L,
#holdTable .S,
#holdTable .T,
#holdTable .Z,
#nextTable .J,
#nextTable .L,
#nextTable .S,
#nextTable .T,
#nextTable .Z {
transform: translateX(50%);
}
#messagesSpan { #messagesSpan {
position: absolute; position: absolute;
top: 5%; top: 5%;
left: 50%; left: 50%;
transform: translate(-50%, 0); transform: translate(-50%, 0);
color: rgba(255, 255, 255, 0.8); color: #fffc;
text-shadow: 1px 1px rgba(0, 0, 0, 0.8); text-shadow: 1px 1px #000c;
font-size: 3vmin; font-size: 3vmin;
text-align: center; text-align: center;
} }
@@ -212,3 +263,28 @@ tr.cleared-line-animation {
animation-timing-function: (0.4, 0, 0.6, 1); animation-timing-function: (0.4, 0, 0.6, 1);
animation-duration: 2s; animation-duration: 2s;
} }
#statsModal table {
border-collapse: collapse;
}
#statsModal th {
padding-left: 0;
padding-right: 0.5rem;
padding-bottom: 0.2rem;
border-left: 0.5rem solid transparent;
border-right: 0;
}
#statsModal td {
padding-left: 0.5rem;
padding-right: 0;
padding-bottom: 0.2rem;
border-left: 0;
border-right: 0.5rem solid transparent;
}
#statsModal tr:last-child th,
#statsModal tr:last-child td {
border-bottom: none;
}

View File

@@ -3,19 +3,30 @@ body {
background-size: cover; background-size: cover;
} }
.card { body[data-bs-theme="dark"] {
background-color: rgba(37, 41, 45, 30%); --bs-body-bg: #2125296b;
} }
.mino:not(.ghost):not(.locking) { .btn-dark {
--bs-btn-bg: #2125296b;
}
.card {
background-color: rgba(37, 41, 45, 40%);
}
tr.matrix td:not(.mino) {
border-left : none;
border-bottom: none;
}
.mino:not(.ghost):not(.locking):not(.disabled) {
padding: 1px; padding: 1px;
position: relative; position: relative;
z-index: 0; z-index: 0;
box-shadow: border-radius: 4px;
-1px -1px 4px rgba(128, 128, 128, 25%), background-color: rgba(128, 128, 128, 25%);
-1px 1px 4px rgba(128, 128, 128, 25%), box-shadow: 0px 0px 8px rgba(128, 128, 128, 75%);
1px -1px 4px rgba(128, 128, 128, 25%),
1px 1px 4px rgba(128, 128, 128, 25%);
} }
.mino:not(.ghost):not(.locking):before { .mino:not(.ghost):not(.locking):before {
@@ -23,7 +34,7 @@ body {
position: absolute; position: absolute;
z-index: -1; z-index: -1;
inset: 0; inset: 0;
margin: 1px; margin: 1px 1px 0px 0px;
padding: 2px; padding: 2px;
border-radius: 4px; border-radius: 4px;
--glint-x: calc(50% + 50% * (var(--piece-column) - var(--column))/10); --glint-x: calc(50% + 50% * (var(--piece-column) - var(--column))/10);
@@ -37,23 +48,24 @@ body {
linear-gradient(#fff 0 0); linear-gradient(#fff 0 0);
mask-mode: luminance; mask-mode: luminance;
mask-composite: intersect; mask-composite: intersect;
} }
.ghost.mino { .ghost.mino {
background: transparent; background: rgba(242, 255, 255, 10%);
border: 2px solid rgba(255, 255, 255, 0.3); border : 2px solid rgba(255, 255, 255, 0.3);
border-radius: 3px; border-radius: 3px;
box-shadow: box-shadow: 0px 0px 10px rgba(242, 255, 255, 75%);
-1px -1px 8px rgba(242, 255, 255, 32%),
-1px 1px 8px rgba(242, 255, 255, 32%),
1px -1px 8px rgba(242, 255, 255, 32%),
1px 1px 8px rgba(242, 255, 255, 32%);
} }
.moving.mino { .moving.mino {
border-width: 1px; box-shadow: 0px 0px 5px rgba(128, 128, 128, 75%);
background: rgba(186, 211, 255, 30%); }
border-color: rgba(242, 255, 255, 0.7);
.moving.mino:not(.locking) {
padding: 2px;
background: rgba(186, 211, 255, 30%);
border: none;
border-radius: 4px;
} }
.locking.mino { .locking.mino {
@@ -61,11 +73,17 @@ body {
background: rgba(186, 211, 255, 70%); background: rgba(186, 211, 255, 70%);
border-color: rgba(242, 255, 255, 0.7); border-color: rgba(242, 255, 255, 0.7);
border-radius: 4px; border-radius: 4px;
box-shadow: box-shadow: 0px 0px 10px rgba(242, 255, 255, 100%);
-1px -1px 8px rgba(186, 211, 255, 27%), }
-1px 1px 8px rgba(186, 211, 255, 27%),
1px -1px 8px rgba(186, 211, 255, 27%), .disabled.mino {
1px 1px 8px rgba(186, 211, 255, 27%); opacity: 60%;
position: relative;
}
.disabled.mino:before {
opacity: 50%;
box-shadow: none;
} }
@keyframes locked-animation { @keyframes locked-animation {
@@ -75,8 +93,3 @@ body {
border-color: white; border-color: white;
} }
} }
.locked.mino {
animation: locked-animation;
animation-duration: 0.2s;
}

View File

@@ -5,7 +5,12 @@
.minoes-table { .minoes-table {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
filter: drop-shadow(5px 8px 0 rgba(9, 9, 9, 22%)); filter:
drop-shadow(-2px 0 0 white)
drop-shadow(2px 0 0 white)
drop-shadow(0 -2px 0 white)
drop-shadow(0 2px 0 white)
drop-shadow(5px 8px 0 rgba(9, 9, 9, 22%));
} }
.minoes-table tr { .minoes-table tr {
@@ -32,47 +37,48 @@ tr.matrix td:not(.mino) {
width: inherit; width: inherit;
height: inherit; height: inherit;
display: block; display: block;
box-shadow: 0 -6px 0 var(--light-color); box-shadow: 0 -6px 0 var(--box-shadow-color);
} }
.I.mino { .I.mino {
--background-color: #42AFE1; --background-color: #42AFE1;
--light-color: #6CEAFF; --box-shadow-color: #6CEAFF;
} }
.J.mino { .J.mino {
--background-color: #1165B5; --background-color: #1165B5;
--light-color: #339BFF; --box-shadow-color: #339BFF;
} }
.L.mino { .L.mino {
--background-color: #F38927; --background-color: #F38927;
--light-color: #FFBA59; --box-shadow-color: #FFBA59;
} }
.O.mino { .O.mino {
--background-color: #F6D03C; --background-color: #F6D03C;
--light-color: #FFFF7F; --box-shadow-color: #FFFF7F;
} }
.S.mino { .S.mino {
--background-color: #51B84D; --background-color: #51B84D;
--light-color: #84F880; --box-shadow-color: #84F880;
} }
.T.mino { .T.mino {
--background-color: #9739A2; --background-color: #9739A2;
--light-color: #D958E9; --box-shadow-color: #D958E9;
} }
.Z.mino { .Z.mino {
--background-color: #EB4F65; --background-color: #EB4F65;
--light-color: #FF7F79; --box-shadow-color: #FF7F79;
} }
.ghost.mino { .ghost.mino {
opacity: 50%; opacity: 5%;
box-shadow: none; box-shadow: none;
transform: translateY(-6px);
} }
.moving.mino { .moving.mino {
@@ -80,7 +86,8 @@ tr.matrix td:not(.mino) {
} }
.locking.mino { .locking.mino {
filter: saturate(50%) brightness(130%); --background-color: white;
--box-shadow-color: #DDD;
} }
.locked.mino { .locked.mino {
@@ -88,6 +95,10 @@ tr.matrix td:not(.mino) {
animation-duration: 0.2s; animation-duration: 0.2s;
} }
.disabled.mino {
filter: brightness(50%) contrast(50%);
}
@keyframes locked-animation { @keyframes locked-animation {
from { from {
filter: saturate(50%) brightness(300%); filter: saturate(50%) brightness(300%);
@@ -102,3 +113,13 @@ tr.matrix td:not(.mino) {
background-color: transparent; background-color: transparent;
} }
} }
@keyframes trail-animation {
from {
background-color: #ceffff10;
filter: saturate(50%) brightness(110%);
}
to {
background-color: transparent;
}
}

148
css/opera.css Normal file
View File

@@ -0,0 +1,148 @@
body {
--bs-gutter-x: 0;
background: black !important;
}
#screenRow {
gap: 0 !important;
margin: 0;
text-transform: uppercase;
letter-spacing: .1em;
}
#screenRow {
--bs-gutter-x: 0;
}
.card {
background: black;
border: none;
border-radius: 0;
margin-bottom: 0.5em !important;
}
.card-header,
.card-header th{
background: transparent;
font-weight: 400 !important;
font-size: 1.3em;
border: none;
}
#screenRow .table {
--bs-border-width: 0;
}
#holdTable {
margin: 0 0 0 auto !important;
}
#holdTable,
#nextTable {
border-bottom: 2px solid white;
}
#statsTable {
margin-right: 1.5em;
}
#statsTable tr {
display: flex;
flex-flow: column;
align-items: center;
}
#statsTable td,
#statsTable th {
margin: 0;
padding: 0 !important;
width: auto;
height: auto;
text-transform: uppercase;
}
#statsTable th {
display: inline;
flex-flow: row;
font-size: 0.8em;
text-align: center;
width: 200%;
}
#statsTable td {
font-size: 1.3em;
font-weight: 600;
color: white;
}
td#timeCell {
text-align: center;
}
#matrixCard {
background: transparent;
border-top: none;
border-left: 4px solid white;
border-right: 4px solid white;
border-bottom: 4px solid white;
}
.mino {
padding: 0;
opacity: 100%;
border-width: 1px;
border-style: solid;
}
.I.mino {
background-color: #42AFE1;
border-color: #6CEAFF;
}
.J.mino {
background-color: #1165B5;
border-color: #339BFF;
}
.L.mino {
background-color: #F38927;
border-color: #FFBA59;
}
.O.mino {
background-color: #F6D03C;
border-color: #FFFF7F;
}
.S.mino {
background-color: #32ee3e;
border-color: #84F880;
}
.T.mino {
background-color: #9739A2;
border-color: #D958E9;
}
.Z.mino {
background-color: #EB4F65;
border-color: #FF7F79;
}
.ghost.mino {
background-color: #fff4;
border-color: #fff8;
}
.moving.mino {
filter: saturate(80%) brightness(150%);
}
.locking.mino {
filter: saturate(50%) brightness(200%);
}
.disabled.mino {
filter: brightness(50%) contrast(80%);
opacity: 70%;
}

View File

@@ -1,97 +0,0 @@
body {
background-image: url("pop/bg.png");
background-size: cover;
}
.card {
background-color: rgba(37, 41, 45, 50%);
}
@supports (backdrop-filter: blur()) {
.card {
background-color: rgba(33, 37, 41, 20%);
backdrop-filter: blur(3px);
}
}
tr.matrix td:not(.mino) {
border-left : 1px solid #222;
border-right : 1px solid #222;
border-top : 1px solid #111;
border-bottom: 1px solid #111;
}
.mino {
background: rgba(255, 255, 255, 33%);
border: 6px solid var(--color);
padding: 0;
opacity: 100%;
border-radius: 4px;
filter: blur(1px);
box-shadow: 0 0 10px var(--color);
}
.I.mino {
--color: #9bf6ff;
}
.J.mino {
--color: #a0c4ff;
}
.L.mino {
--color: #ffd6a5;
}
.O.mino {
--color: #fdffb6;
}
.T.mino {
--color: #bdb2ff;
}
.S.mino {
--color: #caffbf;
}
.Z.mino {
--color: #ffadad;
}
.ghost.mino {
filter: brightness(150%) blur(2px);
opacity: 50%;
}
.moving.mino {
filter: brightness(120%);
}
.locking.mino {
--color: white;
filter: blur(2px);
}
@keyframes locked-animation {
from {
background: white;
--color: white;
}
}
.locked.mino {
animation: locked-animation;
animation-duration: 0.2s;
}
@keyframes cleared-line-animation {
from {
background-color: rgb(206, 255, 255, 40%);
filter: saturate(50%) brightness(300%);
box-shadow: -200px 0 10px white, 200px 0 10px white;
}
to {
box-shadow: -200px 0 50px transparent, 200px 0 50px transparent;
}
}

View File

@@ -45,8 +45,11 @@
#statsTable, #statsTable,
.card, .card,
.card-header,
#messagesSpan { #messagesSpan {
font-family: "Early GameBoy", monospace; font-family: "Early GameBoy", monospace;
font-smooth: never;
-webkit-font-smoothing: none;
color: #254806; color: #254806;
text-shadow: -1px -1px 3px rgba(0, 0, 0, 40%), 1px 1px 1px rgba(0, 0, 0, 40%); text-shadow: -1px -1px 3px rgba(0, 0, 0, 40%), 1px 1px 1px rgba(0, 0, 0, 40%);
} }
@@ -76,7 +79,7 @@
text-shadow: -2px -2px #808302, -2px 2px #808302, 2px -2px #808302, 2px 2px #808302; text-shadow: -2px -2px #808302, -2px 2px #808302, 2px -2px #808302, 2px 2px #808302;
} }
td { .minoes-table td {
border: 0 !important; border: 0 !important;
} }
@@ -114,8 +117,8 @@ td {
} }
@keyframes blinker { @keyframes blinker {
50% { 35% {
opacity: 0; opacity: 50%;
} }
} }
@@ -123,7 +126,8 @@ td {
animation: blinker 0.08s step-start infinite; animation: blinker 0.08s step-start infinite;
} }
.ghost.mino { .ghost.mino,
.disabled.mino {
opacity: 50%; opacity: 50%;
} }
@@ -131,6 +135,10 @@ td {
animation: none; animation: none;
} }
.hard-dropped-table-animation {
animation: hard-dropped-table-animation steps(1) .2s;
}
@keyframes cleared-line-animation { @keyframes cleared-line-animation {
10%, 30%, 50%, 70%, 90% { 10%, 30%, 50%, 70%, 90% {
opacity: 0; opacity: 0;

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

370
css/stereo.css Normal file
View File

@@ -0,0 +1,370 @@
body {
background-image: url(stereo/bg.jpg),
radial-gradient(circle at center,
#39444f 0%,
#2c323b 25%,
#293036 28%,
#252b32 34%,
#242930 38%,
#1a1d22 52%,
#191c22 53%,
#151519 63%,
#141418 65%,
#0f0f12 74%,
#0a0c0d 100%);
background-repeat: space;
background-position: center;
background-size: cover;
}
#sceneDiv {
perspective: 500px;
cursor: grab;
}
#sceneDiv:active {
cursor: grabbing;
}
#sceneDiv * {
transform-style: preserve-3d;
}
#screenRow {
display: block;
transform: translateZ(var(--tZ)) rotateX(calc((var(--rX)) * 1deg)) rotateY(calc((var(--rY)) * 1deg));
}
#screenRow * {
display: block;
}
#screenRow .col {
display: inline-block !important;
width: max-content;
height: 100%;
vertical-align: top;
}
.card {
background: #36394180;
}
#matrixCard {
background-image: none;
}
#screenRow .card>* {
transform: translateZ(var(--cell-side));
}
#screenRow .card-header {
background-color: transparent;
border: none;
}
.card,
.card-header {
text-shadow:
calc(-0.3px * var(--rY)) calc(0.4px * var(--rX)) 5px #0008;
}
#holdTable .mino {
--row: 7;
--column: -5;
}
#nextTable .mino {
--row: 15;
--column: 15;
}
.minoes-table th,
.minoes-table td {
display: inline-block !important;
width: max-content;
}
.minoes-table tr {
width: max-content;
height: var(--cell-side);
}
#statsTable tr {
display: table;
width: 100%;
}
#statsTable th,
#statsTable td {
display: table-cell;
border: 0;
}
tr.matrix td:not(.mino) {
border: 0;
}
.minoes-table td {
width: var(--cell-side) !important;
height: var(--cell-side) !important;
overflow: visible;
}
.minoes-table .mino,
.minoes-table .mino::before,
.minoes-table .mino + :not(.mino)::before,
.minoes-table .mino::after {
--light-x: calc(-0.5 - var(--rY) / 30 - var(--column) / 10 + 1);
--light-y: calc(-0.5 + var(--rX) / 20 - var(--row) / 6 + 4);
--center-color: hsla(var(--h), var(--s), calc(var(--l) * var(--light) * 1.1), var(--a));
--edge-color: hsla(var(--h), var(--s), calc(var(--l) * (var(--light) * 0.9)), var(--a));
background: var(--center-color);
border-radius: 2px;
border: 2px outset var(--center-color);
}
.minoes-table .mino::before,
.minoes-table .mino + :not(.mino)::before,
.minoes-table .mino::after,
td.trail-animation::before,
td.trail-animation::after,
tr.cleared-line-animation td::before,
tr.cleared-line-animation td::after {
content: '';
position: absolute;
top: 0;
left: 0;
display: block;
width: var(--cell-side);
height: var(--cell-side);
}
/* Front face */
.minoes-table .mino,
td.trail-animation {
--light: calc(
1
+ (var(--light-y) * 0.3)
+ (var(--light-x) * 0.2)
);
--center-x: calc(35% + var(--light-x) * 10%);
--center-y: calc(35% + var(--light-y) * 10%);
background: radial-gradient(
circle at var(--center-x) var(--center-y),
var(--center-color) 15%,
var(--edge-color) 100%
);
box-shadow: 0 0 7px hsla(var(--h), var(--s), calc(var(--l) * var(--light) * 1.3), 20%);
}
/* Left face */
.minoes-table .mino::before,
td.trail-animation::before,
tr.cleared-line-animation td::before,
.left .minoes-table .mino + .mino::before {
--light: calc(
1.1
+ (var(--light-x) * -0.2)
+ (var(--light-y) * 0.15)
);
transform: translate3d(-1.5px, -1.5px, calc(-1 * var(--cell-side))) rotateY(-90deg);
transform-origin: left;
}
/* Right face */
.right .minoes-table .mino + .mino::before,
.minoes-table .mino + :not(.mino)::before,
.right td.trail-animation::before,
.right tr.cleared-line-animation td::before {
--light: calc(
0.85
+ (var(--light-x) * -0.2)
+ (var(--light-y) * -0.15)
);
--center-x: calc(65% + var(--light-x) * 10%);
--center-y: calc(35% + var(--light-y) * 10%);
filter: saturate(0.95);
transform: translate3d(0, 0, calc(-1 * var(--cell-side))) rotateY(-90deg);
transform-origin: left;
}
.right .minoes-table .mino:last-child::before {
transform: translate3d(-1.5px, -1.5px, calc(-1 * var(--cell-side))) rotateY(90deg) !important;
transform-origin: right !important;
}
/* Top face */
.minoes-table .mino::after,
td.trail-animation::after,
tr.cleared-line-animation td::after {
--light: calc(
1.5
+ (var(--light-y) * 0.2)
);
transform: translate3d(-1.5px, -1.5px, calc(-1 * var(--cell-side))) rotateX(90deg);
transform-origin: top;
}
/* Bottom face */
.bottom .minoes-table .mino::after,
.bottom td.trail-animation::after,
.bottom tr.cleared-line-animation td::after {
--light: calc(
1.1
+ (var(--light-y) * -0.3)
);
--center-x: calc(65% + var(--light-x) * 10%);
--center-y: calc(65% + var(--light-y) * 10%);
filter: saturate(0.95);
transform: translate3d(-1.5px, -1.5px, calc(-1 * var(--cell-side))) rotateX(-90deg);
transform-origin: bottom;
}
.J.mino, .J.mino + :not(.mino) { --h: 210deg; --s: 78%; --l: 52%; --a: 0.75; }
.L.mino, .L.mino + :not(.mino) { --h: 28deg; --s: 85%; --l: 52%; --a: 0.75; }
.O.mino, .O.mino + :not(.mino) { --h: 48deg; --s: 88%; --l: 52%; --a: 0.75; }
.I.mino, .I.mino + :not(.mino) { --h: 200deg; --s: 70%; --l: 52%; --a: 0.75; }
.S.mino, .S.mino + :not(.mino) { --h: 118deg; --s: 45%; --l: 52%; --a: 0.75; }
.T.mino, .T.mino + :not(.mino) { --h: 293deg; --s: 48%; --l: 52%; --a: 0.75; }
.Z.mino, .Z.mino + :not(.mino) { --h: 352deg; --s: 75%; --l: 52%; --a: 0.75; }
.ghost.mino, .ghost.mino + :not(.mino) { --h: 0deg; --s: 0%; --l: 55%; --a: 0.40; }
.locking.mino, .locking.mino + :not(.mino) { --h: 0deg; --s: 0%; --l: 92%; --a: 0.72; }
.disabled.mino, .disabled.mino + :not(.mino) { --h: 0deg; --s: 0%; --l: 45%; --a: 0.72; }
#holdTable .J + :not(.mino),
#holdTable .L + :not(.mino),
#holdTable .S + :not(.mino),
#holdTable .T + :not(.mino),
#holdTable .Z + :not(.mino),
#nextTable .J + :not(.mino),
#nextTable .L + :not(.mino),
#nextTable .S + :not(.mino),
#nextTable .T + :not(.mino),
#nextTable .Z + :not(.mino) {
transform: translateX(50%);
}
@keyframes trail-animation {
from {
background-color: hsla(180, 100%, 100%, 0.1);
border-color: hsla(180, 100%, 100%, 0.1);
}
to {
background-color: transparent;
border-color: transparent;
}
}
td.trail-animation::before,
td.trail-animation::after {
animation: trail-animation ease-out .3s;
}
@keyframes locked-animation {
from {
--h: 0deg; --s: 0%; --l: 100%; --a: 1;
box-shadow: 0 0 10px hsla(180, 100%, 100%, 0.2);
}
}
.locked.mino::before,
.locked.mino::after {
animation: locked-animation;
animation-duration: .2s;
}
@keyframes cleared-line-animation {
from {
background-color: white !important;
box-shadow: 0 0 0 white;
}
to {
background-color: #fff0;
box-shadow: 0 0 100px transparent;
}
}
tr.cleared-line-animation td::before,
tr.cleared-line-animation td::after {
animation: cleared-line-animation ease-out .3s;
}
@keyframes show-level-animation {
from {
opacity: 1;
transform: translateY(200%);
}
50% {
transform: translateY(0) scaleY(1);
line-height: var(--bs-body-line-height);
}
to {
opacity: 1;
transform: translateY(-100%) scaleY(0);
line-height: 0;
}
}
@keyframes zoom-in-animation {
from {
opacity: 1;
transform: scale3d(0.3, 0.3, 0.3);
line-height: var(--bs-body-line-height);
}
30% {
transform: scale3d(1, 1, 1);
}
80% {
transform: scale3d(1, 1, 1);
line-height: var(--bs-body-line-height);
}
to {
opacity: 1;
transform: scale3d(1.5, 0, 1);
line-height: 0;
}
}
@keyframes rotate-in-animation {
0% {
opacity: 1;
transform: rotate(200deg);
line-height: var(--bs-body-line-height);
}
30% {
transform: translateZ(0);
transform: scale3d(1, 1, 1);
}
80% {
transform: scale3d(1, 1, 1);
line-height: var(--bs-body-line-height);
}
to {
opacity: 1;
transform: scale3d(1.5, 0, 1);
line-height: 0;
}
}
@keyframes game-over-animation {
from {
opacity: 1;
transform: translateY(200%);
}
to {
opacity: 1;
transform: translateY(0) scaleY(1);
line-height: var(--bs-body-line-height);
}
}

BIN
css/stereo/bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

76
css/synthwave.css Normal file
View File

@@ -0,0 +1,76 @@
body {
background-image: url(synthwave/bg.png);
background-size: cover;
}
body[data-bs-theme="dark"] {
--bs-body-bg: #2125296b;
}
.btn-dark {
--bs-btn-bg: #2125296b;
}
.card {
background: #25292d66;
}
#matrixCard {
background-image: radial-gradient(#2226, #25292d66);
}
.card,
#matrixCard {
background: repeating-linear-gradient(transparent, #111 1px);
backdrop-filter: blur(3px);
}
.minoes-table {
background: transparent;
}
.mino {
background: var(--color);
border: 3px solid var(--border);
box-shadow: 0 0 8px var(--border);
}
.I {
--color: #00eaf866;
--border: #00eaf5;
}
.J {
--color: #00a9f766;
--border: #00a9f7;
}
.L {
--color: #f9b60066;
--border: #f9b600;
}
.O {
--color: #e3e04966;
--border: #e3e049;
}
.S {
--color: #7bd59e66;
--border: #7bd59e;
}
.T {
--color: #d136e266;
--border: #d136e2;
}
.Z {
--color: #E67D8666;
--border: #E67D86;
}
.ghost {
--color: #fff4;
--border: #fff5;
}

View File

Before

Width:  |  Height:  |  Size: 444 KiB

After

Width:  |  Height:  |  Size: 444 KiB

View File

@@ -4,20 +4,32 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>Quatuor</title> <title>Quatuor</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark"> <meta name="color-scheme" content="dark">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.4/font/bootstrap-icons.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.4/font/bootstrap-icons.css">
<link rel="stylesheet" href="css/common.css"> <link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/classic.css" title="Classique"> <link rel="stylesheet" href="css/classic.css" title="Thème sélectionné" id="selectedStyleSheet">
<link rel="alternate stylesheet" href="css/minimal.css" title="Minimal"> <link rel="alternate stylesheet" href="css/classic.css" title="Classique">
<link rel="alternate stylesheet" href="css/electro.css" title="Électro"> <link rel="alternate stylesheet" href="css/minimal.css" title="Minimal">
<link rel="alternate stylesheet" href="css/pop.css" title="Pop"> <link rel="alternate stylesheet" href="css/electro.css" title="Électro">
<link rel="alternate stylesheet" href="css/retro.css" title="Rétro"> <link rel="alternate stylesheet" href="css/synthwave.css" title="Synthwave">
<link rel="apple-touch-icon" sizes="180x180" href="favicons/apple-touch-icon.png"> <link rel="alternate stylesheet" href="css/retro.css" title="Rétro">
<link rel="icon" type="image/png" sizes="32x32" href="favicons/T-2.png"> <link rel="alternate stylesheet" href="css/opera.css" title="Opéra">
<link rel="icon" type="image/png" sizes="16x16" href="favicons/favicon-16x16.png"> <link rel="alternate stylesheet" href="css/stereo.css" title="Stéréo">
<link rel="apple-touch-icon" sizes="180x180" href="favicons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="favicons/T-2.png">
<link rel="icon" type="image/png" sizes="16x16" href="favicons/favicon-16x16.png">
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<meta property="og:title" content="Quatuor"/>
<meta property="og:type" content="game"/>
<meta property="og:url" content="https://adrien.malingrey.fr/jeux/quatuor/"/>
<meta property="og:image" content="https://adrien.malingrey.fr/jeux/quatuor/thumbnail.png"/>
<meta property="og:image:width" content="288"/>
<meta property="og:image:height" content="288"/>
<meta property="og:description" content="Un jeu avec un quatuor de blocs qui tombent."/>
<meta property="og:locale" content="fr_FR"/>
<meta property="og:site_name" content="adrien.malingrey.fr"/>
</head> </head>
<body data-bs-theme="dark"> <body data-bs-theme="dark">
@@ -30,47 +42,52 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form name="settingsForm" class="needs-validation" novalidate> <form name="settingsForm" class="needs-validation" novalidate>
<fieldset id="keyBindFielset" class="row g-2 mb-3 align-items-center text-center"> <fieldset id="keyBindFielset" class="row g-2 mb-3 align-items-center text-center"><legend class="text-start">Commandes</legend>
<legend class="text-start">Commandes</legend>
<label for="moveLeftInput" title="Gauche" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-arrow-left"></i></label> <label for="moveLeftInput" title="Gauche" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-arrow-left"></i></label>
<div class="col-4"><input name="moveLeft" id="moveLeftInput" type="text" class="form-control text-center btn btn-dark" value="←" onclick="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div> <div class="col-4"><input name="moveLeft" id="moveLeftInput" type="text" class="form-control text-center" value="←" onfocus="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div>
<div class="col-4"><input name="moveRight" id="moveRightInput" type="text" class="form-control text-center btn btn-dark" value="→" onclick="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div> <div class="col-4"><input name="moveRight" id="moveRightInput" type="text" class="form-control text-center" value="→" onfocus="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div>
<label for="moveRightInput" title="Droite" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-arrow-right"></i></label> <label for="moveRightInput" title="Droite" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-arrow-right"></i></label>
<label for="rotateCounterclockwiseInput" title="Rotation anti-horaire" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-arrow-counterclockwise"></i></label> <label for="rotateCounterclockwiseInput" title="Rotation anti-horaire" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-arrow-counterclockwise"></i></label>
<div class="col-4"><input name="rotateCounterclockwise" id="rotateCounterclockwiseInput" type="text" class="form-control text-center btn btn-dark" value="w" onclick="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div> <div class="col-4"><input name="rotateCounterclockwise" id="rotateCounterclockwiseInput" type="text" class="form-control text-center" value="w" onfocus="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div>
<div class="col-4"><input name="rotateClockwise" id="rotateClockwiseInput" type="text" class="form-control text-center btn btn-dark" value="↑" onclick="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div> <div class="col-4"><input name="rotateClockwise" id="rotateClockwiseInput" type="text" class="form-control text-center" value="↑" onfocus="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div>
<label for="rotateClockwiseInput" title="Rotation horaire" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-arrow-clockwise"></i></label> <label for="rotateClockwiseInput" title="Rotation horaire" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-arrow-clockwise"></i></label>
<label for="softDropInput" title="Chute lente" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-arrow-down-short"></i></label> <label for="softDropInput" title="Chute lente" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-arrow-down-short"></i></label>
<div class="col-4"><input name="softDrop" id="softDropInput" type="text" class="form-control text-center btn btn-dark" value="↓" onclick="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div> <div class="col-4"><input name="softDrop" id="softDropInput" type="text" class="form-control text-center" value="↓" onfocus="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div>
<div class="col-4"><input name="hardDrop" id="hardDropInput" type="text" class="form-control text-center btn btn-dark" value="Espace" onclick="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div> <div class="col-4"><input name="hardDrop" id="hardDropInput" type="text" class="form-control text-center" value="Espace" onfocus="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div>
<label for="hardDropInput" title="Chute rapide" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-download"></i></label> <label for="hardDropInput" title="Chute rapide" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-download"></i></label>
<label for="holdInput" title="Échanger la pièce" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-arrow-left-right"></i></label> <label for="holdInput" title="Échanger la pièce" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-arrow-left-right"></i></label>
<div class="col-4"><input name="hold" id="holdInput" type="text" class="form-control text-center btn btn-dark" value="c" onclick="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div> <div class="col-4"><input name="hold" id="holdInput" type="text" class="form-control text-center" value="c" onfocus="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div>
<div class="col-4"><input name="pause" id="pauseInput" type="text" class="form-control text-center btn btn-dark" value="Échap." onclick="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div> <div class="col-4"><input name="pause" id="pauseInput" type="text" class="form-control text-center" value="Échap." onfocus="changeKey(this)" placeholder="Touche ?" title="Modifier la touche" required></div>
<label for="pauseInput" title="Pause" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-pause"></i></label> <label for="pauseInput" title="Pause" class="col-2 col-form-label d-flex align-items-center justify-content-center"><i class="bi bi-pause"></i></label>
</fieldset> </fieldset>
<fieldset id="autorepeatFieldset" class="row g-2 mb-3 align-items-center text-center"> <fieldset id="autorepeatFieldset" class="row g-2 mb-3 align-items-center text-center"><!--<legend class="text-start">Répétition automatique</legend>-->
<legend class="text-start">Répétition automatique</legend>
<label for="arrInput" class="col-2 col-form-label"><abbr title="Automatic Repeat Rate : période de répétition de l'action">ARR</abbr></label> <label for="arrInput" class="col-2 col-form-label"><abbr title="Automatic Repeat Rate : période de répétition de l'action">ARR</abbr></label>
<div class="col-4"><div class="input-group"><input name="arr" id="arrInput" type="number" class="form-control text-center" value="50" min="2" max="200" step="1"><div class="input-group-text">ms</div></div></div> <div class="col-4"><div class="input-group"><input name="arr" id="arrInput" type="number" class="form-control text-center" value="50" min="2" max="200" step="1"><div class="input-group-text">ms</div></div></div>
<div class="col-4"><div class="input-group"><input name="das" id="dasInput" type="number" class="form-control text-center" value="300" min="100" max="500" step="5"><div class="input-group-text">ms</div></div></div> <div class="col-4"><div class="input-group"><input name="das" id="dasInput" type="number" class="form-control text-center" value="300" min="100" max="500" step="5"><div class="input-group-text">ms</div></div></div>
<label for="dasInput" class="col-2 col-form-label"><abbr title="Delayed AutoShift : délai initial avant répétition">DAS</abbr></label> <label for="dasInput" class="col-2 col-form-label"><abbr title="Delayed AutoShift : délai initial avant répétition">DAS</abbr></label>
</fieldset> </fieldset>
<fieldset class="row g-2 mb-3 align-items-center text-center"> <fieldset class="row g-2 mb-3 align-items-center text-center"><legend class="text-start">Interface</legend>
<legend class="text-start">Interface</legend>
<label for="stylesheetSelect" class="col-2 col-form-label">Thème</label> <label for="stylesheetSelect" class="col-2 col-form-label">Thème</label>
<div class="col-4"><select name="stylesheet" id="stylesheetSelect" class="form-select" oninput="document.selectedStyleSheetSet=this.value"> <div class="col-4"><select name="stylesheet" id="stylesheetSelect" class="form-select" oninput="selectedStyleSheet.href = this.value">
<option selected>Classique</option> <option value="css/classic.css" selected>Classique</option>
<option>Minimal</option> <option value="css/minimal.css">Minimal</option>
<option>Pop</option> <option value="css/synthwave.css">Synthwave</option>
<option>Électro</option> <option value="css/electro.css">Électro</option>
<option>Rétro</option> <option value="css/retro.css">Rétro</option>
<option value="css/opera.css">Opéra</option>
<option value="css/stereo.css">Stéréo</option>
</select></div> </select></div>
<div class="col-4 d-flex align-items-baseline"><input id="sfxVolumeRange" class="form-range" type="range" min="0" max="1" step="any" value="0.7"></div> <div class="col-4">
<div class="form-check form-switch text-start">
<input id="fullscreenCheckbox" type="checkbox" role="switch" class="form-check-input" tabindex="0">
<label for="fullscreenCheckbox" class="form-check-label">Plein écran</label>
</div>
</div>
<div class="col-2"></div>
<label for="sfxVolumeRange" class="col-2 col-form-label">Volume</label> <label for="sfxVolumeRange" class="col-2 col-form-label">Volume</label>
<div class="col-4 d-flex align-items-baseline"><input name="sfxVolumeRange" id="sfxVolumeRange" class="form-range" type="range" min="0" max="1" step="any" value="0.7"></div>
</fieldset> </fieldset>
<fieldset class="row g-2 mb-3 align-items-center text-center"> <fieldset class="row g-2 mb-3 align-items-center text-center"><legend class="text-start">Partie</legend>
<legend class="text-start">Partie</legend>
<label for="levelInput" class="col-2 col-form-label text-center">Niveau</label> <label for="levelInput" class="col-2 col-form-label text-center">Niveau</label>
<div class="col-4"> <div class="col-4">
<input name="startLevel" id="levelInput" type="number" class="form-control text-center" value="1" min="1" max="15"> <input name="startLevel" id="levelInput" type="number" class="form-control text-center" value="1" min="1" max="15">
@@ -85,14 +102,14 @@
</div> </div>
</div> </div>
<div class="container-fluid d-flex vh-100 justify-content-center d-flex align-items-center"> <div id="sceneDiv" class="container-fluid d-flex vh-100 justify-content-center d-flex align-items-center">
<div id="screenRow" class="row row-cols-auto align-items-start gap-2"> <div id="screenRow" class="row row-cols-auto align-items-start gap-2">
<div class="col d-flex flex-column align-items-end"> <div class="col d-flex flex-column align-items-end">
<div class="card mb-4"> <div class="card shadow mb-4 w-100">
<div class="card-header text-center"><strong>HOLD</strong></div> <div class="card-header fw-bold text-uppercase text-center">Hold</div>
<div class="card-body p-0"> <div class="card-body p-0">
<table id="holdTable" class="minoes-table"> <table id="holdTable" class="minoes-table m-auto">
<tr class="buffer-zone"><td></td><td></td><td></td><td></td><td></td><td></td></tr> <tr class="buffer-zone"><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr class="buffer-zone"><td></td><td></td><td></td><td></td><td></td><td></td></tr> <tr class="buffer-zone"><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr class="buffer-zone"><td></td><td></td><td></td><td></td><td></td><td></td></tr> <tr class="buffer-zone"><td></td><td></td><td></td><td></td><td></td><td></td></tr>
@@ -103,11 +120,11 @@
</div> </div>
<div class="card shadow"> <div class="card shadow">
<table id="statsTable" class="table mb-0"> <table id="statsTable" class="table mb-0">
<tr><th>Score</th> <td id="scoreCell">0</td> </tr> <tr class="card-header fw-bold text-uppercase"><th>Score</th><td id="scoreCell">0</td> </tr>
<tr><th>Meilleur<br/>score</th><td id="highScoreCell"><script>document.write(Number(localStorage["highScore"]) || 0)</script></td></tr> <tr><th>Meilleur<br/>score</th><td id="highScoreCell"><script>document.write(Number(localStorage["highScore"]) || 0)</script></td></tr>
<tr><th>Niveau</th><td id="levelCell">0</td> </tr> <tr><th>Niveau</th> <td id="levelCell">0</td> </tr>
<tr><th>But</th> <td id="goalCell">0</td> </tr> <tr><th>But</th> <td id="goalCell">0</td> </tr>
<tr><th>Temps</th> <td id="timeCell">00:00</td> </tr> <tr><th>Temps</th> <td id="timeCell">00:00:00</td> </tr>
</table> </table>
</div> </div>
</div> </div>
@@ -149,7 +166,7 @@
<div class="col"> <div class="col">
<div class="card shadow"> <div class="card shadow">
<div class="card-header text-center"><strong>NEXT</strong></div> <div class="card-header fw-bold text-uppercase text-center">Next</div>
<div class="card-body p-0"> <div class="card-body p-0">
<table id="nextTable" class="minoes-table caption-top"> <table id="nextTable" class="minoes-table caption-top">
<tr class="buffer-zone"><td></td><td></td><td></td><td></td><td></td><td></td></tr> <tr class="buffer-zone"><td></td><td></td><td></td><td></td><td></td><td></td></tr>
@@ -176,21 +193,21 @@
</div> </div>
<div class="modal fade" id="statsModal" tabindex="-1"> <div class="modal fade" id="statsModal" data-bs-backdrop="static" tabindex="-1">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h2 class="modal-title w-100 text-center">Fin</h2> <h2 class="modal-title w-100 text-center">Fin</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <!-- <button type="button" class="btn-close" data-bs-dismiss="modal"></button> -->
</div> </div>
<div class="modal-body p-0"> <div class="modal-body p-0">
<table class="table mb-0"> <table class="table mb-0">
<tr><th>Score </th><td id="statsModalScoreCell"> </td><th>Niveau </th><td id="statsModalLevelCell"> </td></tr> <tr><th>Score</th> <td id="statsModalScoreCell"></td> <th>Quatuors</th> <td id="statsModalNbQuatuors"></td> </tr>
<tr><th>Meilleur score</th><td id="statsModalHighScoreCell"> </td><th>Temps </th><td id="statsModalTimeCell"> </td></tr> <tr><th>Meilleur score</th><td id="statsModalHighScoreCell"></td> <th>Pirouettes</th> <td id="statsModalNbTSpin"></td></td> </tr>
<tr><th>Lignes </th><td id="statsModaltotalClearedLines"></td><th>Lignes par minute </th><td id="statsModaltotalClearedLinesPM"></td></tr> <tr><th>Temps</th> <td id="statsModalTimeCell"></td> <th>Plus long combo</th> <td id="statsModalMaxCombo"></td> </tr>
<tr><th>Quatuors </th><td id="statsModalNbQuatuors"> </td><th>Plus long combo </th><td id="statsModalMaxCombo"> </td></tr> <tr><th>Niveau</th> <td id="statsModalLevelCell"></td> <th>Plus long bout à bout</th><td id="statsModalMaxB2B"></td> </tr>
<tr><th>Pirouettes </th><td id="statsModalNbTSpin"> </td><th>Plus long bout en bout</th><td id="statsModalMaxB2B"> </td></tr> <tr><th>Lignes</th> <td id="statsModaltotalClearedLines"></td><th>Lignes par minute</th> <td id="statsModaltotalClearedLinesPM"></td></tr>
</table> </table>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@@ -221,8 +238,6 @@
<script src="js/game_logic.js" language="Javascript" type="text/javascript"></script> <script src="js/game_logic.js" language="Javascript" type="text/javascript"></script>
<script src="js/interface.js" language="Javascript" type="text/javascript"></script> <script src="js/interface.js" language="Javascript" type="text/javascript"></script>
<script src="js/app.js" language="Javascript" type="text/javascript"></script> <script src="js/app.js" language="Javascript" type="text/javascript"></script>
<script> <script>navigator?.serviceWorker.register('js/service-worker.js')</script>
</script>
</body> </body>
</html> </html>

131
js/app.js
View File

@@ -1,23 +1,45 @@
let scheduler = new Scheduler() let scheduler = new Scheduler()
let settings = new Settings() let settings = new Settings()
let stats = new Stats() let stats = new Stats()
let holdQueue = new MinoesTable("holdTable") let holdQueue = new HoldQueue()
let matrix = new Matrix() let matrix = new Matrix()
let nextQueue = new NextQueue() let nextQueue = new NextQueue()
let playing = false let playing = false
let lastActionSucceded = true //let lastActionSucceded = true
let favicon let favicon
window.onload = function(event) { window.onload = function(event) {
document.selectedStyleSheetSet = stylesheetSelect.value document.selectedStyleSheetSet = selectedStyleSheet.title
selectedStyleSheet.href = stylesheetSelect.value
favicon = document.querySelector("link[rel~='icon']") favicon = document.querySelector("link[rel~='icon']")
fullscreenCheckbox.onchange = function() {
if (this.checked) {
document.documentElement.requestFullscreen()
} else {
document.exitFullscreen()
}
}
restart() restart()
} }
document.onfullscreenchange = function() {
if (document.fullscreenElement) {
fullscreenCheckbox.checked = true
} else {
fullscreenCheckbox.checked = false
if (playing) pauseSettings()
}
}
document.onfullscreenerror = function() {
fullscreenCheckbox.checked = false
}
function restart() { function restart() {
stats.modal.hide() stats.modal.hide()
holdQueue.init() holdQueue.init()
holdQueue.redraw()
stats.init() stats.init()
matrix.init() matrix.init()
nextQueue.init() nextQueue.init()
@@ -95,7 +117,8 @@ function ticktack() {
function generate(piece) { function generate(piece) {
matrix.piece = piece || nextQueue.shift() matrix.piece = piece || nextQueue.shift()
lastActionSucceded = true if (!piece && holdQueue.piece) holdQueue.drawPiece()
//lastActionSucceded = true
favicon.href = matrix.piece.favicon_href favicon.href = matrix.piece.favicon_href
if (matrix.piece.canMove(TRANSLATION.NONE)) { if (matrix.piece.canMove(TRANSLATION.NONE)) {
@@ -120,8 +143,10 @@ let playerActions = {
scheduler.clearTimeout(lockDown) scheduler.clearTimeout(lockDown)
playSound(hardDropSound) playSound(hardDropSound)
while (matrix.piece.move(TRANSLATION.DOWN, ROTATION.NONE, true)) stats.score += 2 while (matrix.piece.move(TRANSLATION.DOWN, ROTATION.NONE, true)) stats.score += 2
matrix.table.classList.add("hard-dropped-table-animation") matrixCard.classList.remove("hard-dropped-table-animation")
lockDown(true) matrixCard.offsetHeight;
matrixCard.classList.add("hard-dropped-table-animation") // restart animation
lockDown()
return true return true
}, },
@@ -130,13 +155,12 @@ let playerActions = {
scheduler.clearInterval(fall) scheduler.clearInterval(fall)
scheduler.clearTimeout(lockDown) scheduler.clearTimeout(lockDown)
let heldPiece = holdQueue.piece let piece = matrix.piece
matrix.piece.facing = FACING.NORTH piece.facing = FACING.NORTH
matrix.piece.locked = false piece.locked = false
holdQueue.piece = matrix.piece generate(holdQueue.piece)
holdQueue.piece.holdEnabled = false matrix.piece.holdEnabled = false
holdQueue.piece.locked = false holdQueue.piece = piece
generate(heldPiece)
} }
}, },
@@ -158,12 +182,13 @@ function onkeydown(event) {
if (!pressedKeys.has(event.key)) { if (!pressedKeys.has(event.key)) {
pressedKeys.add(event.key) pressedKeys.add(event.key)
action = settings.keyBind[event.key] action = settings.keyBind[event.key]
if (action()) { /*if (action()) {
lastActionSucceded = true lastActionSucceded = true
} else if (lastActionSucceded || !(action in REPEATABLE_ACTIONS)) { } else if (lastActionSucceded || !(action in REPEATABLE_ACTIONS)) {
wallSound.play() playSound(wallSound)
lastActionSucceded = false lastActionSucceded = false
} }*/
action()
if (REPEATABLE_ACTIONS.includes(action)) { if (REPEATABLE_ACTIONS.includes(action)) {
actionsQueue.unshift(action) actionsQueue.unshift(action)
scheduler.clearTimeout(repeat) scheduler.clearTimeout(repeat)
@@ -185,12 +210,13 @@ function repeat() {
function autorepeat() { function autorepeat() {
if (actionsQueue.length) { if (actionsQueue.length) {
if (actionsQueue[0]()) { /*if (actionsQueue[0]()) {
lastActionSucceded = true lastActionSucceded = true
} else if (lastActionSucceded) { } else if (lastActionSucceded) {
wallSound.play() wallSound.play()
lastActionSucceded = false lastActionSucceded = false
} }*/
actionsQueue[0]()
} }
else scheduler.clearInterval(autorepeat) else scheduler.clearInterval(autorepeat)
} }
@@ -202,9 +228,12 @@ function onkeyup(event) {
action = settings.keyBind[event.key] action = settings.keyBind[event.key]
if (actionsQueue.includes(action)) { if (actionsQueue.includes(action)) {
actionsQueue.splice(actionsQueue.indexOf(action), 1) actionsQueue.splice(actionsQueue.indexOf(action), 1)
if (!actionsQueue.length) { scheduler.clearTimeout(repeat)
scheduler.clearTimeout(repeat) scheduler.clearInterval(autorepeat)
scheduler.clearInterval(autorepeat) if (actionsQueue.length) {
if (actionsQueue[0] == playerActions.softDrop) scheduler.setInterval(autorepeat, settings.fallPeriod/20)
else scheduler.setTimeout(repeat, settings.das)
} else {
matrix.drawPiece() matrix.drawPiece()
} }
} }
@@ -215,15 +244,12 @@ function fall() {
matrix.piece.move(TRANSLATION.DOWN) matrix.piece.move(TRANSLATION.DOWN)
} }
function lockDown(hardDropped=false) { function lockDown() {
scheduler.clearTimeout(lockDown) scheduler.clearTimeout(lockDown)
scheduler.clearInterval(fall) scheduler.clearInterval(fall)
if (lastActionSucceded && !hardDropped) playSound(wallSound)
if (matrix.lock()) { if (matrix.lock()) {
let tSpin = matrix.piece.tSpin stats.lockDown(matrix.piece.tSpin, matrix.clearLines())
let nbClearedLines = matrix.clearLines()
stats.lockDown(nbClearedLines, tSpin)
generate() generate()
} else { } else {
@@ -240,7 +266,7 @@ messagesSpan.onanimationend = function(event) {
} }
function gameOver() { function gameOver() {
matrix.piece.locked = false matrix.piece.locked = true
matrix.drawPiece() matrix.drawPiece()
document.onkeydown = null document.onkeydown = null
@@ -257,7 +283,54 @@ window.onbeforeunload = function(event) {
if (playing) return false; if (playing) return false;
} }
// Play with 3D
let mousedown = false
let rX0 = -15
let rY0 = 0
let clientX0 = 0
let clientY0 = 0
if ('serviceWorker' in navigator) { sceneDiv.onmousedown = function(event) {
navigator.serviceWorker.register('service-worker.js'); mousedown = true
rX0 = parseInt(getComputedStyle(screenRow).getPropertyValue("--rX"))
dy0 = parseInt(getComputedStyle(screenRow).getPropertyValue("--rY"))
clientX0 = event.clientX
clientY0 = event.clientY
}
sceneDiv.onmousemove = function(event) {
if (mousedown) {
event.preventDefault()
event.stopPropagation()
rX = (rX0 - 0.5 * (event.clientY - clientY0)) % 360
screenRow.style.setProperty("--rX", rX)
if (rX >= 0) {
screenRow.classList.remove("top")
screenRow.classList.add("bottom")
} else {
screenRow.classList.add("top")
screenRow.classList.remove("bottom")
}
rY = (rY0 + 0.5 * (event.clientX - clientX0)) % 360
screenRow.style.setProperty("--rY", rY)
if (rY <= 0) {
screenRow.classList.remove("left")
screenRow.classList.add("right")
} else {
screenRow.classList.add("left")
screenRow.classList.remove("right")
}
}
}
sceneDiv.onmouseup = document.onmouseleave = function(event) {
mousedown = false
}
sceneDiv.onwheel = function(event) {
event.preventDefault()
event.stopPropagation()
let tZ = parseInt(getComputedStyle(screenRow).getPropertyValue("--tZ"))
tZ -= event.deltaY
screenRow.style.setProperty("--tZ", tZ + "px")
} }

View File

@@ -44,29 +44,6 @@ const FACING = {
WEST: 3, WEST: 3,
} }
const KEY_NAMES = new Proxy({
["ArrowLeft"] : "←",
["←"] : "ArrowLeft",
["ArrowRight"] : "→",
["→"] : "ArrowRight",
["ArrowUp"] : "↑",
["↑"] : "ArrowUp",
["ArrowDown"] : "↓",
["↓"] : "ArrowDown",
[" "] : "Espace",
["Espace"] : " ",
["Escape"] : "Échap.",
["Échap."] : "Escape",
["Backspace"] : "Ret. arrière",
["Ret. arrière"]: "Backspace",
["Enter"] : "Entrée",
["Entrée"] : "Enter",
}, {
get(obj, keyName) {
return keyName in obj? obj[keyName] : keyName
}
})
/* Customize Array to be use as position */ /* Customize Array to be use as position */
Object.defineProperties(Array.prototype, { Object.defineProperties(Array.prototype, {
"x": { "x": {
@@ -194,6 +171,20 @@ class MinoesTable {
MinoesTable.prototype.init_center = [2, 2] MinoesTable.prototype.init_center = [2, 2]
class HoldQueue extends MinoesTable {
constructor() {
super("holdTable")
}
drawPiece(piece=this.piece, className=piece.className) {
if (!matrix.piece.holdEnabled) {
className += " disabled"
}
super.drawPiece(piece, className)
}
}
class NextQueue extends MinoesTable { class NextQueue extends MinoesTable {
constructor() { constructor() {
super("nextTable") super("nextTable")
@@ -260,8 +251,12 @@ class Matrix extends MinoesTable {
if (piece.locked) className += " locking" if (piece.locked) className += " locking"
if (piece==this.piece && actionsQueue.length) className += " moving" if (piece==this.piece && actionsQueue.length) className += " moving"
super.drawPiece(piece, className) super.drawPiece(piece, className)
matrix.table.style.setProperty('--piece-column', this.piece.center.x) this.table.style.setProperty('--piece-column', this.piece.center.x)
matrix.table.style.setProperty('--piece-row', this.piece.center.y) this.table.style.setProperty('--piece-row', this.piece.center.y)
}
clearPiece(piece=this.piece, className="") {
super.drawPiece(piece, className)
} }
redraw() { redraw() {
@@ -337,7 +332,7 @@ class Tetromino {
let success = this.canMove(translation, rotation) let success = this.canMove(translation, rotation)
if (success) { if (success) {
scheduler.clearTimeout(lockDown) scheduler.clearTimeout(lockDown)
matrix.drawPiece(this, hardDropped? "trail-animation" : "") matrix.clearPiece(this, hardDropped? "trail-animation" : "")
this.center = success.center this.center = success.center
if (rotation) this.facing = success.facing if (rotation) this.facing = success.facing
this.lastRotation = rotation this.lastRotation = rotation
@@ -350,10 +345,10 @@ class Tetromino {
matrix.drawPiece() matrix.drawPiece()
return true return true
} else if (!hardDropped && translation == TRANSLATION.DOWN) { } else if (!hardDropped && translation == TRANSLATION.DOWN) {
this.locked = true this.locked = true
if (!scheduler.timeoutTasks.has(lockDown)) if (!scheduler.timeoutTasks.has(lockDown))
scheduler.setTimeout(lockDown, stats.lockDelay) scheduler.setTimeout(lockDown, stats.lockDelay)
matrix.drawPiece() matrix.drawPiece()
} }
} }

View File

@@ -1,3 +1,27 @@
const KEY_NAMES = new Proxy({
["ArrowLeft"] : "←",
["←"] : "ArrowLeft",
["ArrowRight"] : "→",
["→"] : "ArrowRight",
["ArrowUp"] : "↑",
["↑"] : "ArrowUp",
["ArrowDown"] : "↓",
["↓"] : "ArrowDown",
[" "] : "Espace",
["Espace"] : " ",
["Escape"] : "Échap.",
["Échap."] : "Escape",
["Backspace"] : "Ret. arrière",
["Ret. arrière"]: "Backspace",
["Enter"] : "Entrée",
["Entrée"] : "Enter",
}, {
get(target, key) {
return key in target? target[key] : key
}
})
class Settings { class Settings {
constructor() { constructor() {
this.form = settingsForm this.form = settingsForm
@@ -7,20 +31,15 @@ class Settings {
} }
load() { load() {
for (let element of settingsForm.elements) { this.form.querySelectorAll("[name]").forEach(element => {
if (element.name) { if (element.name in localStorage)
if (localStorage[element.name]) element.value = localStorage[element.name] element.value = localStorage[element.name]
} })
}
window.document.selectedStyleSheetSet = stylesheetSelect.value window.document.selectedStyleSheetSet = stylesheetSelect.value
} }
save() { save() {
for (let element of settingsForm.elements) { this.form.querySelectorAll("[name]").forEach(element => localStorage[element.name] = element.value)
if (element.name) {
localStorage[element.name] = element.value
}
}
} }
init() { init() {
@@ -49,7 +68,12 @@ class Settings {
this[input.name] = input.checked == true this[input.name] = input.checked == true
} }
this.keyBind = {} this.keyBind = new Proxy({}, {
get: (target, key) => target[key.toLowerCase()],
set: (target, key, value) => target[key.toLowerCase()] = value,
has: (target, key) => key.toLowerCase() in target
})
for (let actionName in playerActions) { for (let actionName in playerActions) {
this.keyBind[settings[actionName]] = playerActions[actionName] this.keyBind[settings[actionName]] = playerActions[actionName]
} }
@@ -59,13 +83,29 @@ class Settings {
function changeKey(input) { function changeKey(input) {
prevValue = input.value prevValue = input.value
input.value = "" input.value = ""
keyInputs = Array.from(input.form.querySelectorAll("input[type='text']"))
input.onkeydown = function (event) { input.onkeydown = function (event) {
event.preventDefault() event.preventDefault()
input.value = KEY_NAMES[event.key] input.value = KEY_NAMES[event.key]
input.blur() keyInputs.forEach(input => {
input.setCustomValidity("")
input.classList.remove("is-invalid")
})
keyInputs.sort((input1, input2) => {
if(input1.value == input2.value) {
input1.setCustomValidity("Touche déjà utilisée")
input1.classList.add("is-invalid")
input2.setCustomValidity("Touche déjà utilisée")
input2.classList.add("is-invalid")
}
return input1.value > input2.value
})
if (input.checkValidity()) {
input.blur()
}
} }
input.onblur = function (event) { input.onblur = function (event) {
if (input.value == "") input.value = prevValue if (!input.value) input.value = prevValue
input.onkeydown = null input.onkeydown = null
input.onblur = null input.onblur = null
} }
@@ -171,7 +211,7 @@ class Stats {
return new Date() - this.startTime return new Date() - this.startTime
} }
lockDown(nbClearedLines, tSpin) { lockDown(tSpin, nbClearedLines) {
this.totalClearedLines += nbClearedLines this.totalClearedLines += nbClearedLines
if (nbClearedLines == 4) this.nbQuatuors++ if (nbClearedLines == 4) this.nbQuatuors++
if (tSpin == T_SPIN.T_SPIN) this.nbTSpin++ if (tSpin == T_SPIN.T_SPIN) this.nbTSpin++
@@ -229,13 +269,13 @@ class Stats {
messagesSpan.addNewChild("div", { messagesSpan.addNewChild("div", {
className: "zoom-in-animation", className: "zoom-in-animation",
style: "animation-delay: .4s", style: "animation-delay: .4s",
innerHTML: `BOUT EN BOUT<br/>${b2bScore}` innerHTML: `BOUT À BOUT<br/>${b2bScore}`
}) })
} else { } else {
messagesSpan.addNewChild("div", { messagesSpan.addNewChild("div", {
className: "zoom-in-animation", className: "zoom-in-animation",
style: "animation-delay: .4s", style: "animation-delay: .4s",
innerHTML: `BOUT EN BOUT x${this.b2b}<br/>${b2bScore}` innerHTML: `BOUT À BOUT x${this.b2b}<br/>${b2bScore}`
}) })
} }
this.score += b2bScore this.score += b2bScore
@@ -245,7 +285,7 @@ class Stats {
messagesSpan.addNewChild("div", { messagesSpan.addNewChild("div", {
className: "zoom-in-animation", className: "zoom-in-animation",
style: "animation-delay: .4s", style: "animation-delay: .4s",
innerHTML: `FIN DU BOUT EN BOUT` innerHTML: `FIN DU BOUT À BOUT`
}) })
} }
this.b2b = -1 this.b2b = -1

View File

@@ -16,7 +16,7 @@ Copyright 2015, 2019, 2020 Google LLC. All Rights Reserved.
const OFFLINE_VERSION = 1; const OFFLINE_VERSION = 1;
const CACHE_NAME = "offline"; const CACHE_NAME = "offline";
// Customize this with a different URL if needed. // Customize this with a different URL if needed.
const OFFLINE_URL = "index.html"; const OFFLINE_URL = "../index.html";
self.addEventListener("install", (event) => { self.addEventListener("install", (event) => {
event.waitUntil( event.waitUntil(