Verrouille chrono et cube au viewport mobile

This commit is contained in:
2026-04-12 18:02:26 +02:00
parent de962eb8a0
commit 9444b9b64f
3 changed files with 206 additions and 1 deletions

16
app.js
View File

@@ -37,6 +37,17 @@ const MODES = {
}, },
}; };
syncViewportHeight();
window.addEventListener("resize", syncViewportHeight);
window.addEventListener("pageshow", syncViewportHeight);
window.addEventListener("orientationchange", syncViewportHeight);
window.visualViewport?.addEventListener("resize", syncViewportHeight);
window.visualViewport?.addEventListener("scroll", syncViewportHeight);
window.setTimeout(syncViewportHeight, 0);
window.requestAnimationFrame(() => {
window.requestAnimationFrame(syncViewportHeight);
});
let match = readStoredMatch(); let match = readStoredMatch();
let dirty = false; let dirty = false;
@@ -79,6 +90,11 @@ switch (PAGE) {
break; break;
} }
function syncViewportHeight() {
const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
document.documentElement.style.setProperty("--app-viewport-height", `${Math.round(viewportHeight)}px`);
}
function initSetupPage() { function initSetupPage() {
const form = document.querySelector("#setupForm"); const form = document.querySelector("#setupForm");
const summary = document.querySelector("#setupSummary"); const summary = document.querySelector("#setupSummary");

View File

@@ -21,7 +21,7 @@
<link rel="stylesheet" href="styles.css" /> <link rel="stylesheet" href="styles.css" />
</head> </head>
<body data-page="chrono" class="phase-body"> <body data-page="chrono" class="phase-body">
<main class="phase-shell"> <main class="phase-shell chrono-stage">
<header class="phase-header"> <header class="phase-header">
<a class="brand-link brand-link-logo" href="application.html" aria-label="Retour à l'application"> <a class="brand-link brand-link-logo" href="application.html" aria-label="Retour à l'application">
<img class="brand-link-icon" src="logo.png" alt="Icône ChessCubing" /> <img class="brand-link-icon" src="logo.png" alt="Icône ChessCubing" />

View File

@@ -2131,3 +2131,192 @@ textarea:focus {
justify-self: end; justify-self: end;
} }
} }
/* Lock chrono and cube to the visual viewport to avoid iOS bottom gaps. */
body[data-page="chrono"],
body[data-page="cube"] {
position: fixed;
inset: 0;
width: 100%;
min-height: var(--app-viewport-height, 100dvh);
height: var(--app-viewport-height, 100dvh);
max-height: var(--app-viewport-height, 100dvh);
overflow: hidden;
overscroll-behavior: none;
}
body[data-page="chrono"] .chrono-stage,
body[data-page="cube"] .cube-shell {
position: absolute;
inset: 0;
width: 100%;
min-width: 0;
margin: 0;
min-height: 100%;
height: 100%;
max-height: 100%;
display: grid;
grid-template-rows: auto auto minmax(0, 1fr);
gap: 0.55rem;
padding:
calc(var(--phase-top-offset) + 0.55rem)
calc(var(--safe-right) + 0.5rem)
calc(var(--safe-bottom) + 0.55rem)
calc(var(--safe-left) + 0.5rem);
overflow: hidden;
}
body[data-page="chrono"] .faceoff-board,
body[data-page="cube"] .faceoff-board {
min-height: 0;
height: 100%;
align-self: stretch;
}
body[data-page="chrono"] .status-strip {
flex: 0 0 auto;
}
body[data-page="chrono"] .player-zone,
body[data-page="chrono"] .phase-spine {
min-height: 0;
display: flex;
}
body[data-page="chrono"] .zone-inner {
flex: 1 1 auto;
min-height: 0;
display: grid;
grid-template-rows: auto minmax(0, 1fr) auto;
}
body[data-page="chrono"] .zone-button {
min-height: 0;
height: 100%;
flex: 1 1 auto;
}
body[data-page="chrono"] .phase-spine {
align-items: stretch;
}
body[data-page="chrono"] .spine-button {
width: 100%;
}
@media (max-width: 900px) {
body[data-page="chrono"] .chrono-stage,
body[data-page="cube"] .cube-shell {
grid-template-rows: auto auto minmax(0, 1fr);
gap: 0.35rem;
padding:
calc(var(--phase-top-offset) + 0.45rem)
calc(var(--safe-right) + 0.45rem)
calc(var(--safe-bottom) + 0.45rem)
calc(var(--safe-left) + 0.45rem);
}
body[data-page="chrono"] .phase-header,
body[data-page="cube"] .phase-header {
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.35rem;
padding: 0.5rem 0.55rem;
}
body[data-page="chrono"] .phase-title,
body[data-page="cube"] .phase-title {
display: none;
}
body[data-page="chrono"] .brand-link-logo,
body[data-page="cube"] .brand-link-logo {
justify-self: start;
}
body[data-page="chrono"] .utility-button,
body[data-page="cube"] .utility-button {
justify-self: end;
}
body[data-page="chrono"] .status-strip,
body[data-page="cube"] .status-strip {
gap: 0.3rem;
}
body[data-page="chrono"] .status-card,
body[data-page="cube"] .status-card {
min-height: 3rem;
padding: 0.3rem 0.2rem;
border-radius: 18px;
}
body[data-page="chrono"] .status-card span,
body[data-page="cube"] .status-card span {
font-size: 0.62rem;
}
body[data-page="chrono"] .status-card strong,
body[data-page="cube"] .status-card strong {
font-size: 0.9rem;
}
body[data-page="chrono"] .faceoff-board,
body[data-page="cube"] .faceoff-board {
grid-template-columns: 1fr;
grid-template-rows: minmax(0, 1fr) auto minmax(0, 1fr);
gap: 0.35rem;
}
body[data-page="chrono"] .phase-spine,
body[data-page="cube"] .phase-spine {
display: block;
}
body[data-page="chrono"] .phase-spine .spine-card,
body[data-page="cube"] .phase-spine .spine-card {
display: none;
}
body[data-page="chrono"] .zone-inner,
body[data-page="cube"] .zone-inner {
padding: 0.5rem;
gap: 0.4rem;
border-radius: 20px;
}
body[data-page="chrono"] .zone-head h2,
body[data-page="cube"] .zone-head h2 {
font-size: 0.92rem;
}
body[data-page="chrono"] .zone-stats strong,
body[data-page="cube"] .zone-stats strong {
font-size: 0.86rem;
}
body[data-page="chrono"] .zone-stats span,
body[data-page="cube"] .zone-stats span {
font-size: 0.72rem;
}
body[data-page="chrono"] .seat-tag,
body[data-page="cube"] .seat-tag {
padding: 0.22rem 0.55rem;
font-size: 0.62rem;
}
body[data-page="chrono"] .zone-button,
body[data-page="cube"] .zone-button {
padding: 0.75rem;
border-radius: 22px;
font-size: clamp(1.22rem, 7vw, 1.75rem);
}
body[data-page="chrono"] .spine-button,
body[data-page="cube"] .spine-button {
min-height: 2.7rem;
padding: 0.6rem;
border-radius: 18px;
font-size: 0.9rem;
}
}