114 lines
4.8 KiB
HTML
114 lines
4.8 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
<meta name="theme-color" content="#0f1720" />
|
|
<title>Arrowstack Airship</title>
|
|
<style>
|
|
body {
|
|
touch-action: none;
|
|
overscroll-behavior: none;
|
|
}
|
|
|
|
#mobile-controls {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 100;
|
|
pointer-events: none;
|
|
user-select: none;
|
|
-webkit-user-select: none;
|
|
}
|
|
|
|
.mobile-control {
|
|
position: absolute;
|
|
min-width: 62px;
|
|
height: 62px;
|
|
padding: 0 14px;
|
|
border: 1px solid rgba(255, 255, 255, 0.35);
|
|
border-radius: 18px;
|
|
background: rgba(7, 16, 24, 0.68);
|
|
color: #e8f2f7;
|
|
font: 700 18px/1 system-ui, sans-serif;
|
|
backdrop-filter: blur(6px);
|
|
-webkit-backdrop-filter: blur(6px);
|
|
pointer-events: auto;
|
|
touch-action: none;
|
|
}
|
|
|
|
.mobile-control:active,
|
|
.mobile-control.active {
|
|
background: rgba(140, 211, 255, 0.34);
|
|
transform: scale(0.96);
|
|
}
|
|
|
|
.mobile-control.left { left: max(14px, env(safe-area-inset-left)); bottom: max(18px, env(safe-area-inset-bottom)); }
|
|
.mobile-control.right { left: max(88px, calc(env(safe-area-inset-left) + 74px)); bottom: max(18px, env(safe-area-inset-bottom)); }
|
|
.mobile-control.throttle { right: max(14px, env(safe-area-inset-right)); bottom: max(92px, calc(env(safe-area-inset-bottom) + 74px)); }
|
|
.mobile-control.reverse { right: max(14px, env(safe-area-inset-right)); bottom: max(18px, env(safe-area-inset-bottom)); }
|
|
.mobile-control.port { left: max(14px, env(safe-area-inset-left)); bottom: max(100px, calc(env(safe-area-inset-bottom) + 82px)); font-size: 13px; }
|
|
.mobile-control.starboard { right: max(88px, calc(env(safe-area-inset-right) + 74px)); bottom: max(18px, env(safe-area-inset-bottom)); font-size: 13px; }
|
|
.mobile-control.restart { top: max(14px, env(safe-area-inset-top)); right: max(14px, env(safe-area-inset-right)); width: 48px; min-width: 48px; height: 48px; border-radius: 50%; }
|
|
|
|
@media (hover: hover) and (pointer: fine) {
|
|
#mobile-controls { display: none; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<div id="game"></div>
|
|
</div>
|
|
|
|
<div id="mobile-controls" aria-label="Touch controls">
|
|
<button class="mobile-control left" data-code="ArrowLeft" data-key="ArrowLeft" data-keycode="37" aria-label="Steer left">←</button>
|
|
<button class="mobile-control right" data-code="ArrowRight" data-key="ArrowRight" data-keycode="39" aria-label="Steer right">→</button>
|
|
<button class="mobile-control throttle" data-code="ArrowUp" data-key="ArrowUp" data-keycode="38" aria-label="Throttle">▲</button>
|
|
<button class="mobile-control reverse" data-code="ArrowDown" data-key="ArrowDown" data-keycode="40" aria-label="Reverse">▼</button>
|
|
<button class="mobile-control port" data-code="KeyQ" data-key="q" data-keycode="81" aria-label="Fire port broadside">PORT</button>
|
|
<button class="mobile-control starboard" data-code="KeyE" data-key="e" data-keycode="69" aria-label="Fire starboard broadside">STARBOARD</button>
|
|
<button class="mobile-control restart" data-code="KeyR" data-key="r" data-keycode="82" aria-label="Restart">↻</button>
|
|
</div>
|
|
|
|
<script type="module" src="/src/game-v2.js"></script>
|
|
<script type="module">
|
|
const dispatchKey = (type, button) => {
|
|
const keyCode = Number(button.dataset.keycode);
|
|
const event = new KeyboardEvent(type, {
|
|
key: button.dataset.key,
|
|
code: button.dataset.code,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
});
|
|
|
|
Object.defineProperty(event, 'keyCode', { get: () => keyCode });
|
|
Object.defineProperty(event, 'which', { get: () => keyCode });
|
|
window.dispatchEvent(event);
|
|
};
|
|
|
|
for (const button of document.querySelectorAll('.mobile-control')) {
|
|
const release = (event) => {
|
|
event.preventDefault();
|
|
dispatchKey('keyup', button);
|
|
button.classList.remove('active');
|
|
};
|
|
|
|
button.addEventListener('pointerdown', (event) => {
|
|
event.preventDefault();
|
|
button.setPointerCapture?.(event.pointerId);
|
|
dispatchKey('keydown', button);
|
|
button.classList.add('active');
|
|
});
|
|
|
|
button.addEventListener('pointerup', release);
|
|
button.addEventListener('pointercancel', release);
|
|
button.addEventListener('lostpointercapture', () => {
|
|
dispatchKey('keyup', button);
|
|
button.classList.remove('active');
|
|
});
|
|
button.addEventListener('contextmenu', (event) => event.preventDefault());
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|