improve jstris skin

This commit is contained in:
2026-03-09 21:03:45 +01:00
parent 4bac3face1
commit 3bb9f302ed
3 changed files with 296 additions and 273 deletions

View File

@@ -11,6 +11,7 @@
background-image: var(--skin-url); background-image: var(--skin-url);
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
vertical-align: middle;
} }
.result { .result {
@@ -20,7 +21,7 @@
.selection { .selection {
background-position-x: calc(-1 * var(--sprite-pos) * var(--cell-size)); background-position-x: calc(-1 * var(--sprite-pos) * var(--cell-size));
--nb-sprites: 4; --nb-sprites: 4;
--sprite-pos: 3; --sprite-pos: 4;
} }
.minoes-table { .minoes-table {
@@ -31,7 +32,6 @@
background-image: url(jstris-skin/jstris-grid.png); background-image: url(jstris-skin/jstris-grid.png);
background-position: bottom; background-position: bottom;
background-repeat: no-repeat; background-repeat: no-repeat;
background-blend-mode: screen;
} }
tr.matrix td:not(.mino) { tr.matrix td:not(.mino) {
@@ -76,13 +76,19 @@ tr.matrix td:not(.mino) {
} }
.ghost { .ghost {
--sprite-pos: 0; --sprite-pos: 1;
opacity: 50%;
} }
.disabled { .disabled {
--sprite-pos: 1; --sprite-pos: 0;
} }
.locking.mino { .locking.mino {
filter: saturate(60%) brightness(180%); filter: saturate(60%) brightness(180%);
} }
#holdTable .mino,
#nextTable .mino {
box-shadow: 4px 4px 10px #0002;
}

View File

@@ -76,19 +76,10 @@
<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"><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="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="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="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"> <div class="col-4">
<select name="stylesheet" id="stylesheetSelect" class="form-select" <select name="stylesheet" id="stylesheetSelect" class="form-select"
oninput="selectedStyleSheet.href = this.value; skinURLdiv.style.setProperty('display', this.value === 'css/tetrio-skin.css' || this.value === 'css/jstris-skin.css' ? 'flex' : 'none')"> oninput="selectedStyleSheet.href = this.value; skinURLSelect.disabled = this.value !== 'css/jstris-skin.css';">
<option value="css/classic.css" selected>Classique</option> <option value="css/classic.css" selected>Classique</option>
<option value="css/neo-classic.css" selected>Néo-classique</option> <option value="css/neo-classic.css" selected>Néo-classique</option>
<option value="css/synthwave.css">Synthwave</option> <option value="css/synthwave.css">Synthwave</option>
@@ -100,15 +91,21 @@
<option value="css/jstris-skin.css">Sample...</option> <option value="css/jstris-skin.css">Sample...</option>
</select> </select>
</div> </div>
<div id="skinURLdiv" class="col-6" style="display: none;"> <div class="col-4">
<div class="col-8"> <select name="skinURL" id="skinURLSelect" class="form-select" disabled
<select name="skinURL" id="skinURLInput" class="form-select" value="https://i.imgur.com/0l7LFMT.png"
oninput="document.documentElement.style.setProperty('--skin-url', `url(${this.value})`)"> oninput="document.documentElement.style.setProperty('--skin-url', `url(${this.value})`)">
</select> </select>
</script>
</div> </div>
<label for="skinURLInput" class="col-4 col-form-label">Sample</label> <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="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>
<div class="col-2"></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>

View File

@@ -1,48 +1,47 @@
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] });
}) selectedStyleSheet.href = stylesheetSelect.value;
selectedStyleSheet.href = stylesheetSelect.value skinURLSelect.disabled = stylesheetSelect.value !== 'css/jstris-skin.css';
if (stylesheetSelect.value === "css/tetrio-skin.css" || stylesheetSelect.value === "css/jstris-skin.css") {
skinURLdiv.style.setProperty('display', 'flex')
} else {
skinURLdiv.style.setProperty('display', 'none')
}
fetch('https://konsola5.github.io/jstris-customization-database/jstrisCustomizationDatabase.json') fetch(
'https://konsola5.github.io/jstris-customization-database/jstrisCustomizationDatabase.json',
)
.then(response => response.json()) .then(response => response.json())
.then(json => { .then(json => {
for (const group in json) { for (const group in json) {
@@ -54,12 +53,18 @@ class Settings {
option.textContent = `${skin.name} (${skin.author})`; option.textContent = `${skin.name} (${skin.author})`;
optgroup.appendChild(option); optgroup.appendChild(option);
}); });
skinURLInput.appendChild(optgroup); skinURLSelect.appendChild(optgroup);
} }
$('#skinURLInput').select2({ $('#skinURLSelect').select2({
templateResult: state => state.id ? $(`<span class="option result" style="--skin-url: url(${state.id})" title="${state.text}"></span>`): state.text, templateResult: state =>
templateSelection: state => state.id ? $(`<span class="option selection" style="--skin-url: url(${state.id})" title="${state.text}"></span>`): state.text, state.id
? $(`<img class="option result" src="${state.id}" title="${state.text}" loading="lazy"/>`)
: state.text,
templateSelection: state =>
state.id
? $(`<span class="option selection" style="--skin-url: url(${state.id})" title="${state.text}" loading="lazy"></span>`)
: state.text,
theme: 'bootstrap-5', theme: 'bootstrap-5',
width: '100%', width: '100%',
selectionCssClass: 'form-select', selectionCssClass: 'form-select',
@@ -67,318 +72,333 @@ class Settings {
dropdownParent: $('#settingsModal'), dropdownParent: $('#settingsModal'),
placeholder: "URL de l'image", placeholder: "URL de l'image",
tags: true, tags: true,
createTag: function(params) { });
return { if (localStorage['skinURL']) {
id: params.term, if (
text: "Ajouté manuellement", $('#skinURLSelect').find("option[value='" + localStorage['skinURL'] + "']")
newTag: true .length
}; ) {
}, $('#skinURLSelect').val(localStorage['skinURL']).trigger('change');
})
if (localStorage["skinURL"]) {
if ($('#skinURLInput').find("option[value='" + localStorage["skinURL"] + "']").length) {
$('#skinURLInput').val(localStorage["skinURL"]).trigger('change');
} else { } else {
var option = new Option(localStorage["skinURL"], "Ajouté manuellement", localStorage["skinURL"], true, true); var option = new Option(
$('#skinURLInput').append(option).trigger('change'); localStorage['skinURL'],
'Ajouté manuellement',
localStorage['skinURL'],
true,
true,
);
$('#skinURLSelect').append(option).trigger('change');
} }
document.documentElement.style.setProperty('--skin-url', `url(${localStorage["skinURL"]})`) document.documentElement.style.setProperty(
'--skin-url',
`url(${localStorage['skinURL']})`,
);
} }
}); });
} }
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();
} }