mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-18 06:41:27 +02:00
feat(mobile): viewport, responsive layout, and touch controls
The layout was already partly responsive (max-width:100vw, min(480px,90vw)
modals, canvas max-width:100%, and the renderer centres a fixed viewport on the
player) but had no viewport meta, so none of it engaged on phones.
- Add the viewport (+charset) meta — unlocks the existing responsive rules.
- Mobile media query (<=820px): stack the 3-column grid into one column
(header/status/game/chat/input/footer), shrink fonts, and cap the fixed-width
play/history/leaderboard modals to 94vw so they fit instead of overflowing.
- On-screen D-pad + swipe-to-move (new touchControls.js), both emitting the
same player_move {direction} event the keyboard uses — no game-logic change.
Shown on small screens or any touch device (body.touch).
- Remove the leftover "GAME AREA" debug overlay.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<title>Wownerogue - A Wownero Roguelike</title>
|
||||
<link rel="stylesheet" type="text/css" href="styles/stylesheet.css">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" id="favicon">
|
||||
@@ -81,6 +83,7 @@
|
||||
<script src="js/network/socketHandlers.js"></script>
|
||||
<script src="js/network/smirkAuth.js"></script>
|
||||
<script src="js/input/inputHandler.js"></script>
|
||||
<script src="js/input/touchControls.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
@@ -368,5 +371,13 @@
|
||||
}, 100); // 100ms delay to ensure everything is loaded
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- On-screen movement D-pad (shown on touch/mobile via CSS) -->
|
||||
<div id="touch-dpad" aria-hidden="true">
|
||||
<button id="dpad-up" type="button" aria-label="Move up">▲</button>
|
||||
<button id="dpad-left" type="button" aria-label="Move left">◀</button>
|
||||
<button id="dpad-right" type="button" aria-label="Move right">▶</button>
|
||||
<button id="dpad-down" type="button" aria-label="Move down">▼</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Touch controls for mobile: an on-screen D-pad and swipe-to-move on the game area.
|
||||
* Both emit the SAME 'player_move' { direction } event the keyboard already uses, so no
|
||||
* game logic changes. The D-pad markup lives in index.html and is shown via CSS on small
|
||||
* screens; swipe is bound to #game-display.
|
||||
*/
|
||||
(function () {
|
||||
function move(direction) {
|
||||
if (direction && window.socket && window.socket.connected) {
|
||||
window.socket.emit('player_move', { direction: direction });
|
||||
}
|
||||
}
|
||||
|
||||
function initDpad() {
|
||||
var map = { 'dpad-up': 'up', 'dpad-down': 'down', 'dpad-left': 'left', 'dpad-right': 'right' };
|
||||
Object.keys(map).forEach(function (id) {
|
||||
var el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
// touchstart drives touch input; preventDefault cancels the emulated click so a
|
||||
// single tap doesn't move twice. click handles mouse (desktop testing).
|
||||
el.addEventListener('touchstart', function (e) { e.preventDefault(); move(map[id]); }, { passive: false });
|
||||
el.addEventListener('click', function () { move(map[id]); });
|
||||
});
|
||||
}
|
||||
|
||||
function initSwipe() {
|
||||
var area = document.getElementById('game-display');
|
||||
if (!area) return;
|
||||
var sx = 0, sy = 0, tracking = false;
|
||||
var THRESHOLD = 24; // px before a drag counts as a swipe
|
||||
area.addEventListener('touchstart', function (e) {
|
||||
if (e.touches.length !== 1) { tracking = false; return; }
|
||||
sx = e.touches[0].clientX; sy = e.touches[0].clientY; tracking = true;
|
||||
}, { passive: true });
|
||||
area.addEventListener('touchend', function (e) {
|
||||
if (!tracking) return;
|
||||
tracking = false;
|
||||
var t = e.changedTouches[0];
|
||||
var dx = t.clientX - sx, dy = t.clientY - sy;
|
||||
var adx = Math.abs(dx), ady = Math.abs(dy);
|
||||
if (adx < THRESHOLD && ady < THRESHOLD) return; // a tap, not a swipe
|
||||
if (adx > ady) move(dx > 0 ? 'right' : 'left');
|
||||
else move(dy > 0 ? 'down' : 'up');
|
||||
}, { passive: true });
|
||||
}
|
||||
|
||||
function init() {
|
||||
// Mark touch devices so the D-pad also shows on touch tablets / landscape phones
|
||||
// wider than the mobile media-query breakpoint.
|
||||
if ('ontouchstart' in window || (navigator.maxTouchPoints || 0) > 0) {
|
||||
document.body.classList.add('touch');
|
||||
}
|
||||
initDpad();
|
||||
initSwipe();
|
||||
}
|
||||
if (document.readyState !== 'loading') init();
|
||||
else document.addEventListener('DOMContentLoaded', init);
|
||||
})();
|
||||
+79
-12
@@ -86,18 +86,6 @@
|
||||
}
|
||||
|
||||
/* Add debug overlay to see container bounds */
|
||||
.rotdis::before {
|
||||
content: "GAME AREA";
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
color: rgba(255,255,255,0.3);
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Chat messages area */
|
||||
.chat-container {
|
||||
grid-area: chat;
|
||||
@@ -325,3 +313,82 @@ li.debug {
|
||||
color: #9cf6ff;
|
||||
}
|
||||
|
||||
|
||||
/* =========================================================================
|
||||
Mobile / touch support
|
||||
========================================================================= */
|
||||
#touch-dpad { display: none; }
|
||||
|
||||
@media (max-width: 820px) {
|
||||
/* Stack the desktop 3-column grid into a single column */
|
||||
.container {
|
||||
grid-template-areas:
|
||||
"header"
|
||||
"status"
|
||||
"game"
|
||||
"chat"
|
||||
"input"
|
||||
"footer";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto auto 1fr auto auto auto;
|
||||
height: 100vh;
|
||||
height: 100dvh; /* exclude mobile browser chrome where supported */
|
||||
}
|
||||
|
||||
.banner { font-size: 16px; padding: 4px 6px; }
|
||||
#staticbanner { font-size: 15px !important; }
|
||||
.status {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid #444;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 14px;
|
||||
padding: 6px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.chat-container { min-height: 0; }
|
||||
#messageBox { max-height: 16vh; }
|
||||
.footer { font-size: 9px; padding: 2px 4px; line-height: 1.3; }
|
||||
|
||||
/* Fixed-width modals would overflow tiny screens — make them fit */
|
||||
#payment-ui, #history-panel, #leaderboard-panel {
|
||||
min-width: 0 !important;
|
||||
width: 94vw !important;
|
||||
max-width: 94vw !important;
|
||||
max-height: 88vh !important;
|
||||
overflow-y: auto !important;
|
||||
padding: 14px !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* On-screen D-pad — styling defined unconditionally; shown on small screens (media query
|
||||
above) OR any touch device (body.touch set by touchControls.js for landscape/tablets). */
|
||||
@media (max-width: 820px) { #touch-dpad { display: grid; } }
|
||||
body.touch #touch-dpad { display: grid; }
|
||||
#touch-dpad {
|
||||
grid-template-areas: ". up ." "left . right" ". down .";
|
||||
gap: 8px;
|
||||
position: fixed;
|
||||
right: 12px;
|
||||
bottom: 70px;
|
||||
z-index: 1500;
|
||||
opacity: 0.55;
|
||||
touch-action: none;
|
||||
}
|
||||
#touch-dpad button {
|
||||
width: 54px; height: 54px;
|
||||
font-size: 22px;
|
||||
background: rgba(0,40,0,0.85);
|
||||
color: #0f0;
|
||||
border: 2px solid #0f0;
|
||||
border-radius: 10px;
|
||||
padding: 0;
|
||||
-webkit-user-select: none; user-select: none;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
#touch-dpad button:active { background: #0f0; color: #000; }
|
||||
#dpad-up { grid-area: up; }
|
||||
#dpad-down { grid-area: down; }
|
||||
#dpad-left { grid-area: left; }
|
||||
#dpad-right { grid-area: right; }
|
||||
|
||||
Reference in New Issue
Block a user