22 Commits

Author SHA1 Message Date
b8b743871e defaults, round 2026-03-11 18:10:20 +01:00
018d623143 fix space in url 2026-03-11 16:18:32 +01:00
54cb17fb88 use https://you.have.fail/tetrioplus/ skins 2026-03-11 15:05:34 +01:00
7bdb9524ad sanitize input 2026-03-10 03:23:30 +01:00
923ec9f95d improve jstris skin 2026-03-10 02:06:05 +01:00
1d9a521595 escapeMarkup 2026-03-09 21:49:02 +01:00
3bb9f302ed improve jstris skin 2026-03-09 21:03:45 +01:00
4bac3face1 use https://konsola5.github.io/jstris-customization-database/ 2026-03-09 19:06:48 +01:00
b2601e9c24 tweaks 2026-03-09 01:50:18 +01:00
89b08832d1 ghost opacity 2026-03-08 05:35:43 +01:00
7ed61e135b support for jstris skin as well 2026-03-08 05:28:16 +01:00
a49c95f1aa no overlay 2026-03-08 04:08:48 +01:00
d0a2f1b7f9 use external skins 2026-03-07 23:13:45 +01:00
aed0601933 a bit of 3D 2026-03-05 21:14:34 +01:00
896b26684a sprites 2026-03-05 09:03:17 +01:00
bf18998caa fix paste error 2026-03-05 08:46:10 +01:00
f261f01684 less blinks 2026-03-05 02:01:38 +01:00
49e56427b1 optimization 2026-03-04 23:54:27 +01:00
b72276b76c neo-classic theme 2026-03-04 19:50:44 +01:00
d4d0dfaf30 merge and rename themes 2026-03-04 08:52:24 +01:00
5ba379d968 ridge 2026-03-04 00:17:41 +01:00
f6ebe87434 shadow 2026-03-03 16:50:32 +01:00
34 changed files with 15139 additions and 503 deletions

View File

