diff --git a/html/index.html b/html/index.html index 9e4b17c..9684d57 100644 --- a/html/index.html +++ b/html/index.html @@ -1,6 +1,8 @@ + + Wownerogue - A Wownero Roguelike @@ -81,6 +83,7 @@ +
@@ -368,5 +371,13 @@ }, 100); // 100ms delay to ensure everything is loaded }); + + + \ No newline at end of file diff --git a/html/js/input/touchControls.js b/html/js/input/touchControls.js new file mode 100644 index 0000000..67a1352 --- /dev/null +++ b/html/js/input/touchControls.js @@ -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); +})(); diff --git a/html/styles/stylesheet.css b/html/styles/stylesheet.css index 2ec323f..85294b5 100644 --- a/html/styles/stylesheet.css +++ b/html/styles/stylesheet.css @@ -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; }