p5-phone 1.4.4 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +121 -5
  2. package/dist/p5-phone.js +105 -2
  3. package/dist/p5-phone.min.js +3 -3
  4. package/examples/Phone Sensor Examples/microphone/01_mic_level/index.html +2 -2
  5. package/examples/Phone Sensor Examples/movement/01_orientation_basic/index.html +1 -1
  6. package/examples/Phone Sensor Examples/movement/02_rotational_velocity/index.html +1 -1
  7. package/examples/Phone Sensor Examples/movement/03_acceleration/index.html +1 -1
  8. package/examples/Phone Sensor Examples/sound/01_dual_audio/index.html +1 -1
  9. package/examples/Phone Sensor Examples/sound/02_volume_touches/index.html +1 -1
  10. package/examples/Phone Sensor Examples/touch/01_touch_basic/index.html +1 -1
  11. package/examples/Phone Sensor Examples/touch/02_touch_zones/index.html +1 -1
  12. package/examples/Phone Sensor Examples/touch/03_touch_count/index.html +1 -1
  13. package/examples/Phone Sensor Examples/touch/04_touch_distance/index.html +1 -1
  14. package/examples/Phone Sensor Examples/touch/05_touch_angle/index.html +1 -1
  15. package/examples/Phone Sensor Examples/vibration/01_haptic_feedback/README.md +51 -0
  16. package/examples/Phone Sensor Examples/vibration/01_haptic_feedback/index.html +28 -0
  17. package/examples/Phone Sensor Examples/vibration/01_haptic_feedback/sketch.js +177 -0
  18. package/examples/Phone Sensor Examples - Minimal/microphone/01_mic_level/index.html +1 -1
  19. package/examples/Phone Sensor Examples - Minimal/movement/01_orientation_basic/index.html +1 -1
  20. package/examples/Phone Sensor Examples - Minimal/movement/02_rotational_velocity/index.html +1 -1
  21. package/examples/Phone Sensor Examples - Minimal/movement/03_acceleration/index.html +1 -1
  22. package/examples/Phone Sensor Examples - Minimal/sound/01_sound_basic/index.html +1 -1
  23. package/examples/Phone Sensor Examples - Minimal/sound/02_sound_amplitude/index.html +1 -1
  24. package/examples/Phone Sensor Examples - Minimal/touch/01_touch_basic/index.html +1 -1
  25. package/examples/Phone Sensor Examples - Minimal/touch/02_touch_zones/index.html +1 -1
  26. package/examples/Phone Sensor Examples - Minimal/touch/03_touch_count/index.html +1 -1
  27. package/examples/Phone Sensor Examples - Minimal/touch/04_touch_distance/index.html +1 -1
  28. package/examples/Phone Sensor Examples - Minimal/touch/05_touch_angle/index.html +1 -1
  29. package/examples/Phone and Gif/collision/index.html +1 -1
  30. package/examples/Phone and Gif/fetch/index.html +1 -1
  31. package/examples/Phone and Gif/fly/index.html +1 -1
  32. package/examples/Phone and Gif/roll/index.html +1 -1
  33. package/examples/UXcompare/button-vs-movement/index.html +1 -1
  34. package/examples/UXcompare/button-vs-orientation/index.html +1 -1
  35. package/examples/UXcompare/button-vs-shake/index.html +1 -1
  36. package/examples/UXcompare/gyroscope-demo/index.html +1 -1
  37. package/examples/UXcompare/microphone-demo/index.html +1 -1
  38. package/examples/UXcompare/slider-vs-angle/index.html +1 -1
  39. package/examples/UXcompare/slider-vs-distance/index.html +1 -1
  40. package/examples/UXcompare/slider-vs-microphone/index.html +1 -1
  41. package/examples/UXcompare/slider-vs-touches/index.html +1 -1
  42. package/examples/UXcompare/sliders-vs-acceleration/index.html +1 -1
  43. package/examples/UXcompare/sliders-vs-rotation/index.html +1 -1
  44. package/examples/blankTemplate/index.html +1 -1
  45. package/examples/homepage/index.html +805 -88
  46. package/package.json +5 -5
  47. package/src/p5-phone.js +105 -2
  48. package/src/permissionMic.js +0 -240
  49. package/src/permissionsGesture.js +0 -213
  50. package/src/permissionsGyro.js +0 -246
package/README.md CHANGED
@@ -1,15 +1,14 @@
1
1
  # p5-phone
