The Gamepad API, Widely available since 2019, reads USB and Bluetooth controllers. For privacy, a pad appears only after the user presses a button, which fires gamepadconnected. There are no input events: poll navigator.getGamepads() every frame. With mapping === 'standard', buttons[0] is the bottom face button and axes[0] the left stick's horizontal position (-1 to 1).
(function loop() {
const pad = navigator.getGamepads()[0]; // [null, null, null, null] until a press
if (pad && Math.abs(pad.axes[0]) > 0.1) hero.x += pad.axes[0] * 5; // 0.1 dead zone
if (pad?.buttons[0].pressed) pad.vibrationActuator?.playEffect('dual-rumble', { duration: 150 });
requestAnimationFrame(loop);
})();