Ajuste le mobile et les chronos cube

This commit is contained in:
2026-04-12 12:08:54 +02:00
parent 12a4ce8b97
commit f6ddc99388
3 changed files with 279 additions and 75 deletions

226
app.js
View File

@@ -596,13 +596,13 @@ function initCubePage() {
}); });
refs.whiteButton?.addEventListener("click", () => { refs.whiteButton?.addEventListener("click", () => {
captureCubeTime(match, "white"); handleCubeTap("white");
dirty = true; dirty = true;
render(); render();
}); });
refs.blackButton?.addEventListener("click", () => { refs.blackButton?.addEventListener("click", () => {
captureCubeTime(match, "black"); handleCubeTap("black");
dirty = true; dirty = true;
render(); render();
}); });
@@ -618,17 +618,6 @@ function initCubePage() {
return; return;
} }
if (!match.cube.running && match.cube.times.white === null && match.cube.times.black === null) {
startCubePhase(match);
dirty = true;
render();
return;
}
if (match.cube.running) {
return;
}
if (match.cube.times.white !== null && match.cube.times.black !== null) { if (match.cube.times.white !== null && match.cube.times.black !== null) {
if (match.config.mode === "twice" && match.cube.times.white === match.cube.times.black) { if (match.config.mode === "twice" && match.cube.times.white === match.cube.times.black) {
replayCubePhase(match); replayCubePhase(match);
@@ -655,6 +644,23 @@ function initCubePage() {
replaceTo("index.html"); replaceTo("index.html");
}); });
function handleCubeTap(color) {
if (match.result || match.phase !== "cube") {
return;
}
if (match.cube.times[color] !== null) {
return;
}
if (match.cube.playerState[color].running) {
captureCubeTime(match, color);
return;
}
startCubeTimer(match, color);
}
function renderCubeZone(color) { function renderCubeZone(color) {
const isWhite = color === "white"; const isWhite = color === "white";
const button = isWhite ? refs.whiteButton : refs.blackButton; const button = isWhite ? refs.whiteButton : refs.blackButton;
@@ -662,10 +668,11 @@ function initCubePage() {
const result = isWhite ? refs.whiteResult : refs.blackResult; const result = isWhite ? refs.whiteResult : refs.blackResult;
const cap = isWhite ? refs.whiteCap : refs.blackCap; const cap = isWhite ? refs.whiteCap : refs.blackCap;
const hint = isWhite ? refs.whiteHint : refs.blackHint; const hint = isWhite ? refs.whiteHint : refs.blackHint;
const playerState = match.cube.playerState[color];
const time = match.cube.times[color]; const time = match.cube.times[color];
name.textContent = playerName(match, color); name.textContent = playerName(match, color);
result.textContent = time === null ? "--" : formatStopwatch(time); result.textContent = formatCubePlayerTime(match, color);
cap.textContent = renderCubeCap(match, time); cap.textContent = renderCubeCap(match, time);
if (match.result) { if (match.result) {
@@ -682,29 +689,23 @@ function initCubePage() {
return; return;
} }
if (!match.cube.running) {
if (time !== null) { if (time !== null) {
button.textContent = "Temps capture"; button.textContent = "Temps enregistre";
button.disabled = true; button.disabled = true;
hint.textContent = "Arret deja enregistre pour ce joueur."; hint.textContent = "Ce joueur a deja termine son cube.";
} else {
button.textContent = "Pret";
button.disabled = true;
hint.textContent = "Attente du demarrage de la phase cube.";
}
return;
}
if (time !== null) {
button.textContent = "Temps capture";
button.disabled = true;
hint.textContent = "Le temps de ce joueur est deja enregistre.";
return; return;
} }
if (playerState.running) {
button.textContent = "J'ai fini le cube"; button.textContent = "J'ai fini le cube";
button.disabled = false; button.disabled = false;
hint.textContent = "Tape au moment exact ou le cube est resolu."; hint.textContent = "Tape au moment exact ou le cube est resolu.";
return;
}
button.textContent = "Demarrer mon chrono";
button.disabled = false;
hint.textContent = "Chaque joueur lance son propre chrono quand il commence vraiment.";
} }
function render() { function render() {
@@ -721,14 +722,6 @@ function initCubePage() {
refs.spineText.textContent = "Retournez a la configuration pour relancer une rencontre."; refs.spineText.textContent = "Retournez a la configuration pour relancer une rencontre.";
refs.primaryButton.textContent = "Retour a l'accueil"; refs.primaryButton.textContent = "Retour a l'accueil";
refs.helpStatus.textContent = "Le match est termine."; refs.helpStatus.textContent = "Le match est termine.";
} else if (match.cube.running) {
refs.centerLabel.textContent = "Etat";
refs.centerValue.textContent = "Cube en cours";
refs.spineLabel.textContent = "Arrets";
refs.spineHeadline.textContent = "Attendre les deux fins";
refs.spineText.textContent = "Chaque joueur tape sur sa grande zone des qu'il a resolu son cube.";
refs.primaryButton.textContent = "Chrono cube en cours";
refs.helpStatus.textContent = "Phase cube en cours. Les arrets sont saisis directement par les joueurs.";
} else if ( } else if (
match.cube.times.white !== null && match.cube.times.white !== null &&
match.cube.times.black !== null && match.cube.times.black !== null &&
@@ -750,19 +743,40 @@ function initCubePage() {
refs.spineText.textContent = "Appliquer le resultat du cube pour preparer le block suivant."; refs.spineText.textContent = "Appliquer le resultat du cube pour preparer le block suivant.";
refs.primaryButton.textContent = "Appliquer et ouvrir la page chrono"; refs.primaryButton.textContent = "Appliquer et ouvrir la page chrono";
refs.helpStatus.textContent = refs.spineText.textContent; refs.helpStatus.textContent = refs.spineText.textContent;
} else if (match.cube.running) {
refs.centerLabel.textContent = "Etat";
refs.centerValue.textContent = "Chronos lances";
refs.spineLabel.textContent = "Arrets";
refs.spineHeadline.textContent = "Chaque joueur se chronometre";
refs.spineText.textContent = "Chaque joueur demarre quand il veut, puis retape sa zone une fois le cube termine.";
refs.primaryButton.textContent = "Attendre les deux temps";
refs.helpStatus.textContent = refs.spineText.textContent;
} else if (match.cube.times.white !== null || match.cube.times.black !== null) {
refs.centerLabel.textContent = "Etat";
refs.centerValue.textContent = "Un temps saisi";
refs.spineLabel.textContent = "Suite";
refs.spineHeadline.textContent = "Attendre l'autre joueur";
refs.spineText.textContent = "Le deuxieme joueur peut encore demarrer puis arreter son propre chrono quand il le souhaite.";
refs.primaryButton.textContent = "Attendre le deuxieme temps";
refs.helpStatus.textContent = refs.spineText.textContent;
} else { } else {
refs.centerLabel.textContent = "Etat"; refs.centerLabel.textContent = "Etat";
refs.centerValue.textContent = "Pret"; refs.centerValue.textContent = "Pret";
refs.spineLabel.textContent = "Depart"; refs.spineLabel.textContent = "Depart libre";
refs.spineHeadline.textContent = "Lancer la phase cube"; refs.spineHeadline.textContent = "Chaque joueur lance son chrono";
refs.spineText.textContent = "Demarrez le chrono commun, puis laissez chaque joueur utiliser uniquement sa grande zone."; refs.spineText.textContent = "Au debut de sa resolution, chaque joueur tape sur sa grande zone pour demarrer son propre chrono.";
refs.primaryButton.textContent = "Demarrer la phase cube"; refs.primaryButton.textContent = "En attente des joueurs";
refs.helpStatus.textContent = "La phase cube n'a pas encore commence."; refs.helpStatus.textContent = refs.spineText.textContent;
} }
renderCubeZone("black"); renderCubeZone("black");
renderCubeZone("white"); renderCubeZone("white");
refs.primaryButton.disabled = match.result ? false : match.cube.running; refs.primaryButton.disabled =
!match.result &&
!(
match.cube.times.white !== null &&
match.cube.times.black !== null
);
} }
render(); render();
@@ -819,6 +833,10 @@ function createMatch(config) {
white: null, white: null,
black: null, black: null,
}, },
playerState: {
white: createCubePlayerState(),
black: createCubePlayerState(),
},
round: 1, round: 1,
history: [], history: [],
}, },
@@ -867,11 +885,26 @@ function normalizeRecoveredMatch(storedMatch) {
startedAt: null, startedAt: null,
elapsedMs: 0, elapsedMs: 0,
times: { white: null, black: null }, times: { white: null, black: null },
playerState: {
white: createCubePlayerState(),
black: createCubePlayerState(),
},
round: 1, round: 1,
history: [], history: [],
}; };
} }
if (!storedMatch.cube.playerState) {
storedMatch.cube.playerState = {
white: createCubePlayerState(),
black: createCubePlayerState(),
};
}
storedMatch.cube.playerState.white = normalizeCubePlayerState(storedMatch.cube.playerState.white);
storedMatch.cube.playerState.black = normalizeCubePlayerState(storedMatch.cube.playerState.black);
storedMatch.cube.running = isAnyCubeTimerRunning(storedMatch);
if (!storedMatch.doubleCoup) { if (!storedMatch.doubleCoup) {
storedMatch.doubleCoup = { storedMatch.doubleCoup = {
eligible: false, eligible: false,
@@ -968,11 +1001,15 @@ function openCubePhase(storedMatch) {
storedMatch.cube.startedAt = null; storedMatch.cube.startedAt = null;
storedMatch.cube.elapsedMs = 0; storedMatch.cube.elapsedMs = 0;
storedMatch.cube.times = { white: null, black: null }; storedMatch.cube.times = { white: null, black: null };
storedMatch.cube.playerState = {
white: createCubePlayerState(),
black: createCubePlayerState(),
};
storedMatch.cube.round = 1; storedMatch.cube.round = 1;
logEvent(storedMatch, `Page cube ouverte. Cube n${storedMatch.cube.number} designe.`); logEvent(storedMatch, `Page cube ouverte. Cube n${storedMatch.cube.number} designe.`);
} }
function startCubePhase(storedMatch) { function startCubeTimer(storedMatch, color) {
if (!storedMatch || storedMatch.phase !== "cube" || storedMatch.result) { if (!storedMatch || storedMatch.phase !== "cube" || storedMatch.result) {
return; return;
} }
@@ -981,25 +1018,41 @@ function startCubePhase(storedMatch) {
storedMatch.cube.number = pickCubeNumber(); storedMatch.cube.number = pickCubeNumber();
} }
if (storedMatch.cube.times[color] !== null || storedMatch.cube.playerState[color].running) {
return;
}
const playerState = storedMatch.cube.playerState[color];
playerState.running = true;
playerState.startedAt = Date.now();
storedMatch.cube.running = true; storedMatch.cube.running = true;
storedMatch.cube.startedAt = Date.now(); logEvent(
storedMatch.cube.elapsedMs = 0; storedMatch,
logEvent(storedMatch, `Phase cube demarree sur le cube n${storedMatch.cube.number}.`); `${playerName(storedMatch, color)} demarre son chrono cube sur le cube n${storedMatch.cube.number}.`,
);
} }
function captureCubeTime(storedMatch, color) { function captureCubeTime(storedMatch, color) {
if ( if (
!storedMatch || !storedMatch ||
storedMatch.phase !== "cube" || storedMatch.phase !== "cube" ||
!storedMatch.cube.running || !storedMatch.cube.playerState[color].running ||
storedMatch.cube.times[color] !== null storedMatch.cube.times[color] !== null
) { ) {
return; return;
} }
const elapsedMs = Date.now() - storedMatch.cube.startedAt; const playerState = storedMatch.cube.playerState[color];
const elapsedMs = playerState.elapsedMs + (Date.now() - playerState.startedAt);
storedMatch.cube.times[color] = elapsedMs; storedMatch.cube.times[color] = elapsedMs;
storedMatch.cube.elapsedMs = Math.max(storedMatch.cube.elapsedMs, elapsedMs); playerState.elapsedMs = elapsedMs;
playerState.startedAt = null;
playerState.running = false;
storedMatch.cube.elapsedMs = Math.max(
getCubePlayerElapsed(storedMatch, "white"),
getCubePlayerElapsed(storedMatch, "black"),
);
storedMatch.cube.running = isAnyCubeTimerRunning(storedMatch);
logEvent(storedMatch, `${playerName(storedMatch, color)} arrete le cube en ${formatStopwatch(elapsedMs)}.`); logEvent(storedMatch, `${playerName(storedMatch, color)} arrete le cube en ${formatStopwatch(elapsedMs)}.`);
if ( if (
@@ -1059,6 +1112,10 @@ function prepareNextTwiceBlock(storedMatch, winner) {
storedMatch.cube.startedAt = null; storedMatch.cube.startedAt = null;
storedMatch.cube.elapsedMs = 0; storedMatch.cube.elapsedMs = 0;
storedMatch.cube.times = { white: null, black: null }; storedMatch.cube.times = { white: null, black: null };
storedMatch.cube.playerState = {
white: createCubePlayerState(),
black: createCubePlayerState(),
};
storedMatch.cube.number = null; storedMatch.cube.number = null;
if (hadDouble) { if (hadDouble) {
@@ -1085,6 +1142,10 @@ function prepareNextTimeBlock(storedMatch) {
storedMatch.cube.startedAt = null; storedMatch.cube.startedAt = null;
storedMatch.cube.elapsedMs = 0; storedMatch.cube.elapsedMs = 0;
storedMatch.cube.times = { white: null, black: null }; storedMatch.cube.times = { white: null, black: null };
storedMatch.cube.playerState = {
white: createCubePlayerState(),
black: createCubePlayerState(),
};
storedMatch.cube.number = null; storedMatch.cube.number = null;
logEvent( logEvent(
storedMatch, storedMatch,
@@ -1134,6 +1195,10 @@ function replayCubePhase(storedMatch) {
storedMatch.cube.startedAt = null; storedMatch.cube.startedAt = null;
storedMatch.cube.elapsedMs = 0; storedMatch.cube.elapsedMs = 0;
storedMatch.cube.times = { white: null, black: null }; storedMatch.cube.times = { white: null, black: null };
storedMatch.cube.playerState = {
white: createCubePlayerState(),
black: createCubePlayerState(),
};
storedMatch.cube.round += 1; storedMatch.cube.round += 1;
logEvent(storedMatch, `Phase cube relancee (tentative ${storedMatch.cube.round}).`); logEvent(storedMatch, `Phase cube relancee (tentative ${storedMatch.cube.round}).`);
} }
@@ -1288,15 +1353,9 @@ function renderCubeElapsed(storedMatch) {
return "00:00.0"; return "00:00.0";
} }
if (storedMatch.cube.running) { return formatStopwatch(
return formatStopwatch(Date.now() - storedMatch.cube.startedAt); Math.max(getCubePlayerElapsed(storedMatch, "white"), getCubePlayerElapsed(storedMatch, "black")),
} );
if (storedMatch.cube.elapsedMs > 0) {
return formatStopwatch(storedMatch.cube.elapsedMs);
}
return "00:00.0";
} }
function renderCubeCap(storedMatch, time) { function renderCubeCap(storedMatch, time) {
@@ -1502,3 +1561,52 @@ function readWindowNameState() {
return null; return null;
} }
} }
function createCubePlayerState() {
return {
running: false,
startedAt: null,
elapsedMs: 0,
};
}
function normalizeCubePlayerState(playerState) {
return {
running: Boolean(playerState?.running),
startedAt: typeof playerState?.startedAt === "number" ? playerState.startedAt : null,
elapsedMs: typeof playerState?.elapsedMs === "number" ? playerState.elapsedMs : 0,
};
}
function isAnyCubeTimerRunning(storedMatch) {
return storedMatch.cube.playerState.white.running || storedMatch.cube.playerState.black.running;
}
function getCubePlayerElapsed(storedMatch, color) {
const playerState = storedMatch.cube.playerState[color];
if (storedMatch.cube.times[color] !== null) {
return storedMatch.cube.times[color];
}
if (playerState.running && playerState.startedAt) {
return playerState.elapsedMs + (Date.now() - playerState.startedAt);
}
return playerState.elapsedMs;
}
function formatCubePlayerTime(storedMatch, color) {
const elapsed = getCubePlayerElapsed(storedMatch, color);
const playerState = storedMatch.cube.playerState[color];
if (playerState.running) {
return formatStopwatch(elapsed);
}
if (elapsed <= 0 && storedMatch.cube.times[color] === null) {
return "--";
}
return formatStopwatch(elapsed);
}

View File

@@ -30,7 +30,7 @@
<strong id="cubeBlockLabel">1</strong> <strong id="cubeBlockLabel">1</strong>
</article> </article>
<article class="status-card"> <article class="status-card">
<span>Chrono cube</span> <span>Temps max</span>
<strong id="cubeElapsed">00:00.0</strong> <strong id="cubeElapsed">00:00.0</strong>
</article> </article>
<article class="status-card wide"> <article class="status-card wide">
@@ -54,7 +54,7 @@
</div> </div>
<button class="zone-button dark-button" id="blackCubeButton" type="button"> <button class="zone-button dark-button" id="blackCubeButton" type="button">
Prêt Démarrer mon chrono
</button> </button>
<p class="zone-foot" id="blackHintCube"></p> <p class="zone-foot" id="blackHintCube"></p>
@@ -69,7 +69,7 @@
</div> </div>
<button class="button primary spine-button" id="primaryCubeButton" type="button"> <button class="button primary spine-button" id="primaryCubeButton" type="button">
Démarrer la phase cube En attente des joueurs
</button> </button>
</article> </article>
@@ -87,7 +87,7 @@
</div> </div>
<button class="zone-button light-button" id="whiteCubeButton" type="button"> <button class="zone-button light-button" id="whiteCubeButton" type="button">
Prêt Démarrer mon chrono
</button> </button>
<p class="zone-foot" id="whiteHintCube"></p> <p class="zone-foot" id="whiteHintCube"></p>

View File

@@ -697,8 +697,7 @@ textarea:focus {
.setup-grid, .setup-grid,
.mode-grid, .mode-grid,
.preset-grid, .preset-grid,
.faceoff-board, .faceoff-board {
.status-strip {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
@@ -714,6 +713,10 @@ textarea:focus {
} }
@media (max-width: 900px) { @media (max-width: 900px) {
.phase-body {
overflow-y: auto;
}
.setup-shell, .setup-shell,
.phase-shell { .phase-shell {
width: min(100% - 1rem, 100%); width: min(100% - 1rem, 100%);
@@ -734,18 +737,102 @@ textarea:focus {
display: grid; display: grid;
} }
.phase-shell {
gap: 0.55rem;
padding: 0.55rem 0 0.55rem;
}
.phase-header {
grid-template-columns: auto 1fr auto;
gap: 0.45rem;
text-align: center;
}
.phase-title h1 {
font-size: clamp(1.55rem, 5.8vw, 2.1rem);
}
.phase-subtitle {
font-size: 0.92rem;
}
.status-strip {
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.45rem;
}
.status-card {
min-height: 4.5rem;
padding: 0.65rem 0.45rem;
}
.status-card strong {
font-size: clamp(1.25rem, 4vw, 1.7rem);
}
.faceoff-board { .faceoff-board {
grid-template-rows: minmax(0, 1fr) auto minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) auto minmax(0, 1fr);
gap: 0.7rem; gap: 0.45rem;
} }
.opponent-zone .mirrored-mobile { .opponent-zone .mirrored-mobile {
transform: rotate(180deg); transform: rotate(180deg);
} }
.zone-inner {
padding: 0.7rem;
gap: 0.55rem;
}
.zone-head {
align-items: center;
}
.zone-head h2 {
font-size: 1.25rem;
}
.zone-stats strong {
font-size: 1.05rem;
}
.zone-button { .zone-button {
min-height: clamp(180px, 28vh, 340px); min-height: clamp(118px, 18vh, 180px);
font-size: clamp(1.9rem, 7vw, 3.2rem); font-size: clamp(1.7rem, 6.6vw, 2.7rem);
}
.zone-foot {
min-height: 1.3rem;
font-size: 0.88rem;
}
.phase-spine {
gap: 0.45rem;
}
.spine-card {
padding: 0.75rem;
}
.spine-card strong {
font-size: 1.15rem;
}
.spine-card p {
font-size: 0.92rem;
}
.spine-button {
min-height: 3.4rem;
}
.brand-link {
justify-self: start;
}
.utility-button {
justify-self: end;
width: auto;
} }
} }
@@ -755,25 +842,34 @@ textarea:focus {
} }
.status-card { .status-card {
min-height: 5.6rem; min-height: 4rem;
padding: 0.55rem 0.35rem;
}
.status-card span {
font-size: 0.76rem;
} }
.status-card strong { .status-card strong {
font-size: 1.9rem; font-size: 1.18rem;
} }
.zone-head,
.modal-head, .modal-head,
.section-heading { .section-heading {
flex-direction: column; flex-direction: column;
} }
.zone-stats { .zone-head {
text-align: left; flex-direction: row;
align-items: center;
} }
.button, .button,
.spine-button { .spine-button {
width: 100%; width: 100%;
} }
.utility-button {
width: auto;
}
} }