From 9eb1c2db3a9ba3285c8c3e756828315070ec66d7 Mon Sep 17 00:00:00 2001 From: type-two Date: Tue, 21 Jul 2026 01:37:37 +1000 Subject: [PATCH] THE THUMBS: phones get their keys back MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A touch strip for devices with no wheel and no keyboard — which is most of the phones the SHARE links land on. Each button IS its key: pointer down dispatches the real keydown, pointer up the real keyup, so the holds (crack charge, spoon drain) behave exactly like the physical key, and the heat arrows synthesise real wheel ticks at the canvas. Drags already worked — the input layer was pointer-events from birth. Only appears on touch devices (desktop cooks keep a clean bench; __tp() summons it for the curious), hides while the title is up, and the small- screen pass folds the rest: the title stacks and stops overflowing, the ledger fits, the judge's eye steps up out of the strip's way. Verified at 375x812: no horizontal scroll, buttons drive the real input path (heat arrows walked the poach knob 0 to 7 through the button handlers), pad hidden at title / shown in service, day-21 pot playable. Co-Authored-By: Claude Fable 5 --- src/main.ts | 5 ++++ src/style.css | 59 ++++++++++++++++++++++++++++++++++++++++++++++ src/ui/touchpad.ts | 50 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 src/ui/touchpad.ts 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); +}