@@ -3408,6 +3408,76 @@ window.toggleNarratorSetting = toggleNarratorSetting;
34083408window . toggleMusicSetting = toggleMusicSetting ;
34093409window . toggleSFXSetting = toggleSFXSetting ;
34103410window . toggleShakeSetting = toggleShakeSetting ;
3411+
3412+ // --- Gamepad / Quest Controller Support ---
3413+ // Adds a simple virtual cursor controlled by gamepad axes and maps the primary
3414+ // button to mouse/pointer events so the Quest browser's controller can interact
3415+ // with the game's UI and canvas.
3416+ ( ( ) => {
3417+ let gpIndex = null ;
3418+ let lastPrimary = false ;
3419+ const cursor = { x : window . innerWidth / 2 , y : window . innerHeight / 2 } ;
3420+
3421+ const el = document . createElement ( 'div' ) ;
3422+ el . id = 'gp-cursor' ;
3423+ el . style . cssText = 'position:fixed;left:0;top:0;width:16px;height:16px;background:rgba(255,255,255,0.9);border-radius:50%;box-shadow:0 0 12px rgba(0,200,255,0.9);transform:translate(-50%,-50%);pointer-events:none;z-index:99999;display:none;' ;
3424+ document . body . appendChild ( el ) ;
3425+
3426+ window . addEventListener ( 'gamepadconnected' , ( e ) => {
3427+ gpIndex = e . gamepad . index ;
3428+ el . style . display = 'block' ;
3429+ console . log ( '[Gamepad] connected' , e . gamepad . id , 'index' , gpIndex ) ;
3430+ } ) ;
3431+ window . addEventListener ( 'gamepaddisconnected' , ( e ) => {
3432+ if ( gpIndex === e . gamepad . index ) gpIndex = null ;
3433+ el . style . display = 'none' ;
3434+ console . log ( '[Gamepad] disconnected' , e . gamepad . id ) ;
3435+ } ) ;
3436+
3437+ function simulateMouse ( type , x , y ) {
3438+ const target = document . elementFromPoint ( x , y ) ;
3439+ if ( ! target ) return ;
3440+ const ev = new MouseEvent ( type , {
3441+ view : window ,
3442+ bubbles : true ,
3443+ cancelable : true ,
3444+ clientX : Math . round ( x ) ,
3445+ clientY : Math . round ( y )
3446+ } ) ;
3447+ target . dispatchEvent ( ev ) ;
3448+ }
3449+
3450+ function poll ( ) {
3451+ const gps = navigator . getGamepads && navigator . getGamepads ( ) ;
3452+ const gp = ( gpIndex !== null && gps && gps [ gpIndex ] ) ? gps [ gpIndex ] : ( gps && gps [ 0 ] ) ;
3453+ if ( gp ) {
3454+ // Typical mapping: axes[0]/[1] = left stick
3455+ const lx = gp . axes [ 0 ] || 0 ;
3456+ const ly = gp . axes [ 1 ] || 0 ;
3457+ const sensitivity = Math . max ( window . innerWidth , window . innerHeight ) * 0.02 ; // screen scaled speed
3458+ cursor . x = Math . min ( window . innerWidth , Math . max ( 0 , cursor . x + lx * sensitivity ) ) ;
3459+ cursor . y = Math . min ( window . innerHeight , Math . max ( 0 , cursor . y + ly * sensitivity ) ) ;
3460+ el . style . left = cursor . x + 'px' ;
3461+ el . style . top = cursor . y + 'px' ;
3462+
3463+ // Primary button (A) usually index 0; treat pressed->mousedown, release->mouseup+click
3464+ const primary = ! ! ( gp . buttons && gp . buttons [ 0 ] && gp . buttons [ 0 ] . pressed ) ;
3465+ if ( primary && ! lastPrimary ) {
3466+ simulateMouse ( 'pointerdown' , cursor . x , cursor . y ) ;
3467+ simulateMouse ( 'mousedown' , cursor . x , cursor . y ) ;
3468+ } else if ( ! primary && lastPrimary ) {
3469+ simulateMouse ( 'pointerup' , cursor . x , cursor . y ) ;
3470+ simulateMouse ( 'mouseup' , cursor . x , cursor . y ) ;
3471+ simulateMouse ( 'click' , cursor . x , cursor . y ) ;
3472+ }
3473+ lastPrimary = primary ;
3474+ }
3475+ requestAnimationFrame ( poll ) ;
3476+ }
3477+
3478+ // Start polling loop (will be inert until a gamepad appears)
3479+ requestAnimationFrame ( poll ) ;
3480+ } ) ( ) ;
34113481window . toggleSlowMoSetting = toggleSlowMoSetting ;
34123482window . closeSettings = closeSettings ;
34133483window . initMode = initMode ;
0 commit comments