Ajoute un résumé modal à la fin du cube

This commit is contained in:
2026-04-12 20:09:44 +02:00
parent 9f98168934
commit 8eeb359a08
3 changed files with 252 additions and 13 deletions

165
app.js
View File

@@ -661,6 +661,22 @@ function initCubePage() {
closeHelpButton: document.querySelector("#closeCubeHelpButton"),
helpModal: document.querySelector("#cubeHelpModal"),
helpStatus: document.querySelector("#cubeHelpStatus"),
resultModal: document.querySelector("#cubeResultModal"),
closeResultButton: document.querySelector("#closeCubeResultButton"),
resultModalTitle: document.querySelector("#cubeResultModalTitle"),
resultSummary: document.querySelector("#cubeResultSummary"),
resultWinner: document.querySelector("#cubeResultWinner"),
resultOutcome: document.querySelector("#cubeResultOutcome"),
resultWhiteName: document.querySelector("#cubeResultWhiteName"),
resultBlackName: document.querySelector("#cubeResultBlackName"),
resultWhiteTime: document.querySelector("#cubeResultWhiteTime"),
resultBlackTime: document.querySelector("#cubeResultBlackTime"),
resultWhiteDetail: document.querySelector("#cubeResultWhiteDetail"),
resultBlackDetail: document.querySelector("#cubeResultBlackDetail"),
resultWhiteClock: document.querySelector("#cubeResultWhiteClock"),
resultBlackClock: document.querySelector("#cubeResultBlackClock"),
resultActionButton: document.querySelector("#cubeResultActionButton"),
resultDismissButton: document.querySelector("#cubeResultDismissButton"),
replayCubeButton: document.querySelector("#replayCubeButton"),
resetButton: document.querySelector("#cubeResetButton"),
};
@@ -669,9 +685,12 @@ function initCubePage() {
black: createCubeHoldIntent(),
};
let cubeHoldAnimationFrameId = null;
let resultModalKey = null;
const openModal = () => toggleModal(refs.helpModal, true);
const closeModal = () => toggleModal(refs.helpModal, false);
const openResultModal = () => toggleModal(refs.resultModal, true);
const closeResultModal = () => toggleModal(refs.resultModal, false);
refs.openHelpButton?.addEventListener("click", openModal);
refs.closeHelpButton?.addEventListener("click", closeModal);
@@ -681,6 +700,14 @@ function initCubePage() {
closeModal();
}
});
refs.closeResultButton?.addEventListener("click", closeResultModal);
refs.resultDismissButton?.addEventListener("click", closeResultModal);
refs.resultModal?.addEventListener("click", (event) => {
const target = event.target;
if (target instanceof HTMLElement && target.dataset.closeCubeResultModal === "true") {
closeResultModal();
}
});
bindCubeButton("white", refs.whiteButton);
bindCubeButton("black", refs.blackButton);
@@ -697,27 +724,47 @@ function initCubePage() {
}
if (match.cube.times.white !== null && match.cube.times.black !== null) {
if (match.config.mode === "twice" && match.cube.times.white === match.cube.times.black) {
replayCubePhase(match);
dirty = true;
render();
return;
}
applyCubeOutcome(match);
dirty = true;
persistMatch();
navigateTo("chrono.html");
openResultModal();
}
});
refs.resultActionButton?.addEventListener("click", () => {
if (!match || match.result || match.phase !== "cube") {
closeResultModal();
return;
}
if (match.cube.times.white === null || match.cube.times.black === null) {
closeResultModal();
return;
}
if (match.config.mode === "twice" && match.cube.times.white === match.cube.times.black) {
closeResultModal();
resultModalKey = null;
replayCubePhase(match);
dirty = true;
render();
return;
}
closeResultModal();
applyCubeOutcome(match);
dirty = true;
persistMatch();
navigateTo("chrono.html");
});
refs.replayCubeButton?.addEventListener("click", () => {
closeResultModal();
resultModalKey = null;
replayCubePhase(match);
dirty = true;
render();
});
refs.resetButton?.addEventListener("click", () => {
closeResultModal();
clearMatch();
replaceTo(SETUP_PAGE);
});
@@ -975,6 +1022,97 @@ function initCubePage() {
hint.textContent = "Maintenez la grande zone 2 secondes, puis relachez pour lancer votre chrono.";
}
function getCubeResultState() {
if (!match || match.phase !== "cube") {
return null;
}
const white = match.cube.times.white;
const black = match.cube.times.black;
if (white === null || black === null) {
return null;
}
const whiteName = playerName(match, "white");
const blackName = playerName(match, "black");
if (match.config.mode === "time") {
const preview = getTimeAdjustmentPreview(match, white, black);
if (!preview) {
return null;
}
return {
key: `time:${match.blockNumber}:${match.cube.round}:${white}:${black}`,
title: "Résumé du cube",
winner: preview.winner ? playerName(match, preview.winner) : "Egalite",
outcome:
preview.blockType === "minus" ? "Bloc - a appliquer" : "Bloc + a appliquer",
summary:
"Validez ce résumé pour appliquer les impacts chrono puis revenir à la page chrono.",
actionLabel: "Appliquer et ouvrir la page chrono",
whiteName,
blackName,
whiteTime: `Temps cube ${formatStopwatch(white)}`,
blackTime: `Temps cube ${formatStopwatch(black)}`,
whiteDetail: `Impact chrono ${formatSignedStopwatch(preview.whiteDelta)}`,
blackDetail: `Impact chrono ${formatSignedStopwatch(preview.blackDelta)}`,
whiteClock: `Chrono apres ${formatSignedClock(preview.whiteAfter)}`,
blackClock: `Chrono apres ${formatSignedClock(preview.blackAfter)}`,
};
}
const winner = white < black ? "white" : black < white ? "black" : null;
const isTie = winner === null;
return {
key: `twice:${match.blockNumber}:${match.cube.round}:${white}:${black}`,
title: isTie ? "Egalite parfaite" : "Résumé du cube",
winner: winner ? playerName(match, winner) : "Egalite parfaite",
outcome: isTie ? "Rejouer la phase cube" : `${playerName(match, winner)} ouvrira la partie suivante`,
summary: isTie
? "Le règlement Twice impose de rejouer immédiatement la phase cube."
: "Validez ce résultat pour préparer la partie suivante.",
actionLabel: isTie ? "Rejouer la phase cube" : "Appliquer et ouvrir la page chrono",
whiteName,
blackName,
whiteTime: `Temps cube ${formatStopwatch(white)}`,
blackTime: `Temps cube ${formatStopwatch(black)}`,
whiteDetail: winner === "white" ? "Gagne la phase cube" : isTie ? "Egalite parfaite" : "Ne gagne pas la phase cube",
blackDetail: winner === "black" ? "Gagne la phase cube" : isTie ? "Egalite parfaite" : "Ne gagne pas la phase cube",
whiteClock: "Aucun impact chrono en mode Twice",
blackClock: "Aucun impact chrono en mode Twice",
};
}
function renderResultModal() {
const resultState = getCubeResultState();
if (!resultState) {
closeResultModal();
resultModalKey = null;
return;
}
refs.resultModalTitle.textContent = resultState.title;
refs.resultSummary.textContent = resultState.summary;
refs.resultWinner.textContent = resultState.winner;
refs.resultOutcome.textContent = resultState.outcome;
refs.resultWhiteName.textContent = resultState.whiteName;
refs.resultBlackName.textContent = resultState.blackName;
refs.resultWhiteTime.textContent = resultState.whiteTime;
refs.resultBlackTime.textContent = resultState.blackTime;
refs.resultWhiteDetail.textContent = resultState.whiteDetail;
refs.resultBlackDetail.textContent = resultState.blackDetail;
refs.resultWhiteClock.textContent = resultState.whiteClock;
refs.resultBlackClock.textContent = resultState.blackClock;
refs.resultActionButton.textContent = resultState.actionLabel;
if (resultState.key !== resultModalKey) {
resultModalKey = resultState.key;
openResultModal();
}
}
function render() {
const blockHeading = formatBlockHeading(match, match.blockNumber);
const timePreview =
@@ -1009,7 +1147,7 @@ function initCubePage() {
refs.spineLabel.textContent = "Reglement";
refs.spineHeadline.textContent = "Rejouer la phase cube";
refs.spineText.textContent = "Le mode Twice impose de relancer immediatement la phase cube en cas d'egalite parfaite.";
refs.primaryButton.textContent = "Rejouer la phase cube";
refs.primaryButton.textContent = "Voir le résumé du cube";
refs.helpStatus.textContent = refs.spineText.textContent;
} else if (match.cube.times.white !== null && match.cube.times.black !== null) {
if (timePreview) {
@@ -1030,7 +1168,7 @@ function initCubePage() {
refs.spineHeadline.textContent = "Ouvrir la page chrono";
refs.spineText.textContent = "Appliquer le resultat du cube pour preparer la partie suivante.";
}
refs.primaryButton.textContent = "Appliquer et ouvrir la page chrono";
refs.primaryButton.textContent = "Voir le résumé du cube";
refs.helpStatus.textContent = refs.spineText.textContent;
} else if (match.cube.running) {
refs.centerLabel.textContent = "Etat";
@@ -1060,6 +1198,7 @@ function initCubePage() {
renderCubeZone("black");
renderCubeZone("white");
renderResultModal();
refs.primaryButton.disabled =
!match.result &&
!(

View File

@@ -135,6 +135,60 @@
</div>
</section>
<section class="modal hidden" id="cubeResultModal" aria-hidden="true">
<div class="modal-backdrop" data-close-cube-result-modal="true"></div>
<div class="modal-card result-modal-card">
<div class="modal-head">
<div>
<p class="eyebrow">Fin de phase cube</p>
<h2 id="cubeResultModalTitle">Résumé du cube</h2>
</div>
<button class="button ghost small" id="closeCubeResultButton" type="button">
Fermer
</button>
</div>
<p class="section-copy" id="cubeResultSummary"></p>
<div class="cube-result-overview">
<article class="result-pill-card">
<span>Vainqueur cube</span>
<strong id="cubeResultWinner">--</strong>
</article>
<article class="result-pill-card">
<span>Suite</span>
<strong id="cubeResultOutcome">--</strong>
</article>
</div>
<div class="cube-result-player-grid">
<article class="cube-result-player-card">
<span class="seat-tag light-seat">Blanc</span>
<strong id="cubeResultWhiteName">Blanc</strong>
<span id="cubeResultWhiteTime">Temps cube --</span>
<span id="cubeResultWhiteDetail">Résultat --</span>
<span id="cubeResultWhiteClock">Chrono après --</span>
</article>
<article class="cube-result-player-card">
<span class="seat-tag dark-seat">Noir</span>
<strong id="cubeResultBlackName">Noir</strong>
<span id="cubeResultBlackTime">Temps cube --</span>
<span id="cubeResultBlackDetail">Résultat --</span>
<span id="cubeResultBlackClock">Chrono après --</span>
</article>
</div>
<div class="modal-actions">
<button class="button primary" id="cubeResultActionButton" type="button">
Appliquer et ouvrir la page chrono
</button>
<button class="button ghost" id="cubeResultDismissButton" type="button">
Revenir à la phase cube
</button>
</div>
</div>
</section>
<script type="module" src="app.js"></script>
</body>
</html>

View File

@@ -888,6 +888,47 @@ body[data-page="cube"] .zone-button.cube-hold-ready::after {
margin-top: 1rem;
}
.result-modal-card {
width: min(880px, 100%);
}
.cube-result-overview {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.8rem;
margin-top: 1rem;
}
.result-pill-card,
.cube-result-player-card {
display: grid;
gap: 0.35rem;
padding: 1rem;
border-radius: 22px;
border: 1px solid var(--panel-border);
background: var(--panel-alt);
}
.result-pill-card span,
.cube-result-player-card span {
color: var(--muted);
}
.result-pill-card strong {
font-size: clamp(1.3rem, 3vw, 2rem);
}
.cube-result-player-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.8rem;
margin-top: 1rem;
}
.cube-result-player-card strong {
font-size: clamp(1.15rem, 2.8vw, 1.8rem);
}
.rules-shell {
position: relative;
width: min(1220px, calc(100% - 2rem));
@@ -1219,6 +1260,11 @@ body[data-page="cube"] .zone-button.cube-hold-ready::after {
display: grid;
}
.cube-result-overview,
.cube-result-player-grid {
grid-template-columns: 1fr;
}
.phase-shell {
gap: 0.55rem;
padding: 0.55rem 0 0.55rem;