fix(ui): robust default layout for club tablets

This commit is contained in:
Frank Schwenk
2026-09-24 16:35:28 +02:00
parent c876a80b7e
commit c93c9035ae
20 changed files with 299 additions and 525 deletions
+1 -46
View File
@@ -1,5 +1,5 @@
import { h } from 'preact';
import { useEffect, useCallback } from 'preact/hooks';
import { useCallback } from 'preact/hooks';
import {
useGameState,
@@ -35,51 +35,6 @@ export default function App() {
const validationModal = useValidationModal();
const completionModal = useCompletionModal();
// Keep layout height stable when the virtual keyboard opens on cheap Android tablets.
useEffect(() => {
const root = document.documentElement;
let rafId = 0;
let orientationTimer1 = 0;
let orientationTimer2 = 0;
const updateViewportVars = () => {
cancelAnimationFrame(rafId);
rafId = requestAnimationFrame(() => {
const viewport = window.visualViewport;
const layoutHeight = window.innerHeight;
const appHeight = viewport?.height ?? layoutHeight;
const keyboardOffset = viewport
? Math.max(0, layoutHeight - viewport.height - viewport.offsetTop)
: 0;
root.style.setProperty('--app-height', `${Math.round(appHeight)}px`);
root.style.setProperty('--keyboard-offset', `${Math.round(keyboardOffset)}px`);
});
};
const handleOrientationChange = () => {
updateViewportVars();
orientationTimer1 = window.setTimeout(updateViewportVars, 100);
orientationTimer2 = window.setTimeout(updateViewportVars, 350);
};
updateViewportVars();
window.addEventListener('resize', updateViewportVars);
window.addEventListener('orientationchange', handleOrientationChange);
window.visualViewport?.addEventListener('resize', updateViewportVars);
window.visualViewport?.addEventListener('scroll', updateViewportVars);
return () => {
cancelAnimationFrame(rafId);
window.clearTimeout(orientationTimer1);
window.clearTimeout(orientationTimer2);
window.removeEventListener('resize', updateViewportVars);
window.removeEventListener('orientationchange', handleOrientationChange);
window.visualViewport?.removeEventListener('resize', updateViewportVars);
window.visualViewport?.removeEventListener('scroll', updateViewportVars);
};
}, []);
// Game lifecycle handlers
const handleCreateGame = useCallback(async (gameData: any) => {
try {
+1 -1
View File
@@ -1,6 +1,6 @@
/* FullscreenToggle — top-right keeps bottom controls clear on scoreboard */
.fullscreenToggle {
position: fixed;
position: absolute;
top: calc(var(--space-md) + var(--safe-area-top));
right: calc(var(--space-md) + var(--safe-area-right));
width: var(--touch-target-comfortable);
+2 -2
View File
@@ -10,9 +10,9 @@ const FullscreenToggle = () => {
// Toggle fullscreen mode for the document
const handleToggle = useCallback(() => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
document.documentElement.requestFullscreen().catch(() => {});
} else {
document.exitFullscreen();
document.exitFullscreen().catch(() => {});
}
}, []);