diff --git a/public/js/ui-input-handler-module.js b/public/js/ui-input-handler-module.js index ffa700d..112ee45 100644 --- a/public/js/ui-input-handler-module.js +++ b/public/js/ui-input-handler-module.js @@ -308,6 +308,18 @@ class UIInputHandlerModule extends BaseModule { normalizeProcessState(state) { const playbackCoordinator = this.getModule('playback-coordinator'); const isPlaying = Boolean(playbackCoordinator?.isPlaying); + // The player is in control when an input prompt is open AND the book is not actively + // playing a sentence (the timeline owns webglBookPlaybackActive). Then the cursor must + // show the input/server state, never the playback feather — even if a stale playing-* + // state lingers — so strip the playback overlay. While a sentence is actually playing + // the feather wins, even if an input mode is still set from the previous turn. + const playbackActive = document.documentElement.dataset.webglBookPlaybackActive === 'true'; + const awaitingPlayer = !playbackActive && ['choice', 'text', 'end'].includes(this.inputMode); + if (awaitingPlayer) { + if (state === 'playing-ready') return 'ready'; + if (state === 'playing-generating') return 'waiting-generating'; + return state; + } if (isPlaying && state === 'ready') { return 'playing-ready'; @@ -345,6 +357,12 @@ class UIInputHandlerModule extends BaseModule { this.setInputModeDataset(); const state = document.documentElement.dataset.processState || 'loading'; this.setInputAvailability(this.inputMode === 'text' && state === 'ready'); + // Opening an input-awaiting prompt hands control to the player; reflect that in the + // cursor immediately instead of leaving the prior playback state showing (the live + // flow does not always dispatch a fresh process-state when the prompt appears). + if (this.inputMode !== 'none') { + this.setProcessState('ready', { reason: `input-mode:${this.inputMode}` }); + } } setInputModeDataset() { diff --git a/public/js/webgl-book-lab.js b/public/js/webgl-book-lab.js index 8d06795..0541247 100644 --- a/public/js/webgl-book-lab.js +++ b/public/js/webgl-book-lab.js @@ -7,7 +7,9 @@ import { OutputPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postproces import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260610-book-timeline-l'; const canvas = document.getElementById('scene'); -canvas.style.cursor = 'grab'; +// The canvas inherits the document-level process-state cursor (awaiting input / server / +// background / animation) so the 3D scene communicates game state like the overlay does. +// A grab cursor is shown only transiently while actively right-drag-rotating the camera. const tableDebugModes = { none: 0, shadow: 1, @@ -4432,7 +4434,7 @@ function installCameraControls() { cameraRig.dragging = false; cameraRig.navigationActive = false; cameraRig.keys.clear(); - canvas.style.cursor = 'grab'; + canvas.style.cursor = ''; canvas.releasePointerCapture(event.pointerId); }); @@ -4440,7 +4442,7 @@ function installCameraControls() { cameraRig.dragging = false; cameraRig.navigationActive = false; cameraRig.keys.clear(); - canvas.style.cursor = 'grab'; + canvas.style.cursor = ''; }); canvas.addEventListener('wheel', (event) => {