2
- ![P5Phone.png](P5Phone.png)
2
+ ![P5Phone.png](p5-phone.png)
3
+ *Illustration by [Angela Torchio](https://angelatorchio.com/)*
3
4
 
4
- **Simplified mobile hardware access for p5.js** - handle sensors, microphone, touch, and browser gestures with ease.
5
-
6
- ## [Link for Interactive Examples](https://digitalfuturesocadu.github.io/P5-Phone-Interactions/examples/homepage)
5
+ ## [Link for Interactive Examples](https://npuckett.github.io/p5-phone/examples/homepage)
7
6
 
8
7
  # Overview
9
8
  P5.js on mobile provides unique opportunities and challenges. The main P5 framework does an excellent job of making it easy to read data from various phone inputs and sensors, however it doesn't deal with the realities of contemporary browser's built in gestures and security protocols.
10
9
  That's where this library comes in:
11
10
 
12
- - Simplifies accessing phone hardware from the browser (accelerometers, gyroscopes, microphone)
11
+ - Simplifies accessing phone hardware from the browser (accelerometers, gyroscopes, microphone, vibration motor)
13
12
  - Simplifies disabling default phone gestures (Zoom, refresh, back, etc)
14
13
  - Simplifies enabling audio output
15
14
  - Simplifies using an on-screen console to display errors and debug info
@@ -61,6 +60,7 @@ This library simplifies access to the following p5.js mobile sensor and audio co
61
60
  - [Motion Sensor Activation](#motion-sensor-activation)
62
61
  - [Microphone Activation](#microphone-activation)
63
62
  - [Sound Output Activation](#sound-output-activation)
63
+ - [Vibration Motor (Android Only)](#vibration-motor-android-only)
64
64
  - [Debug System](#debug-system)
65
65
 
66
66
  ### CDN (Recommended)
@@ -192,10 +192,17 @@ enableMicButton(text) // Button-based microphone activation
192
192
  enableSoundTap(message) // Tap anywhere to enable sound playback
193
193
  enableSoundButton(text) // Button-based sound activation
194
194
 
195
+ // Vibration motor (Android only)
196
+ enableVibrationTap(message) // Tap anywhere to enable vibration
197
+ enableVibrationButton(text) // Button-based vibration activation
198
+ vibrate(pattern) // Trigger vibration (duration or pattern array)
199
+ stopVibration() // Stop any ongoing vibration
200
+
195
201
  // Status variables (check these in your code)
196
202
  window.sensorsEnabled // Boolean: true when motion sensors are active
197
203
  window.micEnabled // Boolean: true when microphone is active
198
204
  window.soundEnabled // Boolean: true when sound output is active
205
+ window.vibrationEnabled // Boolean: true when vibration is available (Android only)
199
206
 
200
207
  // Debug system (enhanced in v1.4.0)
201
208
  showDebug() // Show on-screen debug panel with automatic error catching
@@ -226,6 +233,7 @@ this.enableGyroTap('Tap to start');
226
233
  - `window.sensorsEnabled` - Boolean indicating if motion sensors are active
227
234
  - `window.micEnabled` - Boolean indicating if microphone is active
228
235
  - `window.soundEnabled` - Boolean indicating if sound output is active
236
+ - `window.vibrationEnabled` - Boolean indicating if vibration is available (Android only)
229
237
 
230
238
  **Usage:**
231
239
  ```javascript
@@ -245,6 +253,11 @@ function draw() {
245
253
  // Safe to play sounds
246
254
  mySound.play();
247
255
  }
256
+
257
+ if (window.vibrationEnabled) {
258
+ // Safe to use vibration (Android only)
259
+ vibrate(50);
260
+ }
248
261
  }
249
262
 
250
263
  // You can also use them for conditional UI
@@ -454,6 +467,109 @@ function mousePressed() {
454
467
  }
455
468
  ```
456
469
 
470
+ ### Vibration Motor (Android Only)
471
+
472
+ **Purpose:** Access the device's vibration motor for haptic feedback and tactile interactions.
473
+
474
+ **⚠️ Platform Support:**
475
+ - ✅ **Android** - Full support in Chrome and most Android browsers
476
+ - ❌ **iOS** - Not supported (Vibration API not available on iOS devices)
477
+
478
+ **Important:** The vibration feature will automatically detect if the device supports vibration. On iOS or unsupported devices, `window.vibrationEnabled` will be `false` and vibration calls will be safely ignored with console warnings.
479
+
480
+ **Commands:**
481
+ - `enableVibrationTap(message)` - Tap anywhere on screen to enable vibration
482
+ - `enableVibrationButton(text)` - Creates a button with custom text to enable vibration
483
+ - `vibrate(pattern)` - Trigger vibration with a duration (ms) or pattern array
484
+ - `stopVibration()` - Stop any ongoing vibration
485
+
486
+ **Usage:**
487
+ ```javascript
488
+ function setup() {
489
+ createCanvas(windowWidth, windowHeight);
490
+
491
+ // Enable vibration with tap (Android only)
492
+ enableVibrationTap('Tap to enable vibration');
493
+
494
+ // Or use a button
495
+ // enableVibrationButton('Enable Haptics');
496
+ }
497
+
498
+ function draw() {
499
+ background(220);
500
+
501
+ if (window.vibrationEnabled) {
502
+ text('Vibration ready! Tap anywhere', 20, 20);
503
+ } else {
504
+ text('Vibration not available', 20, 20);
505
+ }
506
+ }
507
+
508
+ function mousePressed() {
509
+ if (window.vibrationEnabled) {
510
+ // Simple vibration - 50ms pulse
511
+ vibrate(50);
512
+ }
513
+ }
514
+ ```
515
+
516
+ **Vibration Patterns:**
517
+ ```javascript
518
+ // Single vibration (duration in milliseconds)
519
+ vibrate(100); // Vibrate for 100ms
520
+
521
+ // Pattern: [vibrate, pause, vibrate, pause, ...]
522
+ vibrate([100, 50, 100]); // Short-short pattern
523
+ vibrate([200, 100, 200, 100, 200]); // Triple pulse
524
+ vibrate([50, 50, 50, 50, 500]); // Quick taps then long
525
+
526
+ // Stop any ongoing vibration
527
+ stopVibration();
528
+ ```
529
+
530
+ **Common Use Cases:**
531
+ ```javascript
532
+ // Haptic feedback for button presses
533
+ function mousePressed() {
534
+ if (window.vibrationEnabled) {
535
+ vibrate(20); // Quick tap feedback
536
+ }
537
+ }
538
+
539
+ // Touch zones with different haptic patterns
540
+ function touchStarted() {
541
+ if (window.vibrationEnabled) {
542
+ if (mouseX < width/2) {
543
+ vibrate(50); // Left side - short pulse
544
+ } else {
545
+ vibrate([50, 30, 50]); // Right side - double pulse
546
+ }
547
+ }
548
+ return false;
549
+ }
550
+
551
+ // Collision detection
552
+ function checkCollision() {
553
+ if (collision && window.vibrationEnabled) {
554
+ vibrate([100, 50, 100, 50, 200]); // Alert pattern
555
+ }
556
+ }
557
+
558
+ // Game events
559
+ function gameOver() {
560
+ if (window.vibrationEnabled) {
561
+ vibrate(500); // Long vibration for game over
562
+ }
563
+ }
564
+ ```
565
+
566
+ **Best Practices:**
567
+ - Use short vibrations (20-100ms) for subtle feedback
568
+ - Use patterns for more complex haptic responses
569
+ - Always check `window.vibrationEnabled` before calling `vibrate()`
570
+ - Don't overuse - vibration can quickly drain battery
571
+ - Test on Android devices as iOS doesn't support vibration
572
+
457
573
  ### Debug System
458
574
 
459
575
  **Purpose:** Essential on-screen debugging system for mobile development where traditional browser dev tools aren't accessible. Provides automatic error catching, timestamped logging, and color-coded messages.
package/dist/p5-phone.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * p5-phone v1.4.4
2
+ * p5-phone v1.5.0
3
3
  * Simplified mobile hardware access for p5.js - handle sensors, microphone, touch, and browser gestures with ease
4
- * https://github.com/DigitalFuturesOCADU/p5-phone
4
+ * https://github.com/npuckett/p5-phone
5
5
  *
6
6
  * Copyright (c) 2025 Nick Puckett
7
7
  * Released under the MIT License
@@ -85,6 +85,7 @@ window.sensorsEnabled = false;
85
85
  window.micEnabled = false;
86
86
  window.soundEnabled = false;
87
87
  window.gesturesLocked = false;
88
+ window.vibrationEnabled = false;
88
89
 
89
90
  // Internal state
90
91
  let _permissionsInitialized = false;
@@ -176,6 +177,30 @@ function enableSoundTap(message = 'Tap screen to enable sound') {
176
177
  });
177
178
  }
178
179
 
180
+ /**
181
+ * Enable vibration motor with a button interface
182
+ * Creates a start button that user must click
183
+ * Note: Vibration API is supported on Android, but not iOS
184
+ */
185
+ function enableVibrationButton(buttonText = 'ENABLE VIBRATION', statusText = 'Enabling vibration...') {
186
+ _createPermissionButton(buttonText, statusText, async () => {
187
+ await _requestVibrationPermission();
188
+ console.log('✅ Vibration enabled via button');
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Enable vibration motor with tap-to-start
194
+ * User taps anywhere on screen to enable
195
+ * Note: Vibration API is supported on Android, but not iOS
196
+ */
197
+ function enableVibrationTap(message = 'Tap screen to enable vibration') {
198
+ _createTapToEnable(message, async () => {
199
+ await _requestVibrationPermission();
200
+ console.log('✅ Vibration enabled via tap');
201
+ });
202
+ }
203
+
179
204
  /**
180
205
  * Enable both motion sensors and microphone with a button interface
181
206
  * Creates a start button that user must click to enable both
@@ -200,6 +225,39 @@ function enableAllTap(message = 'Tap screen to enable motion sensors & microphon
200
225
  });
201
226
  }
202
227
 
228
+ /**
229
+ * Trigger vibration on device
230
+ * @param {number|number[]} pattern - Duration in ms or pattern array [vibrate, pause, vibrate, ...]
231
+ *
232
+ * Examples:
233
+ * vibrate(200); // Single 200ms vibration
234
+ * vibrate([100, 50, 100]); // Pattern: vibrate 100ms, pause 50ms, vibrate 100ms
235
+ *
236
+ * Note: Only works if vibrationEnabled is true and device supports vibration
237
+ */
238
+ function vibrate(pattern) {
239
+ if (!window.vibrationEnabled) {
240
+ console.warn('⚠️ Vibration not enabled. Call enableVibrationTap() or enableVibrationButton() first.');
241
+ return false;
242
+ }
243
+
244
+ if (!navigator.vibrate) {
245
+ console.warn('⚠️ Vibration API not supported on this device');
246
+ return false;
247
+ }
248
+
249
+ return navigator.vibrate(pattern);
250
+ }
251
+
252
+ /**
253
+ * Stop any ongoing vibration
254
+ */
255
+ function stopVibration() {
256
+ if (navigator.vibrate) {
257
+ navigator.vibrate(0);
258
+ }
259
+ }
260
+
203
261
  // =========================================
204
262
  // INTERNAL PERMISSION HANDLERS
205
263
  // =========================================
@@ -281,6 +339,42 @@ async function _requestSoundOutput() {
281
339
  }
282
340
  }
283
341
 
342
+ async function _requestVibrationPermission() {
343
+ try {
344
+ // Check if Vibration API is supported
345
+ if (!navigator.vibrate) {
346
+ console.warn('⚠️ Vibration API not supported on this device (likely iOS)');
347
+ if (_debugVisible) {
348
+ debugWarn('Vibration API not supported on this device');
349
+ }
350
+ window.vibrationEnabled = false;
351
+ _notifySketchReady();
352
+ return;
353
+ }
354
+
355
+ // Test vibration with a short pulse
356
+ const vibrateSuccess = navigator.vibrate(1);
357
+
358
+ if (vibrateSuccess) {
359
+ window.vibrationEnabled = true;
360
+ console.log('✅ Vibration enabled');
361
+ } else {
362
+ console.warn('⚠️ Vibration API available but vibration failed');
363
+ window.vibrationEnabled = false;
364
+ }
365
+
366
+ _notifySketchReady();
367
+
368
+ } catch (error) {
369
+ console.error('Vibration permission error:', error);
370
+ if (_debugVisible) {
371
+ debugError('Vibration permission error:', error);
372
+ }
373
+ window.vibrationEnabled = false;
374
+ _notifySketchReady();
375
+ }
376
+ }
377
+
284
378
  function _notifySketchReady() {
285
379
  // Call userSetupComplete if it exists
286
380
  if (typeof userSetupComplete === 'function') {
@@ -293,6 +387,7 @@ function _notifySketchReady() {
293
387
  sensors: window.sensorsEnabled,
294
388
  microphone: window.micEnabled,
295
389
  sound: window.soundEnabled,
390
+ vibration: window.vibrationEnabled,
296
391
  gestures: window.gesturesLocked
297
392
  }
298
393
  }));
@@ -835,6 +930,10 @@ window.enableMicTap = enableMicTap;
835
930
  window.enableMicButton = enableMicButton;
836
931
  window.enableSoundTap = enableSoundTap;
837
932
  window.enableSoundButton = enableSoundButton;
933
+ window.enableVibrationTap = enableVibrationTap;
934
+ window.enableVibrationButton = enableVibrationButton;
935
+ window.vibrate = vibrate;
936
+ window.stopVibration = stopVibration;
838
937
  window.enableAllTap = enableAllTap;
839
938
  window.enableAllButton = enableAllButton;
840
939
 
@@ -1047,6 +1146,10 @@ if (typeof p5 !== 'undefined' && p5.prototype) {
1047
1146
  p5.prototype.enableMicButton = enableMicButton;
1048
1147
  p5.prototype.enableSoundTap = enableSoundTap;
1049
1148
  p5.prototype.enableSoundButton = enableSoundButton;
1149
+ p5.prototype.enableVibrationTap = enableVibrationTap;
1150
+ p5.prototype.enableVibrationButton = enableVibrationButton;
1151
+ p5.prototype.vibrate = vibrate;
1152
+ p5.prototype.stopVibration = stopVibration;
1050
1153
  p5.prototype.enableAllTap = enableAllTap;
1051
1154
  p5.prototype.enableAllButton = enableAllButton;
1052
1155
 
@@ -1,10 +1,10 @@
1
1
  /*!
2
- * p5-phone v1.4.4
2
+ * p5-phone v1.5.0
3
3
  * Simplified mobile hardware access for p5.js - handle sensors, microphone, touch, and browser gestures with ease
4
- * https://github.com/DigitalFuturesOCADU/p5-phone
4
+ * https://github.com/npuckett/p5-phone
5
5
  *
6
6
  * Copyright (c) 2025 Nick Puckett
7
7
  * Released under the MIT License
8
8
  * https://opensource.org/licenses/MIT
9
9
  */
10
- window._originalConsoleError=console.error,window._originalConsoleWarn=console.warn,window._debugErrorHandlersSet||(window._debugErrorHandlersSet=!0,window._earlyErrors=window._earlyErrors||[],window.addEventListener("error",function(e){const n=`${e.error?.message||e.message||"Unknown error"} (${e.filename?e.filename.split("/").pop():"unknown file"}:${e.lineno||"unknown line"})`;console.error("🚨 Error caught:",n),e.error?.stack&&console.error("Stack:",e.error.stack),window._earlyErrors.push({type:"error",message:"JavaScript Error: "+n,stack:e.error?.stack}),!1===window.SHOW_DEBUG||window._debugVisible||"function"==typeof showDebug&&showDebug(),window._debugVisible&&"function"==typeof debugError&&(debugError("JavaScript Error:",n),e.error?.stack&&debugError("Stack trace:",e.error.stack))}),window.addEventListener("unhandledrejection",function(e){const n=e.reason?.message||e.reason||"Unknown promise rejection";console.error("🚨 Promise rejection caught:",n),window._earlyErrors.push({type:"error",message:"Unhandled Promise Rejection: "+n}),window._debugVisible&&"function"==typeof debugError&&debugError("Unhandled Promise Rejection:",n)})),window.sensorsEnabled=!1,window.micEnabled=!1,window.soundEnabled=!1,window.gesturesLocked=!1;let _permissionsInitialized=!1,_micInstance=null;function lockGestures(){window.gesturesLocked||(console.log("🔒 Locking mobile gestures..."),_initializeGestureBlocking(),_initializeP5TouchOverrides(),window.gesturesLocked=!0,console.log("✅ Mobile gestures locked"))}function enableGyroButton(e="ENABLE MOTION SENSORS",n="Requesting motion sensors..."){_createPermissionButton(e,n,async()=>{await _requestMotionPermissions(),console.log("✅ Gyroscope enabled via button")})}function enableGyroTap(e="Tap screen to enable motion sensors"){_createTapToEnable(e,async()=>{await _requestMotionPermissions(),console.log("✅ Gyroscope enabled via tap")})}function enableMicButton(e="ENABLE MICROPHONE",n="Requesting microphone access..."){_createPermissionButton(e,n,async()=>{await _requestMicrophonePermissions(),console.log("✅ Microphone enabled via button")})}function enableMicTap(e="Tap screen to enable microphone"){_createTapToEnable(e,async()=>{await _requestMicrophonePermissions(),console.log("✅ Microphone enabled via tap")})}function enableSoundButton(e="ENABLE SOUND",n="Enabling audio..."){_createPermissionButton(e,n,async()=>{await _requestSoundOutput(),console.log("✅ Sound output enabled via button")})}function enableSoundTap(e="Tap screen to enable sound"){_createTapToEnable(e,async()=>{await _requestSoundOutput(),console.log("✅ Sound output enabled via tap")})}function enableAllButton(e="ENABLE MOTION & MICROPHONE",n="Requesting permissions..."){_createPermissionButton(e,n,async()=>{await _requestMotionPermissions(),await _requestMicrophonePermissions(),console.log("✅ Motion sensors and microphone enabled via button")})}function enableAllTap(e="Tap screen to enable motion sensors & microphone"){_createTapToEnable(e,async()=>{await _requestMotionPermissions(),await _requestMicrophonePermissions(),console.log("✅ Motion sensors and microphone enabled via tap")})}async function _requestMotionPermissions(){try{if("undefined"!=typeof DeviceOrientationEvent&&"function"==typeof DeviceOrientationEvent.requestPermission){const e=await DeviceOrientationEvent.requestPermission();if(console.log("Orientation permission:",e),"undefined"!=typeof DeviceMotionEvent&&"function"==typeof DeviceMotionEvent.requestPermission){const e=await DeviceMotionEvent.requestPermission();console.log("Motion permission:",e)}}window.sensorsEnabled=!0,_notifySketchReady()}catch(e){console.error("Motion sensor permission error:",e),_debugVisible&&debugError("Motion sensor permission error:",e),window.sensorsEnabled=!0,_notifySketchReady()}}async function _requestMicrophonePermissions(){try{"undefined"!=typeof userStartAudio&&await userStartAudio(),"undefined"!=typeof mic&&mic&&mic.start?(mic.start(),_micInstance=mic,window.micEnabled=!0):console.warn("No microphone object found. Create one with: mic = new p5.AudioIn();"),_notifySketchReady()}catch(e){console.error("Microphone permission error:",e),_debugVisible&&debugError("Microphone permission error:",e),_notifySketchReady()}}async function _requestSoundOutput(){try{"undefined"!=typeof userStartAudio&&await userStartAudio(),window.soundEnabled=!0,_notifySketchReady()}catch(e){console.error("Sound output error:",e),_debugVisible&&debugError("Sound output error:",e),window.soundEnabled=!0,_notifySketchReady()}}function _notifySketchReady(){"function"==typeof userSetupComplete&&userSetupComplete(),window.dispatchEvent(new CustomEvent("permissionsReady",{detail:{sensors:window.sensorsEnabled,microphone:window.micEnabled,sound:window.soundEnabled,gestures:window.gesturesLocked}}))}function _createPermissionButton(e,n,o){_removeExistingUI();const t=document.createElement("button");t.id="permissionButton",t.textContent=e,t.style.cssText="\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n padding: 20px 40px;\n font-size: 18px;\n font-weight: bold;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n border: none;\n border-radius: 12px;\n cursor: pointer;\n z-index: 999999;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n box-shadow: 0 4px 15px rgba(0,0,0,0.2);\n transition: transform 0.2s ease;\n touch-action: manipulation;\n ";const i=document.createElement("div");i.id="permissionStatus",i.textContent=n,i.style.cssText="\n position: fixed;\n top: 60%;\n left: 50%;\n transform: translate(-50%, 0);\n color: white;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n text-align: center;\n z-index: 999998;\n display: none;\n ",t.addEventListener("mouseenter",()=>{t.style.transform="translate(-50%, -50%) scale(1.05)"}),t.addEventListener("mouseleave",()=>{t.style.transform="translate(-50%, -50%) scale(1)"});const r=async()=>{t.parentNode&&(t.style.display="none",i.style.display="block",await o(),i.style.display="none",_removeExistingUI())};t.addEventListener("click",r),t.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),r()}),t.addEventListener("pointerup",function(e){e.preventDefault(),e.stopPropagation(),r()}),document.body.appendChild(t),document.body.appendChild(i)}function _createTapToEnable(e,n){_removeExistingUI();const o=document.createElement("div");o.id="tapOverlay",o.style.cssText="\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.8);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 999999;\n cursor: pointer;\n touch-action: manipulation;\n ";const t=document.createElement("div");t.textContent=e,t.style.cssText="\n color: white;\n font-size: 24px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n text-align: center;\n padding: 40px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n border-radius: 12px;\n background: rgba(255, 255, 255, 0.1);\n backdrop-filter: blur(10px);\n ",o.appendChild(t);const i=async()=>{o.parentNode&&(t.textContent="Enabling...",await n(),o.parentNode&&document.body.removeChild(o))};o.addEventListener("click",i),o.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),i()}),o.addEventListener("pointerup",function(e){e.preventDefault(),e.stopPropagation(),i()}),document.body.appendChild(o)}function _removeExistingUI(){const e=document.getElementById("permissionButton"),n=document.getElementById("permissionStatus"),o=document.getElementById("tapOverlay");e&&e.remove(),n&&n.remove(),o&&o.remove()}function _initializeGestureBlocking(){window.history.pushState(null,"",window.location.href),window.onpopstate=function(){window.history.pushState(null,"",window.location.href)},window.addEventListener("beforeunload",function(e){e.preventDefault(),e.returnValue=""}),_initializeEdgeSwipePrevention(),_initializeOtherGesturePrevention()}function _initializeEdgeSwipePrevention(){let e=0,n=0;document.addEventListener("touchstart",function(o){o.touches&&o.touches.length>0&&(e=o.touches[0].clientX,n=o.touches[0].clientY,(e<20||e>window.innerWidth-20)&&o.preventDefault())},{passive:!1,capture:!0}),document.addEventListener("touchmove",function(o){if(!o.touches||0===o.touches.length)return;let t=o.touches[0].clientX,i=o.touches[0].clientY,r=t-e,s=i-n;(e<20&&r>0||e>window.innerWidth-20&&r<0)&&(o.preventDefault(),o.stopPropagation()),0===window.pageYOffset&&s>0&&o.preventDefault(),!o.target||"CANVAS"!==o.target.tagName||document.getElementById("tapOverlay")||document.getElementById("permissionButton")||o.preventDefault()},{passive:!1,capture:!0})}function _initializeOtherGesturePrevention(){document.addEventListener("gesturestart",function(e){e.preventDefault()}),document.addEventListener("gesturechange",function(e){e.preventDefault()}),document.addEventListener("gestureend",function(e){e.preventDefault()});let e=0;document.addEventListener("touchend",function(n){if(n.target&&("tapOverlay"===n.target.id||n.target.closest("#tapOverlay")||"permissionButton"===n.target.id||"permissionStatus"===n.target.id||n.target.closest("#permissionButton")||n.target.closest("#permissionStatus")))return;const o=Date.now();o-e<=300&&n.preventDefault(),e=o},!1),window.oncontextmenu=function(e){return e.preventDefault(),e.stopPropagation(),!1}}function _initializeP5TouchOverrides(){setTimeout(()=>{if(window._setupDone)_overrideP5Touch();else{const e=setInterval(()=>{window._setupDone&&(_overrideP5Touch(),clearInterval(e))},100)}},100)}function _overrideP5Touch(){const e=window.touchStarted||function(){},n=window.touchMoved||function(){},o=window.touchEnded||function(){},t=window.mousePressed||function(){},i=window.mouseDragged||function(){},r=window.mouseReleased||function(){};window.touchStarted=function(n){return e(n),!1},window.touchMoved=function(e){return n(e),!1},window.touchEnded=function(e){return o(e),!1},window.mousePressed=function(e){return t(e),!1},window.mouseDragged=function(e){return i(e),!1},window.mouseReleased=function(e){return r(e),!1}}document.addEventListener("DOMContentLoaded",function(){const e=document.getElementById("startButton"),n=document.getElementById("statusText");e&&n&&(console.warn("⚠️ Legacy HTML elements detected. Consider using the new API functions instead."),e.addEventListener("click",async()=>{e.classList.add("hidden"),n.classList.remove("hidden"),n.textContent="Requesting permissions...",await _requestMotionPermissions(),await _requestMicrophonePermissions(),n.classList.add("hidden")}),lockGestures())});let _debugPanel=null,_debugVisible=!1,_debugMessages=[];const MAX_DEBUG_MESSAGES=20;function showDebug(){_createDebugPanel(),_debugPanel.style.display="block",_debugVisible=!0,window._debugVisible=!0,_setupConsoleOverrides(),_displayEarlyErrors()}function hideDebug(){_debugPanel&&(_debugPanel.style.display="none",_debugVisible=!1)}function toggleDebug(){_debugVisible?hideDebug():showDebug()}function debug(...e){console.log(...e);_addDebugMessage(e.map(e=>{if("object"==typeof e&&null!==e)try{return JSON.stringify(e,null,2)}catch(n){return String(e)}return String(e)}).join(" "),"log")}function debugError(...e){(window._originalConsoleError||console.error).apply(console,e);_addDebugMessage(`❌ ERROR: ${e.map(e=>{if("object"==typeof e&&null!==e)try{return JSON.stringify(e,null,2)}catch(n){return String(e)}return String(e)}).join(" ")}`,"error")}function debugWarn(...e){(window._originalConsoleWarn||console.warn).apply(console,e);_addDebugMessage(`⚠️ WARNING: ${e.map(e=>{if("object"==typeof e&&null!==e)try{return JSON.stringify(e,null,2)}catch(n){return String(e)}return String(e)}).join(" ")}`,"warning")}function _addDebugMessage(e,n="log"){const o={text:`[${(new Date).toLocaleTimeString("en-US",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}] ${e}`,type:n};_debugMessages.push(o),_debugMessages.length>20&&_debugMessages.shift(),_debugPanel&&_updateDebugDisplay()}function _setupConsoleOverrides(){window._consoleOverrideSet||(window._consoleOverrideSet=!0,window._originalConsoleError=console.error,window._originalConsoleWarn=console.warn,console.error=function(...e){window._originalConsoleError.apply(console,e),_debugVisible&&debugError(...e)},console.warn=function(...e){window._originalConsoleWarn.apply(console,e),_debugVisible&&debugWarn(...e)})}function _displayEarlyErrors(){window._earlyErrors&&window._earlyErrors.length>0&&(debugError(`🚨 Found ${window._earlyErrors.length} early error(s):`),window._earlyErrors.forEach(e=>{debugError(e.message),e.stack&&debugError("Stack trace:",e.stack)}),window._earlyErrors=[])}function _createDebugPanel(){if(_debugPanel)return;_debugPanel=document.createElement("div"),_debugPanel.id="mobile-debug-panel",_debugPanel.innerHTML='\n <div id="mobile-debug-header">\n <span>Debug</span>\n <button id="mobile-debug-close">×</button>\n </div>\n <div id="mobile-debug-content"></div>\n ';const e=document.createElement("style");e.textContent="\n #mobile-debug-panel {\n position: fixed;\n top: 20px;\n right: 20px;\n width: 350px;\n max-width: calc(100vw - 40px);\n max-height: 400px;\n background: rgba(0, 0, 0, 0.9);\n color: #ffffff;\n font-family: 'Courier New', monospace;\n font-size: 12px;\n border-radius: 8px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n z-index: 10000;\n display: none;\n }\n \n #mobile-debug-header {\n background: rgba(255, 255, 255, 0.1);\n padding: 8px 12px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2);\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-radius: 8px 8px 0 0;\n }\n \n #mobile-debug-header span {\n font-weight: bold;\n font-size: 13px;\n }\n \n #mobile-debug-close {\n background: none;\n border: none;\n color: #ffffff;\n font-size: 18px;\n cursor: pointer;\n padding: 0;\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n \n #mobile-debug-close:hover {\n background: rgba(255, 255, 255, 0.1);\n border-radius: 4px;\n }\n \n #mobile-debug-content {\n padding: 12px;\n max-height: 340px;\n overflow-y: auto;\n word-wrap: break-word;\n line-height: 1.4;\n }\n \n .debug-message {\n margin-bottom: 4px;\n white-space: pre-wrap;\n }\n \n .debug-message.error {\n color: #ff6b6b;\n background: rgba(255, 107, 107, 0.1);\n padding: 4px;\n border-radius: 3px;\n border-left: 3px solid #ff6b6b;\n }\n \n .debug-message.warning {\n color: #ffd93d;\n background: rgba(255, 217, 61, 0.1);\n padding: 4px;\n border-radius: 3px;\n border-left: 3px solid #ffd93d;\n }\n \n .debug-timestamp {\n color: #888;\n font-size: 10px;\n }\n \n @media (max-width: 480px) {\n #mobile-debug-panel {\n width: calc(100vw - 20px);\n right: 10px;\n top: 10px;\n }\n }\n ",document.head.appendChild(e),document.body.appendChild(_debugPanel),document.getElementById("mobile-debug-close").onclick=hideDebug,_updateDebugDisplay()}function _updateDebugDisplay(){if(!_debugPanel)return;const e=document.getElementById("mobile-debug-content");e&&(e.innerHTML=_debugMessages.map(e=>"string"==typeof e?`<div class="debug-message">${e}</div>`:`<div class="debug-message ${e.type}">${e.text}</div>`).join(""),e.scrollTop=e.scrollHeight)}debug.clear=function(){_debugMessages=[],_debugPanel&&_updateDebugDisplay(),console.clear()},window.debug=debug,window.debugError=debugError,window.debugWarn=debugWarn,window.showDebug=showDebug,window.hideDebug=hideDebug,window.toggleDebug=toggleDebug,window.lockGestures=lockGestures,window.enableGyroTap=enableGyroTap,window.enableGyroButton=enableGyroButton,window.enableMicTap=enableMicTap,window.enableMicButton=enableMicButton,window.enableSoundTap=enableSoundTap,window.enableSoundButton=enableSoundButton,window.enableAllTap=enableAllTap,window.enableAllButton=enableAllButton,"undefined"!=typeof p5&&p5.prototype&&(p5.prototype.lockGestures=lockGestures,p5.prototype.enableGyroTap=enableGyroTap,p5.prototype.enableGyroButton=enableGyroButton,p5.prototype.enableMicTap=enableMicTap,p5.prototype.enableMicButton=enableMicButton,p5.prototype.enableSoundTap=enableSoundTap,p5.prototype.enableSoundButton=enableSoundButton,p5.prototype.enableAllTap=enableAllTap,p5.prototype.enableAllButton=enableAllButton,p5.prototype.showDebug=showDebug,p5.prototype.hideDebug=hideDebug,p5.prototype.toggleDebug=toggleDebug,p5.prototype.debug=debug,p5.prototype.debugError=debugError,p5.prototype.debugWarn=debugWarn,console.log("✅ Mobile p5.js Permissions: p5.prototype functions registered"));
10
+ window._originalConsoleError=console.error,window._originalConsoleWarn=console.warn,window._debugErrorHandlersSet||(window._debugErrorHandlersSet=!0,window._earlyErrors=window._earlyErrors||[],window.addEventListener("error",function(e){const n=`${e.error?.message||e.message||"Unknown error"} (${e.filename?e.filename.split("/").pop():"unknown file"}:${e.lineno||"unknown line"})`;console.error("🚨 Error caught:",n),e.error?.stack&&console.error("Stack:",e.error.stack),window._earlyErrors.push({type:"error",message:"JavaScript Error: "+n,stack:e.error?.stack}),!1===window.SHOW_DEBUG||window._debugVisible||"function"==typeof showDebug&&showDebug(),window._debugVisible&&"function"==typeof debugError&&(debugError("JavaScript Error:",n),e.error?.stack&&debugError("Stack trace:",e.error.stack))}),window.addEventListener("unhandledrejection",function(e){const n=e.reason?.message||e.reason||"Unknown promise rejection";console.error("🚨 Promise rejection caught:",n),window._earlyErrors.push({type:"error",message:"Unhandled Promise Rejection: "+n}),window._debugVisible&&"function"==typeof debugError&&debugError("Unhandled Promise Rejection:",n)})),window.sensorsEnabled=!1,window.micEnabled=!1,window.soundEnabled=!1,window.gesturesLocked=!1,window.vibrationEnabled=!1;let _permissionsInitialized=!1,_micInstance=null;function lockGestures(){window.gesturesLocked||(console.log("🔒 Locking mobile gestures..."),_initializeGestureBlocking(),_initializeP5TouchOverrides(),window.gesturesLocked=!0,console.log("✅ Mobile gestures locked"))}function enableGyroButton(e="ENABLE MOTION SENSORS",n="Requesting motion sensors..."){_createPermissionButton(e,n,async()=>{await _requestMotionPermissions(),console.log("✅ Gyroscope enabled via button")})}function enableGyroTap(e="Tap screen to enable motion sensors"){_createTapToEnable(e,async()=>{await _requestMotionPermissions(),console.log("✅ Gyroscope enabled via tap")})}function enableMicButton(e="ENABLE MICROPHONE",n="Requesting microphone access..."){_createPermissionButton(e,n,async()=>{await _requestMicrophonePermissions(),console.log("✅ Microphone enabled via button")})}function enableMicTap(e="Tap screen to enable microphone"){_createTapToEnable(e,async()=>{await _requestMicrophonePermissions(),console.log("✅ Microphone enabled via tap")})}function enableSoundButton(e="ENABLE SOUND",n="Enabling audio..."){_createPermissionButton(e,n,async()=>{await _requestSoundOutput(),console.log("✅ Sound output enabled via button")})}function enableSoundTap(e="Tap screen to enable sound"){_createTapToEnable(e,async()=>{await _requestSoundOutput(),console.log("✅ Sound output enabled via tap")})}function enableVibrationButton(e="ENABLE VIBRATION",n="Enabling vibration..."){_createPermissionButton(e,n,async()=>{await _requestVibrationPermission(),console.log("✅ Vibration enabled via button")})}function enableVibrationTap(e="Tap screen to enable vibration"){_createTapToEnable(e,async()=>{await _requestVibrationPermission(),console.log("✅ Vibration enabled via tap")})}function enableAllButton(e="ENABLE MOTION & MICROPHONE",n="Requesting permissions..."){_createPermissionButton(e,n,async()=>{await _requestMotionPermissions(),await _requestMicrophonePermissions(),console.log("✅ Motion sensors and microphone enabled via button")})}function enableAllTap(e="Tap screen to enable motion sensors & microphone"){_createTapToEnable(e,async()=>{await _requestMotionPermissions(),await _requestMicrophonePermissions(),console.log("✅ Motion sensors and microphone enabled via tap")})}function vibrate(e){return window.vibrationEnabled?navigator.vibrate?navigator.vibrate(e):(console.warn("⚠️ Vibration API not supported on this device"),!1):(console.warn("⚠️ Vibration not enabled. Call enableVibrationTap() or enableVibrationButton() first."),!1)}function stopVibration(){navigator.vibrate&&navigator.vibrate(0)}async function _requestMotionPermissions(){try{if("undefined"!=typeof DeviceOrientationEvent&&"function"==typeof DeviceOrientationEvent.requestPermission){const e=await DeviceOrientationEvent.requestPermission();if(console.log("Orientation permission:",e),"undefined"!=typeof DeviceMotionEvent&&"function"==typeof DeviceMotionEvent.requestPermission){const e=await DeviceMotionEvent.requestPermission();console.log("Motion permission:",e)}}window.sensorsEnabled=!0,_notifySketchReady()}catch(e){console.error("Motion sensor permission error:",e),_debugVisible&&debugError("Motion sensor permission error:",e),window.sensorsEnabled=!0,_notifySketchReady()}}async function _requestMicrophonePermissions(){try{"undefined"!=typeof userStartAudio&&await userStartAudio(),"undefined"!=typeof mic&&mic&&mic.start?(mic.start(),_micInstance=mic,window.micEnabled=!0):console.warn("No microphone object found. Create one with: mic = new p5.AudioIn();"),_notifySketchReady()}catch(e){console.error("Microphone permission error:",e),_debugVisible&&debugError("Microphone permission error:",e),_notifySketchReady()}}async function _requestSoundOutput(){try{"undefined"!=typeof userStartAudio&&await userStartAudio(),window.soundEnabled=!0,_notifySketchReady()}catch(e){console.error("Sound output error:",e),_debugVisible&&debugError("Sound output error:",e),window.soundEnabled=!0,_notifySketchReady()}}async function _requestVibrationPermission(){try{if(!navigator.vibrate)return console.warn("⚠️ Vibration API not supported on this device (likely iOS)"),_debugVisible&&debugWarn("Vibration API not supported on this device"),window.vibrationEnabled=!1,void _notifySketchReady();navigator.vibrate(1)?(window.vibrationEnabled=!0,console.log("✅ Vibration enabled")):(console.warn("⚠️ Vibration API available but vibration failed"),window.vibrationEnabled=!1),_notifySketchReady()}catch(e){console.error("Vibration permission error:",e),_debugVisible&&debugError("Vibration permission error:",e),window.vibrationEnabled=!1,_notifySketchReady()}}function _notifySketchReady(){"function"==typeof userSetupComplete&&userSetupComplete(),window.dispatchEvent(new CustomEvent("permissionsReady",{detail:{sensors:window.sensorsEnabled,microphone:window.micEnabled,sound:window.soundEnabled,vibration:window.vibrationEnabled,gestures:window.gesturesLocked}}))}function _createPermissionButton(e,n,o){_removeExistingUI();const t=document.createElement("button");t.id="permissionButton",t.textContent=e,t.style.cssText="\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n padding: 20px 40px;\n font-size: 18px;\n font-weight: bold;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n border: none;\n border-radius: 12px;\n cursor: pointer;\n z-index: 999999;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n box-shadow: 0 4px 15px rgba(0,0,0,0.2);\n transition: transform 0.2s ease;\n touch-action: manipulation;\n ";const i=document.createElement("div");i.id="permissionStatus",i.textContent=n,i.style.cssText="\n position: fixed;\n top: 60%;\n left: 50%;\n transform: translate(-50%, 0);\n color: white;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n text-align: center;\n z-index: 999998;\n display: none;\n ",t.addEventListener("mouseenter",()=>{t.style.transform="translate(-50%, -50%) scale(1.05)"}),t.addEventListener("mouseleave",()=>{t.style.transform="translate(-50%, -50%) scale(1)"});const r=async()=>{t.parentNode&&(t.style.display="none",i.style.display="block",await o(),i.style.display="none",_removeExistingUI())};t.addEventListener("click",r),t.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),r()}),t.addEventListener("pointerup",function(e){e.preventDefault(),e.stopPropagation(),r()}),document.body.appendChild(t),document.body.appendChild(i)}function _createTapToEnable(e,n){_removeExistingUI();const o=document.createElement("div");o.id="tapOverlay",o.style.cssText="\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.8);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 999999;\n cursor: pointer;\n touch-action: manipulation;\n ";const t=document.createElement("div");t.textContent=e,t.style.cssText="\n color: white;\n font-size: 24px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n text-align: center;\n padding: 40px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n border-radius: 12px;\n background: rgba(255, 255, 255, 0.1);\n backdrop-filter: blur(10px);\n ",o.appendChild(t);const i=async()=>{o.parentNode&&(t.textContent="Enabling...",await n(),o.parentNode&&document.body.removeChild(o))};o.addEventListener("click",i),o.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),i()}),o.addEventListener("pointerup",function(e){e.preventDefault(),e.stopPropagation(),i()}),document.body.appendChild(o)}function _removeExistingUI(){const e=document.getElementById("permissionButton"),n=document.getElementById("permissionStatus"),o=document.getElementById("tapOverlay");e&&e.remove(),n&&n.remove(),o&&o.remove()}function _initializeGestureBlocking(){window.history.pushState(null,"",window.location.href),window.onpopstate=function(){window.history.pushState(null,"",window.location.href)},window.addEventListener("beforeunload",function(e){e.preventDefault(),e.returnValue=""}),_initializeEdgeSwipePrevention(),_initializeOtherGesturePrevention()}function _initializeEdgeSwipePrevention(){let e=0,n=0;document.addEventListener("touchstart",function(o){o.touches&&o.touches.length>0&&(e=o.touches[0].clientX,n=o.touches[0].clientY,(e<20||e>window.innerWidth-20)&&o.preventDefault())},{passive:!1,capture:!0}),document.addEventListener("touchmove",function(o){if(!o.touches||0===o.touches.length)return;let t=o.touches[0].clientX,i=o.touches[0].clientY,r=t-e,a=i-n;(e<20&&r>0||e>window.innerWidth-20&&r<0)&&(o.preventDefault(),o.stopPropagation()),0===window.pageYOffset&&a>0&&o.preventDefault(),!o.target||"CANVAS"!==o.target.tagName||document.getElementById("tapOverlay")||document.getElementById("permissionButton")||o.preventDefault()},{passive:!1,capture:!0})}function _initializeOtherGesturePrevention(){document.addEventListener("gesturestart",function(e){e.preventDefault()}),document.addEventListener("gesturechange",function(e){e.preventDefault()}),document.addEventListener("gestureend",function(e){e.preventDefault()});let e=0;document.addEventListener("touchend",function(n){if(n.target&&("tapOverlay"===n.target.id||n.target.closest("#tapOverlay")||"permissionButton"===n.target.id||"permissionStatus"===n.target.id||n.target.closest("#permissionButton")||n.target.closest("#permissionStatus")))return;const o=Date.now();o-e<=300&&n.preventDefault(),e=o},!1),window.oncontextmenu=function(e){return e.preventDefault(),e.stopPropagation(),!1}}function _initializeP5TouchOverrides(){setTimeout(()=>{if(window._setupDone)_overrideP5Touch();else{const e=setInterval(()=>{window._setupDone&&(_overrideP5Touch(),clearInterval(e))},100)}},100)}function _overrideP5Touch(){const e=window.touchStarted||function(){},n=window.touchMoved||function(){},o=window.touchEnded||function(){},t=window.mousePressed||function(){},i=window.mouseDragged||function(){},r=window.mouseReleased||function(){};window.touchStarted=function(n){return e(n),!1},window.touchMoved=function(e){return n(e),!1},window.touchEnded=function(e){return o(e),!1},window.mousePressed=function(e){return t(e),!1},window.mouseDragged=function(e){return i(e),!1},window.mouseReleased=function(e){return r(e),!1}}document.addEventListener("DOMContentLoaded",function(){const e=document.getElementById("startButton"),n=document.getElementById("statusText");e&&n&&(console.warn("⚠️ Legacy HTML elements detected. Consider using the new API functions instead."),e.addEventListener("click",async()=>{e.classList.add("hidden"),n.classList.remove("hidden"),n.textContent="Requesting permissions...",await _requestMotionPermissions(),await _requestMicrophonePermissions(),n.classList.add("hidden")}),lockGestures())});let _debugPanel=null,_debugVisible=!1,_debugMessages=[];const MAX_DEBUG_MESSAGES=20;function showDebug(){_createDebugPanel(),_debugPanel.style.display="block",_debugVisible=!0,window._debugVisible=!0,_setupConsoleOverrides(),_displayEarlyErrors()}function hideDebug(){_debugPanel&&(_debugPanel.style.display="none",_debugVisible=!1)}function toggleDebug(){_debugVisible?hideDebug():showDebug()}function debug(...e){console.log(...e);_addDebugMessage(e.map(e=>{if("object"==typeof e&&null!==e)try{return JSON.stringify(e,null,2)}catch(n){return String(e)}return String(e)}).join(" "),"log")}function debugError(...e){(window._originalConsoleError||console.error).apply(console,e);_addDebugMessage(`❌ ERROR: ${e.map(e=>{if("object"==typeof e&&null!==e)try{return JSON.stringify(e,null,2)}catch(n){return String(e)}return String(e)}).join(" ")}`,"error")}function debugWarn(...e){(window._originalConsoleWarn||console.warn).apply(console,e);_addDebugMessage(`⚠️ WARNING: ${e.map(e=>{if("object"==typeof e&&null!==e)try{return JSON.stringify(e,null,2)}catch(n){return String(e)}return String(e)}).join(" ")}`,"warning")}function _addDebugMessage(e,n="log"){const o={text:`[${(new Date).toLocaleTimeString("en-US",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}] ${e}`,type:n};_debugMessages.push(o),_debugMessages.length>20&&_debugMessages.shift(),_debugPanel&&_updateDebugDisplay()}function _setupConsoleOverrides(){window._consoleOverrideSet||(window._consoleOverrideSet=!0,window._originalConsoleError=console.error,window._originalConsoleWarn=console.warn,console.error=function(...e){window._originalConsoleError.apply(console,e),_debugVisible&&debugError(...e)},console.warn=function(...e){window._originalConsoleWarn.apply(console,e),_debugVisible&&debugWarn(...e)})}function _displayEarlyErrors(){window._earlyErrors&&window._earlyErrors.length>0&&(debugError(`🚨 Found ${window._earlyErrors.length} early error(s):`),window._earlyErrors.forEach(e=>{debugError(e.message),e.stack&&debugError("Stack trace:",e.stack)}),window._earlyErrors=[])}function _createDebugPanel(){if(_debugPanel)return;_debugPanel=document.createElement("div"),_debugPanel.id="mobile-debug-panel",_debugPanel.innerHTML='\n <div id="mobile-debug-header">\n <span>Debug</span>\n <button id="mobile-debug-close">×</button>\n </div>\n <div id="mobile-debug-content"></div>\n ';const e=document.createElement("style");e.textContent="\n #mobile-debug-panel {\n position: fixed;\n top: 20px;\n right: 20px;\n width: 350px;\n max-width: calc(100vw - 40px);\n max-height: 400px;\n background: rgba(0, 0, 0, 0.9);\n color: #ffffff;\n font-family: 'Courier New', monospace;\n font-size: 12px;\n border-radius: 8px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n z-index: 10000;\n display: none;\n }\n \n #mobile-debug-header {\n background: rgba(255, 255, 255, 0.1);\n padding: 8px 12px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2);\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-radius: 8px 8px 0 0;\n }\n \n #mobile-debug-header span {\n font-weight: bold;\n font-size: 13px;\n }\n \n #mobile-debug-close {\n background: none;\n border: none;\n color: #ffffff;\n font-size: 18px;\n cursor: pointer;\n padding: 0;\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n \n #mobile-debug-close:hover {\n background: rgba(255, 255, 255, 0.1);\n border-radius: 4px;\n }\n \n #mobile-debug-content {\n padding: 12px;\n max-height: 340px;\n overflow-y: auto;\n word-wrap: break-word;\n line-height: 1.4;\n }\n \n .debug-message {\n margin-bottom: 4px;\n white-space: pre-wrap;\n }\n \n .debug-message.error {\n color: #ff6b6b;\n background: rgba(255, 107, 107, 0.1);\n padding: 4px;\n border-radius: 3px;\n border-left: 3px solid #ff6b6b;\n }\n \n .debug-message.warning {\n color: #ffd93d;\n background: rgba(255, 217, 61, 0.1);\n padding: 4px;\n border-radius: 3px;\n border-left: 3px solid #ffd93d;\n }\n \n .debug-timestamp {\n color: #888;\n font-size: 10px;\n }\n \n @media (max-width: 480px) {\n #mobile-debug-panel {\n width: calc(100vw - 20px);\n right: 10px;\n top: 10px;\n }\n }\n ",document.head.appendChild(e),document.body.appendChild(_debugPanel),document.getElementById("mobile-debug-close").onclick=hideDebug,_updateDebugDisplay()}function _updateDebugDisplay(){if(!_debugPanel)return;const e=document.getElementById("mobile-debug-content");e&&(e.innerHTML=_debugMessages.map(e=>"string"==typeof e?`<div class="debug-message">${e}</div>`:`<div class="debug-message ${e.type}">${e.text}</div>`).join(""),e.scrollTop=e.scrollHeight)}debug.clear=function(){_debugMessages=[],_debugPanel&&_updateDebugDisplay(),console.clear()},window.debug=debug,window.debugError=debugError,window.debugWarn=debugWarn,window.showDebug=showDebug,window.hideDebug=hideDebug,window.toggleDebug=toggleDebug,window.lockGestures=lockGestures,window.enableGyroTap=enableGyroTap,window.enableGyroButton=enableGyroButton,window.enableMicTap=enableMicTap,window.enableMicButton=enableMicButton,window.enableSoundTap=enableSoundTap,window.enableSoundButton=enableSoundButton,window.enableVibrationTap=enableVibrationTap,window.enableVibrationButton=enableVibrationButton,window.vibrate=vibrate,window.stopVibration=stopVibration,window.enableAllTap=enableAllTap,window.enableAllButton=enableAllButton,"undefined"!=typeof p5&&p5.prototype&&(p5.prototype.lockGestures=lockGestures,p5.prototype.enableGyroTap=enableGyroTap,p5.prototype.enableGyroButton=enableGyroButton,p5.prototype.enableMicTap=enableMicTap,p5.prototype.enableMicButton=enableMicButton,p5.prototype.enableSoundTap=enableSoundTap,p5.prototype.enableSoundButton=enableSoundButton,p5.prototype.enableVibrationTap=enableVibrationTap,p5.prototype.enableVibrationButton=enableVibrationButton,p5.prototype.vibrate=vibrate,p5.prototype.stopVibration=stopVibration,p5.prototype.enableAllTap=enableAllTap,p5.prototype.enableAllButton=enableAllButton,p5.prototype.showDebug=showDebug,p5.prototype.hideDebug=hideDebug,p5.prototype.toggleDebug=toggleDebug,p5.prototype.debug=debug,p5.prototype.debugError=debugError,p5.prototype.debugWarn=debugWarn,console.log("✅ Mobile p5.js Permissions: p5.prototype functions registered"));
@@ -2,9 +2,9 @@
2
2
  <html>