@@ -1,5 +1,5 @@
:root { :root {
--cell-side: 25px; --cell-size: 25px;
--rX: -15; --rX: -15;
--rY: 0; --rY: 0;
--tZ: 25px; --tZ: 25px;
@@ -62,16 +62,16 @@ td#timeCell {
} }
#matrixTable { #matrixTable {
margin-top: calc(-1 * var(--buffer-zone-rows) * var(--cell-side)); margin-top: calc(-1 * var(--buffer-zone-rows) * var(--cell-size));
} }
@keyframes hard-dropped-table-animation { @keyframes hard-dropped-table-animation {
25% { 50% {
transform: translateY(3px); transform: translate(0, 5px);
} }
} }
.hard-dropped-table-animation { .hard-dropped-table-animation {
animation: hard-dropped-table-animation .2s; animation: hard-dropped-table-animation ease-in-out .2s;
} }
tr.buffer-zone td:not(.mino) { tr.buffer-zone td:not(.mino) {
@@ -87,8 +87,8 @@ tr.matrix td:not(.mino) {
td { td {
overflow: hidden; overflow: hidden;
width: var(--cell-side); width: var(--cell-size);
height: var(--cell-side); height: var(--cell-size);
box-sizing: border-box; box-sizing: border-box;
} }
@@ -157,6 +157,7 @@ tr.cleared-line-animation {
text-shadow: 1px 1px #000c; text-shadow: 1px 1px #000c;
font-size: 3vmin; font-size: 3vmin;
text-align: center; text-align: center;
z-index: 200;
} }
#messagesSpan div { #messagesSpan div {
@@ -288,3 +289,11 @@ tr.cleared-line-animation {
#statsModal tr:last-child td { #statsModal tr:last-child td {
border-bottom: none; border-bottom: none;
} }
.select2-dropdown {
width: min-content !important;
}
.select2-results__group {
display: inline-block;
}

204
css/_select2-dark.css Normal file
View File

@@ -0,0 +1,204 @@
body .select2-container--bootstrap-5 .select2-selection {
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
border: var(--bs-border-width) solid var(--bs-border-color);
}
body
.select2-container--bootstrap-5.select2-container--focus
.select2-selection,
body
.select2-container--bootstrap-5.select2-container--open
.select2-selection {
border-color: var(--bs-link-hover-color);
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
body
.select2-container--bootstrap-5
.select2-selection--multiple
.select2-selection__clear,
body
.select2-container--bootstrap-5
.select2-selection--single
.select2-selection__clear {
background: transparent
url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23676a6d'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e")
50%/0.75rem auto no-repeat;
}
body
.select2-container--bootstrap-5
.select2-selection--multiple
.select2-selection__clear:hover,
body
.select2-container--bootstrap-5
.select2-selection--single
.select2-selection__clear:hover {
background: transparent
url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e")
50%/0.75rem auto no-repeat;
}
body .select2-container--bootstrap-5 .select2-dropdown {
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
border-color: var(--bs-link-hover-color);
}
body
.select2-container--bootstrap-5
.select2-dropdown
.select2-search
.select2-search__field {
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
background-clip: padding-box;
border: var(--bs-border-width) solid var(--bs-border-color);
}
body
.select2-container--bootstrap-5
.select2-dropdown
.select2-search
.select2-search__field:focus {
border-color: var(--bs-link-hover-color);
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
body
.select2-container--bootstrap-5
.select2-dropdown
.select2-results__options
.select2-results__option.select2-results__message {
color: #6c757d;
}
body
.select2-container--bootstrap-5
.select2-dropdown
.select2-results__options
.select2-results__option.select2-results__option--highlighted {
color: var(--bs-body-color);
background-color: var(--bs-light-bg-subtle) !important;
}
body
.select2-container--bootstrap-5
.select2-dropdown
.select2-results__options
.select2-results__option.select2-results__option--selected,
body
.select2-container--bootstrap-5
.select2-dropdown
.select2-results__options
.select2-results__option[aria-selected="true"]:not(
.select2-results__option--highlighted
) {
color: var(--bs-body-color);
background-color: var(--bs-dark-bg-subtle);
}
body
.select2-container--bootstrap-5
.select2-dropdown
.select2-results__options
.select2-results__option.select2-results__option--disabled,
body
.select2-container--bootstrap-5
.select2-dropdown
.select2-results__options
.select2-results__option[aria-disabled="true"] {
color: #6c757d;
}
body
.select2-container--bootstrap-5
.select2-dropdown
.select2-results__options
.select2-results__option[role="group"]
.select2-results__group {
color: #6c757d;
}
body
.select2-container--bootstrap-5
.select2-selection--single
.select2-selection__rendered {
color: var(--bs-body-color);
}
body
.select2-container--bootstrap-5
.select2-selection--single
.select2-selection__rendered
.select2-selection__placeholder {
color: #6c757d;
}
body
.select2-container--bootstrap-5
.select2-selection--multiple
.select2-selection__rendered
.select2-selection__choice {
color: var(--bs-body-color);
border: var(--bs-border-width) solid var(--bs-border-color);
}
body
.select2-container--bootstrap-5.select2-container--disabled
.select2-selection,
body
.select2-container--bootstrap-5.select2-container--disabled.select2-container--focus
.select2-selection {
color: #6c757d;
background-color: var(--bs-light-bg-subtle);
border-color: var(--bs-dark-bg-subtle);
}
.is-valid + body .select2-container--bootstrap-5 .select2-selection,
.was-validated
select:valid
+ body
.select2-container--bootstrap-5
.select2-selection {
border-color: #198754;
}
.is-valid
+ body
.select2-container--bootstrap-5.select2-container--focus
.select2-selection,
.is-valid
+ body
.select2-container--bootstrap-5.select2-container--open
.select2-selection,
.was-validated
select:valid
+ body
.select2-container--bootstrap-5.select2-container--focus
.select2-selection,
.was-validated
select:valid
+ body
.select2-container--bootstrap-5.select2-container--open
.select2-selection {
border-color: #198754;
box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);
}
.is-invalid + body .select2-container--bootstrap-5 .select2-selection,
.was-validated
select:invalid
+ body
.select2-container--bootstrap-5
.select2-selection {
border-color: #dc3545;
}
.is-invalid
+ body
.select2-container--bootstrap-5.select2-container--focus
.select2-selection,
.is-invalid
+ body
.select2-container--bootstrap-5.select2-container--open
.select2-selection,
.was-validated
select:invalid
+ body
.select2-container--bootstrap-5.select2-container--focus
.select2-selection,
.was-validated
select:invalid
+ body
.select2-container--bootstrap-5.select2-container--open
.select2-selection {
border-color: #dc3545;
box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
}

View File

@@ -12,7 +12,6 @@ tr.matrix td:not(.mino) {
--color: hsl(var(--hue), var(--saturation), 40%); --color: hsl(var(--hue), var(--saturation), 40%);
--light: hsl(var(--hue), calc(0.66 * var(--saturation)), 84%); --light: hsl(var(--hue), calc(0.66 * var(--saturation)), 84%);
--top: hsl(var(--hue), calc(0.6 * var(--saturation)), 68%); --top: hsl(var(--hue), calc(0.6 * var(--saturation)), 68%);
--right: hsl(var(--hue), calc(0.4 * var(--saturation)), 20%);
background-color: var(--color); background-color: var(--color);
background-image: background-image:
radial-gradient( radial-gradient(
@@ -40,8 +39,7 @@ tr.matrix td:not(.mino) {
inset 2px 0 4px rgba(0,0,0,.06), inset 2px 0 4px rgba(0,0,0,.06),
inset -2px 0 4px rgba(0,0,0,.12), inset -2px 0 4px rgba(0,0,0,.12),
0 -4px 0 var(--top), 0 -4px 0 var(--top),
2px -3px 0 var(--right), 0 2px 4px #0008;
3px 2px 6px #0004;
filter: saturate(1.1) contrast(1.05); filter: saturate(1.1) contrast(1.05);
} }
@@ -82,7 +80,7 @@ tr.matrix td:not(.mino) {
.ghost { .ghost {
border: 3px solid #fff2; border: 3px solid #fff2;
padding: 2px; padding: 3px;
background-color: #fff2; background-color: #fff2;
background-clip: content-box; background-clip: content-box;
background-image: none; background-image: none;

61
css/heavy-metal.css Normal file
View File

@@ -0,0 +1,61 @@
#sceneDiv {
perspective: 500px;
}
#screenRow {
transform: rotateX(15deg);
}
#screenRow .card {
box-shadow:
0 10px 3px #25292d,
0 15px 0 var(--bs-card-border-color) !important;;
}
tr.matrix td:not(.mino) {
border-left: none;
border-bottom: none;
}
.mino {
background-color: hsl(var(--hue), 55%, 55%);
background-image: linear-gradient(30deg, #fff4, transparent);
border: 1px outset hsl(var(--hue), 55%, 45%);
border-radius: 2px;
box-shadow:
0 10px 3px hsl(var(--hue), 70%, 10%),
0 15px 0 hsla(var(--hue), 90%, 40%, 70%);
opacity: 80%;
backdrop-filter: blur(6px);
}
.I {
--hue: 197;
}
.J {
--hue: 217;
}
.L {
--hue: 36;
}
.O {
--hue: 60;
}
.S {
--hue: 113;
}
.T {
--hue: 268;
}
.Z {
--hue: 0;
}
.ghost {
opacity: 30%;
}

221
css/jazz.css Normal file
View File

@@ -0,0 +1,221 @@
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:first-of-type {
border-bottom: 3px solid white;
}
.card-header,
.card-header th{
background: transparent;
font-weight: 400 !important;
font-size: 1.3em;
border: none;
}
#screenRow .table {
--bs-border-width: 0;
}
.minoes-table {
display: flex;
flex-direction: column;
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 {
display: flex;
position: relative;
flex-direction: row;
z-index: calc(100 - var(--row));
}
#holdTable {
margin: 0 !important;
}
#statsTable {
width: 10rem;
}
#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: 3px solid white;
border-right: 3px solid white;
border-bottom: 3px solid white;
}
tr.matrix td:not(.mino) {
border: 0;
}
.minoes-table td {
display: inline-block;
width: var(--cell-size);
height: var(--cell-size);
padding: 0 !important;
z-index: calc(200 - var(--row));
}
.mino {
width: inherit;
height: inherit;
display: block;
padding: 0;
opacity: 100%;
border-width: 1px;
border-style: solid;
box-shadow: 0 -6px 0 var(--box-shadow-color);
}
.I.mino {
background-color: #42AFE1;
border-color: #6CEAFF;
--box-shadow-color: #6CEAFF;
}
.J.mino {
background-color: #1165B5;
border-color: #339BFF;
--box-shadow-color: #339BFF;
}
.L.mino {
background-color: #F38927;
border-color: #FFBA59;
--box-shadow-color: #FFBA59;
}
.O.mino {
background-color: #F6D03C;
border-color: #FFFF7F;
--box-shadow-color: #FFFF7F;
}
.S.mino {
background-color: #32ee3e;
border-color: #84F880;
--box-shadow-color: #84F880;
}
.T.mino {
background-color: #9739A2;
border-color: #D958E9;
--box-shadow-color: #D958E9;
}
.Z.mino {
background-color: #EB4F65;
border-color: #FF7F79;
--box-shadow-color: #FF7F79;
}
.ghost.mino {
background-color: #fff4;
border: none;
opacity: 5%;
box-shadow: none;
transform: translateY(-3px);
}
.moving.mino {
filter: saturate(80%) brightness(130%);
}
.locking.mino {
filter: saturate(20%) brightness(300%);
}
.locked.mino {
animation: locked-animation;
animation-duration: 0.2s;
}
.disabled.mino {
filter: brightness(50%) contrast(65%);
opacity: 70%;
}
@keyframes locked-animation {
from {
filter: saturate(50%) brightness(300%);
}
}
@keyframes cleared-line-animation {
from {
background-color: #eeeeee;
}
to {
background-color: transparent;
}
}
@keyframes trail-animation {
from {
background-color: #ceffff05;
filter: saturate(50%) brightness(110%);
}
to {
background-color: transparent;
}
}

118
css/jazz2.css Normal file
View File

@@ -0,0 +1,118 @@
.minoes-table {
display: flex;
flex-direction: column;
filter: drop-shadow(0 8px 1px #0008);
}
.minoes-table tr {
display: flex;
position: relative;
flex-direction: row;
z-index: calc(100 - var(--row));
}
tr.matrix td:not(.mino) {
border: none;
border-right: 1px solid #8884;
}
.minoes-table td {
display: inline-block;
width: var(--cell-size);
height: var(--cell-size);
padding: 0 !important;
z-index: calc(200 - var(--row));
}
.mino {
width: inherit;
height: inherit;
display: block;
padding: 0;
opacity: 100%;
border: none;
box-shadow: 0 -6px 0 var(--box-shadow-color);
}
.I.mino {
background-color: #0293b0;
--box-shadow-color: #05d2f2;
}
.J.mino {
background-color: #2c69c2;
--box-shadow-color: #7bb7f6;
}
.L.mino {
background-color: #fa9b23;
--box-shadow-color: #FFBA59;
}
.O.mino {
background-color: #f9d92c;
--box-shadow-color: #fff194;
}
.S.mino {
background-color: #01a493;
--box-shadow-color: #03e7d3;;
}
.T.mino {
background-color: #6830d1;
--box-shadow-color: #bb88fc;
}
.Z.mino {
background-color: #ee2b58;
--box-shadow-color: #fd4487;;
}
.ghost.mino {
background-color: #fff8;
box-shadow: none;
transform: blue(4px);
}
.moving.mino {
filter: saturate(80%) brightness(130%);
}
.locking.mino {
filter: saturate(20%) brightness(300%);
}
.locked.mino {
animation: locked-animation;
animation-duration: 0.2s;
}
.disabled.mino {
filter: brightness(50%) contrast(65%);
}
@keyframes locked-animation {
from {
filter: saturate(50%) brightness(300%);
}
}
@keyframes cleared-line-animation {
from {
background-color: #eeeeee;
}
to {
background-color: transparent;
}
}
@keyframes trail-animation {
from {
background-color: #ceffff05;
filter: saturate(50%) brightness(110%);
}
to {
background-color: transparent;
}
}

95
css/jstris-skin.css Normal file
View File

@@ -0,0 +1,95 @@
:root {
--cell-size: 24px;
--sprite-size: calc(100% / 8);
--skin-url: url(https://i.imgur.com/HqGYC5G.png);
}
.card {
background-color: #1c1c1c;
}
.card-body {
background-color: black;
}
#matrixCard {
background-image: url(jstris-skin/jstris-grid.png);
background-position: bottom;
background-repeat: no-repeat;
}
tr.matrix td:not(.mino) {
border: none;
}
.mino {
background-image: var(--skin-url);
background-size: cover;
background-repeat: no-repeat;
background-position-x: calc(var(--sprite-pos) * var(--sprite-size));
}
.I {
--sprite-pos: 6;
}
.J {
--sprite-pos: 7;
}
.L {
--sprite-pos: 3;
}
.O {
--sprite-pos: 4;
}
.S {
--sprite-pos: 5;
}
.T {
--sprite-pos: 8;
}
.Z {
--sprite-pos: 2;
}
.ghost {
--sprite-pos: 1;
opacity: 50%;
}
.disabled {
--sprite-pos: 0;
}
.locking.mino {
filter: saturate(60%) brightness(180%);
}
#holdTable .mino,
#nextTable .mino {
box-shadow: 4px 4px 10px #0002;
}
.option {
display: inline-block;
height: var(--cell-size);
}
.result {
--nb-sprites: 9;
}
.select2-container:not(.select2-container--disabled) .selection {
--nb-sprites: 4;
--sprite-pos: 4;
width: calc(var(--nb-sprites) * var(--cell-size));
background-position-x: calc(-1 * var(--sprite-pos) * var(--cell-size));
background-image: var(--skin-url);
background-size: cover;
background-repeat: no-repeat;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -26,8 +26,8 @@ tr.matrix td:not(.mino) {
.minoes-table td { .minoes-table td {
display: inline-block; display: inline-block;
width: var(--cell-side); width: var(--cell-size);
height: var(--cell-side); height: var(--cell-size);
padding: 0 !important; padding: 0 !important;
z-index: calc(200 - var(--row)); z-index: calc(200 - var(--row));
} }

89
css/neo-classic.css Normal file
View File

@@ -0,0 +1,89 @@
.minoes-table tr {
z-index: calc(100 - var(--row));
position: sticky;
}
tr.matrix td:not(.mino) {
border-left: none;
border-bottom: none;
}
.mino {
--color: hsl(var(--hue), var(--saturation), 60%);
--dark: hsl(var(--hue), var(--saturation), 28%);
--light: hsl(var(--hue), calc(0.66 * var(--saturation)), 90%);
--border: hsl(var(--hue), var(--saturation), 40%);
--top: hsl(var(--hue), calc(0.6 * var(--saturation)), 75%);
position: relative;
background-color: var(--dark);
background-image: radial-gradient(
ellipse 200% 120% at 50% 75%,
#fff8 10%,
var(--dark) 28%,
#fff8 38%,
var(--dark) 48%
);
border: 1px outset var(--border);
border-radius: 3px;
box-shadow:
inset 3px 0 4px rgba(0,0,0,.06),
inset -3px 0 4px rgba(0,0,0,.12),
0 -3px 0 var(--top),
0 2px 2px #0004;
}
.mino:after {
--size: calc(var(--cell-size) - 12px);
position: absolute;
content: "";
box-sizing: content-box;
width: var(--size);
height: var(--size);
opacity: 40%;
background: var(--color);
left: 3px;
top: 3px;
border: 2px solid var(--border);
border-top-color: white;
}
.I {
--hue: 193;
--saturation: 100%;
}
.J {
--hue: 215;
--saturation: 100%;
}
.L {
--hue: 25;
--saturation: 100%;
}
.O {
--hue: 42;
--saturation: 100%;
}
.S {
--hue: 95;
--saturation: 100%;
}
.T {
--hue: 300;
--saturation: 56%;
}
.Z {
--hue: 357;
--saturation: 84%;
}
.ghost {
--hue: 0;
--saturation: 0%;
opacity: 30%;
}

View File

@@ -1,14 +1,15 @@
:root { :root {
--cell-side: 20px; --cell-size: 20px;
--sprite-size: 40px;
} }
@font-face { @font-face {
font-family: "Early GameBoy"; font-family: "Early GameBoy";
src: url("retro/Early GameBoy.ttf"); src: url("old-school/Early GameBoy.ttf");
} }
#screenRow { #screenRow {
background-image: url("retro/bg.png"); background-image: url("old-school/bg.png");
background-size: 10px; background-size: 10px;
padding: 40px 20px; padding: 40px 20px;
border: 3px inset black; border: 3px inset black;
@@ -22,7 +23,7 @@
.card { .card {
background: #8D8E04; background: #8D8E04;
border-radius: 0; border-radius: 0;
border-image-source: url("retro/border-sm.png"); border-image-source: url("old-school/border-sm.png");
border-image-slice: 25; border-image-slice: 25;
border-image-width: 13px; border-image-width: 13px;
border-image-repeat: repeat; border-image-repeat: repeat;
@@ -37,7 +38,7 @@
#matrixCard { #matrixCard {
background: #808302; background: #808302;
border-image-source: url("retro/border-lg.png"); border-image-source: url("old-school/border-lg.png");
border-image-slice: 30; border-image-slice: 30;
border-image-width: 15px; border-image-width: 15px;
border-image-outset: 15px; border-image-outset: 15px;
@@ -46,7 +47,7 @@
#statsTable, #statsTable,
.card, .card,
.card-header, .card-header,
#messagesSpan { #messagesSpan div {
font-family: "Early GameBoy", monospace; font-family: "Early GameBoy", monospace;
font-smooth: never; font-smooth: never;
-webkit-font-smoothing: none; -webkit-font-smoothing: none;
@@ -85,35 +86,37 @@
.mino { .mino {
box-shadow: -2px -2px 5px rgba(0, 0, 0, 40%), 1px 1px 2px rgba(0, 0, 0, 40%); box-shadow: -2px -2px 5px rgba(0, 0, 0, 40%), 1px 1px 2px rgba(0, 0, 0, 40%);
background-size: 100%; background-size: cover;
background-image: url("old-school/sprites.png");
background-position: calc(var(--sprite-pos) * var(--sprite-size)) 0px;
} }
.I.mino { .I.mino {
background-image: url("retro/I-mino.png") --sprite-pos: 0;
} }
.J.mino { .J.mino {
background-image: url("retro/J-mino.png") --sprite-pos: 1;
} }
.L.mino { .L.mino {
background-image: url("retro/L-mino.png") --sprite-pos: 2;
} }
.O.mino { .O.mino {
background-image: url("retro/O-mino.png") --sprite-pos: 3;
} }
.S.mino { .S.mino {
background-image: url("retro/S-mino.png") --sprite-pos: 4;
} }
.T.mino { .T.mino {
background-image: url("retro/T-mino.png") --sprite-pos: 5;
} }
.Z.mino { .Z.mino {
background-image: url("retro/Z-mino.png") --sprite-pos: 6;
} }
@keyframes blinker { @keyframes blinker {
@@ -123,7 +126,7 @@
} }
.locking.mino { .locking.mino {
animation: blinker 0.08s step-start infinite; filter: brightness(135%) contrast(70%);
} }
.ghost.mino, .ghost.mino,

View File

Before

Width:  |  Height:  |  Size: 870 B

After

Width:  |  Height:  |  Size: 870 B

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 298 KiB

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
css/old-school/sprites.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,3 +1,10 @@
:root {
--rbw: 4px;
--tl: calc(-1 * var(--rbw));
--cell-size-opposite: calc(-1 * var(--cell-size));
--t3d: translate3d(var(--tl), var(--tl), var(--cell-size-opposite));
}
body { body {
background-image: url(stereo/bg.jpg), background-image: url(stereo/bg.jpg),
radial-gradient(circle at center, radial-gradient(circle at center,
@@ -26,17 +33,24 @@ body {
cursor: grabbing; cursor: grabbing;
} }
#sceneDiv * {
transform-style: preserve-3d;
}
#screenRow { #screenRow {
--light-rX: calc(-1 * var(--rY) / 30);
--light-rY: calc(var(--rX) / 20);
display: block; display: block;
transform: translateZ(var(--tZ)) rotateX(calc((var(--rX)) * 1deg)) rotateY(calc((var(--rY)) * 1deg)); transform: translateZ(var(--tZ)) rotateX(calc((var(--rX)) * 1deg)) rotateY(calc((var(--rY)) * 1deg));
} }
#screenRow * { #sceneDiv,
#screenRow,
#screenRow .col,
#screenRow .card,
#screenRow .card-body,
.minoes-table,
.minoes-table tbody,
.minoes-table tr,
.minoes-table td {
display: block; display: block;
transform-style: preserve-3d;
} }
#screenRow .col { #screenRow .col {
@@ -55,7 +69,7 @@ body {
} }
#screenRow .card>* { #screenRow .card>* {
transform: translateZ(var(--cell-side)); transform: translateZ(var(--cell-size));
} }
#screenRow .card-header { #screenRow .card-header {
@@ -87,7 +101,7 @@ body {
.minoes-table tr { .minoes-table tr {
width: max-content; width: max-content;
height: var(--cell-side); height: var(--cell-size);
} }
#statsTable tr { #statsTable tr {
@@ -106,27 +120,30 @@ tr.matrix td:not(.mino) {
} }
.minoes-table td { .minoes-table td {
width: var(--cell-side) !important; width: var(--cell-size);
height: var(--cell-side) !important; height: var(--cell-size);
overflow: visible; overflow: visible;
position: relative;
} }
.minoes-table .mino, .mino,
.minoes-table .mino::before, .mino::before,
.minoes-table .mino + :not(.mino)::before, .mino + :not(.mino)::before,
.minoes-table .mino::after { .mino::after {
--light-x: calc(-0.5 - var(--rY) / 30 - var(--column) / 10 + 1); --light-pX: calc(0.5 - var(--column) / 10);
--light-y: calc(-0.5 + var(--rX) / 20 - var(--row) / 6 + 4); --light-pY: calc(3.5 - var(--row) / 6);
--light-x: calc(var(--light-rX) + var(--light-pX));
--light-y: calc(var(--light-rY) + var(--light-pY));
--center-color: hsla(var(--h), var(--s), calc(var(--l) * var(--light) * 1.1), var(--a)); --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)); --edge-color: hsla(var(--h), var(--s), calc(var(--l) * (var(--light) * 0.9)), var(--a));
background: var(--center-color); background: var(--center-color);
border-radius: 2px; border-radius: 4px;
border: 2px outset var(--center-color); border: 2px outset var(--center-color);
} }
.minoes-table .mino::before, .mino::before,
.minoes-table .mino + :not(.mino)::before, .mino + :not(.mino)::before,
.minoes-table .mino::after, .mino::after,
td.trail-animation::before, td.trail-animation::before,
td.trail-animation::after, td.trail-animation::after,
tr.cleared-line-animation td::before, tr.cleared-line-animation td::before,
@@ -136,12 +153,12 @@ tr.cleared-line-animation td::after {
top: 0; top: 0;
left: 0; left: 0;
display: block; display: block;
width: var(--cell-side); width: var(--cell-size);
height: var(--cell-side); height: var(--cell-size);
} }
/* Front face */ /* Front face */
.minoes-table .mino, .mino,
td.trail-animation { td.trail-animation {
--light: calc( --light: calc(
1 1
@@ -155,26 +172,28 @@ td.trail-animation {
var(--center-color) 15%, var(--center-color) 15%,
var(--edge-color) 100% var(--edge-color) 100%
); );
box-shadow: 0 0 7px hsla(var(--h), var(--s), calc(var(--l) * var(--light) * 1.3), 20%); box-shadow: 0 0 6px hsla(var(--h), var(--s), calc(var(--l) * var(--light) * 1.3), 40%);
border-style: ridge;
border-width: var(--rbw);
} }
/* Left face */ /* Left face */
.minoes-table .mino::before, .mino::before,
td.trail-animation::before, td.trail-animation::before,
tr.cleared-line-animation td::before, tr.cleared-line-animation td::before,
.left .minoes-table .mino + .mino::before { .mino + .mino::before {
--light: calc( --light: calc(
1.1 1.1
+ (var(--light-x) * -0.2) + (var(--light-x) * -0.2)
+ (var(--light-y) * 0.15) + (var(--light-y) * 0.15)
); );
transform: translate3d(-1.5px, -1.5px, calc(-1 * var(--cell-side))) rotateY(-90deg); transform: var(--t3d) rotateY(-90deg);
transform-origin: left; transform-origin: left;
} }
/* Right face */ /* Right face */
.right .minoes-table .mino + .mino::before, .mino + :not(.mino)::before,
.minoes-table .mino + :not(.mino)::before, .right .mino + .mino::before,
.right td.trail-animation::before, .right td.trail-animation::before,
.right tr.cleared-line-animation td::before { .right tr.cleared-line-animation td::before {
--light: calc( --light: calc(
@@ -185,29 +204,29 @@ tr.cleared-line-animation td::before,
--center-x: calc(65% + var(--light-x) * 10%); --center-x: calc(65% + var(--light-x) * 10%);
--center-y: calc(35% + var(--light-y) * 10%); --center-y: calc(35% + var(--light-y) * 10%);
filter: saturate(0.95); filter: saturate(0.95);
transform: translate3d(0, 0, calc(-1 * var(--cell-side))) rotateY(-90deg); transform: translate3d(0, 0, var(--cell-size-opposite)) rotateY(-90deg);
transform-origin: left; transform-origin: left;
} }
.right .minoes-table .mino:last-child::before { .right .mino:last-child::before {
transform: translate3d(-1.5px, -1.5px, calc(-1 * var(--cell-side))) rotateY(90deg) !important; transform: var(--t3d) rotateY(90deg) !important;
transform-origin: right !important; transform-origin: right !important;
} }
/* Top face */ /* Top face */
.minoes-table .mino::after, .mino::after,
td.trail-animation::after, td.trail-animation::after,
tr.cleared-line-animation td::after { tr.cleared-line-animation td::after {
--light: calc( --light: calc(
1.5 1.5
+ (var(--light-y) * 0.2) + (var(--light-y) * 0.2)
); );
transform: translate3d(-1.5px, -1.5px, calc(-1 * var(--cell-side))) rotateX(90deg); transform: var(--t3d) rotateX(90deg);
transform-origin: top; transform-origin: top;
} }
/* Bottom face */ /* Bottom face */
.bottom .minoes-table .mino::after, .bottom .mino::after,
.bottom td.trail-animation::after, .bottom td.trail-animation::after,
.bottom tr.cleared-line-animation td::after { .bottom tr.cleared-line-animation td::after {
--light: calc( --light: calc(
@@ -217,17 +236,17 @@ tr.cleared-line-animation td::after {
--center-x: calc(65% + var(--light-x) * 10%); --center-x: calc(65% + var(--light-x) * 10%);
--center-y: calc(65% + var(--light-y) * 10%); --center-y: calc(65% + var(--light-y) * 10%);
filter: saturate(0.95); filter: saturate(0.95);
transform: translate3d(-1.5px, -1.5px, calc(-1 * var(--cell-side))) rotateX(-90deg); transform: var(--t3d) rotateX(-90deg);
transform-origin: bottom; transform-origin: bottom;
} }
.J.mino, .J.mino + :not(.mino) { --h: 210deg; --s: 78%; --l: 52%; --a: 0.75; } .J, .J + :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; } .L, .L + :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; } .O, .O + :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; } .I, .I + :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; } .S, .S + :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; } .T, .T + :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; } .Z, .Z + :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; } .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; } .locking.mino, .locking.mino + :not(.mino) { --h: 0deg; --s: 0%; --l: 92%; --a: 0.72; }
@@ -340,8 +359,7 @@ tr.cleared-line-animation td::after {
} }
30% { 30% {
transform: translateZ(0); transform: translateZ(0) scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
} }
80% { 80% {

43
css/svg.css Normal file
View File

@@ -0,0 +1,43 @@
tr.matrix td:not(.mino),
tr.matrix td.mino {
border-left: none;
border-right: 1px solid #333;
border-top: 1px solid #333;
border-bottom: none;
}
.mino {
background-size: cover
}
.I {
background-image: url(https://www.svgrepo.com/show/395902/blue-square.svg)
}
.J {
background-image: url(https://www.svgrepo.com/show/395944/brown-square.svg);
}
.L {
background-image: url(https://www.svgrepo.com/show/397681/orange-square.svg);
}
.O {
background-image: url(https://www.svgrepo.com/show/398716/yellow-square.svg);
}
.S {
background-image: url(https://www.svgrepo.com/show/396582/green-square.svg);
}
.T {
background-image: url(https://www.svgrepo.com/show/398143/purple-square.svg);
}
.Z {
background-image: url(https://www.svgrepo.com/show/397699/red-square.svg);
}
.ghost {
background-image: url(https://www.svgrepo.com/show/398610/white-large-square.svg);
}

View File

@@ -1,5 +1,5 @@
body { body {
background-image: url(synthwave/bg.png); background-image: url(synthwave/wtCSusF.jpeg);
background-size: cover; background-size: cover;
} }
@@ -21,18 +21,31 @@ body[data-bs-theme="dark"] {
.card, .card,
#matrixCard { #matrixCard {
background: repeating-linear-gradient(transparent, #111 1px); background: repeating-linear-gradient(transparent, transparent 2px, #1114 2px, #1114 5px);
backdrop-filter: blur(3px); backdrop-filter: blur(3px);
} }
.minoes-table { .minoes-table {
background: transparent; background: transparent;
border-spacing: 2px;
}
#matrixTable {
margin-top: calc(-1 * var(--buffer-zone-rows) * var(--cell-size) - 8px);
}
#matrixTable td:not(.mino) {
border-color: #8881;
border-top: none;
border-bottom: none;
} }
.mino { .mino {
background: var(--color); background: var(--color);
border: 3px solid var(--border); border: 3px solid var(--border);
border-radius: 6px;
box-shadow: 0 0 8px var(--border); box-shadow: 0 0 8px var(--border);
filter: blur(0.5px);
} }
.I { .I {

BIN
css/synthwave/wtCSusF.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 KiB

84
css/tetrio-skin.css Normal file
View File

@@ -0,0 +1,84 @@
:root {
--cell-size: 30px;
--sprite-size: round(100% / 11, 1px);
--skin-url: url(https://you.have.fail/ed/at/tetrioplus/data/content/skin/Haley%20Halcyon/tetrio_gameboy_plus.svg);
}
tr.matrix td:not(.mino) {
border-left: none;
border-bottom: none;
}
.mino {
background-image: var(--skin-url);
background-size: cover;
background-repeat: no-repeat;
background-position-x: calc(var(--sprite-pos) * var(--sprite-size));
}
.I {
--sprite-pos: 4;
}
.J {
--sprite-pos: 5;
}
.L {
--sprite-pos: 1;
}
.O {
--sprite-pos: 2;
}
.S {
--sprite-pos: 3;
}
.T {
--sprite-pos: 6;
}
.Z {
--sprite-pos: 0;
}
.ghost {
--sprite-pos: 7;
opacity: 40%;
}
.disabled {
--sprite-pos: 8;
}
.locking.mino {
filter: saturate(60%) brightness(180%);
}
#holdTable .mino,
#nextTable .mino {
box-shadow: 4px 4px 10px #0002;
}
.option {
--cell-size: 24px;
display: inline-block;
height: var(--cell-size);
}
.result {
--nb-sprites: 9;
}
.select2-container:not(.select2-container--disabled) .selection {
--padded-cell-size: calc(var(--cell-size) + 1px);
--nb-sprites: 4;
--sprite-pos: 2;
width: calc(var(--nb-sprites) * var(--padded-cell-size) - 1px);
background-position-x: calc(-1 * var(--sprite-pos) * var(--padded-cell-size));
background-image: var(--skin-url);
background-size: cover;
background-repeat: no-repeat;
}

View File

@@ -1,5 +1,5 @@
<!doctype html> <!doctype html>
<html lang="fr"> <html lang="fr" data-bs-theme="dark">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -8,15 +8,23 @@
<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="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css"/>
<link rel="stylesheet" href="css/classic.css" title="Thème sélectionné" id="selectedStyleSheet"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" />
<link rel="stylesheet" href="css/_select2-dark.css">
<link rel="stylesheet" href="css/_common.css">
<link rel="stylesheet" href="css/jstris-skin.css" title="Thème sélectionné" id="selectedStyleSheet">
<link rel="alternate stylesheet" href="css/jstris-skin.css" title="Sample...">
<link rel="alternate stylesheet" href="css/classic.css" title="Classique"> <link rel="alternate stylesheet" href="css/classic.css" title="Classique">
<link rel="alternate stylesheet" href="css/minimal.css" title="Minimal"> <link rel="alternate stylesheet" href="css/neo-classic.css" title="Néo-classique">
<link rel="alternate stylesheet" href="css/electro.css" title="Électro"> <link rel="alternate stylesheet" href="css/electro.css" title="Électro">
<link rel="alternate stylesheet" href="css/synthwave.css" title="Synthwave"> <link rel="alternate stylesheet" href="css/synthwave.css" title="Synthwave">
<link rel="alternate stylesheet" href="css/retro.css" title="Rétro"> <link rel="alternate stylesheet" href="css/heavy-metal.css" title="Heavy metal">
<link rel="alternate stylesheet" href="css/opera.css" title="Opéra"> <link rel="alternate stylesheet" href="css/jazz.css" title="Jazz">
<link rel="alternate stylesheet" href="css/stereo.css" title="Stéréo"> <link rel="alternate stylesheet" href="css/old-school.css" title="Old School">
<link rel="alternate stylesheet" href="css/stereo.css" title="Stéréo (3D)">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.1.0-rc.0/js/select2.min.js"></script>
<link rel="apple-touch-icon" sizes="180x180" href="favicons/apple-touch-icon.png"> <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="32x32" href="favicons/T-2.png">
<link rel="icon" type="image/png" sizes="16x16" href="favicons/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="favicons/favicon-16x16.png">
@@ -27,12 +35,12 @@
<meta property="og:image" content="https://adrien.malingrey.fr/jeux/quatuor/thumbnail.png"/> <meta property="og:image" content="https://adrien.malingrey.fr/jeux/quatuor/thumbnail.png"/>
<meta property="og:image:width" content="288"/> <meta property="og:image:width" content="288"/>
<meta property="og:image:height" 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:description" content="Un jeu de quatuors de blocs qui tombent."/>
<meta property="og:locale" content="fr_FR"/> <meta property="og:locale" content="fr_FR"/>
<meta property="og:site_name" content="adrien.malingrey.fr"/> <meta property="og:site_name" content="adrien.malingrey.fr"/>
</head> </head>
<body data-bs-theme="dark"> <body>
<div class="modal fade" id="settingsModal" data-bs-backdrop="static" data-bs-keyboard="false"> <div class="modal fade" id="settingsModal" data-bs-backdrop="static" data-bs-keyboard="false">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
@@ -60,7 +68,8 @@
<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> <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"><!--<legend class="text-start">Répétition automatique</legend>--> <fieldset id="autorepeatFieldset" class="row g-2 mb-3 align-items-center text-center">
<!--<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>
@@ -68,15 +77,28 @@
</fieldset> </fieldset>
<fieldset class="row g-2 mb-3 align-items-center text-center"><legend class="text-start">Interface</legend> <fieldset class="row g-2 mb-3 align-items-center text-center"><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="selectedStyleSheet.href = this.value"> <div class="col-4">
<option value="css/classic.css" selected>Classique</option> <select name="stylesheet" id="stylesheetSelect" class="form-select">
<option value="css/minimal.css">Minimal</option> <option value="css/jstris-skin.css" selected>Sample JStris...</option>
<option value="css/tetrio-skin.css">Sample Tetr.io...</option>
<option value="css/classic.css">Classique</option>
<option value="css/neo-classic.css">Néo-classique</option>
<option value="css/synthwave.css">Synthwave</option> <option value="css/synthwave.css">Synthwave</option>
<option value="css/electro.css">Électro</option> <option value="css/electro.css">Électro</option>
<option value="css/retro.css">Rétro</option> <option value="css/heavy-metal.css">Heavy metal</option>
<option value="css/opera.css">Opéra</option> <option value="css/jazz.css">Jazz</option>
<option value="css/stereo.css">Stéréo</option> <option value="css/old-school.css">Old School</option>
</select></div> <option value="css/stereo.css">Stéréo (3D)</option>
</select>
</div>
<div class="col-4">
<select name="skinURL" id="skinURLSelect" class="form-select"
oninput="document.documentElement.style.setProperty('--skin-url', `url(${encodeURI(this.value)})`)">
</select>
</div>
<label for="skinURLSelect" class="col-2 col-form-label">Sample</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>
<div class="col-4"> <div class="col-4">
<div class="form-check form-switch text-start"> <div class="form-check form-switch text-start">
<input id="fullscreenCheckbox" type="checkbox" role="switch" class="form-check-input" tabindex="0"> <input id="fullscreenCheckbox" type="checkbox" role="switch" class="form-check-input" tabindex="0">
@@ -84,8 +106,6 @@
</div> </div>
</div> </div>
<div class="col-2"></div> <div class="col-2"></div>
<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"><legend class="text-start">Partie</legend> <fieldset class="row g-2 mb-3 align-items-center text-center"><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>
@@ -193,7 +213,7 @@
</div> </div>
<div class="modal fade" id="statsModal" data-bs-backdrop="static" tabindex="-1"> <div class="modal fade" id="statsModal" data-bs-backdrop="static" data-bs-keyboard="false" 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">
@@ -204,7 +224,7 @@
<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>Quatuors</th> <td id="statsModalNbQuatuors"></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>Pirouettes</th> <td id="statsModalNbTSpin"></td></td> </tr> <tr><th>Meilleur score</th><td id="statsModalHighScoreCell"></td> <th>Pirouettes</th> <td id="statsModalNbTSpin"></td> </tr>
<tr><th>Temps</th> <td id="statsModalTimeCell"></td> <th>Plus long combo</th> <td id="statsModalMaxCombo"></td> </tr> <tr><th>Temps</th> <td id="statsModalTimeCell"></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>Niveau</th> <td id="statsModalLevelCell"></td> <th>Plus long bout à 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> <tr><th>Lignes</th> <td id="statsModaltotalClearedLines"></td><th>Lignes par minute</th> <td id="statsModaltotalClearedLinesPM"></td></tr>
@@ -234,7 +254,6 @@
<audio id="quatuorSound" src="sounds/BRRDC1.wav" preload="auto" type="audio/wav"></audio> <audio id="quatuorSound" src="sounds/BRRDC1.wav" preload="auto" type="audio/wav"></audio>
</span> </span>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
<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>

438
js/app.js
View File

@@ -1,130 +1,104 @@
let scheduler = new Scheduler()
let settings = new Settings()
let stats = new Stats()
let holdQueue = new HoldQueue()
let matrix = new Matrix()
let nextQueue = new NextQueue()
let playing = false
//let lastActionSucceded = true
let favicon
window.onload = function(event) {
document.selectedStyleSheetSet = selectedStyleSheet.title
selectedStyleSheet.href = stylesheetSelect.value
favicon = document.querySelector("link[rel~='icon']")
fullscreenCheckbox.onchange = function() {
if (this.checked) {
document.documentElement.requestFullscreen()
} else {
document.exitFullscreen()
}
}
restart()
}
document.onfullscreenchange = function () { document.onfullscreenchange = function () {
if (document.fullscreenElement) { if (document.fullscreenElement) {
fullscreenCheckbox.checked = true fullscreenCheckbox.checked = true;
} else { } else {
fullscreenCheckbox.checked = false fullscreenCheckbox.checked = false;
if (playing) pauseSettings() if (playing) pauseSettings();
}
} }
};
document.onfullscreenerror = function () { document.onfullscreenerror = function () {
fullscreenCheckbox.checked = false fullscreenCheckbox.checked = false;
} };
function restart() { function restart() {
stats.modal.hide() stats.modal.hide();
holdQueue.init() holdQueue.init();
holdQueue.redraw() holdQueue.redraw();
stats.init() stats.init();
matrix.init() matrix.init();
nextQueue.init() nextQueue.init();
settings.init() settings.init();
pauseSettings() pauseSettings();
} }
function pauseSettings() { function pauseSettings() {
scheduler.clearInterval(fall) scheduler.clearInterval(fall);
scheduler.clearTimeout(lockDown) scheduler.clearTimeout(lockDown);
scheduler.clearTimeout(repeat) scheduler.clearTimeout(repeat);
scheduler.clearInterval(autorepeat) scheduler.clearInterval(autorepeat);
scheduler.clearInterval(ticktack) scheduler.clearInterval(ticktack);
stats.pauseTime = stats.time stats.pauseTime = stats.time;
document.onkeydown = null document.onkeydown = null;
settings.show() settings.show();
} }
function newGame(event) { function newGame(event) {
if (!settings.form.checkValidity()) { if (!settings.form.checkValidity()) {
event.preventDefault() event.preventDefault();
event.stopPropagation() event.stopPropagation();
settings.form.reportValidity() settings.form.reportValidity();
settings.form.classList.add('was-validated') settings.form.classList.add('was-validated');
} else { } else {
const audioContext = new AudioContext() const audioContext = new AudioContext();
for(const sound of document.getElementsByTagName("audio")) { for (const sound of document.getElementsByTagName('audio')) {
sound.preservesPitch = false sound.preservesPitch = false;
audioContext.createMediaElementSource(sound).connect(audioContext.destination) audioContext.createMediaElementSource(sound).connect(audioContext.destination);
} }
levelInput.name = "level" levelInput.name = 'level';
levelInput.disabled = true levelInput.disabled = true;
titleHeader.innerHTML = "PAUSE" titleHeader.innerHTML = 'PAUSE';
resumeButton.innerHTML = "Reprendre" resumeButton.innerHTML = 'Reprendre';
event.target.onsubmit = resume event.target.onsubmit = resume;
stats.level = levelInput.valueAsNumber stats.level = levelInput.valueAsNumber;
localStorage["startLevel"] = levelInput.value localStorage['startLevel'] = levelInput.value;
playing = true playing = true;
onblur = pauseSettings onblur = pauseSettings;
resume(event) resume(event);
} }
} }
function resume(event) { function resume(event) {
event.preventDefault() event.preventDefault();
event.stopPropagation() event.stopPropagation();
settings.form.reportValidity() settings.form.reportValidity();
settings.form.classList.add('was-validated') settings.form.classList.add('was-validated');
if (settings.form.checkValidity()) { if (settings.form.checkValidity()) {
for(const sound of document.getElementsByTagName("audio")) for (const sound of document.getElementsByTagName('audio'))
sound.volume = sfxVolumeRange.value sound.volume = sfxVolumeRange.value;
settings.modal.hide() settings.modal.hide();
settings.getInputs() settings.getInputs();
document.onkeydown = onkeydown document.onkeydown = onkeydown;
document.onkeyup = onkeyup document.onkeyup = onkeyup;
stats.time = stats.pauseTime stats.time = stats.pauseTime;
scheduler.setInterval(ticktack, 1000) scheduler.setInterval(ticktack, 1000);
if (matrix.piece) scheduler.setInterval(fall, stats.fallPeriod) if (matrix.piece) scheduler.setInterval(fall, stats.fallPeriod);
else generate() else generate();
} }
} }
function ticktack() { function ticktack() {
timeCell.innerText = stats.timeFormat.format(stats.time) timeCell.innerText = stats.timeFormat.format(stats.time);
} }
function generate(piece) { function generate(piece) {
matrix.piece = piece || nextQueue.shift() matrix.piece = piece || nextQueue.shift();
if (!piece && holdQueue.piece) holdQueue.drawPiece() if (!piece && holdQueue.piece) holdQueue.drawPiece();
//lastActionSucceded = true //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)) {
scheduler.setInterval(fall, stats.fallPeriod) scheduler.setInterval(fall, stats.fallPeriod);
} else { } else {
gameOver() // block out gameOver(); // block out
} }
} }
@@ -140,71 +114,72 @@ let playerActions = {
softDrop: () => matrix.piece.move(TRANSLATION.DOWN) && ++stats.score, softDrop: () => matrix.piece.move(TRANSLATION.DOWN) && ++stats.score,
hardDrop: function () { hardDrop: function () {
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;
matrixCard.classList.remove("hard-dropped-table-animation") matrixCard.classList.remove('hard-dropped-table-animation');
matrixCard.offsetHeight; matrixCard.offsetHeight;
matrixCard.classList.add("hard-dropped-table-animation") // restart animation matrixCard.classList.add('hard-dropped-table-animation'); // restart animation
lockDown() lockDown();
return true return true;
}, },
hold: function () { hold: function () {
if (matrix.piece.holdEnabled) { if (matrix.piece.holdEnabled) {
scheduler.clearInterval(fall) scheduler.clearInterval(fall);
scheduler.clearTimeout(lockDown) scheduler.clearTimeout(lockDown);
let piece = matrix.piece let piece = matrix.piece;
piece.facing = FACING.NORTH piece.facing = FACING.NORTH;
piece.locked = false piece.locked = false;
generate(holdQueue.piece) generate(holdQueue.piece);
matrix.piece.holdEnabled = false matrix.piece.holdEnabled = false;
holdQueue.piece = piece holdQueue.piece = piece;
} }
}, },
pause: pauseSettings, pause: pauseSettings,
} };
// Handle player inputs // Handle player inputs
const REPEATABLE_ACTIONS = [ const REPEATABLE_ACTIONS = [
playerActions.moveLeft, playerActions.moveLeft,
playerActions.moveRight, playerActions.moveRight,
playerActions.softDrop playerActions.softDrop,
] ];
pressedKeys = new Set() pressedKeys = new Set();
actionsQueue = [] actionsQueue = [];
function onkeydown(event) { function onkeydown(event) {
if (event.key in settings.keyBind) { if (event.key in settings.keyBind) {
event.preventDefault() event.preventDefault();
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)) {
playSound(wallSound) playSound(wallSound)
lastActionSucceded = false lastActionSucceded = false
}*/ }*/
action() action();
if (REPEATABLE_ACTIONS.includes(action)) { if (REPEATABLE_ACTIONS.includes(action)) {
actionsQueue.unshift(action) actionsQueue.unshift(action);
scheduler.clearTimeout(repeat) scheduler.clearTimeout(repeat);
scheduler.clearInterval(autorepeat) scheduler.clearInterval(autorepeat);
if (action == playerActions.softDrop) scheduler.setInterval(autorepeat, settings.fallPeriod/20) if (action == playerActions.softDrop)
else scheduler.setTimeout(repeat, settings.das) scheduler.setInterval(autorepeat, settings.fallPeriod / 20);
else scheduler.setTimeout(repeat, settings.das);
} }
matrix.drawPiece() matrix.drawPiece();
} }
} }
} }
function repeat() { function repeat() {
if (actionsQueue.length) { if (actionsQueue.length) {
actionsQueue[0]() actionsQueue[0]();
scheduler.setInterval(autorepeat, settings.arr) scheduler.setInterval(autorepeat, settings.arr);
} }
} }
@@ -216,121 +191,220 @@ function autorepeat() {
wallSound.play() wallSound.play()
lastActionSucceded = false lastActionSucceded = false
}*/ }*/
actionsQueue[0]() actionsQueue[0]();
} } else scheduler.clearInterval(autorepeat);
else scheduler.clearInterval(autorepeat)
} }
function onkeyup(event) { function onkeyup(event) {
if (event.key in settings.keyBind) { if (event.key in settings.keyBind) {
event.preventDefault() event.preventDefault();
pressedKeys.delete(event.key) pressedKeys.delete(event.key);
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);
scheduler.clearTimeout(repeat) scheduler.clearTimeout(repeat);
scheduler.clearInterval(autorepeat) scheduler.clearInterval(autorepeat);
if (actionsQueue.length) { if (actionsQueue.length) {
if (actionsQueue[0] == playerActions.softDrop) scheduler.setInterval(autorepeat, settings.fallPeriod/20) if (actionsQueue[0] == playerActions.softDrop)
else scheduler.setTimeout(repeat, settings.das) scheduler.setInterval(autorepeat, settings.fallPeriod / 20);
else scheduler.setTimeout(repeat, settings.das);
} else { } else {
matrix.drawPiece() matrix.drawPiece();
} }
} }
} }
} }
function fall() { function fall() {
matrix.piece.move(TRANSLATION.DOWN) matrix.piece.move(TRANSLATION.DOWN);
} }
function lockDown() { function lockDown() {
scheduler.clearTimeout(lockDown) scheduler.clearTimeout(lockDown);
scheduler.clearInterval(fall) scheduler.clearInterval(fall);
if (matrix.lock()) { if (matrix.lock()) {
stats.lockDown(matrix.piece.tSpin, matrix.clearLines()) stats.lockDown(matrix.piece.tSpin, matrix.clearLines());
generate() generate();
} else { } else {
gameOver() // lock out gameOver(); // lock out
} }
} }
onanimationend = function (event) { onanimationend = function (event) {
event.target.classList.remove(event.animationName) event.target.classList.remove(event.animationName);
} };
messagesSpan.onanimationend = function (event) { messagesSpan.onanimationend = function (event) {
event.target.remove() event.target.remove();
} };
function gameOver() { function gameOver() {
matrix.piece.locked = true matrix.piece.locked = true;
matrix.drawPiece() matrix.drawPiece();
document.onkeydown = null document.onkeydown = null;
onblur = null onblur = null;
scheduler.clearInterval(ticktack) scheduler.clearInterval(ticktack);
playing = false playing = false;
stats.show() stats.show();
} }
window.onbeforeunload = function (event) { window.onbeforeunload = function (event) {
stats.save() stats.save();
settings.save() settings.save();
if (playing) return false; if (playing) return false;
} };
// Play with 3D // Play with 3D
let mousedown = false let mousedown = false;
let rX0 = -15 let rX0 = -15;
let rY0 = 0 let rY0 = 0;
let clientX0 = 0 let clientX0 = 0;
let clientY0 = 0 let clientY0 = 0;
sceneDiv.onmousedown = function (event) { sceneDiv.onmousedown = function (event) {
mousedown = true mousedown = true;
rX0 = parseInt(getComputedStyle(screenRow).getPropertyValue("--rX")) rX0 = parseInt(getComputedStyle(screenRow).getPropertyValue('--rX'));
dy0 = parseInt(getComputedStyle(screenRow).getPropertyValue("--rY")) dy0 = parseInt(getComputedStyle(screenRow).getPropertyValue('--rY'));
clientX0 = event.clientX clientX0 = event.clientX;
clientY0 = event.clientY clientY0 = event.clientY;
} };
sceneDiv.onmousemove = function (event) { sceneDiv.onmousemove = function (event) {
if (mousedown) { if (mousedown) {
event.preventDefault() event.preventDefault();
event.stopPropagation() event.stopPropagation();
rX = (rX0 - 0.5 * (event.clientY - clientY0)) % 360 rX = (rX0 - 0.5 * (event.clientY - clientY0)) % 360;
screenRow.style.setProperty("--rX", rX) screenRow.style.setProperty('--rX', rX);
if (rX >= 0) { if (rX >= 0) {
screenRow.classList.remove("top") screenRow.classList.remove('top');
screenRow.classList.add("bottom") screenRow.classList.add('bottom');
} else { } else {
screenRow.classList.add("top") screenRow.classList.add('top');
screenRow.classList.remove("bottom") screenRow.classList.remove('bottom');
} }
rY = (rY0 + 0.5 * (event.clientX - clientX0)) % 360 rY = (rY0 + 0.5 * (event.clientX - clientX0)) % 360;
screenRow.style.setProperty("--rY", rY) screenRow.style.setProperty('--rY', rY);
if (rY <= 0) { if (rY <= 0) {
screenRow.classList.remove("left") screenRow.classList.remove('left');
screenRow.classList.add("right") screenRow.classList.add('right');
} else { } else {
screenRow.classList.add("left") screenRow.classList.add('left');
screenRow.classList.remove("right") screenRow.classList.remove('right');
}
} }
} }
};
sceneDiv.onmouseup = document.onmouseleave = function (event) { sceneDiv.onmouseup = document.onmouseleave = function (event) {
mousedown = false mousedown = false;
};
fullscreenCheckbox.onchange = function () {
if (this.checked) {
document.documentElement.requestFullscreen();
} else {
document.exitFullscreen();
} }
};
sceneDiv.onwheel = function (event) { sceneDiv.onwheel = function (event) {
event.preventDefault() event.preventDefault();
event.stopPropagation() event.stopPropagation();
let tZ = parseInt(getComputedStyle(screenRow).getPropertyValue("--tZ")) let tZ = parseInt(getComputedStyle(screenRow).getPropertyValue('--tZ'));
tZ -= event.deltaY tZ -= event.deltaY;
screenRow.style.setProperty("--tZ", tZ + "px") screenRow.style.setProperty('--tZ', tZ + 'px');
};
$.fn.select2.defaults.set("templateResult", (state) =>
state.id
? $(`<img class="option result" src="${state.id}" title="${state.text}" loading="lazy"/>`)
: state.text
)
$.fn.select2.defaults.set("templateSelection", (state) =>
state.id
? $(`<span class="option selection" style="--skin-url: url(${state.id})" title="${state.text}" alt="${state.id}" loading="lazy"></span>`)
: state.text
)
$.fn.select2.defaults.set("theme", "bootstrap-5")
$.fn.select2.defaults.set("selectionCssClass", 'form-select')
$.fn.select2.defaults.set("dropdownParent", $('#settingsModal'))
$.fn.select2.defaults.set("dropdownAutoWidth", true)
$.fn.select2.defaults.set("placeholder", "URL de l'image")
$.fn.select2.defaults.set("tags", true)
$.fn.select2.defaults.set("createTag", function (params) {
const url = encodeURI(params.term);
if (/^(https?:\/\/.*\.(?:png|jpg|jpeg|gif|bmp|webp|svg))$/i.test(url)) {
return {
id: url,
text: 'Source externe',
newTag: true,
};
} }
});
stylesheetSelect.oninput = function (event) {
selectedStyleSheet.href = this.value;
$("#skinURLSelect").empty();
switch (this.value) {
case 'css/jstris-skin.css':
skinURLSelect.disabled = false;
fetch('https://konsola5.github.io/jstris-customization-database/jstrisCustomizationDatabase.json')
.then(response => response.json())
.then(json => {
const data = [];
for (const group in json) {
const groupData = {
text: group,
children: json[group].map(skin => ({
id: skin.link,
text: `${skin.name} (${skin.author})\n${skin.description}`,
})),
};
data.push(groupData);
}
$('#skinURLSelect').select2({data: data});
});
break;
case 'css/tetrio-skin.css':
skinURLSelect.disabled = false;
fetch("https://you.have.fail/tetrioplus/data/data.json")
.then((resp) => resp.json())
.then((json) => {
const data = json
.filter((item) => (item.type == "skin" && item.format == "tetrioraster"))
.map((skin) => {
return {
id: `https://you.have.fail/tetrioplus/data/${skin.path}`,
text:`${skin.name} (${skin.author})`
}
})
$('#skinURLSelect').select2({data: data});
})
break;
default:
skinURLSelect.disabled = true;
break;
}
}
let scheduler = new Scheduler();
let settings = new Settings();
let stats = new Stats();
let holdQueue = new HoldQueue();
let matrix = new Matrix();
let nextQueue = new NextQueue();
let playing = false;
//let lastActionSucceded = true
let favicon = document.querySelector("link[rel~='icon']");
window.onload = function (event) {
restart();
};

