nuts / commit
added vibration toggle and allow all platforms to run
index.html | 1 + src/compat.ts | 20 ++++++++------------ src/ui.ts | 16 ++++++++++++++++ src/vibration.ts | 11 +++++++++++ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/index.html b/index.html index 9ae4a9d..7255daa 100644 --- a/index.html +++ b/index.html @@ -79,6 +79,7 @@ <h2>Settings</h2> <button id="test-vibrate-btn" class="action-btn">Test vibration</button> + <button id="vibrate-toggle-btn" class="action-btn active">Vibration: On</button> <button id="close-settings" class="close-btn">Done</button> <div class="share-section"> diff --git a/src/compat.ts b/src/compat.ts index b35c5f7..e08244b 100644 --- a/src/compat.ts +++ b/src/compat.ts @@ -1,6 +1,11 @@ export type CompatResult = | { ok: true } - | { ok: false; reason: 'no-touch' | 'no-motion' | 'no-vibration' } + | { ok: false; reason: 'no-touch' | 'no-motion' } + +/** True if the Vibration API is available (Chrome Android). */ +export function hasVibration(): boolean { + return 'vibrate' in navigator && typeof navigator.vibrate === 'function' +} /** * Check whether this platform can run the full experience. @@ -17,26 +22,17 @@ export function checkCompat(): CompatResult { return { ok: false, reason: 'no-motion' } } - // Must have the Vibration API - if (!('vibrate' in navigator) || typeof navigator.vibrate !== 'function') { - return { ok: false, reason: 'no-vibration' } - } - return { ok: true } } const MESSAGES: Record<string, { headline: string; body: string }> = { 'no-touch': { headline: 'Phone only', - body: 'This app needs a touchscreen to work.\nOpen it on your Android phone in Chrome.', + body: 'This app needs a touchscreen to work.\nOpen it on your phone in Chrome.', }, 'no-motion': { headline: 'No accelerometer', - body: 'This app needs a motion sensor to work.\nOpen it on your Android phone in Chrome.', - }, - 'no-vibration': { - headline: 'No haptics', - body: 'This app needs vibration to work.\nOpen it in Chrome on your Android phone\n— Firefox and iOS don\'t support haptics.', + body: 'This app needs a motion sensor to work.\nOpen it on your phone in Chrome.', }, } diff --git a/src/ui.ts b/src/ui.ts index 34c2871..18a1c4d 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -1,6 +1,8 @@ import { CONFIG } from './config' import { dbg } from './debug' import QRCode from 'qrcode' +import { hasVibration } from './compat' +import { setVibrateEnabled, isVibrateEnabled } from './vibration' export type ItemKind = 'random' | 'nut' | 'gem' | 'balloon' | 'metalnut' @@ -36,6 +38,7 @@ export function initUI(callbacks: UICallbacks): void { const settingsPanel = document.getElementById('settings-panel') as HTMLDivElement const testVibrateBtn = document.getElementById('test-vibrate-btn') as HTMLButtonElement + const vibrateToggleBtn = document.getElementById('vibrate-toggle-btn') as HTMLButtonElement const closeBtn = document.getElementById('close-settings') as HTMLButtonElement const backdrop = settingsPanel.querySelector('.panel-backdrop') as HTMLDivElement const qrCanvas = document.getElementById('qr-canvas') as HTMLCanvasElement @@ -105,6 +108,12 @@ export function initUI(callbacks: UICallbacks): void { // ── Settings panel ─────────────────────────────────────────────────────── toolbar.hidden = false + // Hide vibration controls on unsupported platforms + if (!hasVibration()) { + testVibrateBtn.hidden = true + vibrateToggleBtn.hidden = true + } + settingsBtn.addEventListener('click', () => { settingsPanel.hidden = false refreshQR() @@ -121,6 +130,13 @@ export function initUI(callbacks: UICallbacks): void { setTimeout(() => { testVibrateBtn.textContent = orig }, 1500) }) + vibrateToggleBtn.addEventListener('click', () => { + const on = !isVibrateEnabled() + setVibrateEnabled(on) + vibrateToggleBtn.textContent = on ? 'Vibration: On' : 'Vibration: Off' + vibrateToggleBtn.classList.toggle('active', on) + }) + copyLinkBtn.addEventListener('click', () => { const url = callbacks.getShareURL() const orig = copyLinkBtn.textContent diff --git a/src/vibration.ts b/src/vibration.ts index 57bab60..734767e 100644 --- a/src/vibration.ts +++ b/src/vibration.ts @@ -4,12 +4,21 @@ import { dbg } from './debug' let lastVibrationAt = 0 let flashFn: (() => void) | undefined let unlocked = false +let vibrateEnabled = true /** Register a visual fallback called when the Vibration API is unavailable. */ export function setFlashFallback(fn: () => void): void { flashFn = fn } +export function setVibrateEnabled(on: boolean): void { + vibrateEnabled = on +} + +export function isVibrateEnabled(): boolean { + return vibrateEnabled +} + /** * Call once from a user-gesture handler (e.g. the tap-to-start). * Chrome requires at least one vibrate() call from a gesture context before @@ -56,6 +65,7 @@ export function vibrate(durationMs: number): void { // Chrome won't vibrate if the page is backgrounded or display is off if (document.visibilityState !== 'visible') return lastVibrationAt = now + if (!vibrateEnabled) return if (canVibrate()) { const ok = navigator.vibrate(durationMs) dbg('vibrate', durationMs, 'ms ->', ok) @@ -70,6 +80,7 @@ export function vibrateShake(): void { const now = Date.now() if (now - lastVibrationAt < CONFIG.vibrationCooldownMs) return lastVibrationAt = now + if (!vibrateEnabled) return if (canVibrate()) { navigator.vibrate([25, 15, 25]) } else {