3
3
  <head>
4
4
  <title>P5.js Mobile - Microphone Level</title>
5
- <script src="https://cdn.jsdelivr.net/npm/p5@1.11.0/lib/p5.min.js"></script>
5
+ <script src="https://cdn.jsdelivr.net/npm/p5@1.11.10/lib/p5.min.js"></script>
6
6
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.0/addons/p5.sound.min.js"></script>
7
- <script src="https://cdn.jsdelivr.net/npm/mobile-p5-permissions@1.4.4/dist/p5.mobile-permissions.min.js"></script>
7
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.4.4/dist/p5-phone.min.js"></script>
8
8
  <style>
9
9
  body {
10
10
  margin: 0;
@@ -18,7 +18,7 @@
18
18
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/p5.js"></script>
19
19
 
20
20
  <!-- Load the mobile p5.js permissions library -->
21
- <script src="https://cdn.jsdelivr.net/npm/mobile-p5-permissions@1.4.4/dist/p5.mobile-permissions.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.4.4/dist/p5-phone.js"></script>
22
22
 
23
23
  </head>
24
24
  <body>
@@ -18,7 +18,7 @@
18
18
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/p5.js"></script>
19
19
 
20
20
  <!-- Load the mobile p5.js permissions library -->
21
- <script src="https://cdn.jsdelivr.net/npm/mobile-p5-permissions@1.4.4/dist/p5.mobile-permissions.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.4.4/dist/p5-phone.js"></script>
22
22
 
23
23
  </head>
24
24
  <body>
@@ -18,7 +18,7 @@
18
18
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/p5.js"></script>
19
19
 
20
20
  <!-- Load the mobile p5.js permissions library -->
21
- <script src="https://cdn.jsdelivr.net/npm/mobile-p5-permissions@1.4.4/dist/p5.mobile-permissions.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.4.4/dist/p5-phone.js"></script>
22
22
 
23
23
  </head>
24
24
  <body>
@@ -21,7 +21,7 @@
21
21
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/addons/p5.sound.min.js"></script>
22
22
 
23
23
  <!-- Load the mobile p5.js permissions library -->
24
- <script src="https://cdn.jsdelivr.net/npm/mobile-p5-permissions@1.4.4/dist/p5.mobile-permissions.min.js"></script>
24
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.4.4/dist/p5-phone.min.js"></script>
25
25
 
26
26
  </head>
27
27
  <body>
@@ -21,7 +21,7 @@
21
21
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/addons/p5.sound.min.js"></script>
22
22
 
23
23
  <!-- Load the mobile p5.js permissions library -->
24
- <script src="https://cdn.jsdelivr.net/npm/mobile-p5-permissions@1.4.4/dist/p5.mobile-permissions.min.js"></script>
24
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.4.4/dist/p5-phone.min.js"></script>
25
25
 
26
26
  </head>
27
27
  <body>
@@ -18,7 +18,7 @@
18
18
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/p5.min.js"></script>
19
19
 
20
20
  <!-- Load the mobile p5.js permissions library -->
21
- <script src="https://cdn.jsdelivr.net/npm/mobile-p5-permissions@1.4.4/dist/p5.mobile-permissions.min.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.4.4/dist/p5-phone.min.js"></script>
22
22
 
23
23
  </head>
24
24
  <body>
@@ -18,7 +18,7 @@
18
18
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/p5.min.js"></script>
19
19
 
20
20
  <!-- Load the mobile p5.js permissions library -->
21
- <script src="https://cdn.jsdelivr.net/npm/mobile-p5-permissions@1.4.4/dist/p5.mobile-permissions.min.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.4.4/dist/p5-phone.min.js"></script>
22
22
 
23
23
  </head>
24
24
  <body>
@@ -18,7 +18,7 @@
18
18
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/p5.min.js"></script>
19
19
 
20
20
  <!-- Load the mobile p5.js permissions library -->
21
- <script src="https://cdn.jsdelivr.net/npm/mobile-p5-permissions@1.4.4/dist/p5.mobile-permissions.min.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.4.4/dist/p5-phone.min.js"></script>
22
22
 
23
23
  </head>
24
24
  <body>
@@ -18,7 +18,7 @@
18
18
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/p5.min.js"></script>
19
19
 
20
20
  <!-- Load the mobile p5.js permissions library -->
21
- <script src="https://cdn.jsdelivr.net/npm/mobile-p5-permissions@1.4.4/dist/p5.mobile-permissions.min.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.4.4/dist/p5-phone.min.js"></script>
22
22
 
23
23
  </head>
24
24
  <body>
@@ -18,7 +18,7 @@
18
18
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/p5.min.js"></script>
19
19
 
20
20
  <!-- Load the mobile p5.js permissions library -->
21
- <script src="https://cdn.jsdelivr.net/npm/mobile-p5-permissions@1.4.4/dist/p5.mobile-permissions.min.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.4.4/dist/p5-phone.min.js"></script>
22
22
 
23
23
  </head>
24
24
  <body>
@@ -0,0 +1,51 @@
1
+ # Haptic Feedback Example
2
+
3
+ This example demonstrates how to use the vibration motor on Android devices to provide haptic feedback for different touch interactions.
4
+
5
+ ## Features
6
+
7
+ - **Four Touch Zones**: Screen divided into 2x2 grid with different vibration patterns
8
+ - **Quick Tap**: 20ms single pulse for subtle feedback
9
+ - **Double Tap**: Two 50ms pulses with 30ms pause between
10
+ - **Triple Tap**: Three 50ms pulses for more prominent feedback
11
+ - **Long Press**: 200ms extended pulse for significant actions
12
+
13
+ ## Android Only
14
+
15
+ ⚠️ **Important**: Vibration is only supported on Android devices. iOS does not support the Vibration API, so this example will not provide haptic feedback on iPhones or iPads.
16
+
17
+ ## How It Works
18
+
19
+ 1. Tap screen to enable vibration (browser security requirement)
20
+ 2. Touch different zones to experience different vibration patterns
21
+ 3. Each zone has a distinct pattern:
22
+ - Single number = duration in milliseconds
23
+ - Array = pattern of [vibrate, pause, vibrate, pause, ...]
24
+
25
+ ## Key Concepts
26
+
27
+ - `enableVibrationTap()` - Request permission to use vibration
28
+ - `window.vibrationEnabled` - Check if vibration is available
29
+ - `vibrate(pattern)` - Trigger vibration with duration or pattern
30
+ - Combining visual and haptic feedback for enhanced UX
31
+
32
+ ## Code Highlights
33
+
34
+ ```javascript
35
+ // Enable vibration with tap
36
+ enableVibrationTap('Tap to enable vibration');
37
+
38
+ // Single vibration
39
+ vibrate(50); // 50ms pulse
40
+
41
+ // Pattern vibration
42
+ vibrate([100, 50, 100]); // vibrate-pause-vibrate
43
+ ```
44
+
45
+ ## Use Cases
46
+
47
+ - Button press feedback
48
+ - Game interactions
49
+ - Alert notifications
50
+ - Touch zone confirmation
51
+ - Gesture completion feedback
@@ -0,0 +1,28 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Haptic Feedback - Mobile p5.js</title>
7
+
8
+ <!-- Basic CSS to remove browser defaults and align canvas -->
9
+ <style>
10
+ body {
11
+ margin: 0;
12
+ padding: 0;
13
+ overflow: hidden;
14
+ }
15
+ </style>
16
+
17
+ <!-- Load p5.js library -->
18
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/p5.min.js"></script>
19
+
20
+ <!-- Load the mobile p5.js permissions library -->
21
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.4.4/dist/p5-phone.min.js"></script>
22
+
23
+ </head>
24
+ <body>
25
+ <!-- Load the p5.js sketch -->
26
+ <script src="sketch.js"></script>
27
+ </body>
28
+ </html>