View File

@@ -1,335 +1,360 @@
const KEY_NAMES = new Proxy({ const KEY_NAMES = new Proxy(
["ArrowLeft"] : "←", {
["←"] : "ArrowLeft", ['ArrowLeft']: '←',
["ArrowRight"] : "→", ['←']: 'ArrowLeft',
["→"] : "ArrowRight", ['ArrowRight']: '→',
["ArrowUp"] : "↑", ['→']: 'ArrowRight',
["↑"] : "ArrowUp", ['ArrowUp']: '↑',
["ArrowDown"] : "↓", ['↑']: 'ArrowUp',
["↓"] : "ArrowDown", ['ArrowDown']: '↓',
[" "] : "Espace", ['↓']: 'ArrowDown',
["Espace"] : " ", [' ']: 'Espace',
["Escape"] : "Échap.", ['Espace']: ' ',
["Échap."] : "Escape", ['Escape']: 'Échap.',
["Backspace"] : "Ret. arrière", ['Échap.']: 'Escape',
["Ret. arrière"]: "Backspace", ['Backspace']: 'Ret. arrière',
["Enter"] : "Entrée", ['Ret. arrière']: 'Backspace',
["Entrée"] : "Enter", ['Enter']: 'Entrée',
}, { ['Entrée']: 'Enter',
},
{
get(target, key) { get(target, key) {
return key in target? target[key] : key return key in target ? target[key] : key;
} },
}) },
);
class Settings { class Settings {
constructor() { constructor() {
this.form = settingsForm this.form = settingsForm;
this.load() this.load();
this.modal = new bootstrap.Modal('#settingsModal') this.modal = new bootstrap.Modal('#settingsModal');
settingsModal.addEventListener('shown.bs.modal', () => resumeButton.focus()) settingsModal.addEventListener('shown.bs.modal', () => resumeButton.focus());
} }
load() { load() {
this.form.querySelectorAll("[name]").forEach(element => { this.form.querySelectorAll('[name]').forEach(element => {
if (element.name in localStorage) if (element.name in localStorage) element.value = localStorage[element.name];
element.value = localStorage[element.name] });
}) stylesheetSelect.oninput();
window.document.selectedStyleSheetSet = stylesheetSelect.value if (localStorage['skinURL']) {
if ($('#skinURLSelect').find("option[value='" + localStorage['skinURL'] + "']").length) {
$('#skinURLSelect').val(localStorage['skinURL']).trigger('change');
} else {
var option = new Option(
'Source externe',
localStorage['skinURL'],
true,
true,
);
$('#skinURLSelect').append(option).trigger('change');
}
skinURLSelect.oninput();
}
} }
save() { save() {
this.form.querySelectorAll("[name]").forEach(element => localStorage[element.name] = element.value) this.form
.querySelectorAll('[name]')
.forEach(element => (localStorage[element.name] = element.value));
} }
init() { init() {
this.form.onsubmit = newGame this.form.onsubmit = newGame;
levelInput.name = "startLevel" levelInput.name = 'startLevel';
levelInput.disabled = false levelInput.disabled = false;
titleHeader.innerHTML = "QUATUOR" titleHeader.innerHTML = 'QUATUOR';
resumeButton.innerHTML = "Jouer" resumeButton.innerHTML = 'Jouer';
} }
show() { show() {
resumeButton.disabled = false resumeButton.disabled = false;
settings.form.classList.remove('was-validated') settings.form.classList.remove('was-validated');
settings.modal.show() settings.modal.show();
settings.form.reportValidity() settings.form.reportValidity();
} }
getInputs() { getInputs() {
for (let input of this.form.querySelectorAll("input[type='text']")) { for (let input of this.form.querySelectorAll("input[type='text']")) {
this[input.name] = KEY_NAMES[input.value] this[input.name] = KEY_NAMES[input.value];
} }
for (let input of this.form.querySelectorAll("input[type='number'], input[type='range']")) { for (let input of this.form.querySelectorAll("input[type='number'], input[type='range']")) {
this[input.name] = input.valueAsNumber this[input.name] = input.valueAsNumber;
} }
for (let input of this.form.querySelectorAll("input[type='checkbox']")) { for (let input of this.form.querySelectorAll("input[type='checkbox']")) {
this[input.name] = input.checked == true this[input.name] = input.checked == true;
} }
this.keyBind = new Proxy({}, { this.keyBind = new Proxy(
{},
{
get: (target, key) => target[key.toLowerCase()], get: (target, key) => target[key.toLowerCase()],
set: (target, key, value) => target[key.toLowerCase()] = value, set: (target, key, value) => (target[key.toLowerCase()] = value),
has: (target, key) => key.toLowerCase() in target 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];
} }
} }
} }
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']")) 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];
keyInputs.forEach(input => { keyInputs.forEach(input => {
input.setCustomValidity("") input.setCustomValidity('');
input.classList.remove("is-invalid") input.classList.remove('is-invalid');
}) });
keyInputs.sort((input1, input2) => { keyInputs.sort((input1, input2) => {
if (input1.value == input2.value) { if (input1.value == input2.value) {
input1.setCustomValidity("Touche déjà utilisée") input1.setCustomValidity('Touche déjà utilisée');
input1.classList.add("is-invalid") input1.classList.add('is-invalid');
input2.setCustomValidity("Touche déjà utilisée") input2.setCustomValidity('Touche déjà utilisée');
input2.classList.add("is-invalid") input2.classList.add('is-invalid');
} }
return input1.value > input2.value return input1.value > input2.value;
}) });
if (input.checkValidity()) { if (input.checkValidity()) {
input.blur() 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;
};
} }
}
class Stats { class Stats {
constructor() { constructor() {
this.modal = new bootstrap.Modal('#statsModal') this.modal = new bootstrap.Modal('#statsModal');
this.load() this.load();
} }
load() { load() {
this.highScore = Number(localStorage["highScore"]) || 0 this.highScore = Number(localStorage['highScore']) || 0;
} }
init() { init() {
levelInput.value = localStorage["startLevel"] || 1 levelInput.value = localStorage['startLevel'] || 1;
this.score = 0 this.score = 0;
this.goal = 0 this.goal = 0;
this.combo = 0 this.combo = 0;
this.b2b = 0 this.b2b = 0;
this.startTime = new Date() this.startTime = new Date();
this.lockDelay = DELAY.LOCK this.lockDelay = DELAY.LOCK;
this.totalClearedLines = 0 this.totalClearedLines = 0;
this.nbQuatuors = 0 this.nbQuatuors = 0;
this.nbTSpin = 0 this.nbTSpin = 0;
this.maxCombo = 0 this.maxCombo = 0;
this.maxB2B = 0 this.maxB2B = 0;
} }
set score(score) { set score(score) {
this._score = score this._score = score;
scoreCell.innerText = score.toLocaleString() scoreCell.innerText = score.toLocaleString();
if (score > this.highScore) { if (score > this.highScore) {
this.highScore = score this.highScore = score;
} }
} }
get score() { get score() {
return this._score return this._score;
} }
set highScore(highScore) { set highScore(highScore) {
this._highScore = highScore this._highScore = highScore;
highScoreCell.innerText = highScore.toLocaleString() highScoreCell.innerText = highScore.toLocaleString();
} }
get highScore() { get highScore() {
return this._highScore return this._highScore;
} }
set level(level) { set level(level) {
this._level = level this._level = level;
this.goal += level * 5 this.goal += level * 5;
if (level <= 20) { if (level <= 20) {
this.fallPeriod = 1000 * Math.pow(0.8 - ((level - 1) * 0.007), level - 1) this.fallPeriod = 1000 * Math.pow(0.8 - (level - 1) * 0.007, level - 1);
} }
if (level > 15) if (level > 15) this.lockDelay = 500 * Math.pow(0.9, level - 15);
this.lockDelay = 500 * Math.pow(0.9, level - 15) levelInput.value = level;
levelInput.value = level levelCell.innerText = level;
levelCell.innerText = level messagesSpan.addNewChild('div', {
messagesSpan.addNewChild("div", { className: "show-level-animation", innerHTML: `<h1>NIVEAU<br/>${this.level}</h1>` }) className: 'show-level-animation',
innerHTML: `<h1>NIVEAU<br/>${this.level}</h1>`,
});
} }
get level() { get level() {
return this._level return this._level;
} }
set goal(goal) { set goal(goal) {
this._goal = goal this._goal = goal;
goalCell.innerText = goal goalCell.innerText = goal;
} }
get goal() { get goal() {
return this._goal return this._goal;
} }
set combo(combo) { set combo(combo) {
this._combo = combo this._combo = combo;
if (combo > this.maxCombo) this.maxCombo = combo if (combo > this.maxCombo) this.maxCombo = combo;
} }
get combo() { get combo() {
return this._combo return this._combo;
} }
set b2b(b2b) { set b2b(b2b) {
this._b2b = b2b this._b2b = b2b;
if (b2b > this.maxB2B) this.maxB2B = b2b if (b2b > this.maxB2B) this.maxB2B = b2b;
} }
get b2b() { get b2b() {
return this._b2b return this._b2b;
} }
set time(time) { set time(time) {
this.startTime = new Date() - time this.startTime = new Date() - time;
ticktack() ticktack();
} }
get time() { get time() {
return new Date() - this.startTime return new Date() - this.startTime;
} }
lockDown(tSpin, nbClearedLines) { 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++;
// Cleared lines & T-Spin // Cleared lines & T-Spin
let awardedLineClears = AWARDED_LINE_CLEARS[tSpin][nbClearedLines] let awardedLineClears = AWARDED_LINE_CLEARS[tSpin][nbClearedLines];
let patternScore = 100 * this.level * awardedLineClears let patternScore = 100 * this.level * awardedLineClears;
if (tSpin) messagesSpan.addNewChild("div", { if (tSpin)
className: "rotate-in-animation", messagesSpan.addNewChild('div', {
innerHTML: tSpin className: 'rotate-in-animation',
}) innerHTML: tSpin,
if (nbClearedLines) messagesSpan.addNewChild("div", { });
className: "zoom-in-animation", if (nbClearedLines)
innerHTML: CLEARED_LINES_NAMES[nbClearedLines] messagesSpan.addNewChild('div', {
}) className: 'zoom-in-animation',
innerHTML: CLEARED_LINES_NAMES[nbClearedLines],
});
if (patternScore) { if (patternScore) {
messagesSpan.addNewChild("div", { messagesSpan.addNewChild('div', {
className: "zoom-in-animation", className: 'zoom-in-animation',
style: "animation-delay: .2s", style: 'animation-delay: .2s',
innerHTML: patternScore innerHTML: patternScore,
}) });
this.score += patternScore this.score += patternScore;
} }
// Combo // Combo
if (nbClearedLines) { if (nbClearedLines) {
this.combo++ this.combo++;
if (this.combo >= 1) { if (this.combo >= 1) {
let comboScore = (nbClearedLines == 1 ? 20 : 50) * this.combo * this.level let comboScore = (nbClearedLines == 1 ? 20 : 50) * this.combo * this.level;
if (this.combo == 1) { if (this.combo == 1) {
messagesSpan.addNewChild("div", { messagesSpan.addNewChild('div', {
className: "zoom-in-animation", className: 'zoom-in-animation',
style: "animation-delay: .4s", style: 'animation-delay: .4s',
innerHTML: `COMBO<br/>${comboScore}` innerHTML: `COMBO<br/>${comboScore}`,
}) });
} 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: `COMBO x${this.combo}<br/>${comboScore}` innerHTML: `COMBO x${this.combo}<br/>${comboScore}`,
}) });
} }
this.score += comboScore this.score += comboScore;
} }
} else { } else {
this.combo = -1 this.combo = -1;
} }
// Back to back sequence // Back to back sequence
if ((nbClearedLines == 4) || (tSpin && nbClearedLines)) { if (nbClearedLines == 4 || (tSpin && nbClearedLines)) {
this.b2b++ this.b2b++;
if (this.b2b >= 1) { if (this.b2b >= 1) {
let b2bScore = patternScore / 2 let b2bScore = patternScore / 2;
if (this.b2b == 1) { if (this.b2b == 1) {
messagesSpan.addNewChild("div", { messagesSpan.addNewChild('div', {
className: "zoom-in-animation", className: 'zoom-in-animation',
style: "animation-delay: .4s", style: 'animation-delay: .4s',
innerHTML: `BOUT À 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 À BOUT x${this.b2b}<br/>${b2bScore}` innerHTML: `BOUT À BOUT x${this.b2b}<br/>${b2bScore}`,
}) });
} }
this.score += b2bScore this.score += b2bScore;
} }
} else if (nbClearedLines && !tSpin) { } else if (nbClearedLines && !tSpin) {
if (this.b2b >= 1) { if (this.b2b >= 1) {
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 À BOUT` innerHTML: `FIN DU BOUT À BOUT`,
}) });
} }
this.b2b = -1 this.b2b = -1;
} }
// Sound // Sound
if (sfxVolumeRange.value) { if (sfxVolumeRange.value) {
if (nbClearedLines == 4) playSound(quatuorSound, this.combo) if (nbClearedLines == 4) playSound(quatuorSound, this.combo);
else if (nbClearedLines) playSound(lineClearSound, this.combo) else if (nbClearedLines) playSound(lineClearSound, this.combo);
if (tSpin) playSound(tSpinSound, this.combo) if (tSpin) playSound(tSpinSound, this.combo);
} }
this.goal -= awardedLineClears this.goal -= awardedLineClears;
if (this.goal <= 0) this.level++ if (this.goal <= 0) this.level++;
} }
show() { show() {
let time = stats.time let time = stats.time;
statsModalScoreCell.innerText = this.score.toLocaleString() statsModalScoreCell.innerText = this.score.toLocaleString();
statsModalHighScoreCell.innerText = this.highScore.toLocaleString() statsModalHighScoreCell.innerText = this.highScore.toLocaleString();
statsModalLevelCell.innerText = this.level statsModalLevelCell.innerText = this.level;
statsModalTimeCell.innerText = this.timeFormat.format(time) statsModalTimeCell.innerText = this.timeFormat.format(time);
statsModaltotalClearedLines.innerText = this.totalClearedLines statsModaltotalClearedLines.innerText = this.totalClearedLines;
statsModaltotalClearedLinesPM.innerText = (stats.totalClearedLines * 60000 / time).toFixed(2) statsModaltotalClearedLinesPM.innerText = (
statsModalNbQuatuors.innerText = this.nbQuatuors (stats.totalClearedLines * 60000) /
statsModalNbTSpin.innerText = this.nbTSpin time
statsModalMaxCombo.innerText = this.maxCombo ).toFixed(2);
statsModalMaxB2B.innerText = this.maxB2B statsModalNbQuatuors.innerText = this.nbQuatuors;
this.modal.show() statsModalNbTSpin.innerText = this.nbTSpin;
statsModalMaxCombo.innerText = this.maxCombo;
statsModalMaxB2B.innerText = this.maxB2B;
this.modal.show();
} }
save() { save() {
localStorage["highScore"] = this.highScore localStorage['highScore'] = this.highScore;
} }
} }
Stats.prototype.timeFormat = new Intl.DateTimeFormat("fr-FR", { Stats.prototype.timeFormat = new Intl.DateTimeFormat('fr-FR', {
hour: "numeric", hour: 'numeric',
minute: "2-digit", minute: '2-digit',
second: "2-digit", second: '2-digit',
timeZone: "UTC" timeZone: 'UTC',
}) });
function playSound(sound, note = 0) { function playSound(sound, note = 0) {
sound.currentTime = 0 sound.currentTime = 0;
sound.playbackRate = Math.pow(5/4, note) sound.playbackRate = Math.pow(5 / 4, note);
sound.play() sound.play();
} }

File diff suppressed because it is too large Load Diff