Automatise le passage au cube et rend les temps configurables
This commit is contained in:
@@ -6,13 +6,14 @@ Application web mobile-first pour téléphone et tablette, pensée comme applica
|
|||||||
|
|
||||||
- configure une rencontre `Twice` ou `Time`
|
- configure une rencontre `Twice` ou `Time`
|
||||||
- sépare l'application en pages dédiées : configuration, phase chrono, phase cube
|
- sépare l'application en pages dédiées : configuration, phase chrono, phase cube
|
||||||
- gère les blocks de 180 secondes et le temps par coup de 20 secondes
|
- permet de définir librement le temps de block et le temps par coup
|
||||||
- suit les quotas `FAST`, `FREEZE` et `MASTERS`
|
- suit les quotas `FAST`, `FREEZE` et `MASTERS`
|
||||||
- orchestre la phase cube avec désignation du cube, capture des temps et préparation du block suivant
|
- orchestre la phase cube avec désignation du cube, capture des temps et préparation du block suivant
|
||||||
- applique la logique du double coup V2 en `Twice`
|
- applique la logique du double coup V2 en `Twice`
|
||||||
- applique les ajustements `bloc -` et `bloc +` en `Time` avec plafond de 120 s pris en compte
|
- applique les ajustements `bloc -` et `bloc +` en `Time` avec plafond de 120 s pris en compte
|
||||||
- conserve un historique local dans le navigateur
|
- conserve un historique local dans le navigateur
|
||||||
- propose une page chrono pensée pour le téléphone avec deux grandes zones tactiles, une par joueur
|
- propose une page chrono pensée pour le téléphone avec deux grandes zones tactiles, une par joueur
|
||||||
|
- ouvre automatiquement la page cube dès que la phase chess du block est terminée
|
||||||
|
|
||||||
## Hypothèse de produit
|
## Hypothèse de produit
|
||||||
|
|
||||||
|
|||||||
243
app.js
243
app.js
@@ -2,8 +2,8 @@ const PAGE = document.body.dataset.page;
|
|||||||
|
|
||||||
const STORAGE_KEY = "chesscubing-arena-state-v2";
|
const STORAGE_KEY = "chesscubing-arena-state-v2";
|
||||||
const WINDOW_NAME_KEY = "chesscubing-arena-state-v2:";
|
const WINDOW_NAME_KEY = "chesscubing-arena-state-v2:";
|
||||||
const BLOCK_DURATION_MS = 180000;
|
const DEFAULT_BLOCK_DURATION_MS = 180000;
|
||||||
const MOVE_LIMIT_MS = 20000;
|
const DEFAULT_MOVE_LIMIT_MS = 20000;
|
||||||
const TIME_MODE_INITIAL_CLOCK_MS = 600000;
|
const TIME_MODE_INITIAL_CLOCK_MS = 600000;
|
||||||
const CUBE_TIME_CAP_MS = 120000;
|
const CUBE_TIME_CAP_MS = 120000;
|
||||||
|
|
||||||
@@ -40,7 +40,9 @@ let match = readStoredMatch();
|
|||||||
let dirty = false;
|
let dirty = false;
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
normalizeRecoveredMatch(match);
|
if (normalizeRecoveredMatch(match)) {
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
if (syncRunningState(match)) {
|
if (syncRunningState(match)) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
@@ -90,6 +92,8 @@ function initSetupPage() {
|
|||||||
const mode = getRadioValue(form, "mode") || "twice";
|
const mode = getRadioValue(form, "mode") || "twice";
|
||||||
const preset = getRadioValue(form, "preset") || "fast";
|
const preset = getRadioValue(form, "preset") || "fast";
|
||||||
const quota = PRESETS[preset].quota;
|
const quota = PRESETS[preset].quota;
|
||||||
|
const blockDurationMs = getDurationInputMs(form, "blockSeconds", DEFAULT_BLOCK_DURATION_MS);
|
||||||
|
const moveLimitMs = getDurationInputMs(form, "moveSeconds", DEFAULT_MOVE_LIMIT_MS);
|
||||||
const timeImpact =
|
const timeImpact =
|
||||||
mode === "time"
|
mode === "time"
|
||||||
? "Chronos cumules de 10 minutes, ajustes apres chaque phase cube avec plafond de 120 s pris en compte."
|
? "Chronos cumules de 10 minutes, ajustes apres chaque phase cube avec plafond de 120 s pris en compte."
|
||||||
@@ -98,7 +102,7 @@ function initSetupPage() {
|
|||||||
summary.innerHTML = `
|
summary.innerHTML = `
|
||||||
<strong>${MODES[mode].label}</strong>
|
<strong>${MODES[mode].label}</strong>
|
||||||
<span>${PRESETS[preset].description}</span>
|
<span>${PRESETS[preset].description}</span>
|
||||||
<span>Chaque block dure 180 secondes, chaque coup est limite a 20 secondes.</span>
|
<span>Temps configures : block ${formatClock(blockDurationMs)}, coup ${formatClock(moveLimitMs)}.</span>
|
||||||
<span>${timeImpact}</span>
|
<span>${timeImpact}</span>
|
||||||
<span>Quota actif : ${quota} coups par joueur.</span>
|
<span>Quota actif : ${quota} coups par joueur.</span>
|
||||||
`;
|
`;
|
||||||
@@ -116,9 +120,7 @@ function initSetupPage() {
|
|||||||
? resultText(match)
|
? resultText(match)
|
||||||
: match.phase === "cube"
|
: match.phase === "cube"
|
||||||
? "Page cube prete"
|
? "Page cube prete"
|
||||||
: match.awaitingBlockClosure
|
: "Page chrono prete";
|
||||||
? "Fin de block a confirmer"
|
|
||||||
: "Page chrono prete";
|
|
||||||
|
|
||||||
resumeCard.innerHTML = `
|
resumeCard.innerHTML = `
|
||||||
<strong>${escapeHtml(match.config.matchLabel)}</strong>
|
<strong>${escapeHtml(match.config.matchLabel)}</strong>
|
||||||
@@ -155,6 +157,8 @@ function initSetupPage() {
|
|||||||
matchLabel: sanitizeText(data.get("matchLabel")) || "Rencontre ChessCubing",
|
matchLabel: sanitizeText(data.get("matchLabel")) || "Rencontre ChessCubing",
|
||||||
mode: getRadioValue(form, "mode") || "twice",
|
mode: getRadioValue(form, "mode") || "twice",
|
||||||
preset: getRadioValue(form, "preset") || "fast",
|
preset: getRadioValue(form, "preset") || "fast",
|
||||||
|
blockDurationMs: getDurationInputMs(form, "blockSeconds", DEFAULT_BLOCK_DURATION_MS),
|
||||||
|
moveLimitMs: getDurationInputMs(form, "moveSeconds", DEFAULT_MOVE_LIMIT_MS),
|
||||||
whiteName: sanitizeText(data.get("whiteName")) || "Blanc",
|
whiteName: sanitizeText(data.get("whiteName")) || "Blanc",
|
||||||
blackName: sanitizeText(data.get("blackName")) || "Noir",
|
blackName: sanitizeText(data.get("blackName")) || "Noir",
|
||||||
arbiterName: sanitizeText(data.get("arbiterName")),
|
arbiterName: sanitizeText(data.get("arbiterName")),
|
||||||
@@ -242,14 +246,6 @@ function initChronoPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (match.awaitingBlockClosure) {
|
|
||||||
openCubePhase(match);
|
|
||||||
dirty = true;
|
|
||||||
persistMatch();
|
|
||||||
navigateTo("cube.html");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match.running) {
|
if (match.running) {
|
||||||
pauseBlock(match);
|
pauseBlock(match);
|
||||||
} else {
|
} else {
|
||||||
@@ -266,14 +262,6 @@ function initChronoPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (match.awaitingBlockClosure) {
|
|
||||||
openCubePhase(match);
|
|
||||||
dirty = true;
|
|
||||||
persistMatch();
|
|
||||||
navigateTo("cube.html");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
requestBlockClosure(match, "Cloture manuelle du block demandee par l'arbitre.");
|
requestBlockClosure(match, "Cloture manuelle du block demandee par l'arbitre.");
|
||||||
dirty = true;
|
dirty = true;
|
||||||
render();
|
render();
|
||||||
@@ -297,7 +285,7 @@ function initChronoPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match.currentTurn = opponentOf(match.currentTurn);
|
match.currentTurn = opponentOf(match.currentTurn);
|
||||||
match.moveRemainingMs = MOVE_LIMIT_MS;
|
match.moveRemainingMs = getMoveLimitMs(match);
|
||||||
logEvent(match, "Trait corrige manuellement par l'arbitre.");
|
logEvent(match, "Trait corrige manuellement par l'arbitre.");
|
||||||
dirty = true;
|
dirty = true;
|
||||||
render();
|
render();
|
||||||
@@ -336,17 +324,6 @@ function initChronoPage() {
|
|||||||
|
|
||||||
syncRunningState(match);
|
syncRunningState(match);
|
||||||
|
|
||||||
if (match.awaitingBlockClosure) {
|
|
||||||
if (match.currentTurn !== color) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
registerReliefMove(match);
|
|
||||||
dirty = true;
|
|
||||||
render();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!match.running || match.currentTurn !== color) {
|
if (!match.running || match.currentTurn !== color) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -376,14 +353,6 @@ function initChronoPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (match.awaitingBlockClosure) {
|
|
||||||
openCubePhase(match);
|
|
||||||
dirty = true;
|
|
||||||
persistMatch();
|
|
||||||
navigateTo("cube.html");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match.running) {
|
if (match.running) {
|
||||||
pauseBlock(match);
|
pauseBlock(match);
|
||||||
} else {
|
} else {
|
||||||
@@ -421,19 +390,6 @@ function initChronoPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (match.awaitingBlockClosure) {
|
|
||||||
if (active) {
|
|
||||||
button.textContent = "Coup hors quota";
|
|
||||||
button.disabled = false;
|
|
||||||
hint.textContent = "A utiliser seulement si un roi est encore en echec avant la page cube.";
|
|
||||||
} else {
|
|
||||||
button.textContent = "En attente";
|
|
||||||
button.disabled = true;
|
|
||||||
hint.textContent = "Attente de la reponse adverse ou du passage a la page cube.";
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!match.running) {
|
if (!match.running) {
|
||||||
button.textContent = "Block en pause";
|
button.textContent = "Block en pause";
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
@@ -474,10 +430,17 @@ function initChronoPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!match.result && match.phase === "cube") {
|
||||||
|
persistMatch();
|
||||||
|
navigateTo("cube.html");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
refs.title.textContent = match.config.matchLabel;
|
refs.title.textContent = match.config.matchLabel;
|
||||||
refs.subtitle.textContent = `Block ${match.blockNumber} - ${MODES[match.config.mode].label} - ${renderModeContext(match)}`;
|
refs.subtitle.textContent = `Block ${match.blockNumber} - ${MODES[match.config.mode].label} - ${renderModeContext(match)}`;
|
||||||
refs.blockTimer.textContent = formatClock(match.blockRemainingMs);
|
refs.blockTimer.textContent = formatClock(match.blockRemainingMs);
|
||||||
refs.moveTimer.textContent = formatClock(match.moveRemainingMs);
|
refs.moveTimer.textContent = formatClock(match.moveRemainingMs);
|
||||||
|
refs.arbiterTimeoutButton.textContent = `Depassement ${formatClock(getMoveLimitMs(match))}`;
|
||||||
|
|
||||||
if (match.result) {
|
if (match.result) {
|
||||||
refs.centerLabel.textContent = "Resultat";
|
refs.centerLabel.textContent = "Resultat";
|
||||||
@@ -487,20 +450,13 @@ function initChronoPage() {
|
|||||||
refs.spineText.textContent = "Retournez a la configuration pour lancer une nouvelle rencontre.";
|
refs.spineText.textContent = "Retournez a la configuration pour lancer une nouvelle rencontre.";
|
||||||
refs.primaryButton.textContent = "Retour a l'accueil";
|
refs.primaryButton.textContent = "Retour a l'accueil";
|
||||||
refs.arbiterStatus.textContent = "Le match est termine. Vous pouvez revenir a l'accueil ou reinitialiser.";
|
refs.arbiterStatus.textContent = "Le match est termine. Vous pouvez revenir a l'accueil ou reinitialiser.";
|
||||||
} else if (match.awaitingBlockClosure) {
|
|
||||||
refs.centerLabel.textContent = "Transition";
|
|
||||||
refs.centerValue.textContent = "Fin de block";
|
|
||||||
refs.spineLabel.textContent = "Page suivante";
|
|
||||||
refs.spineHeadline.textContent = "Passer a la page cube";
|
|
||||||
refs.spineText.textContent = `${match.closureReason} Jouez encore des coups hors quota si necessaire, puis ouvrez la page cube.`;
|
|
||||||
refs.primaryButton.textContent = "Ouvrir la page cube";
|
|
||||||
refs.arbiterStatus.textContent = refs.spineText.textContent;
|
|
||||||
} else if (match.running) {
|
} else if (match.running) {
|
||||||
refs.centerLabel.textContent = "Trait";
|
refs.centerLabel.textContent = "Trait";
|
||||||
refs.centerValue.textContent = playerName(match, match.currentTurn);
|
refs.centerValue.textContent = playerName(match, match.currentTurn);
|
||||||
refs.spineLabel.textContent = "Chrono en cours";
|
refs.spineLabel.textContent = "Chrono en cours";
|
||||||
refs.spineHeadline.textContent = `Block ${match.blockNumber} actif`;
|
refs.spineHeadline.textContent = `Block ${match.blockNumber} actif`;
|
||||||
refs.spineText.textContent = "Chaque joueur tape sur sa grande zone quand son coup est termine.";
|
refs.spineText.textContent =
|
||||||
|
"Chaque joueur tape sur sa grande zone quand son coup est termine. La page cube s'ouvrira automatiquement a la fin de la phase chess.";
|
||||||
refs.primaryButton.textContent = "Pause arbitre";
|
refs.primaryButton.textContent = "Pause arbitre";
|
||||||
refs.arbiterStatus.textContent = `Block en cours. Joueur au trait : ${playerName(match, match.currentTurn)}.`;
|
refs.arbiterStatus.textContent = `Block en cours. Joueur au trait : ${playerName(match, match.currentTurn)}.`;
|
||||||
} else {
|
} else {
|
||||||
@@ -508,14 +464,13 @@ function initChronoPage() {
|
|||||||
refs.centerValue.textContent = playerName(match, match.currentTurn);
|
refs.centerValue.textContent = playerName(match, match.currentTurn);
|
||||||
refs.spineLabel.textContent = "Pret";
|
refs.spineLabel.textContent = "Pret";
|
||||||
refs.spineHeadline.textContent = `Block ${match.blockNumber}`;
|
refs.spineHeadline.textContent = `Block ${match.blockNumber}`;
|
||||||
refs.spineText.textContent = "Demarrez le block, puis laissez uniquement les deux grandes zones aux joueurs.";
|
refs.spineText.textContent =
|
||||||
|
"Demarrez le block, puis laissez uniquement les deux grandes zones aux joueurs. La page cube prendra automatiquement le relais.";
|
||||||
refs.primaryButton.textContent = "Demarrer le block";
|
refs.primaryButton.textContent = "Demarrer le block";
|
||||||
refs.arbiterStatus.textContent = `Block pret. ${playerName(match, match.currentTurn)} commencera.`;
|
refs.arbiterStatus.textContent = `Block pret. ${playerName(match, match.currentTurn)} commencera.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
refs.arbiterCloseBlockButton.textContent = match.awaitingBlockClosure
|
refs.arbiterCloseBlockButton.textContent = "Passer au cube";
|
||||||
? "Ouvrir la page cube"
|
|
||||||
: "Clore le block";
|
|
||||||
|
|
||||||
renderZone("black");
|
renderZone("black");
|
||||||
renderZone("white");
|
renderZone("white");
|
||||||
@@ -799,15 +754,15 @@ function initCubePage() {
|
|||||||
function createMatch(config) {
|
function createMatch(config) {
|
||||||
const quota = PRESETS[config.preset].quota;
|
const quota = PRESETS[config.preset].quota;
|
||||||
const newMatch = {
|
const newMatch = {
|
||||||
schemaVersion: 2,
|
schemaVersion: 3,
|
||||||
config,
|
config,
|
||||||
phase: "block",
|
phase: "block",
|
||||||
running: false,
|
running: false,
|
||||||
lastTickAt: null,
|
lastTickAt: null,
|
||||||
blockNumber: 1,
|
blockNumber: 1,
|
||||||
currentTurn: "white",
|
currentTurn: "white",
|
||||||
blockRemainingMs: BLOCK_DURATION_MS,
|
blockRemainingMs: config.blockDurationMs,
|
||||||
moveRemainingMs: MOVE_LIMIT_MS,
|
moveRemainingMs: config.moveLimitMs,
|
||||||
quota,
|
quota,
|
||||||
moves: {
|
moves: {
|
||||||
white: 0,
|
white: 0,
|
||||||
@@ -848,7 +803,10 @@ function createMatch(config) {
|
|||||||
history: [],
|
history: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
logEvent(newMatch, `Match cree en mode ${MODES[config.mode].label}, cadence ${PRESETS[config.preset].label}.`);
|
logEvent(
|
||||||
|
newMatch,
|
||||||
|
`Match cree en mode ${MODES[config.mode].label}, cadence ${PRESETS[config.preset].label}, block ${formatClock(config.blockDurationMs)} et coup ${formatClock(config.moveLimitMs)}.`,
|
||||||
|
);
|
||||||
logEvent(newMatch, "Les Blancs commencent le block 1.");
|
logEvent(newMatch, "Les Blancs commencent le block 1.");
|
||||||
return newMatch;
|
return newMatch;
|
||||||
}
|
}
|
||||||
@@ -863,7 +821,7 @@ function readStoredMatch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parsed = JSON.parse(raw);
|
const parsed = JSON.parse(raw);
|
||||||
if (!parsed || parsed.schemaVersion !== 2) {
|
if (!parsed || !isSupportedSchemaVersion(parsed.schemaVersion)) {
|
||||||
return fromWindowName;
|
return fromWindowName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -874,8 +832,44 @@ function readStoredMatch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalizeRecoveredMatch(storedMatch) {
|
function normalizeRecoveredMatch(storedMatch) {
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
if (!storedMatch.config) {
|
||||||
|
storedMatch.config = {};
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blockDurationMs = getBlockDurationMs(storedMatch);
|
||||||
|
const moveLimitMs = getMoveLimitMs(storedMatch);
|
||||||
|
|
||||||
|
if (storedMatch.schemaVersion !== 3) {
|
||||||
|
storedMatch.schemaVersion = 3;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storedMatch.config.blockDurationMs !== blockDurationMs) {
|
||||||
|
storedMatch.config.blockDurationMs = blockDurationMs;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storedMatch.config.moveLimitMs !== moveLimitMs) {
|
||||||
|
storedMatch.config.moveLimitMs = moveLimitMs;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (storedMatch.phase === "block" && typeof storedMatch.lastTickAt !== "number") {
|
if (storedMatch.phase === "block" && typeof storedMatch.lastTickAt !== "number") {
|
||||||
storedMatch.lastTickAt = null;
|
storedMatch.lastTickAt = null;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof storedMatch.blockRemainingMs !== "number") {
|
||||||
|
storedMatch.blockRemainingMs = blockDurationMs;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof storedMatch.moveRemainingMs !== "number") {
|
||||||
|
storedMatch.moveRemainingMs = moveLimitMs;
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!storedMatch.cube) {
|
if (!storedMatch.cube) {
|
||||||
@@ -892,6 +886,7 @@ function normalizeRecoveredMatch(storedMatch) {
|
|||||||
round: 1,
|
round: 1,
|
||||||
history: [],
|
history: [],
|
||||||
};
|
};
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!storedMatch.cube.playerState) {
|
if (!storedMatch.cube.playerState) {
|
||||||
@@ -899,6 +894,7 @@ function normalizeRecoveredMatch(storedMatch) {
|
|||||||
white: createCubePlayerState(),
|
white: createCubePlayerState(),
|
||||||
black: createCubePlayerState(),
|
black: createCubePlayerState(),
|
||||||
};
|
};
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
storedMatch.cube.playerState.white = normalizeCubePlayerState(storedMatch.cube.playerState.white);
|
storedMatch.cube.playerState.white = normalizeCubePlayerState(storedMatch.cube.playerState.white);
|
||||||
@@ -911,7 +907,15 @@ function normalizeRecoveredMatch(storedMatch) {
|
|||||||
step: 0,
|
step: 0,
|
||||||
starter: "white",
|
starter: "white",
|
||||||
};
|
};
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (storedMatch.awaitingBlockClosure && storedMatch.phase === "block") {
|
||||||
|
openCubePhase(storedMatch, storedMatch.closureReason || "La phase chess etait deja terminee.");
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncRunningState(storedMatch) {
|
function syncRunningState(storedMatch) {
|
||||||
@@ -938,7 +942,10 @@ function syncRunningState(storedMatch) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (storedMatch.blockRemainingMs === 0) {
|
if (storedMatch.blockRemainingMs === 0) {
|
||||||
requestBlockClosure(storedMatch, "Les 180 secondes du block sont ecoulees.");
|
requestBlockClosure(
|
||||||
|
storedMatch,
|
||||||
|
`Le temps de block ${formatClock(getBlockDurationMs(storedMatch))} est ecoule.`,
|
||||||
|
);
|
||||||
} else if (storedMatch.moveRemainingMs === 0) {
|
} else if (storedMatch.moveRemainingMs === 0) {
|
||||||
registerMoveTimeout(storedMatch, true);
|
registerMoveTimeout(storedMatch, true);
|
||||||
}
|
}
|
||||||
@@ -974,19 +981,20 @@ function pauseBlock(storedMatch) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function requestBlockClosure(storedMatch, reason) {
|
function requestBlockClosure(storedMatch, reason) {
|
||||||
if (!storedMatch || storedMatch.awaitingBlockClosure) {
|
if (!storedMatch || storedMatch.phase !== "block") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
storedMatch.running = false;
|
storedMatch.running = false;
|
||||||
storedMatch.lastTickAt = null;
|
storedMatch.lastTickAt = null;
|
||||||
storedMatch.awaitingBlockClosure = true;
|
storedMatch.awaitingBlockClosure = false;
|
||||||
storedMatch.closureReason = reason;
|
storedMatch.closureReason = "";
|
||||||
storedMatch.moveRemainingMs = MOVE_LIMIT_MS;
|
storedMatch.moveRemainingMs = getMoveLimitMs(storedMatch);
|
||||||
logEvent(storedMatch, `${reason} Verifier si des coups hors quota sont encore necessaires avant la page cube.`);
|
logEvent(storedMatch, `${reason} Passage automatique vers la page cube.`);
|
||||||
|
openCubePhase(storedMatch, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
function openCubePhase(storedMatch) {
|
function openCubePhase(storedMatch, reason = "") {
|
||||||
if (!storedMatch) {
|
if (!storedMatch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1006,7 +1014,10 @@ function openCubePhase(storedMatch) {
|
|||||||
black: createCubePlayerState(),
|
black: createCubePlayerState(),
|
||||||
};
|
};
|
||||||
storedMatch.cube.round = 1;
|
storedMatch.cube.round = 1;
|
||||||
logEvent(storedMatch, `Page cube ouverte. Cube n${storedMatch.cube.number} designe.`);
|
logEvent(
|
||||||
|
storedMatch,
|
||||||
|
`${reason ? `${reason} ` : ""}Page cube ouverte. Cube n${storedMatch.cube.number} designe.`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function startCubeTimer(storedMatch, color) {
|
function startCubeTimer(storedMatch, color) {
|
||||||
@@ -1099,8 +1110,8 @@ function prepareNextTwiceBlock(storedMatch, winner) {
|
|||||||
storedMatch.phase = "block";
|
storedMatch.phase = "block";
|
||||||
storedMatch.running = false;
|
storedMatch.running = false;
|
||||||
storedMatch.lastTickAt = null;
|
storedMatch.lastTickAt = null;
|
||||||
storedMatch.blockRemainingMs = BLOCK_DURATION_MS;
|
storedMatch.blockRemainingMs = getBlockDurationMs(storedMatch);
|
||||||
storedMatch.moveRemainingMs = MOVE_LIMIT_MS;
|
storedMatch.moveRemainingMs = getMoveLimitMs(storedMatch);
|
||||||
storedMatch.moves = { white: 0, black: 0 };
|
storedMatch.moves = { white: 0, black: 0 };
|
||||||
storedMatch.currentTurn = winner;
|
storedMatch.currentTurn = winner;
|
||||||
storedMatch.doubleCoup = {
|
storedMatch.doubleCoup = {
|
||||||
@@ -1130,8 +1141,8 @@ function prepareNextTimeBlock(storedMatch) {
|
|||||||
storedMatch.phase = "block";
|
storedMatch.phase = "block";
|
||||||
storedMatch.running = false;
|
storedMatch.running = false;
|
||||||
storedMatch.lastTickAt = null;
|
storedMatch.lastTickAt = null;
|
||||||
storedMatch.blockRemainingMs = BLOCK_DURATION_MS;
|
storedMatch.blockRemainingMs = getBlockDurationMs(storedMatch);
|
||||||
storedMatch.moveRemainingMs = MOVE_LIMIT_MS;
|
storedMatch.moveRemainingMs = getMoveLimitMs(storedMatch);
|
||||||
storedMatch.moves = { white: 0, black: 0 };
|
storedMatch.moves = { white: 0, black: 0 };
|
||||||
storedMatch.doubleCoup = {
|
storedMatch.doubleCoup = {
|
||||||
eligible: false,
|
eligible: false,
|
||||||
@@ -1219,7 +1230,7 @@ function registerCountedMove(storedMatch, { source }) {
|
|||||||
|
|
||||||
storedMatch.moves[actor] += 1;
|
storedMatch.moves[actor] += 1;
|
||||||
storedMatch.lastMover = actor;
|
storedMatch.lastMover = actor;
|
||||||
storedMatch.moveRemainingMs = MOVE_LIMIT_MS;
|
storedMatch.moveRemainingMs = getMoveLimitMs(storedMatch);
|
||||||
|
|
||||||
if (source === "double") {
|
if (source === "double") {
|
||||||
storedMatch.doubleCoup.step = 0;
|
storedMatch.doubleCoup.step = 0;
|
||||||
@@ -1241,7 +1252,7 @@ function registerFreeDoubleMove(storedMatch) {
|
|||||||
|
|
||||||
const actor = storedMatch.currentTurn;
|
const actor = storedMatch.currentTurn;
|
||||||
storedMatch.lastMover = actor;
|
storedMatch.lastMover = actor;
|
||||||
storedMatch.moveRemainingMs = MOVE_LIMIT_MS;
|
storedMatch.moveRemainingMs = getMoveLimitMs(storedMatch);
|
||||||
storedMatch.doubleCoup.step = 2;
|
storedMatch.doubleCoup.step = 2;
|
||||||
logEvent(
|
logEvent(
|
||||||
storedMatch,
|
storedMatch,
|
||||||
@@ -1249,21 +1260,6 @@ function registerFreeDoubleMove(storedMatch) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerReliefMove(storedMatch) {
|
|
||||||
if (!storedMatch || storedMatch.phase !== "block") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actor = storedMatch.currentTurn;
|
|
||||||
storedMatch.lastMover = actor;
|
|
||||||
storedMatch.currentTurn = opponentOf(actor);
|
|
||||||
storedMatch.moveRemainingMs = MOVE_LIMIT_MS;
|
|
||||||
logEvent(
|
|
||||||
storedMatch,
|
|
||||||
`Coup hors quota joue par ${playerName(storedMatch, actor)} avant la page cube.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerMoveTimeout(storedMatch, automatic) {
|
function registerMoveTimeout(storedMatch, automatic) {
|
||||||
if (!storedMatch || storedMatch.phase !== "block") {
|
if (!storedMatch || storedMatch.phase !== "block") {
|
||||||
return;
|
return;
|
||||||
@@ -1273,7 +1269,7 @@ function registerMoveTimeout(storedMatch, automatic) {
|
|||||||
|
|
||||||
if (storedMatch.doubleCoup.step === 1) {
|
if (storedMatch.doubleCoup.step === 1) {
|
||||||
storedMatch.doubleCoup.step = 0;
|
storedMatch.doubleCoup.step = 0;
|
||||||
storedMatch.moveRemainingMs = MOVE_LIMIT_MS;
|
storedMatch.moveRemainingMs = getMoveLimitMs(storedMatch);
|
||||||
logEvent(
|
logEvent(
|
||||||
storedMatch,
|
storedMatch,
|
||||||
`Depassement sur le premier coup gratuit de ${playerName(storedMatch, actor)} : le double coup est annule.`,
|
`Depassement sur le premier coup gratuit de ${playerName(storedMatch, actor)} : le double coup est annule.`,
|
||||||
@@ -1290,10 +1286,10 @@ function registerMoveTimeout(storedMatch, automatic) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
storedMatch.currentTurn = opponentOf(actor);
|
storedMatch.currentTurn = opponentOf(actor);
|
||||||
storedMatch.moveRemainingMs = MOVE_LIMIT_MS;
|
storedMatch.moveRemainingMs = getMoveLimitMs(storedMatch);
|
||||||
logEvent(
|
logEvent(
|
||||||
storedMatch,
|
storedMatch,
|
||||||
`${automatic ? "Temps par coup ecoule." : "Depassement manuel 20 s."} ${playerName(
|
`${automatic ? "Temps par coup ecoule." : `Depassement manuel du temps par coup ${formatClock(getMoveLimitMs(storedMatch))}.`} ${playerName(
|
||||||
storedMatch,
|
storedMatch,
|
||||||
actor,
|
actor,
|
||||||
)} perd son coup, qui reste compte dans le quota.`,
|
)} perd son coup, qui reste compte dans le quota.`,
|
||||||
@@ -1455,6 +1451,8 @@ function loadDemo(form, onRender) {
|
|||||||
setInputValue(form, "matchLabel", "Demo officielle ChessCubing");
|
setInputValue(form, "matchLabel", "Demo officielle ChessCubing");
|
||||||
setRadioValue(form, "mode", "twice");
|
setRadioValue(form, "mode", "twice");
|
||||||
setRadioValue(form, "preset", "freeze");
|
setRadioValue(form, "preset", "freeze");
|
||||||
|
setInputValue(form, "blockSeconds", "180");
|
||||||
|
setInputValue(form, "moveSeconds", "20");
|
||||||
setInputValue(form, "whiteName", "Nora");
|
setInputValue(form, "whiteName", "Nora");
|
||||||
setInputValue(form, "blackName", "Leo");
|
setInputValue(form, "blackName", "Leo");
|
||||||
setInputValue(form, "arbiterName", "Arbitre demo");
|
setInputValue(form, "arbiterName", "Arbitre demo");
|
||||||
@@ -1482,6 +1480,16 @@ function setRadioValue(form, name, value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDurationInputMs(form, name, fallbackMs) {
|
||||||
|
const input = form.querySelector(`[name="${name}"]`);
|
||||||
|
const seconds = Number.parseInt(String(input?.value || ""), 10);
|
||||||
|
if (!Number.isFinite(seconds) || seconds <= 0) {
|
||||||
|
return fallbackMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return seconds * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
function playerName(storedMatch, color) {
|
function playerName(storedMatch, color) {
|
||||||
return color === "white" ? storedMatch.config.whiteName : storedMatch.config.blackName;
|
return color === "white" ? storedMatch.config.whiteName : storedMatch.config.blackName;
|
||||||
}
|
}
|
||||||
@@ -1556,12 +1564,33 @@ function readWindowNameState() {
|
|||||||
|
|
||||||
const raw = window.name.slice(WINDOW_NAME_KEY.length);
|
const raw = window.name.slice(WINDOW_NAME_KEY.length);
|
||||||
const parsed = JSON.parse(raw);
|
const parsed = JSON.parse(raw);
|
||||||
return parsed && parsed.schemaVersion === 2 ? parsed : null;
|
return parsed && isSupportedSchemaVersion(parsed.schemaVersion) ? parsed : null;
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSupportedSchemaVersion(version) {
|
||||||
|
return version === 2 || version === 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBlockDurationMs(storedMatch) {
|
||||||
|
return normalizeDurationMs(storedMatch?.config?.blockDurationMs, DEFAULT_BLOCK_DURATION_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMoveLimitMs(storedMatch) {
|
||||||
|
return normalizeDurationMs(storedMatch?.config?.moveLimitMs, DEFAULT_MOVE_LIMIT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDurationMs(value, fallbackMs) {
|
||||||
|
const duration = Number(value);
|
||||||
|
if (!Number.isFinite(duration) || duration <= 0) {
|
||||||
|
return fallbackMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round(duration);
|
||||||
|
}
|
||||||
|
|
||||||
function createCubePlayerState() {
|
function createCubePlayerState() {
|
||||||
return {
|
return {
|
||||||
running: false,
|
running: false,
|
||||||
|
|||||||
@@ -114,10 +114,10 @@
|
|||||||
Pause / reprise
|
Pause / reprise
|
||||||
</button>
|
</button>
|
||||||
<button class="button secondary" id="arbiterCloseBlockButton" type="button">
|
<button class="button secondary" id="arbiterCloseBlockButton" type="button">
|
||||||
Clore le block
|
Passer au cube
|
||||||
</button>
|
</button>
|
||||||
<button class="button secondary" id="arbiterTimeoutButton" type="button">
|
<button class="button secondary" id="arbiterTimeoutButton" type="button">
|
||||||
Dépassement 20 s
|
Dépassement temps coup
|
||||||
</button>
|
</button>
|
||||||
<button class="button secondary" id="arbiterSwitchTurnButton" type="button">
|
<button class="button secondary" id="arbiterSwitchTurnButton" type="button">
|
||||||
Corriger le trait
|
Corriger le trait
|
||||||
|
|||||||
38
index.html
38
index.html
@@ -21,7 +21,8 @@
|
|||||||
<h1>ChessCubing Arena</h1>
|
<h1>ChessCubing Arena</h1>
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
Une version mobile et tablette organisée par phases : configuration,
|
Une version mobile et tablette organisée par phases : configuration,
|
||||||
page chrono ultra lisible, puis page cube dédiée.
|
page chrono ultra lisible, puis page cube dédiée qui s'ouvre
|
||||||
|
automatiquement à la fin de la phase chess.
|
||||||
</p>
|
</p>
|
||||||
<div class="hero-pills">
|
<div class="hero-pills">
|
||||||
<span>Pages séparées</span>
|
<span>Pages séparées</span>
|
||||||
@@ -118,6 +119,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="field span-2">
|
||||||
|
<legend>Temps personnalisés</legend>
|
||||||
|
<div class="timing-grid">
|
||||||
|
<label class="field">
|
||||||
|
<span>Temps block (secondes)</span>
|
||||||
|
<input
|
||||||
|
name="blockSeconds"
|
||||||
|
type="number"
|
||||||
|
min="30"
|
||||||
|
max="1800"
|
||||||
|
step="5"
|
||||||
|
value="180"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Temps coup (secondes)</span>
|
||||||
|
<input
|
||||||
|
name="moveSeconds"
|
||||||
|
type="number"
|
||||||
|
min="5"
|
||||||
|
max="300"
|
||||||
|
step="1"
|
||||||
|
value="20"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Joueur blanc</span>
|
<span>Joueur blanc</span>
|
||||||
<input name="whiteName" type="text" maxlength="40" placeholder="Blanc" value="Blanc" />
|
<input name="whiteName" type="text" maxlength="40" placeholder="Blanc" value="Blanc" />
|
||||||
@@ -188,15 +217,16 @@
|
|||||||
<strong>Gros boutons uniquement</strong>
|
<strong>Gros boutons uniquement</strong>
|
||||||
<p>
|
<p>
|
||||||
Chaque joueur dispose d'une grande zone tactile pour signaler la
|
Chaque joueur dispose d'une grande zone tactile pour signaler la
|
||||||
fin de son coup, avec le haut de l'écran inversé sur téléphone.
|
fin de son coup, puis l'app ouvre automatiquement la phase cube
|
||||||
|
quand le block chess est terminé.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
<article class="rule-card">
|
<article class="rule-card">
|
||||||
<span class="micro-label">Page cube</span>
|
<span class="micro-label">Page cube</span>
|
||||||
<strong>Une page dédiée</strong>
|
<strong>Une page dédiée</strong>
|
||||||
<p>
|
<p>
|
||||||
Le cube désigné, le chrono commun et les arrêts Blanc / Noir
|
Les deux joueurs lancent et arrêtent leur propre chrono cube sur
|
||||||
sont isolés sur leur propre écran.
|
un écran séparé, toujours en face-à-face sur mobile.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
<article class="rule-card">
|
<article class="rule-card">
|
||||||
|
|||||||
41
styles.css
41
styles.css
@@ -8,14 +8,14 @@
|
|||||||
--grid-line: rgba(92, 234, 255, 0.09);
|
--grid-line: rgba(92, 234, 255, 0.09);
|
||||||
--text: #e4fbff;
|
--text: #e4fbff;
|
||||||
--muted: #7da9b6;
|
--muted: #7da9b6;
|
||||||
--warm: #ffb45d;
|
--warm: #7df7bd;
|
||||||
--warm-strong: #ff7a1f;
|
--warm-strong: #18d97f;
|
||||||
--cool: #75f2ff;
|
--cool: #75f2ff;
|
||||||
--cool-strong: #1ed6ff;
|
--cool-strong: #1ed6ff;
|
||||||
--white-seat: #ffd08a;
|
--white-seat: #b6ffd8;
|
||||||
--dark-seat: #8ef6ff;
|
--dark-seat: #8ef6ff;
|
||||||
--danger: #ff647f;
|
--danger: #ff647f;
|
||||||
--success: #9ff68e;
|
--success: #7df7bd;
|
||||||
--shadow:
|
--shadow:
|
||||||
0 0 0 1px rgba(98, 242, 255, 0.08),
|
0 0 0 1px rgba(98, 242, 255, 0.08),
|
||||||
0 26px 80px rgba(0, 0, 0, 0.56),
|
0 26px 80px rgba(0, 0, 0, 0.56),
|
||||||
@@ -39,9 +39,9 @@ body {
|
|||||||
font-family: "Segoe UI", "Trebuchet MS", sans-serif;
|
font-family: "Segoe UI", "Trebuchet MS", sans-serif;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at top left, rgba(255, 122, 31, 0.18), transparent 22%),
|
radial-gradient(circle at top left, rgba(24, 217, 127, 0.18), transparent 22%),
|
||||||
radial-gradient(circle at 78% 18%, rgba(30, 214, 255, 0.18), transparent 20%),
|
radial-gradient(circle at 78% 18%, rgba(30, 214, 255, 0.18), transparent 20%),
|
||||||
radial-gradient(circle at bottom center, rgba(30, 214, 255, 0.1), transparent 26%),
|
radial-gradient(circle at bottom center, rgba(24, 217, 127, 0.1), transparent 26%),
|
||||||
linear-gradient(180deg, #01040a 0%, #03101b 48%, #041522 100%);
|
linear-gradient(180deg, #01040a 0%, #03101b 48%, #041522 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ body::after {
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at 50% 110%, rgba(30, 214, 255, 0.24), transparent 24%),
|
radial-gradient(circle at 50% 110%, rgba(24, 217, 127, 0.18), transparent 24%),
|
||||||
linear-gradient(180deg, transparent 0 58%, rgba(30, 214, 255, 0.06) 72%, transparent 100%);
|
linear-gradient(180deg, transparent 0 58%, rgba(30, 214, 255, 0.06) 72%, transparent 100%);
|
||||||
mix-blend-mode: screen;
|
mix-blend-mode: screen;
|
||||||
}
|
}
|
||||||
@@ -180,9 +180,9 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button.secondary {
|
.button.secondary {
|
||||||
background: linear-gradient(135deg, rgba(255, 122, 31, 0.18), rgba(255, 180, 93, 0.08));
|
background: linear-gradient(135deg, rgba(24, 217, 127, 0.18), rgba(125, 247, 189, 0.08));
|
||||||
border-color: rgba(255, 180, 93, 0.32);
|
border-color: rgba(125, 247, 189, 0.32);
|
||||||
box-shadow: 0 0 18px rgba(255, 122, 31, 0.1);
|
box-shadow: 0 0 18px rgba(24, 217, 127, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.ghost {
|
.button.ghost {
|
||||||
@@ -343,6 +343,12 @@ p {
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timing-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
.field {
|
.field {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.45rem;
|
gap: 0.45rem;
|
||||||
@@ -435,7 +441,7 @@ textarea:focus {
|
|||||||
.option-card:has(input:checked) {
|
.option-card:has(input:checked) {
|
||||||
border-color: rgba(30, 214, 255, 0.52);
|
border-color: rgba(30, 214, 255, 0.52);
|
||||||
background:
|
background:
|
||||||
linear-gradient(160deg, rgba(30, 214, 255, 0.16), rgba(255, 122, 31, 0.08));
|
linear-gradient(160deg, rgba(30, 214, 255, 0.16), rgba(24, 217, 127, 0.09));
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 0 0 0 1px rgba(30, 214, 255, 0.06),
|
inset 0 0 0 1px rgba(30, 214, 255, 0.06),
|
||||||
0 0 24px rgba(30, 214, 255, 0.08);
|
0 0 24px rgba(30, 214, 255, 0.08);
|
||||||
@@ -561,7 +567,7 @@ textarea:focus {
|
|||||||
|
|
||||||
.status-card.wide {
|
.status-card.wide {
|
||||||
background:
|
background:
|
||||||
linear-gradient(160deg, rgba(30, 214, 255, 0.14), rgba(255, 122, 31, 0.09));
|
linear-gradient(160deg, rgba(30, 214, 255, 0.14), rgba(24, 217, 127, 0.09));
|
||||||
border-color: rgba(30, 214, 255, 0.22);
|
border-color: rgba(30, 214, 255, 0.22);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,9 +621,9 @@ textarea:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.light-seat {
|
.light-seat {
|
||||||
background: rgba(255, 180, 93, 0.12);
|
background: rgba(125, 247, 189, 0.12);
|
||||||
color: var(--white-seat);
|
color: var(--white-seat);
|
||||||
box-shadow: 0 0 16px rgba(255, 122, 31, 0.12);
|
box-shadow: 0 0 16px rgba(24, 217, 127, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-seat {
|
.dark-seat {
|
||||||
@@ -661,12 +667,12 @@ textarea:focus {
|
|||||||
|
|
||||||
.light-button {
|
.light-button {
|
||||||
background:
|
background:
|
||||||
linear-gradient(165deg, rgba(255, 122, 31, 0.24), rgba(255, 180, 93, 0.08)),
|
linear-gradient(165deg, rgba(24, 217, 127, 0.24), rgba(125, 247, 189, 0.08)),
|
||||||
linear-gradient(180deg, rgba(11, 19, 24, 0.96), rgba(5, 10, 14, 0.98));
|
linear-gradient(180deg, rgba(11, 19, 24, 0.96), rgba(5, 10, 14, 0.98));
|
||||||
color: var(--white-seat);
|
color: var(--white-seat);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 0 0 0 1px rgba(255, 180, 93, 0.08),
|
inset 0 0 0 1px rgba(125, 247, 189, 0.08),
|
||||||
0 0 28px rgba(255, 122, 31, 0.08);
|
0 0 28px rgba(24, 217, 127, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-button {
|
.dark-button {
|
||||||
@@ -786,6 +792,7 @@ textarea:focus {
|
|||||||
.setup-grid,
|
.setup-grid,
|
||||||
.mode-grid,
|
.mode-grid,
|
||||||
.preset-grid,
|
.preset-grid,
|
||||||
|
.timing-grid,
|
||||||
.faceoff-board {
|
.faceoff-board {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user