From 795764c35947f1ff9145c717a9bccc91b79a72d4 Mon Sep 17 00:00:00 2001 From: Christophe Date: Sun, 12 Apr 2026 16:33:24 +0200 Subject: [PATCH] Refond le chrono en style pendule --- app.js | 73 +++++++++++-- chrono.html | 107 ++++++++++++++------ styles.css | 287 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 427 insertions(+), 40 deletions(-) diff --git a/app.js b/app.js index 7ab7594..9e31160 100644 --- a/app.js +++ b/app.js @@ -209,10 +209,20 @@ function initChronoPage() { blackHint: document.querySelector("#blackHintChrono"), whiteButton: document.querySelector("#whiteMoveButton"), blackButton: document.querySelector("#blackMoveButton"), + whiteAction: document.querySelector("#whiteActionChrono"), + blackAction: document.querySelector("#blackActionChrono"), + whiteDisplayClock: document.querySelector("#whiteDisplayClock"), + blackDisplayClock: document.querySelector("#blackDisplayClock"), + whiteDisplayMeta: document.querySelector("#whiteDisplayMeta"), + blackDisplayMeta: document.querySelector("#blackDisplayMeta"), + whiteQuota: document.querySelector("#whiteQuotaChrono"), + blackQuota: document.querySelector("#blackQuotaChrono"), whiteZone: document.querySelector("#whiteZone"), blackZone: document.querySelector("#blackZone"), openArbiterButton: document.querySelector("#openArbiterButton"), closeArbiterButton: document.querySelector("#closeArbiterButton"), + primaryLabel: document.querySelector("#primaryChronoLabel"), + quickResetButton: document.querySelector("#quickResetChronoButton"), arbiterModal: document.querySelector("#arbiterModal"), arbiterStatus: document.querySelector("#arbiterStatus"), arbiterPauseButton: document.querySelector("#arbiterPauseButton"), @@ -240,6 +250,15 @@ function initChronoPage() { refs.whiteButton?.addEventListener("click", () => handleChronoTap("white")); refs.blackButton?.addEventListener("click", () => handleChronoTap("black")); refs.primaryButton?.addEventListener("click", handlePrimaryChronoAction); + refs.quickResetButton?.addEventListener("click", () => { + const shouldReset = window.confirm("Reinitialiser le match en cours et revenir a l'accueil ?"); + if (!shouldReset) { + return; + } + + clearMatch(); + replaceTo(SETUP_PAGE); + }); refs.arbiterPauseButton?.addEventListener("click", () => { syncRunningState(match); @@ -371,28 +390,36 @@ function initChronoPage() { const moves = isWhite ? refs.whiteMoves : refs.blackMoves; const clock = isWhite ? refs.whiteClock : refs.blackClock; const hint = isWhite ? refs.whiteHint : refs.blackHint; + const action = isWhite ? refs.whiteAction : refs.blackAction; + const displayClock = isWhite ? refs.whiteDisplayClock : refs.blackDisplayClock; + const displayMeta = isWhite ? refs.whiteDisplayMeta : refs.blackDisplayMeta; + const quota = isWhite ? refs.whiteQuota : refs.blackQuota; const zone = isWhite ? refs.whiteZone : refs.blackZone; const actorName = playerName(match, color); const active = match.currentTurn === color; + const display = renderChronoZoneDisplay(match, color, active); name.textContent = actorName; moves.textContent = `${match.moves[color]} / ${match.quota}`; clock.textContent = match.clocks ? `Chrono ${formatSignedClock(match.clocks[color])}` : `Dernier cube ${renderLastCube(match, color)}`; + displayClock.textContent = display.time; + displayMeta.textContent = display.label; + quota.textContent = `${match.moves[color]} / ${match.quota}`; button.classList.toggle("active-turn", active && !match.result); zone.classList.toggle("active-zone", active && !match.result); if (match.result) { - button.textContent = resultText(match); + action.textContent = resultText(match); button.disabled = true; hint.textContent = "Le match est termine."; return; } if (!match.running) { - button.textContent = "Partie en pause"; + action.textContent = "Partie en pause"; button.disabled = true; hint.textContent = active ? "La partie n'a pas encore demarre ou a ete mise en pause." @@ -401,27 +428,27 @@ function initChronoPage() { } if (!active) { - button.textContent = "Attends"; + action.textContent = "Attends"; button.disabled = true; hint.textContent = `${playerName(match, match.currentTurn)} est en train de jouer.`; return; } if (match.doubleCoup.step === 1) { - button.textContent = "1er coup gratuit"; + action.textContent = "1er coup gratuit"; button.disabled = false; hint.textContent = "Ce coup ne compte pas et ne doit pas donner echec."; return; } if (match.doubleCoup.step === 2) { - button.textContent = "2e coup du double"; + action.textContent = "2e coup du double"; button.disabled = false; hint.textContent = "Ce coup compte dans le quota et l'echec redevient autorise."; return; } - button.textContent = "J'ai fini mon coup"; + action.textContent = "J'ai fini mon coup"; button.disabled = false; hint.textContent = "Tape des que ton coup est joue sur l'echiquier."; } @@ -449,7 +476,10 @@ function initChronoPage() { refs.spineLabel.textContent = "Termine"; refs.spineHeadline.textContent = resultText(match); refs.spineText.textContent = "Retournez a la configuration pour lancer une nouvelle rencontre."; - refs.primaryButton.textContent = "Retour a l'accueil"; + refs.primaryLabel.textContent = "Retour a l'accueil"; + refs.primaryButton.dataset.state = "home"; + refs.primaryButton.setAttribute("aria-label", "Retour a l'accueil"); + refs.primaryButton.title = "Retour a l'accueil"; refs.arbiterStatus.textContent = "Le match est termine. Vous pouvez revenir a l'accueil ou reinitialiser."; } else if (match.running) { refs.centerLabel.textContent = "Trait"; @@ -458,7 +488,10 @@ function initChronoPage() { refs.spineHeadline.textContent = `Partie ${match.blockNumber} active`; 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.primaryLabel.textContent = "Pause arbitre"; + refs.primaryButton.dataset.state = "pause"; + refs.primaryButton.setAttribute("aria-label", "Mettre la partie en pause"); + refs.primaryButton.title = "Mettre la partie en pause"; refs.arbiterStatus.textContent = `Partie en cours. Joueur au trait : ${playerName(match, match.currentTurn)}.`; } else { refs.centerLabel.textContent = "Trait"; @@ -467,7 +500,10 @@ function initChronoPage() { refs.spineHeadline.textContent = `Partie ${match.blockNumber}`; refs.spineText.textContent = "Demarrez la partie, puis laissez uniquement les deux grandes zones aux joueurs. La page cube prendra automatiquement le relais."; - refs.primaryButton.textContent = "Demarrer la partie"; + refs.primaryLabel.textContent = "Demarrer la partie"; + refs.primaryButton.dataset.state = "play"; + refs.primaryButton.setAttribute("aria-label", "Demarrer la partie"); + refs.primaryButton.title = "Demarrer la partie"; refs.arbiterStatus.textContent = `Partie prete. ${playerName(match, match.currentTurn)} commencera.`; } @@ -1336,6 +1372,25 @@ function renderModeContext(storedMatch) { return "Le gagnant du cube ouvrira la prochaine partie"; } +function renderChronoZoneDisplay(storedMatch, color, active) { + if (storedMatch.clocks) { + return { + time: formatSignedClock(storedMatch.clocks[color]), + label: active ? "Chrono actif" : "Chrono joueur", + }; + } + + return active + ? { + time: formatClock(storedMatch.moveRemainingMs), + label: "Temps coup", + } + : { + time: formatClock(storedMatch.blockRemainingMs), + label: "Temps partie", + }; +} + function renderLastCube(storedMatch, color) { const last = storedMatch.cube.history.at(-1); if (!last) { diff --git a/chrono.html b/chrono.html index 56bc7b4..9319438 100644 --- a/chrono.html +++ b/chrono.html @@ -22,37 +22,84 @@
-
- -
-

Phase chrono

-

Partie 1

-

-
- + + + +
-
-
+
+

Phase chrono

+

Partie 1

+

+
Temps partie 03:00
-
+
Temps coup 00:20
-
+
Trait Blanc
+
+

Etat de la partie

+ Pret a demarrer +

+
-
+
@@ -67,25 +114,18 @@

-
-
-

État de la partie

- Prêt à démarrer -

-
- - -
-
@@ -100,7 +140,12 @@

diff --git a/styles.css b/styles.css index d1fb7f3..5f9c184 100644 --- a/styles.css +++ b/styles.css @@ -1857,3 +1857,290 @@ textarea:focus { font-size: 0.9rem; } } + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +body[data-page="chrono"] { + background: #2f2a26; + color: #f6f1ea; +} + +body[data-page="chrono"]::before, +body[data-page="chrono"]::after { + display: none; +} + +body[data-page="chrono"] .phase-shell { + width: 100%; + min-height: calc(100dvh - var(--safe-top) - var(--safe-bottom)); + padding: 0; + gap: 0; + grid-template-rows: auto minmax(0, 1fr); +} + +body[data-page="chrono"] .phase-header { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + align-items: stretch; + gap: 0; + padding: 0; + border: 0; + border-radius: 0; + background: #2b2724; + box-shadow: none; + backdrop-filter: none; +} + +body[data-page="chrono"] .chrono-toolbar-button { + appearance: none; + min-height: clamp(82px, 12vh, 112px); + border: 0; + border-radius: 0; + background: transparent; + color: rgba(255, 255, 255, 0.44); + display: grid; + place-items: center; + cursor: pointer; + transition: + background 160ms ease, + color 160ms ease; +} + +body[data-page="chrono"] .chrono-toolbar-button:hover { + background: rgba(255, 255, 255, 0.04); +} + +body[data-page="chrono"] .chrono-toolbar-button:focus-visible { + outline: 2px solid rgba(255, 255, 255, 0.28); + outline-offset: -4px; +} + +body[data-page="chrono"] .toolbar-icon { + width: clamp(2rem, 3vw, 2.8rem); + height: clamp(2rem, 3vw, 2.8rem); + display: block; +} + +body[data-page="chrono"] #primaryChronoButton { + color: rgba(255, 255, 255, 0.8); +} + +body[data-page="chrono"] #primaryChronoButton .toolbar-icon { + display: none; +} + +body[data-page="chrono"] #primaryChronoButton[data-state="play"] .toolbar-icon-play, +body[data-page="chrono"] #primaryChronoButton[data-state="pause"] .toolbar-icon-pause, +body[data-page="chrono"] #primaryChronoButton[data-state="home"] .toolbar-icon-home { + display: block; +} + +body[data-page="chrono"] .chrono-board { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0; + min-height: 0; +} + +body[data-page="chrono"] .player-zone { + min-height: 0; + color: #111111; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(0, 0, 0, 0.06)), + #7b756f; + transition: + background 160ms ease, + color 160ms ease; +} + +body[data-page="chrono"] .player-zone + .player-zone { + border-left: 1px solid rgba(0, 0, 0, 0.18); +} + +body[data-page="chrono"] .player-zone.active-zone { + color: #ffffff; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.08)), + #db9743; +} + +body[data-page="chrono"] .zone-inner { + height: 100%; + display: grid; + grid-template-rows: auto minmax(0, 1fr) auto; + gap: 1rem; + padding: 1.1rem 1rem 1rem; + border: 0; + border-radius: 0; + background: transparent; +} + +body[data-page="chrono"] .opponent-zone .mirrored-mobile { + transform: none; +} + +body[data-page="chrono"] .zone-head { + align-items: flex-start; + color: inherit; +} + +body[data-page="chrono"] .zone-head h2 { + margin-top: 0.35rem; + font-size: clamp(1.25rem, 2.2vw, 1.85rem); + color: inherit; +} + +body[data-page="chrono"] .zone-stats { + color: inherit; + opacity: 0.7; +} + +body[data-page="chrono"] .zone-stats strong, +body[data-page="chrono"] .zone-stats span { + color: inherit; +} + +body[data-page="chrono"] .zone-stats strong { + font-size: clamp(1.15rem, 2vw, 1.55rem); +} + +body[data-page="chrono"] .zone-stats span { + font-size: clamp(0.82rem, 1.3vw, 0.96rem); +} + +body[data-page="chrono"] .seat-tag { + background: rgba(0, 0, 0, 0.09); + border-color: rgba(0, 0, 0, 0.12); + color: inherit; + box-shadow: none; +} + +body[data-page="chrono"] .player-zone.active-zone .seat-tag { + background: rgba(255, 255, 255, 0.16); + border-color: rgba(255, 255, 255, 0.24); + color: #ffffff; +} + +body[data-page="chrono"] .light-seat, +body[data-page="chrono"] .dark-seat { + background-color: inherit; +} + +body[data-page="chrono"] .zone-button { + min-height: 0; + height: 100%; + padding: 0.9rem 0.8rem; + border: 0; + border-radius: 0; + background: transparent; + color: inherit; + text-shadow: none; + box-shadow: none; +} + +body[data-page="chrono"] .zone-button:hover, +body[data-page="chrono"] .zone-button:disabled { + transform: none; + filter: none; + opacity: 1; +} + +body[data-page="chrono"] .light-button, +body[data-page="chrono"] .dark-button, +body[data-page="chrono"] .zone-button.active-turn { + background: transparent; + color: inherit; + box-shadow: none; +} + +body[data-page="chrono"] .zone-button-shell { + position: relative; + min-height: 100%; + display: grid; + align-content: center; + justify-items: center; + gap: 0.7rem; + text-align: center; +} + +body[data-page="chrono"] .zone-button-meta { + font-size: clamp(0.9rem, 1.5vw, 1.1rem); + font-weight: 700; + letter-spacing: 0.18em; + text-transform: uppercase; + opacity: 0.64; +} + +body[data-page="chrono"] .zone-button-clock { + font-size: clamp(4.3rem, 12vw, 8.6rem); + font-weight: 900; + line-height: 0.9; + letter-spacing: -0.06em; +} + +body[data-page="chrono"] .zone-button-state { + font-size: clamp(1rem, 2vw, 1.45rem); + font-weight: 700; + opacity: 0.86; +} + +body[data-page="chrono"] .zone-button-corner { + position: absolute; + right: 0.15rem; + bottom: 0.1rem; + font-size: clamp(1.1rem, 2vw, 1.6rem); + font-weight: 700; + opacity: 0.28; +} + +body[data-page="chrono"] .zone-foot { + min-height: 2.8rem; + margin: 0; + color: inherit; + opacity: 0.7; + text-align: center; +} + +@media (orientation: portrait), (max-width: 900px) { + body[data-page="chrono"] .chrono-board { + grid-template-columns: 1fr; + grid-template-rows: repeat(2, minmax(0, 1fr)); + } + + body[data-page="chrono"] .player-zone + .player-zone { + border-left: 0; + border-top: 1px solid rgba(0, 0, 0, 0.18); + } + + body[data-page="chrono"] .opponent-zone .mirrored-mobile { + transform: rotate(180deg); + } + + body[data-page="chrono"] .zone-inner { + padding: 0.85rem 0.75rem 0.8rem; + gap: 0.75rem; + } + + body[data-page="chrono"] .zone-button-clock { + font-size: clamp(3.5rem, 17vw, 6.9rem); + } + + body[data-page="chrono"] .zone-button-state { + font-size: clamp(0.92rem, 3.4vw, 1.15rem); + } + + body[data-page="chrono"] .zone-foot { + min-height: 2.2rem; + font-size: 0.84rem; + } +}