diff --git a/src/main.ts b/src/main.ts index 5d44fe4..6933a0b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,7 @@ import * as THREE from 'three'; import { App, makeEnvironment, makeLights } from './core/app'; import { audio } from './core/audio'; +import { installTouchpad } from './ui/touchpad'; import { Game } from './game/game'; import './style.css'; @@ -40,4 +41,8 @@ window.addEventListener('keydown', (e) => { window.setTimeout(() => { w.style.opacity = '0'; }, 1800); }); +installTouchpad(canvas); +// Console cooks can summon the strip on desktop: __tp() +(window as unknown as { __tp: () => void }).__tp = () => installTouchpad(canvas, true); + app.start(); diff --git a/src/style.css b/src/style.css index a2b6d57..89e3e90 100644 --- a/src/style.css +++ b/src/style.css @@ -741,3 +741,62 @@ body.shadow .load { letter-spacing: 0.06em; margin: 2px 0 6px; } + +/* The touch strip - phones get their keys back. */ +#touchpad { + position: fixed; + bottom: 10px; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 6px; + z-index: 35; + touch-action: none; + user-select: none; + -webkit-user-select: none; +} +.tp-btn { + min-width: 44px; + height: 44px; + padding: 0 10px; + border-radius: 10px; + border: 1px solid #5a4a38; + background: rgba(30, 24, 18, 0.82); + color: #e8dcc4; + font: 700 14px ui-monospace, monospace; + touch-action: none; +} +.tp-btn:active { + background: #6a5335; +} +.tp-wide { padding: 0 14px; } +@media (min-width: 900px) and (hover: hover) { + #touchpad { display: none; } /* desktops with a mouse keep the clean bench */ +} + +/* Small screens: the bench folds to fit the phone. */ +#touchpad { + flex-wrap: wrap; + justify-content: center; + max-width: 100vw; + padding: 0 4px; +} +@media (max-width: 600px) { + #judge-eye { + bottom: 104px !important; + width: 60px !important; + height: 60px !important; + } + .title-wrap { + flex-direction: column; + align-items: center; + overflow-x: hidden; + } + .title-txt h1 { font-size: 44px; } + .title-art { max-width: 55vw; } + .title-txt { max-width: 94vw; } + .title-days { max-width: 94vw; } +} + +/* No keys while you read the menu - the title disposes itself on start. */ +body:has(#ui > .title) #touchpad { display: none; } diff --git a/src/ui/touchpad.ts b/src/ui/touchpad.ts new file mode 100644 index 0000000..fcd775c --- /dev/null +++ b/src/ui/touchpad.ts @@ -0,0 +1,50 @@ +/** + * The touch strip: phones have no wheel and no keyboard, and half the kitchen + * runs on both. Each button IS its key — pointer down dispatches keydown, + * pointer up dispatches keyup, so holds (the crack charge, the drain) work + * exactly like the physical key. The heat arrows synthesise wheel ticks. + * Only appears on touch devices; the buttons never steal from the canvas. + */ +export function installTouchpad(canvas: HTMLCanvasElement, force = false): void { + if (!force && !('ontouchstart' in window) && navigator.maxTouchPoints === 0) return; + const pad = document.createElement('div'); + pad.id = 'touchpad'; + document.body.appendChild(pad); + + const key = (label: string, code: string, wide = false) => { + const b = document.createElement('button'); + b.className = `tp-btn${wide ? ' tp-wide' : ''}`; + b.textContent = label; + b.addEventListener('pointerdown', (e) => { + e.preventDefault(); + b.setPointerCapture(e.pointerId); + window.dispatchEvent(new KeyboardEvent('keydown', { code })); + }); + const up = () => window.dispatchEvent(new KeyboardEvent('keyup', { code })); + b.addEventListener('pointerup', up); + b.addEventListener('pointercancel', up); + pad.appendChild(b); + return b; + }; + const wheelBtn = (label: string, dir: 1 | -1) => { + const b = document.createElement('button'); + b.className = 'tp-btn'; + b.textContent = label; + b.addEventListener('pointerdown', (e) => { + e.preventDefault(); + canvas.dispatchEvent(new WheelEvent('wheel', { deltaY: dir * 100, bubbles: true, cancelable: true })); + }); + pad.appendChild(b); + }; + + wheelBtn('▼', -1); // heat down + wheelBtn('▲', 1); // heat up + key('←', 'ArrowLeft'); + key('→', 'ArrowRight'); + key('B', 'KeyB'); + key('F', 'KeyF'); + key('D', 'KeyD'); + key('L', 'KeyL'); + key('HOLD ␣', 'Space', true); + key('SERVE', 'Enter', true); +}