Refond le chrono en style pendule

This commit is contained in:
2026-04-12 16:33:24 +02:00
parent d134947b14
commit 795764c359
3 changed files with 427 additions and 40 deletions

73
app.js
View File

@@ -209,10 +209,20 @@ function initChronoPage() {
blackHint: document.querySelector("#blackHintChrono"), blackHint: document.querySelector("#blackHintChrono"),
whiteButton: document.querySelector("#whiteMoveButton"), whiteButton: document.querySelector("#whiteMoveButton"),
blackButton: document.querySelector("#blackMoveButton"), 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"), whiteZone: document.querySelector("#whiteZone"),
blackZone: document.querySelector("#blackZone"), blackZone: document.querySelector("#blackZone"),
openArbiterButton: document.querySelector("#openArbiterButton"), openArbiterButton: document.querySelector("#openArbiterButton"),
closeArbiterButton: document.querySelector("#closeArbiterButton"), closeArbiterButton: document.querySelector("#closeArbiterButton"),
primaryLabel: document.querySelector("#primaryChronoLabel"),
quickResetButton: document.querySelector("#quickResetChronoButton"),
arbiterModal: document.querySelector("#arbiterModal"), arbiterModal: document.querySelector("#arbiterModal"),
arbiterStatus: document.querySelector("#arbiterStatus"), arbiterStatus: document.querySelector("#arbiterStatus"),
arbiterPauseButton: document.querySelector("#arbiterPauseButton"), arbiterPauseButton: document.querySelector("#arbiterPauseButton"),
@@ -240,6 +250,15 @@ function initChronoPage() {
refs.whiteButton?.addEventListener("click", () => handleChronoTap("white")); refs.whiteButton?.addEventListener("click", () => handleChronoTap("white"));
refs.blackButton?.addEventListener("click", () => handleChronoTap("black")); refs.blackButton?.addEventListener("click", () => handleChronoTap("black"));
refs.primaryButton?.addEventListener("click", handlePrimaryChronoAction); 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", () => { refs.arbiterPauseButton?.addEventListener("click", () => {
syncRunningState(match); syncRunningState(match);
@@ -371,28 +390,36 @@ function initChronoPage() {
const moves = isWhite ? refs.whiteMoves : refs.blackMoves; const moves = isWhite ? refs.whiteMoves : refs.blackMoves;
const clock = isWhite ? refs.whiteClock : refs.blackClock; const clock = isWhite ? refs.whiteClock : refs.blackClock;
const hint = isWhite ? refs.whiteHint : refs.blackHint; 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 zone = isWhite ? refs.whiteZone : refs.blackZone;
const actorName = playerName(match, color); const actorName = playerName(match, color);
const active = match.currentTurn === color; const active = match.currentTurn === color;
const display = renderChronoZoneDisplay(match, color, active);
name.textContent = actorName; name.textContent = actorName;
moves.textContent = `${match.moves[color]} / ${match.quota}`; moves.textContent = `${match.moves[color]} / ${match.quota}`;
clock.textContent = match.clocks clock.textContent = match.clocks
? `Chrono ${formatSignedClock(match.clocks[color])}` ? `Chrono ${formatSignedClock(match.clocks[color])}`
: `Dernier cube ${renderLastCube(match, 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); button.classList.toggle("active-turn", active && !match.result);
zone.classList.toggle("active-zone", active && !match.result); zone.classList.toggle("active-zone", active && !match.result);
if (match.result) { if (match.result) {
button.textContent = resultText(match); action.textContent = resultText(match);
button.disabled = true; button.disabled = true;
hint.textContent = "Le match est termine."; hint.textContent = "Le match est termine.";
return; return;
} }
if (!match.running) { if (!match.running) {
button.textContent = "Partie en pause"; action.textContent = "Partie en pause";
button.disabled = true; button.disabled = true;
hint.textContent = active hint.textContent = active
? "La partie n'a pas encore demarre ou a ete mise en pause." ? "La partie n'a pas encore demarre ou a ete mise en pause."
@@ -401,27 +428,27 @@ function initChronoPage() {
} }
if (!active) { if (!active) {
button.textContent = "Attends"; action.textContent = "Attends";
button.disabled = true; button.disabled = true;
hint.textContent = `${playerName(match, match.currentTurn)} est en train de jouer.`; hint.textContent = `${playerName(match, match.currentTurn)} est en train de jouer.`;
return; return;
} }
if (match.doubleCoup.step === 1) { if (match.doubleCoup.step === 1) {
button.textContent = "1er coup gratuit"; action.textContent = "1er coup gratuit";
button.disabled = false; button.disabled = false;
hint.textContent = "Ce coup ne compte pas et ne doit pas donner echec."; hint.textContent = "Ce coup ne compte pas et ne doit pas donner echec.";
return; return;
} }
if (match.doubleCoup.step === 2) { if (match.doubleCoup.step === 2) {
button.textContent = "2e coup du double"; action.textContent = "2e coup du double";
button.disabled = false; button.disabled = false;
hint.textContent = "Ce coup compte dans le quota et l'echec redevient autorise."; hint.textContent = "Ce coup compte dans le quota et l'echec redevient autorise.";
return; return;
} }
button.textContent = "J'ai fini mon coup"; action.textContent = "J'ai fini mon coup";
button.disabled = false; button.disabled = false;
hint.textContent = "Tape des que ton coup est joue sur l'echiquier."; hint.textContent = "Tape des que ton coup est joue sur l'echiquier.";
} }
@@ -449,7 +476,10 @@ function initChronoPage() {
refs.spineLabel.textContent = "Termine"; refs.spineLabel.textContent = "Termine";
refs.spineHeadline.textContent = resultText(match); refs.spineHeadline.textContent = resultText(match);
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.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."; refs.arbiterStatus.textContent = "Le match est termine. Vous pouvez revenir a l'accueil ou reinitialiser.";
} else if (match.running) { } else if (match.running) {
refs.centerLabel.textContent = "Trait"; refs.centerLabel.textContent = "Trait";
@@ -458,7 +488,10 @@ function initChronoPage() {
refs.spineHeadline.textContent = `Partie ${match.blockNumber} active`; refs.spineHeadline.textContent = `Partie ${match.blockNumber} active`;
refs.spineText.textContent = 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."; "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)}.`; refs.arbiterStatus.textContent = `Partie en cours. Joueur au trait : ${playerName(match, match.currentTurn)}.`;
} else { } else {
refs.centerLabel.textContent = "Trait"; refs.centerLabel.textContent = "Trait";
@@ -467,7 +500,10 @@ function initChronoPage() {
refs.spineHeadline.textContent = `Partie ${match.blockNumber}`; refs.spineHeadline.textContent = `Partie ${match.blockNumber}`;
refs.spineText.textContent = refs.spineText.textContent =
"Demarrez la partie, puis laissez uniquement les deux grandes zones aux joueurs. La page cube prendra automatiquement le relais."; "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.`; 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"; 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) { function renderLastCube(storedMatch, color) {
const last = storedMatch.cube.history.at(-1); const last = storedMatch.cube.history.at(-1);
if (!last) { if (!last) {

View File

@@ -22,37 +22,84 @@
</head> </head>
<body data-page="chrono" class="phase-body"> <body data-page="chrono" class="phase-body">
<main class="phase-shell"> <main class="phase-shell">
<header class="phase-header"> <header class="phase-header chrono-toolbar">
<a class="brand-link brand-link-logo" href="application.html" aria-label="Retour à l'application"> <button
<img class="brand-link-icon" src="logo.png" alt="Icône ChessCubing" /> class="chrono-toolbar-button"
<img class="brand-link-mark" src="transparent.png" alt="Logo ChessCubing" /> id="openArbiterButton"
</a> type="button"
<div class="phase-title"> aria-label="Ouvrir les outils arbitre"
<p class="eyebrow">Phase chrono</p> title="Outils arbitre"
<h1 id="chronoTitle">Partie 1</h1> >
<p id="chronoSubtitle" class="phase-subtitle"></p> <svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true">
</div> <path
<button class="button ghost small utility-button" id="openArbiterButton" type="button"> d="M19.14 12.94a7.43 7.43 0 0 0 .05-.94 7.43 7.43 0 0 0-.05-.94l2.03-1.58a.5.5 0 0 0 .12-.64l-1.92-3.32a.5.5 0 0 0-.6-.22l-2.39.96a7.28 7.28 0 0 0-1.63-.94l-.36-2.54a.49.49 0 0 0-.49-.42h-3.84a.49.49 0 0 0-.49.42l-.36 2.54c-.58.22-1.13.52-1.63.94l-2.39-.96a.5.5 0 0 0-.6.22L2.71 8.84a.5.5 0 0 0 .12.64l2.03 1.58a7.43 7.43 0 0 0-.05.94c0 .31.02.63.05.94l-2.03 1.58a.5.5 0 0 0-.12.64l1.92 3.32a.5.5 0 0 0 .6.22l2.39-.96c.5.42 1.05.72 1.63.94l.36 2.54a.49.49 0 0 0 .49.42h3.84a.49.49 0 0 0 .49-.42l.36-2.54c.58-.22 1.13-.52 1.63-.94l2.39.96a.5.5 0 0 0 .6-.22l1.92-3.32a.5.5 0 0 0-.12-.64l-2.03-1.58ZM12 15.5A3.5 3.5 0 1 1 12 8.5a3.5 3.5 0 0 1 0 7Z"
Arbitre fill="currentColor"
/>
</svg>
<span class="sr-only">Outils arbitre</span>
</button>
<button
class="chrono-toolbar-button"
id="primaryChronoButton"
type="button"
aria-label="Demarrer la partie"
title="Demarrer la partie"
data-state="play"
>
<svg class="toolbar-icon toolbar-icon-play" viewBox="0 0 24 24" aria-hidden="true">
<path d="M8 5.14v13.72a1 1 0 0 0 1.53.85l10.03-6.86a1 1 0 0 0 0-1.7L9.53 4.29A1 1 0 0 0 8 5.14Z" fill="currentColor" />
</svg>
<svg class="toolbar-icon toolbar-icon-pause" viewBox="0 0 24 24" aria-hidden="true">
<path d="M7 5.5h3.5a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-11a1 1 0 0 1 1-1Zm6.5 0H17a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1h-3.5a1 1 0 0 1-1-1v-11a1 1 0 0 1 1-1Z" fill="currentColor" />
</svg>
<svg class="toolbar-icon toolbar-icon-home" viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 4.25 4.75 10.2v8.05a.75.75 0 0 0 .75.75h4.25v-5.25h4.5V19H18.5a.75.75 0 0 0 .75-.75V10.2L12 4.25Z" fill="currentColor" />
</svg>
<span class="sr-only" id="primaryChronoLabel">Demarrer la partie</span>
</button>
<button
class="chrono-toolbar-button"
id="quickResetChronoButton"
type="button"
aria-label="Reinitialiser le match"
title="Reinitialiser le match"
>
<svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true">
<path
d="M12 4.5a7.5 7.5 0 1 1-6.38 3.56H3.75a.75.75 0 0 1 0-1.5h4a.75.75 0 0 1 .75.75v4a.75.75 0 0 1-1.5 0V9.48A6 6 0 1 0 12 6a.75.75 0 0 1 0-1.5Z"
fill="currentColor"
/>
</svg>
<span class="sr-only">Reinitialiser le match</span>
</button> </button>
</header> </header>
<section class="status-strip"> <section class="sr-only" aria-label="Informations du chrono">
<article class="status-card"> <p class="eyebrow">Phase chrono</p>
<h1 id="chronoTitle">Partie 1</h1>
<p id="chronoSubtitle" class="phase-subtitle"></p>
<article>
<span>Temps partie</span> <span>Temps partie</span>
<strong id="blockTimer">03:00</strong> <strong id="blockTimer">03:00</strong>
</article> </article>
<article class="status-card"> <article>
<span>Temps coup</span> <span>Temps coup</span>
<strong id="moveTimer">00:20</strong> <strong id="moveTimer">00:20</strong>
</article> </article>
<article class="status-card wide"> <article>
<span id="chronoCenterLabel">Trait</span> <span id="chronoCenterLabel">Trait</span>
<strong id="chronoCenterValue">Blanc</strong> <strong id="chronoCenterValue">Blanc</strong>
</article> </article>
<article>
<p class="micro-label" id="spineLabel">Etat de la partie</p>
<strong id="spineHeadline">Pret a demarrer</strong>
<p id="spineText"></p>
</article>
</section> </section>
<section class="faceoff-board"> <section class="faceoff-board chrono-board">
<article class="player-zone opponent-zone" id="blackZone"> <article class="player-zone opponent-zone" id="blackZone">
<div class="zone-inner mirrored-mobile"> <div class="zone-inner mirrored-mobile">
<div class="zone-head"> <div class="zone-head">
@@ -67,25 +114,18 @@
</div> </div>
<button class="zone-button dark-button" id="blackMoveButton" type="button"> <button class="zone-button dark-button" id="blackMoveButton" type="button">
En attente <span class="zone-button-shell">
<span class="zone-button-meta" id="blackDisplayMeta">Temps partie</span>
<span class="zone-button-clock" id="blackDisplayClock">03:00</span>
<span class="zone-button-state" id="blackActionChrono">En attente</span>
<span class="zone-button-corner" id="blackQuotaChrono">0 / 6</span>
</span>
</button> </button>
<p class="zone-foot" id="blackHintChrono"></p> <p class="zone-foot" id="blackHintChrono"></p>
</div> </div>
</article> </article>
<article class="phase-spine">
<div class="spine-card">
<p class="micro-label" id="spineLabel">État de la partie</p>
<strong id="spineHeadline">Prêt à démarrer</strong>
<p id="spineText"></p>
</div>
<button class="button primary spine-button" id="primaryChronoButton" type="button">
Démarrer la partie
</button>
</article>
<article class="player-zone" id="whiteZone"> <article class="player-zone" id="whiteZone">
<div class="zone-inner"> <div class="zone-inner">
<div class="zone-head"> <div class="zone-head">
@@ -100,7 +140,12 @@
</div> </div>
<button class="zone-button light-button" id="whiteMoveButton" type="button"> <button class="zone-button light-button" id="whiteMoveButton" type="button">
En attente <span class="zone-button-shell">
<span class="zone-button-meta" id="whiteDisplayMeta">Temps coup</span>
<span class="zone-button-clock" id="whiteDisplayClock">00:20</span>
<span class="zone-button-state" id="whiteActionChrono">En attente</span>
<span class="zone-button-corner" id="whiteQuotaChrono">0 / 6</span>
</span>
</button> </button>
<p class="zone-foot" id="whiteHintChrono"></p> <p class="zone-foot" id="whiteHintChrono"></p>

View File

@@ -1857,3 +1857,290 @@ textarea:focus {
font-size: 0.9rem; 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;
}
}