p5-phone 1.9.2 → 1.9.3

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 (99) hide show
  1. package/README.md +3 -3
  2. package/dist/p5-phone.js +48 -11
  3. package/dist/p5-phone.min.js +1 -1
  4. package/examples/Phone Sensor Examples/microphone/01_mic_level/index.html +2 -2
  5. package/examples/Phone Sensor Examples/microphone/01_mic_level/sketch.js +15 -1
  6. package/examples/Phone Sensor Examples/microphone/02_speech_recognition/index.html +1 -1
  7. package/examples/Phone Sensor Examples/movement/01_orientation_basic/index.html +1 -1
  8. package/examples/Phone Sensor Examples/movement/02_rotational_velocity/index.html +1 -1
  9. package/examples/Phone Sensor Examples/movement/03_acceleration/index.html +1 -1
  10. package/examples/Phone Sensor Examples/movement/03_acceleration/sketch.js +1 -0
  11. package/examples/Phone Sensor Examples/movement/04_device_shaken/index.html +1 -1
  12. package/examples/Phone Sensor Examples/movement/04_device_shaken/sketch.js +10 -6
  13. package/examples/Phone Sensor Examples/movement/05_device_moved/index.html +1 -1
  14. package/examples/Phone Sensor Examples/movement/05_device_moved/sketch.js +21 -7
  15. package/examples/Phone Sensor Examples/movement/06_device_orientation/index.html +1 -1
  16. package/examples/Phone Sensor Examples/nfc/01_nfc_read/index.html +1 -1
  17. package/examples/Phone Sensor Examples/nfc/01_nfc_read/sketch.js +8 -4
  18. package/examples/Phone Sensor Examples/nfc/02_two_tag_effects/index.html +1 -1
  19. package/examples/Phone Sensor Examples/nfc/02_two_tag_effects/sketch.js +2 -2
  20. package/examples/Phone Sensor Examples/sound/01_dual_audio/index.html +2 -2
  21. package/examples/Phone Sensor Examples/sound/01_dual_audio/sketch.js +108 -140
  22. package/examples/Phone Sensor Examples/sound/02_volume_touches/index.html +2 -2
  23. package/examples/Phone Sensor Examples/sound/02_volume_touches/sketch.js +73 -144
  24. package/examples/Phone Sensor Examples/sound/03_motion_synth/index.html +2 -2
  25. package/examples/Phone Sensor Examples/sound/03_motion_synth/sketch.js +70 -23
  26. package/examples/Phone Sensor Examples/touch/01_touch_basic/index.html +1 -1
  27. package/examples/Phone Sensor Examples/touch/02_touch_zones/index.html +1 -1
  28. package/examples/Phone Sensor Examples/touch/03_touch_count/index.html +1 -1
  29. package/examples/Phone Sensor Examples/touch/04_touch_distance/index.html +1 -1
  30. package/examples/Phone Sensor Examples/touch/05_touch_angle/index.html +1 -1
  31. package/examples/Phone Sensor Examples/vibration/01_haptic_feedback/index.html +1 -1
  32. package/examples/Phone Sensor Examples - Minimal/microphone/01_mic_level/index.html +2 -2
  33. package/examples/Phone Sensor Examples - Minimal/microphone/01_mic_level/sketch.js +18 -2
  34. package/examples/Phone Sensor Examples - Minimal/movement/01_orientation_basic/index.html +1 -1
  35. package/examples/Phone Sensor Examples - Minimal/movement/02_rotational_velocity/index.html +1 -1
  36. package/examples/Phone Sensor Examples - Minimal/movement/03_acceleration/index.html +1 -1
  37. package/examples/Phone Sensor Examples - Minimal/movement/04_device_shaken/index.html +1 -1
  38. package/examples/Phone Sensor Examples - Minimal/movement/04_device_shaken/sketch.js +8 -2
  39. package/examples/Phone Sensor Examples - Minimal/movement/05_device_moved/index.html +1 -1
  40. package/examples/Phone Sensor Examples - Minimal/movement/05_device_moved/sketch.js +18 -2
  41. package/examples/Phone Sensor Examples - Minimal/movement/06_device_orientation/index.html +1 -1
  42. package/examples/Phone Sensor Examples - Minimal/sound/01_sound_basic/index.html +2 -2
  43. package/examples/Phone Sensor Examples - Minimal/sound/01_sound_basic/sketch.js +39 -50
  44. package/examples/Phone Sensor Examples - Minimal/sound/02_sound_amplitude/index.html +2 -2
  45. package/examples/Phone Sensor Examples - Minimal/sound/02_sound_amplitude/sketch.js +61 -66
  46. package/examples/Phone Sensor Examples - Minimal/sound/03_motion_synth/index.html +2 -2
  47. package/examples/Phone Sensor Examples - Minimal/sound/03_motion_synth/sketch.js +32 -10
  48. package/examples/Phone Sensor Examples - Minimal/touch/01_touch_basic/index.html +1 -1
  49. package/examples/Phone Sensor Examples - Minimal/touch/02_touch_zones/index.html +1 -1
  50. package/examples/Phone Sensor Examples - Minimal/touch/03_touch_count/index.html +1 -1
  51. package/examples/Phone Sensor Examples - Minimal/touch/04_touch_distance/index.html +1 -1
  52. package/examples/Phone Sensor Examples - Minimal/touch/05_touch_angle/index.html +1 -1
  53. package/examples/Phone Sensor Examples - Minimal/vibration/01_haptic_feedback/index.html +1 -1
  54. package/examples/Phone and Gif/collision/index.html +1 -1
  55. package/examples/Phone and Gif/fetch/index.html +1 -1
  56. package/examples/Phone and Gif/fly/index.html +1 -1
  57. package/examples/Phone and Gif/roll/index.html +1 -1
  58. package/examples/UIStyles/banner-style/index.html +1 -1
  59. package/examples/UIStyles/canvas-style/index.html +1 -1
  60. package/examples/UIStyles/canvas-style/sketch.js +1 -1
  61. package/examples/UIStyles/custom-element/index.html +1 -1
  62. package/examples/UIStyles/custom-element/sketch.js +1 -1
  63. package/examples/UXcompare/button-vs-movement/index.html +1 -1
  64. package/examples/UXcompare/button-vs-orientation/index.html +1 -1
  65. package/examples/UXcompare/button-vs-shake/index.html +1 -1
  66. package/examples/UXcompare/gyroscope-demo/index.html +1 -1
  67. package/examples/UXcompare/microphone-demo/index.html +1 -1
  68. package/examples/UXcompare/slider-vs-angle/index.html +1 -1
  69. package/examples/UXcompare/slider-vs-distance/index.html +1 -1
  70. package/examples/UXcompare/slider-vs-microphone/index.html +1 -1
  71. package/examples/UXcompare/slider-vs-touches/index.html +1 -1
  72. package/examples/UXcompare/sliders-vs-acceleration/index.html +1 -1
  73. package/examples/UXcompare/sliders-vs-rotation/index.html +1 -1
  74. package/examples/blankTemplate/index.html +1 -1
  75. package/examples/homepage/index.html +3 -1
  76. package/examples/homepage/scripts/examples-data.js +51 -34
  77. package/examples/homepage/scripts/render.js +37 -1
  78. package/examples/homepage/styles/main.css +2 -0
  79. package/examples/homepage-v1/index.html +5 -14
  80. package/examples/ml5/Gaze_detector_class/GazeDetector.js +28 -10
  81. package/examples/ml5/Gaze_detector_class/README.md +9 -4
  82. package/examples/ml5/Gaze_detector_class/index.html +8 -4
  83. package/examples/ml5/PHONE_BodyPose_two_points/index.html +7 -3
  84. package/examples/ml5/PHONE_BodyPose_two_points/sketch.js +24 -5
  85. package/examples/ml5/PHONE_FaceMesh_two_points/index.html +7 -3
  86. package/examples/ml5/PHONE_FaceMesh_two_points/sketch.js +24 -5
  87. package/examples/ml5/PHONE_HandPose_two_points/index.html +7 -3
  88. package/examples/ml5/PHONE_HandPose_two_points/sketch.js +101 -6
  89. package/examples/ml5/THREE_BodyPose_two_points/functions.js +3 -1
  90. package/examples/ml5/THREE_BodyPose_two_points/index.html +8 -0
  91. package/examples/ml5/THREE_BodyPose_two_points/sketch.js +22 -7
  92. package/examples/ml5/THREE_FaceMesh_two_points/functions.js +3 -1
  93. package/examples/ml5/THREE_FaceMesh_two_points/index.html +8 -0
  94. package/examples/ml5/THREE_FaceMesh_two_points/sketch.js +22 -7
  95. package/examples/ml5/THREE_HandPose_two_points/index.html +12 -4
  96. package/package.json +1 -1
  97. package/src/p5-phone.js +48 -11
  98. package/examples/ml5/PHONE_ObjectDetection/index.html +0 -27
  99. package/examples/ml5/PHONE_ObjectDetection/sketch.js +0 -236
package/README.md CHANGED
@@ -102,10 +102,10 @@ p5-phone automatically detects the p5.js version and adjusts its internal touch
102
102
 
103
103
  ```html
104
104
  <!-- Minified version (recommended) -->
105
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.1/dist/p5-phone.min.js"></script>
105
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.3/dist/p5-phone.min.js"></script>
106
106
 
107
107
  <!-- Development version (larger, with comments) -->
108
- <!-- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.1/dist/p5-phone.js"></script> -->
108
+ <!-- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.3/dist/p5-phone.js"></script> -->
109
109
  ```
110
110
 
111
111
  ### Basic Setup
@@ -134,7 +134,7 @@ p5-phone automatically detects the p5.js version and adjusts its internal touch
134
134
  <script src="https://cdn.jsdelivr.net/npm/p5.js-compatibility@0.2.0/src/preload.js"></script>
135
135
 
136
136
  <!-- Load p5-phone library -->
137
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.1/dist/p5-phone.min.js"></script>
137
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.3/dist/p5-phone.min.js"></script>
138
138
 
139
139
  </head>
140
140
  <body>
package/dist/p5-phone.js CHANGED
@@ -1678,6 +1678,8 @@ window.toggleDebug = toggleDebug;
1678
1678
  window.lockGestures = lockGestures;
1679
1679
  window.enableGyroTap = enableGyroTap;
1680
1680
  window.enableGyroButton = enableGyroButton;
1681
+ window.enableSensorTap = enableGyroTap;
1682
+ window.enableSensorButton = enableGyroButton;
1681
1683
  window.enableMicTap = enableMicTap;
1682
1684
  window.enableMicButton = enableMicButton;
1683
1685
  window.enableSoundTap = enableSoundTap;
@@ -1699,6 +1701,7 @@ window.enableAllButton = enableAllButton;
1699
1701
 
1700
1702
  // Canvas-first-touch style
1701
1703
  window.enableGyroCanvas = enableGyroCanvas;
1704
+ window.enableSensorCanvas = enableGyroCanvas;
1702
1705
  window.enableMicCanvas = enableMicCanvas;
1703
1706
  window.enableSoundCanvas = enableSoundCanvas;
1704
1707
  window.enableSpeechCanvas = enableSpeechCanvas;
@@ -1709,6 +1712,7 @@ window.enableCameraCanvas = enableCameraCanvas;
1709
1712
 
1710
1713
  // Banner style
1711
1714
  window.enableGyroBanner = enableGyroBanner;
1715
+ window.enableSensorBanner = enableGyroBanner;
1712
1716
  window.enableMicBanner = enableMicBanner;
1713
1717
  window.enableSoundBanner = enableSoundBanner;
1714
1718
  window.enableSpeechBanner = enableSpeechBanner;
@@ -1719,6 +1723,7 @@ window.enableCameraBanner = enableCameraBanner;
1719
1723
 
1720
1724
  // Custom element binding
1721
1725
  window.enableGyroOn = enableGyroOn;
1726
+ window.enableSensorOn = enableGyroOn;
1722
1727
  window.enableMicOn = enableMicOn;
1723
1728
  window.enableSoundOn = enableSoundOn;
1724
1729
  window.enableSpeechOn = enableSpeechOn;
@@ -2171,8 +2176,13 @@ class PhoneCamera {
2171
2176
  return { x: 0, y: 0, width: 0, height: 0, scaleX: 1, scaleY: 1 };
2172
2177
  }
2173
2178
 
2174
- const videoWidth = this._video.width;
2175
- const videoHeight = this._video.height;
2179
+ const videoElement = this.videoElement;
2180
+ const videoWidth = (videoElement && videoElement.videoWidth) || this._video.width;
2181
+ const videoHeight = (videoElement && videoElement.videoHeight) || this._video.height;
2182
+
2183
+ if (!videoWidth || !videoHeight) {
2184
+ return { x: 0, y: 0, width: 0, height: 0, scaleX: 1, scaleY: 1 };
2185
+ }
2176
2186
 
2177
2187
  // Get actual canvas DISPLAY dimensions (not drawing buffer dimensions)
2178
2188
  // In p5.js, the width/height globals represent the logical canvas size
@@ -2370,8 +2380,9 @@ class PhoneCamera {
2370
2380
  * Custom draw method for p5.image() compatibility
2371
2381
  * This allows image(cam, x, y) to work
2372
2382
  */
2373
- _draw() {
2374
- if (!this._ready || !this._video) return;
2383
+ _draw(p5Instance = null) {
2384
+ const videoElement = this.videoElement;
2385
+ if (!this._ready || !this._video || !videoElement || videoElement.readyState < 2) return;
2375
2386
 
2376
2387
  const dims = this.getDimensions();
2377
2388
 
@@ -2391,21 +2402,37 @@ class PhoneCamera {
2391
2402
  this._drawDebugLogged = true;
2392
2403
  }
2393
2404
 
2405
+ const context = (p5Instance && p5Instance.drawingContext) ||
2406
+ (typeof drawingContext !== 'undefined' ? drawingContext : null);
2407
+
2408
+ if (!context || typeof context.drawImage !== 'function') return;
2409
+
2410
+ const pushState = (p5Instance && p5Instance.push) ? p5Instance.push.bind(p5Instance) :
2411
+ (typeof push === 'function' ? push : null);
2412
+ const popState = (p5Instance && p5Instance.pop) ? p5Instance.pop.bind(p5Instance) :
2413
+ (typeof pop === 'function' ? pop : null);
2414
+ const translateCanvas = (p5Instance && p5Instance.translate) ? p5Instance.translate.bind(p5Instance) :
2415
+ (typeof translate === 'function' ? translate : null);
2416
+ const scaleCanvas = (p5Instance && p5Instance.scale) ? p5Instance.scale.bind(p5Instance) :
2417
+ (typeof scale === 'function' ? scale : null);
2418
+
2419
+ if (!pushState || !popState || !translateCanvas || !scaleCanvas) return;
2420
+
2394
2421
  // Save current drawing state
2395
- push();
2422
+ pushState();
2396
2423
 
2397
2424
  // Apply mirroring if needed
2398
2425
  if (this._mirror) {
2399
2426
  // Mirror by flipping around the center of the canvas
2400
- translate(canvasWidth, 0);
2401
- scale(-1, 1);
2427
+ translateCanvas(canvasWidth, 0);
2428
+ scaleCanvas(-1, 1);
2402
2429
  // Draw at the same logical position
2403
- image(this._video, dims.x, dims.y, dims.width, dims.height);
2430
+ context.drawImage(videoElement, dims.x, dims.y, dims.width, dims.height);
2404
2431
  } else {
2405
- image(this._video, dims.x, dims.y, dims.width, dims.height);
2432
+ context.drawImage(videoElement, dims.x, dims.y, dims.width, dims.height);
2406
2433
  }
2407
2434
 
2408
- pop();
2435
+ popState();
2409
2436
  }
2410
2437
  }
2411
2438
 
@@ -2512,7 +2539,7 @@ if (typeof p5 !== 'undefined' && p5.prototype) {
2512
2539
 
2513
2540
  // Always use auto-positioning for PhoneCamera
2514
2541
  // The camera calculates the correct position based on mode (fitHeight, fitWidth, etc)
2515
- cam._draw();
2542
+ cam._draw(this);
2516
2543
  } else {
2517
2544
  // Not a PhoneCamera, use original image function
2518
2545
  originalImage.apply(this, args);
@@ -2534,6 +2561,8 @@ if (typeof p5 !== 'undefined' && p5.prototype && typeof p5.registerAddon !== 'fu
2534
2561
  p5.prototype.lockGestures = lockGestures;
2535
2562
  p5.prototype.enableGyroTap = enableGyroTap;
2536
2563
  p5.prototype.enableGyroButton = enableGyroButton;
2564
+ p5.prototype.enableSensorTap = enableGyroTap;
2565
+ p5.prototype.enableSensorButton = enableGyroButton;
2537
2566
  p5.prototype.enableMicTap = enableMicTap;
2538
2567
  p5.prototype.enableMicButton = enableMicButton;
2539
2568
  p5.prototype.enableSoundTap = enableSoundTap;
@@ -2555,6 +2584,7 @@ if (typeof p5 !== 'undefined' && p5.prototype && typeof p5.registerAddon !== 'fu
2555
2584
 
2556
2585
  // Canvas-first-touch style
2557
2586
  p5.prototype.enableGyroCanvas = enableGyroCanvas;
2587
+ p5.prototype.enableSensorCanvas = enableGyroCanvas;
2558
2588
  p5.prototype.enableMicCanvas = enableMicCanvas;
2559
2589
  p5.prototype.enableSoundCanvas = enableSoundCanvas;
2560
2590
  p5.prototype.enableSpeechCanvas = enableSpeechCanvas;
@@ -2565,6 +2595,7 @@ if (typeof p5 !== 'undefined' && p5.prototype && typeof p5.registerAddon !== 'fu
2565
2595
 
2566
2596
  // Banner style
2567
2597
  p5.prototype.enableGyroBanner = enableGyroBanner;
2598
+ p5.prototype.enableSensorBanner = enableGyroBanner;
2568
2599
  p5.prototype.enableMicBanner = enableMicBanner;
2569
2600
  p5.prototype.enableSoundBanner = enableSoundBanner;
2570
2601
  p5.prototype.enableSpeechBanner = enableSpeechBanner;
@@ -2575,6 +2606,7 @@ if (typeof p5 !== 'undefined' && p5.prototype && typeof p5.registerAddon !== 'fu
2575
2606
 
2576
2607
  // Custom element binding
2577
2608
  p5.prototype.enableGyroOn = enableGyroOn;
2609
+ p5.prototype.enableSensorOn = enableGyroOn;
2578
2610
  p5.prototype.enableMicOn = enableMicOn;
2579
2611
  p5.prototype.enableSoundOn = enableSoundOn;
2580
2612
  p5.prototype.enableSpeechOn = enableSpeechOn;
@@ -2620,6 +2652,8 @@ if (typeof p5 !== 'undefined' && typeof p5.registerAddon === 'function') {
2620
2652
  this.lockGestures = lockGestures;
2621
2653
  this.enableGyroTap = enableGyroTap;
2622
2654
  this.enableGyroButton = enableGyroButton;
2655
+ this.enableSensorTap = enableGyroTap;
2656
+ this.enableSensorButton = enableGyroButton;
2623
2657
  this.enableMicTap = enableMicTap;
2624
2658
  this.enableMicButton = enableMicButton;
2625
2659
  this.enableSoundTap = enableSoundTap;
@@ -2641,6 +2675,7 @@ if (typeof p5 !== 'undefined' && typeof p5.registerAddon === 'function') {
2641
2675
 
2642
2676
  // Canvas-first-touch style
2643
2677
  this.enableGyroCanvas = enableGyroCanvas;
2678
+ this.enableSensorCanvas = enableGyroCanvas;
2644
2679
  this.enableMicCanvas = enableMicCanvas;
2645
2680
  this.enableSoundCanvas = enableSoundCanvas;
2646
2681
  this.enableSpeechCanvas = enableSpeechCanvas;
@@ -2651,6 +2686,7 @@ if (typeof p5 !== 'undefined' && typeof p5.registerAddon === 'function') {
2651
2686
 
2652
2687
  // Banner style
2653
2688
  this.enableGyroBanner = enableGyroBanner;
2689
+ this.enableSensorBanner = enableGyroBanner;
2654
2690
  this.enableMicBanner = enableMicBanner;
2655
2691
  this.enableSoundBanner = enableSoundBanner;
2656
2692
  this.enableSpeechBanner = enableSpeechBanner;
@@ -2661,6 +2697,7 @@ if (typeof p5 !== 'undefined' && typeof p5.registerAddon === 'function') {
2661
2697
 
2662
2698
  // Custom element binding
2663
2699
  this.enableGyroOn = enableGyroOn;
2700
+ this.enableSensorOn = enableGyroOn;
2664
2701
  this.enableMicOn = enableMicOn;
2665
2702
  this.enableSoundOn = enableSoundOn;
2666
2703
  this.enableSpeechOn = enableSpeechOn;
@@ -7,4 +7,4 @@
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,window.vibrationEnabled=!1,window.speechEnabled=!1,window.nfcEnabled=!1,window.nfcError="",window.nfcStatus="idle",window.nfcTagAliases={},window.lastNfcMessage=null,window.lastNfcSerialNumber=null,window.lastNfcAlias="";let _micInstance=null,_nfcReader=null,_nfcAbortController=null;const _p5MajorVersion="undefined"!=typeof p5&&p5.VERSION?parseInt(p5.VERSION.split(".")[0],10):1,_isP5v2=_p5MajorVersion>=2;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 enableSpeechTap(e="Tap to enable speech recognition"){_createTapToEnable(e,async()=>{await _requestSpeechPermission(),console.log("✅ Speech recognition enabled via tap")})}function enableSpeechButton(e="ENABLE SPEECH RECOGNITION",n="Enabling speech recognition..."){_createPermissionButton(e,n,async()=>{await _requestSpeechPermission(),console.log("✅ Speech recognition enabled via button")})}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 enableNfcButton(e="ENABLE NFC",n="Enabling NFC..."){_createPermissionButton(e,n,async()=>{await _requestNfcPermission(),console.log("✅ NFC enabled via button")})}function enableNfcTap(e="Tap screen to enable NFC"){_createTapToEnable(e,async()=>{await _requestNfcPermission(),console.log("✅ NFC enabled via tap")})}function enableAllButton(e="ENABLE MOTION & MICROPHONE",n="Requesting permissions..."){_createPermissionButton(e,n,async()=>{await _requestMotionPermissionsCore(),await _requestMicrophonePermissionsCore(),_notifySketchReady(),console.log("✅ Motion sensors and microphone enabled via button")})}function enableAllTap(e="Tap screen to enable motion sensors & microphone"){_createTapToEnable(e,async()=>{await _requestMotionPermissionsCore(),await _requestMicrophonePermissionsCore(),_notifySketchReady(),console.log("✅ Motion sensors and microphone enabled via tap")})}function enableGyroCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestMotionPermissions(),console.log("✅ Gyroscope enabled via canvas touch")})}function enableMicCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestMicrophonePermissions(),console.log("✅ Microphone enabled via canvas touch")})}function enableSoundCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestSoundOutput(),console.log("✅ Sound output enabled via canvas touch")})}function enableSpeechCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestSpeechPermission(),console.log("✅ Speech recognition enabled via canvas touch")})}function enableVibrationCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestVibrationPermission(),console.log("✅ Vibration enabled via canvas touch")})}function enableNfcCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestNfcPermission(),console.log("✅ NFC enabled via canvas touch")})}function enableAllCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestMotionPermissionsCore(),await _requestMicrophonePermissionsCore(),_notifySketchReady(),console.log("✅ Motion sensors and microphone enabled via canvas touch")})}function enableCameraCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via canvas touch")})}function enableGyroBanner(e="Tap to enable motion sensors",n="top"){_createBannerToEnable(e,n,async()=>{await _requestMotionPermissions(),console.log("✅ Gyroscope enabled via banner")})}function enableMicBanner(e="Tap to enable microphone",n="top"){_createBannerToEnable(e,n,async()=>{await _requestMicrophonePermissions(),console.log("✅ Microphone enabled via banner")})}function enableSoundBanner(e="Tap to enable sound",n="top"){_createBannerToEnable(e,n,async()=>{await _requestSoundOutput(),console.log("✅ Sound output enabled via banner")})}function enableSpeechBanner(e="Tap to enable speech recognition",n="top"){_createBannerToEnable(e,n,async()=>{await _requestSpeechPermission(),console.log("✅ Speech recognition enabled via banner")})}function enableVibrationBanner(e="Tap to enable vibration",n="top"){_createBannerToEnable(e,n,async()=>{await _requestVibrationPermission(),console.log("✅ Vibration enabled via banner")})}function enableNfcBanner(e="Tap to enable NFC",n="top"){_createBannerToEnable(e,n,async()=>{await _requestNfcPermission(),console.log("✅ NFC enabled via banner")})}function enableAllBanner(e="Tap to enable sensors & microphone",n="top"){_createBannerToEnable(e,n,async()=>{await _requestMotionPermissionsCore(),await _requestMicrophonePermissionsCore(),_notifySketchReady(),console.log("✅ Motion sensors and microphone enabled via banner")})}function enableCameraBanner(e="Tap to enable camera",n="top"){_createBannerToEnable(e,n,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via banner")})}function enableGyroOn(e){_bindPermissionTo(e,async()=>{await _requestMotionPermissions(),console.log("✅ Gyroscope enabled via custom element")})}function enableMicOn(e){_bindPermissionTo(e,async()=>{await _requestMicrophonePermissions(),console.log("✅ Microphone enabled via custom element")})}function enableSoundOn(e){_bindPermissionTo(e,async()=>{await _requestSoundOutput(),console.log("✅ Sound output enabled via custom element")})}function enableSpeechOn(e){_bindPermissionTo(e,async()=>{await _requestSpeechPermission(),console.log("✅ Speech recognition enabled via custom element")})}function enableVibrationOn(e){_bindPermissionTo(e,async()=>{await _requestVibrationPermission(),console.log("✅ Vibration enabled via custom element")})}function enableNfcOn(e){_bindPermissionTo(e,async()=>{await _requestNfcPermission(),console.log("✅ NFC enabled via custom element")})}function enableAllOn(e){_bindPermissionTo(e,async()=>{await _requestMotionPermissionsCore(),await _requestMicrophonePermissionsCore(),_notifySketchReady(),console.log("✅ Motion sensors and microphone enabled via custom element")})}function enableCameraOn(e){_bindPermissionTo(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via custom element")})}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)}function stopNfc(){_nfcAbortController&&(_nfcAbortController.abort(),_nfcAbortController=null),_nfcReader=null,window.nfcEnabled=!1,window.nfcStatus="stopped",console.log("NFC scanning stopped")}function _normalizeNfcText(e){return null==e?"":String(e).trim()}function _normalizeNfcTagId(e){return _normalizeNfcText(e).toLowerCase()}function _nfcTextMatches(e,n){const o=_normalizeNfcText(e).toLowerCase(),t=_normalizeNfcText(n).toLowerCase();return""!==o&&o===t}function setNfcTagAlias(e,n){const o=_normalizeNfcTagId(e),t=_normalizeNfcText(n);return o?(t?window.nfcTagAliases[o]=t:delete window.nfcTagAliases[o],_normalizeNfcTagId(window.lastNfcSerialNumber)===o&&(window.lastNfcAlias=t,window.lastNfcMessage&&(window.lastNfcMessage.alias=t)),t):(console.warn("p5-phone: setNfcTagAlias() needs an NFC serial number"),"")}function getNfcTagAlias(e=window.lastNfcSerialNumber){const n=_normalizeNfcTagId(e);return n&&window.nfcTagAliases[n]||""}function isNfcTag(e,n=window.lastNfcSerialNumber){const o=_normalizeNfcTagId(n),t=_normalizeNfcText(e);return!(!o||!t)&&(o===_normalizeNfcTagId(t)||_nfcTextMatches(getNfcTagAlias(n),t))}async function _requestMotionPermissionsCore(){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}catch(e){console.error("Motion sensor permission error:",e),_debugVisible&&debugError("Motion sensor permission error:",e),window.sensorsEnabled=!0}}async function _requestMicrophonePermissionsCore(){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();")}catch(e){console.error("Microphone permission error:",e),_debugVisible&&debugError("Microphone permission error:",e)}}async function _requestSoundOutputCore(){try{"undefined"!=typeof userStartAudio&&await userStartAudio(),window.soundEnabled=!0}catch(e){console.error("Sound output error:",e),_debugVisible&&debugError("Sound output error:",e),window.soundEnabled=!0}}async function _requestSpeechPermissionCore(){try{"undefined"!=typeof userStartAudio&&await userStartAudio(),window.speechEnabled=!0}catch(e){console.error("Speech permission error:",e),_debugVisible&&debugError("Speech permission error:",e)}}async function _requestVibrationPermissionCore(){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"),void(window.vibrationEnabled=!1);navigator.vibrate(1)?(window.vibrationEnabled=!0,console.log("✅ Vibration enabled")):(console.warn("⚠️ Vibration API available but vibration failed"),window.vibrationEnabled=!1)}catch(e){console.error("Vibration permission error:",e),_debugVisible&&debugError("Vibration permission error:",e),window.vibrationEnabled=!1}}async function _requestNfcPermissionCore(){try{return!(!window.nfcEnabled||!_nfcReader)||(window.nfcError="",window.nfcStatus="starting","NDEFReader"in window?(window.nfcStatus="requesting-permission",_nfcAbortController=new AbortController,_nfcReader=new NDEFReader,_nfcReader.onreading=e=>{const n=e.serialNumber||"",o=new TextDecoder,t=[];for(const n of e.message.records){const e={recordType:n.recordType,mediaType:n.mediaType||null,id:n.id||null,data:null,raw:n.data};if("text"===n.recordType||"url"===n.recordType)e.data=o.decode(n.data);else if("mime"===n.recordType&&n.mediaType)try{const t=o.decode(n.data);n.mediaType.includes("json")?e.data=JSON.parse(t):e.data=t}catch(o){e.data=n.data}else e.data=n.data;t.push(e)}const a=getNfcTagAlias(n),i={serialNumber:n,alias:a,records:t};window.lastNfcMessage=i,window.lastNfcSerialNumber=n,window.lastNfcAlias=a,window.nfcStatus="tag-read",window.nfcError="","function"==typeof nfcRead&&nfcRead(i,n),console.log("NFC tag read — serial:",n,"records:",t.length),_debugVisible&&debug("NFC tag read: "+n)},_nfcReader.onreadingerror=e=>{console.warn("⚠️ NFC read error — tag may be incompatible or out of range"),window.nfcError="NFC read error. Make sure the tag is NDEF formatted and hold it near the phone NFC antenna.",_debugVisible&&debugWarn("NFC read error — tag incompatible or out of range")},await _nfcReader.scan({signal:_nfcAbortController.signal}),window.nfcEnabled=!0,window.nfcStatus="scanning",console.log("✅ NFC scanning active"),!0):(console.warn("⚠️ Web NFC API not supported on this device/browser (Android Chrome 89+ required)"),_debugVisible&&debugWarn("Web NFC not supported on this device/browser"),window.nfcEnabled=!1,window.nfcStatus="unsupported",window.nfcError=!1===window.isSecureContext?"NFC requires HTTPS. Serve this sketch from an HTTPS URL, not plain HTTP.":"Web NFC is not supported in this browser. Use Android Chrome 89+ over HTTPS.",!1))}catch(e){return"NotAllowedError"===e.name?(console.warn("⚠️ NFC permission denied by user"),window.nfcStatus="permission-denied",window.nfcError="NFC permission was denied. Reload and tap Allow if Chrome asks.",_debugVisible&&debugWarn("NFC permission denied")):"NotSupportedError"===e.name?(console.warn("⚠️ NFC not supported on this device"),window.nfcStatus="unsupported",window.nfcError="NFC is not supported on this device/browser, or this page is not using HTTPS.",_debugVisible&&debugWarn("NFC not supported on this device")):"SecurityError"===e.name?(console.warn("⚠️ NFC requires a secure HTTPS context"),window.nfcStatus="secure-context-required",window.nfcError="NFC requires HTTPS. Serve this sketch from an HTTPS URL, not plain HTTP.",_debugVisible&&debugWarn("NFC requires HTTPS")):(console.error("NFC permission error:",e),window.nfcStatus="error",window.nfcError=e&&e.message?e.message:"NFC could not start.",_debugVisible&&debugError("NFC error: "+e.message)),window.nfcEnabled=!1,_nfcReader=null,_nfcAbortController=null,!1}}async function _requestMotionPermissions(){await _requestMotionPermissionsCore(),_notifySketchReady()}async function _requestMicrophonePermissions(){await _requestMicrophonePermissionsCore(),_notifySketchReady()}async function _requestSoundOutput(){await _requestSoundOutputCore(),_notifySketchReady()}async function _requestSpeechPermission(){await _requestSpeechPermissionCore(),_notifySketchReady()}async function _requestVibrationPermission(){await _requestVibrationPermissionCore(),_notifySketchReady()}async function _requestNfcPermission(){const e=await _requestNfcPermissionCore();return _notifySketchReady(),e}function _notifySketchReady(){"function"==typeof userSetupComplete&&userSetupComplete(),window.dispatchEvent(new CustomEvent("permissionsReady",{detail:{sensors:window.sensorsEnabled,microphone:window.micEnabled,sound:window.soundEnabled,speech:window.speechEnabled,vibration:window.vibrationEnabled,nfc:window.nfcEnabled,gestures:window.gesturesLocked}}))}function _createPermissionButton(e,n,o){_removeExistingUI();let t=!1;const a=document.createElement("button");a.id="permissionButton",a.textContent=e,a.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 ",a.addEventListener("mouseenter",()=>{a.style.transform="translate(-50%, -50%) scale(1.05)"}),a.addEventListener("mouseleave",()=>{a.style.transform="translate(-50%, -50%) scale(1)"});const r=async()=>{!t&&a.parentNode&&(t=!0,a.style.display="none",i.style.display="block",await o(),i.style.display="none",_removeExistingUI())};a.addEventListener("click",r),a.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),r()}),a.addEventListener("pointerup",function(e){e.preventDefault(),e.stopPropagation(),r()}),document.body.appendChild(a),document.body.appendChild(i)}function _createTapToEnable(e,n){_removeExistingUI();let o=!1;const t=document.createElement("div");t.id="tapOverlay",t.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 a=document.createElement("div");a.textContent=e,a.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 ",t.appendChild(a);const i=async()=>{!o&&t.parentNode&&(o=!0,a.textContent="Enabling...",await n(),t.parentNode&&document.body.removeChild(t))};t.addEventListener("click",i),t.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),i()}),t.addEventListener("pointerup",function(e){e.preventDefault(),e.stopPropagation(),i()}),document.body.appendChild(t)}function _removeExistingUI(){const e=document.getElementById("permissionButton"),n=document.getElementById("permissionStatus"),o=document.getElementById("tapOverlay"),t=document.getElementById("permissionBanner");e&&e.remove(),n&&n.remove(),o&&o.remove(),t&&t.remove()}function _createCanvasToEnable(e,n){_removeExistingUI();let o=!1,t=null;e&&(t=setInterval(()=>{const n=document.querySelector("canvas");n&&"function"==typeof push&&(push(),fill(255,255,255,200),noStroke(),textAlign(CENTER,CENTER),textSize(.04*Math.min(n.width,n.height)),text(e,("undefined"!=typeof width?width:n.width)/2,.9*("undefined"!=typeof height?height:n.height)),pop())},50));const a=async e=>{o||(o=!0,t&&(clearInterval(t),t=null),document.removeEventListener("touchstart",a,!0),document.removeEventListener("mousedown",a,!0),await n())},i=()=>{const e=document.querySelector("canvas");e?(e.addEventListener("touchstart",a,{once:!0,capture:!0}),e.addEventListener("mousedown",a,{once:!0,capture:!0})):setTimeout(i,50)};i()}function _createBannerToEnable(e,n,o){_removeExistingUI();let t=!1;const a=document.createElement("div");a.id="permissionBanner";const i="top"===n;a.style.cssText=`\n position: fixed;\n ${i?"top: 0;":"bottom: 0;"}\n left: 0;\n width: 100%;\n padding: 16px 20px;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n font-size: 16px;\n font-weight: 600;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n text-align: center;\n z-index: 999999;\n cursor: pointer;\n touch-action: manipulation;\n box-shadow: ${i?"0 2px 10px rgba(0,0,0,0.3)":"0 -2px 10px rgba(0,0,0,0.3)"};\n transition: opacity 0.3s ease, transform 0.3s ease;\n transform: translateY(${i?"-100%":"100%"});\n `,a.textContent=e,document.body.appendChild(a),requestAnimationFrame(()=>{requestAnimationFrame(()=>{a.style.transform="translateY(0)"})});const r=async()=>{!t&&a.parentNode&&(t=!0,a.textContent="Enabling...",a.style.pointerEvents="none",await o(),a.style.transform=`translateY(${i?"-100%":"100%"})`,a.style.opacity="0",setTimeout(()=>{a.parentNode&&a.remove()},300))};a.addEventListener("click",r),a.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),r()}),a.addEventListener("pointerup",function(e){e.preventDefault(),e.stopPropagation(),r()})}function _bindPermissionTo(e,n){let o=!1;const t=()=>{const a=document.querySelector(e);if(!a)return console.warn(`p5-phone: Element "${e}" not found. Retrying...`),void setTimeout(t,100);const i=async()=>{o||(o=!0,await n())};a.addEventListener("click",i),a.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),i()})};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",t):t()}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,a=o.touches[0].clientY,i=t-e,r=a-n;(e<20&&i>0||e>window.innerWidth-20&&i<0)&&(o.preventDefault(),o.stopPropagation()),0===window.pageYOffset&&r>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(){let e=0;const n=()=>{e++;"undefined"!=typeof p5&&p5.instance||document.querySelector("canvas")||"function"==typeof window.setup&&"function"==typeof window.draw?_overrideP5Touch():e<50?setTimeout(n,100):console.warn("p5-phone: Could not detect p5.js setup completion. Touch overrides not applied.")};setTimeout(n,100)}function _overrideP5Touch(){const e=window.mousePressed||function(){},n=window.mouseDragged||function(){},o=window.mouseReleased||function(){};if(!_isP5v2){const e=window.touchStarted||function(){},n=window.touchMoved||function(){},o=window.touchEnded||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(n){return e(n),!1},window.mouseDragged=function(e){return n(e),!1},window.mouseReleased=function(e){return o(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,"function"==typeof console.error&&(window._originalConsoleError=console.error),"function"==typeof console.warn&&(window._originalConsoleWarn=console.warn),console.error=function(...e){try{window._originalConsoleError.apply(console,e)}catch(e){}_debugVisible&&debugError(...e)},console.warn=function(...e){try{window._originalConsoleWarn.apply(console,e)}catch(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.enableSpeechTap=enableSpeechTap,window.enableSpeechButton=enableSpeechButton,window.enableVibrationTap=enableVibrationTap,window.enableVibrationButton=enableVibrationButton,window.vibrate=vibrate,window.stopVibration=stopVibration,window.enableNfcTap=enableNfcTap,window.enableNfcButton=enableNfcButton,window.stopNfc=stopNfc,window.setNfcTagAlias=setNfcTagAlias,window.getNfcTagAlias=getNfcTagAlias,window.isNfcTag=isNfcTag,window.enableAllTap=enableAllTap,window.enableAllButton=enableAllButton,window.enableGyroCanvas=enableGyroCanvas,window.enableMicCanvas=enableMicCanvas,window.enableSoundCanvas=enableSoundCanvas,window.enableSpeechCanvas=enableSpeechCanvas,window.enableVibrationCanvas=enableVibrationCanvas,window.enableNfcCanvas=enableNfcCanvas,window.enableAllCanvas=enableAllCanvas,window.enableCameraCanvas=enableCameraCanvas,window.enableGyroBanner=enableGyroBanner,window.enableMicBanner=enableMicBanner,window.enableSoundBanner=enableSoundBanner,window.enableSpeechBanner=enableSpeechBanner,window.enableVibrationBanner=enableVibrationBanner,window.enableNfcBanner=enableNfcBanner,window.enableAllBanner=enableAllBanner,window.enableCameraBanner=enableCameraBanner,window.enableGyroOn=enableGyroOn,window.enableMicOn=enableMicOn,window.enableSoundOn=enableSoundOn,window.enableSpeechOn=enableSpeechOn,window.enableVibrationOn=enableVibrationOn,window.enableNfcOn=enableNfcOn,window.enableAllOn=enableAllOn,window.enableCameraOn=enableCameraOn;class PhoneCamera{constructor(e="user",n=!0,o="fitHeight"){this._active=e,this._mirror=n,this._mode=o,this._fixedWidth=640,this._fixedHeight=480,this._video=null,this._ready=!1,this._p5Instance=window,this._onReadyCallback=null,this._createCaptureRef=null,window._phoneCameras||(window._phoneCameras=[]),window._phoneCameras.push(this)}get ready(){return this._ready}get video(){return this._video}get videoElement(){return this._video?this._video.elt:null}get width(){if(!this._ready)return 0;return this.getDimensions().width}get height(){if(!this._ready)return 0;return this.getDimensions().height}get active(){return this._active}set active(e){"user"===e||"environment"===e?this._active!==e&&(this._active=e,this._ready&&this._switchCamera()):console.error('PhoneCamera: active must be "user" or "environment"')}get mirror(){return this._mirror}set mirror(e){this._mirror=!!e}get mode(){return this._mode}set mode(e){const n=["fitWidth","fitHeight","cover","contain","fixed"];n.includes(e)?this._mode=e:console.error("PhoneCamera: mode must be one of:",n.join(", "))}get fixedWidth(){return this._fixedWidth}set fixedWidth(e){this._fixedWidth=Math.max(1,e)}get fixedHeight(){return this._fixedHeight}set fixedHeight(e){this._fixedHeight=Math.max(1,e)}onReady(e){this._onReadyCallback=e,this._ready&&this._video&&this._video.elt&&this._video.elt.readyState>=2?e():this._video&&this._checkVideoReady()}_initializeCamera(){if(this._ready||this._video)return;const e={video:{facingMode:this._active},audio:!1};this._video=createCapture(e,()=>{this._ready=!0,this._video.hide(),console.log("✅ PhoneCamera ready"),this._checkVideoReady()}),this._video&&this._video.elt&&this._video.elt.addEventListener("loadeddata",()=>{this._ready=!0,this._checkVideoReady()})}_checkVideoReady(e=0){if(this._video&&this._video.elt&&this._video.elt.readyState>=2){if(this._onReadyCallback){const e=this._onReadyCallback;this._onReadyCallback=null,e()}}else e<100?setTimeout(()=>this._checkVideoReady(e+1),100):(console.warn("PhoneCamera: Video failed to reach ready state after 10 seconds"),_debugVisible&&debugWarn("PhoneCamera: Video not ready after timeout. Check camera permissions."))}_switchCamera(){if(!this._video)return;this._ready;this._ready=!1,this._video.remove();const e={video:{facingMode:this._active},audio:!1};this._video=createCapture(e,()=>{this._ready=!0,this._video.hide(),console.log(`✅ PhoneCamera switched to ${this._active} camera`)}),this._video&&this._video.elt&&this._video.elt.addEventListener("loadeddata",()=>{this._ready=!0})}remove(){this._video&&(this._video.remove(),this._video=null),this._ready=!1}getDimensions(){if(!this._ready||!this._video)return{x:0,y:0,width:0,height:0,scaleX:1,scaleY:1};const e=this._video.width,n=this._video.height,o="undefined"!=typeof width?width:window.innerWidth,t="undefined"!=typeof height?height:window.innerHeight;let a,i,r,s;if("fixed"===this._mode)a=this._fixedWidth,i=this._fixedHeight,r=(o-a)/2,s=(t-i)/2;else if("fitWidth"===this._mode)a=o,i=n/e*a,r=0,s=(t-i)/2;else if("fitHeight"===this._mode)i=t,a=e/n*i,r=(o-a)/2,s=0;else if("cover"===this._mode){const l=Math.max(o/e,t/n);a=e*l,i=n*l,r=(o-a)/2,s=(t-i)/2}else if("contain"===this._mode){const l=Math.min(o/e,t/n);a=e*l,i=n*l,r=(o-a)/2,s=(t-i)/2}return{x:r,y:s,width:a,height:i,scaleX:a/e,scaleY:i/n}}mapPoint(e,n){const o=this.getDimensions();let t=e*o.scaleX;const a=n*o.scaleY;this._mirror&&(t=o.width-t);return{x:t+o.x,y:a+o.y}}mapKeypoint(e){if(!e||void 0===e.x||void 0===e.y)return console.warn("PhoneCamera.mapKeypoint: invalid keypoint",e),e;const n=this.mapPoint(e.x,e.y);return{...e,x:n.x,y:n.y}}mapKeypoints(e){return Array.isArray(e)?e.map(e=>this.mapKeypoint(e)):(console.warn("PhoneCamera.mapKeypoints: expected array, got",typeof e),e)}mapBox(e){if(!e)return console.warn("PhoneCamera.mapBox: invalid box",e),e;const n=void 0!==e.x?e.x:e.xMin,o=void 0!==e.y?e.y:e.yMin,t=void 0!==e.width?e.width:e.xMax-e.xMin,a=void 0!==e.height?e.height:e.yMax-e.yMin,i=Number(n),r=Number(o),s=Number(t),l=Number(a);if(!(Number.isFinite(i)&&Number.isFinite(r)&&Number.isFinite(s)&&Number.isFinite(l)))return console.warn("PhoneCamera.mapBox: invalid box",e),e;const d=this.mapPoint(i,r),c=this.mapPoint(i+s,r+l),u=Math.min(d.x,c.x),p=Math.min(d.y,c.y),b=Math.abs(c.x-d.x),h=Math.abs(c.y-d.y);return{...e,x:u,y:p,width:b,height:h,xMin:u,yMin:p,xMax:u+b,yMax:p+h}}mapBoxes(e){return Array.isArray(e)?e.map(e=>this.mapBox(e)):(console.warn("PhoneCamera.mapBoxes: expected array, got",typeof e),e)}_draw(){if(!this._ready||!this._video)return;const e=this.getDimensions(),n="undefined"!=typeof width?width:window.innerWidth;this._drawDebugLogged||(console.log("_draw() params:",{x:e.x,y:e.y,width:e.width,height:e.height,canvasWidth:n,mirror:this._mirror}),this._drawDebugLogged=!0),push(),this._mirror?(translate(n,0),scale(-1,1),image(this._video,e.x,e.y,e.width,e.height)):image(this._video,e.x,e.y,e.width,e.height),pop()}}function createPhoneCamera(e="user",n=!0,o="fitHeight"){return new PhoneCamera(e,n,o)}function enableCameraButton(e="ENABLE CAMERA",n="Starting camera..."){_createPermissionButton(e,n,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via button")})}function enableCameraTap(e="Tap screen to enable camera"){navigator.permissions&&navigator.permissions.query?navigator.permissions.query({name:"camera"}).then(n=>{"granted"===n.state?(console.log("✅ Camera permission already granted - auto-starting"),_requestCameraPermission()):_createTapToEnable(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via tap")})}).catch(()=>{_createTapToEnable(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via tap")})}):_createTapToEnable(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via tap")})}async function _requestCameraPermission(){try{if(void 0!==window._phoneCameras&&Array.isArray(window._phoneCameras))for(let e of window._phoneCameras)!e||e._ready||e._video||e._initializeCamera();"function"==typeof userCameraReady&&userCameraReady(),_notifySketchReady()}catch(e){console.error("Camera permission error:",e),_debugVisible&&debugError("Camera permission error:",e),_notifySketchReady()}}if(window.createPhoneCamera=createPhoneCamera,window.enableCameraButton=enableCameraButton,window.enableCameraTap=enableCameraTap,"undefined"!=typeof p5&&p5.prototype){const e=p5.prototype.image;p5.prototype.image=function(...n){if(n[0]instanceof PhoneCamera){n[0]._draw()}else e.apply(this,n)}}"undefined"!=typeof p5&&p5.prototype&&"function"!=typeof p5.registerAddon&&(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.enableSpeechTap=enableSpeechTap,p5.prototype.enableSpeechButton=enableSpeechButton,p5.prototype.enableVibrationTap=enableVibrationTap,p5.prototype.enableVibrationButton=enableVibrationButton,p5.prototype.vibrate=vibrate,p5.prototype.stopVibration=stopVibration,p5.prototype.enableNfcTap=enableNfcTap,p5.prototype.enableNfcButton=enableNfcButton,p5.prototype.stopNfc=stopNfc,p5.prototype.setNfcTagAlias=setNfcTagAlias,p5.prototype.getNfcTagAlias=getNfcTagAlias,p5.prototype.isNfcTag=isNfcTag,p5.prototype.enableAllTap=enableAllTap,p5.prototype.enableAllButton=enableAllButton,p5.prototype.enableGyroCanvas=enableGyroCanvas,p5.prototype.enableMicCanvas=enableMicCanvas,p5.prototype.enableSoundCanvas=enableSoundCanvas,p5.prototype.enableSpeechCanvas=enableSpeechCanvas,p5.prototype.enableVibrationCanvas=enableVibrationCanvas,p5.prototype.enableNfcCanvas=enableNfcCanvas,p5.prototype.enableAllCanvas=enableAllCanvas,p5.prototype.enableCameraCanvas=enableCameraCanvas,p5.prototype.enableGyroBanner=enableGyroBanner,p5.prototype.enableMicBanner=enableMicBanner,p5.prototype.enableSoundBanner=enableSoundBanner,p5.prototype.enableSpeechBanner=enableSpeechBanner,p5.prototype.enableVibrationBanner=enableVibrationBanner,p5.prototype.enableNfcBanner=enableNfcBanner,p5.prototype.enableAllBanner=enableAllBanner,p5.prototype.enableCameraBanner=enableCameraBanner,p5.prototype.enableGyroOn=enableGyroOn,p5.prototype.enableMicOn=enableMicOn,p5.prototype.enableSoundOn=enableSoundOn,p5.prototype.enableSpeechOn=enableSpeechOn,p5.prototype.enableVibrationOn=enableVibrationOn,p5.prototype.enableNfcOn=enableNfcOn,p5.prototype.enableAllOn=enableAllOn,p5.prototype.enableCameraOn=enableCameraOn,p5.prototype.createPhoneCamera=createPhoneCamera,p5.prototype.enableCameraButton=enableCameraButton,p5.prototype.enableCameraTap=enableCameraTap,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")),"undefined"!=typeof p5&&"function"==typeof p5.registerAddon&&p5.registerAddon(function(e,n,o){o.presetup=function(){this.lockGestures=lockGestures,this.enableGyroTap=enableGyroTap,this.enableGyroButton=enableGyroButton,this.enableMicTap=enableMicTap,this.enableMicButton=enableMicButton,this.enableSoundTap=enableSoundTap,this.enableSoundButton=enableSoundButton,this.enableSpeechTap=enableSpeechTap,this.enableSpeechButton=enableSpeechButton,this.enableVibrationTap=enableVibrationTap,this.enableVibrationButton=enableVibrationButton,this.vibrate=vibrate,this.stopVibration=stopVibration,this.enableNfcTap=enableNfcTap,this.enableNfcButton=enableNfcButton,this.stopNfc=stopNfc,this.setNfcTagAlias=setNfcTagAlias,this.getNfcTagAlias=getNfcTagAlias,this.isNfcTag=isNfcTag,this.enableAllTap=enableAllTap,this.enableAllButton=enableAllButton,this.enableGyroCanvas=enableGyroCanvas,this.enableMicCanvas=enableMicCanvas,this.enableSoundCanvas=enableSoundCanvas,this.enableSpeechCanvas=enableSpeechCanvas,this.enableVibrationCanvas=enableVibrationCanvas,this.enableNfcCanvas=enableNfcCanvas,this.enableAllCanvas=enableAllCanvas,this.enableCameraCanvas=enableCameraCanvas,this.enableGyroBanner=enableGyroBanner,this.enableMicBanner=enableMicBanner,this.enableSoundBanner=enableSoundBanner,this.enableSpeechBanner=enableSpeechBanner,this.enableVibrationBanner=enableVibrationBanner,this.enableNfcBanner=enableNfcBanner,this.enableAllBanner=enableAllBanner,this.enableCameraBanner=enableCameraBanner,this.enableGyroOn=enableGyroOn,this.enableMicOn=enableMicOn,this.enableSoundOn=enableSoundOn,this.enableSpeechOn=enableSpeechOn,this.enableVibrationOn=enableVibrationOn,this.enableNfcOn=enableNfcOn,this.enableAllOn=enableAllOn,this.enableCameraOn=enableCameraOn,this.createPhoneCamera=createPhoneCamera,this.enableCameraButton=enableCameraButton,this.enableCameraTap=enableCameraTap,this.showDebug=showDebug,this.hideDebug=hideDebug,this.toggleDebug=toggleDebug,this.debug=debug,this.debugError=debugError,this.debugWarn=debugWarn},console.log("✅ Mobile p5.js Permissions: registered as p5.js 2.0 addon")});
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,window.speechEnabled=!1,window.nfcEnabled=!1,window.nfcError="",window.nfcStatus="idle",window.nfcTagAliases={},window.lastNfcMessage=null,window.lastNfcSerialNumber=null,window.lastNfcAlias="";let _micInstance=null,_nfcReader=null,_nfcAbortController=null;const _p5MajorVersion="undefined"!=typeof p5&&p5.VERSION?parseInt(p5.VERSION.split(".")[0],10):1,_isP5v2=_p5MajorVersion>=2;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 enableSpeechTap(e="Tap to enable speech recognition"){_createTapToEnable(e,async()=>{await _requestSpeechPermission(),console.log("✅ Speech recognition enabled via tap")})}function enableSpeechButton(e="ENABLE SPEECH RECOGNITION",n="Enabling speech recognition..."){_createPermissionButton(e,n,async()=>{await _requestSpeechPermission(),console.log("✅ Speech recognition enabled via button")})}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 enableNfcButton(e="ENABLE NFC",n="Enabling NFC..."){_createPermissionButton(e,n,async()=>{await _requestNfcPermission(),console.log("✅ NFC enabled via button")})}function enableNfcTap(e="Tap screen to enable NFC"){_createTapToEnable(e,async()=>{await _requestNfcPermission(),console.log("✅ NFC enabled via tap")})}function enableAllButton(e="ENABLE MOTION & MICROPHONE",n="Requesting permissions..."){_createPermissionButton(e,n,async()=>{await _requestMotionPermissionsCore(),await _requestMicrophonePermissionsCore(),_notifySketchReady(),console.log("✅ Motion sensors and microphone enabled via button")})}function enableAllTap(e="Tap screen to enable motion sensors & microphone"){_createTapToEnable(e,async()=>{await _requestMotionPermissionsCore(),await _requestMicrophonePermissionsCore(),_notifySketchReady(),console.log("✅ Motion sensors and microphone enabled via tap")})}function enableGyroCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestMotionPermissions(),console.log("✅ Gyroscope enabled via canvas touch")})}function enableMicCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestMicrophonePermissions(),console.log("✅ Microphone enabled via canvas touch")})}function enableSoundCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestSoundOutput(),console.log("✅ Sound output enabled via canvas touch")})}function enableSpeechCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestSpeechPermission(),console.log("✅ Speech recognition enabled via canvas touch")})}function enableVibrationCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestVibrationPermission(),console.log("✅ Vibration enabled via canvas touch")})}function enableNfcCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestNfcPermission(),console.log("✅ NFC enabled via canvas touch")})}function enableAllCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestMotionPermissionsCore(),await _requestMicrophonePermissionsCore(),_notifySketchReady(),console.log("✅ Motion sensors and microphone enabled via canvas touch")})}function enableCameraCanvas(e="Touch to start"){_createCanvasToEnable(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via canvas touch")})}function enableGyroBanner(e="Tap to enable motion sensors",n="top"){_createBannerToEnable(e,n,async()=>{await _requestMotionPermissions(),console.log("✅ Gyroscope enabled via banner")})}function enableMicBanner(e="Tap to enable microphone",n="top"){_createBannerToEnable(e,n,async()=>{await _requestMicrophonePermissions(),console.log("✅ Microphone enabled via banner")})}function enableSoundBanner(e="Tap to enable sound",n="top"){_createBannerToEnable(e,n,async()=>{await _requestSoundOutput(),console.log("✅ Sound output enabled via banner")})}function enableSpeechBanner(e="Tap to enable speech recognition",n="top"){_createBannerToEnable(e,n,async()=>{await _requestSpeechPermission(),console.log("✅ Speech recognition enabled via banner")})}function enableVibrationBanner(e="Tap to enable vibration",n="top"){_createBannerToEnable(e,n,async()=>{await _requestVibrationPermission(),console.log("✅ Vibration enabled via banner")})}function enableNfcBanner(e="Tap to enable NFC",n="top"){_createBannerToEnable(e,n,async()=>{await _requestNfcPermission(),console.log("✅ NFC enabled via banner")})}function enableAllBanner(e="Tap to enable sensors & microphone",n="top"){_createBannerToEnable(e,n,async()=>{await _requestMotionPermissionsCore(),await _requestMicrophonePermissionsCore(),_notifySketchReady(),console.log("✅ Motion sensors and microphone enabled via banner")})}function enableCameraBanner(e="Tap to enable camera",n="top"){_createBannerToEnable(e,n,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via banner")})}function enableGyroOn(e){_bindPermissionTo(e,async()=>{await _requestMotionPermissions(),console.log("✅ Gyroscope enabled via custom element")})}function enableMicOn(e){_bindPermissionTo(e,async()=>{await _requestMicrophonePermissions(),console.log("✅ Microphone enabled via custom element")})}function enableSoundOn(e){_bindPermissionTo(e,async()=>{await _requestSoundOutput(),console.log("✅ Sound output enabled via custom element")})}function enableSpeechOn(e){_bindPermissionTo(e,async()=>{await _requestSpeechPermission(),console.log("✅ Speech recognition enabled via custom element")})}function enableVibrationOn(e){_bindPermissionTo(e,async()=>{await _requestVibrationPermission(),console.log("✅ Vibration enabled via custom element")})}function enableNfcOn(e){_bindPermissionTo(e,async()=>{await _requestNfcPermission(),console.log("✅ NFC enabled via custom element")})}function enableAllOn(e){_bindPermissionTo(e,async()=>{await _requestMotionPermissionsCore(),await _requestMicrophonePermissionsCore(),_notifySketchReady(),console.log("✅ Motion sensors and microphone enabled via custom element")})}function enableCameraOn(e){_bindPermissionTo(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via custom element")})}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)}function stopNfc(){_nfcAbortController&&(_nfcAbortController.abort(),_nfcAbortController=null),_nfcReader=null,window.nfcEnabled=!1,window.nfcStatus="stopped",console.log("NFC scanning stopped")}function _normalizeNfcText(e){return null==e?"":String(e).trim()}function _normalizeNfcTagId(e){return _normalizeNfcText(e).toLowerCase()}function _nfcTextMatches(e,n){const o=_normalizeNfcText(e).toLowerCase(),t=_normalizeNfcText(n).toLowerCase();return""!==o&&o===t}function setNfcTagAlias(e,n){const o=_normalizeNfcTagId(e),t=_normalizeNfcText(n);return o?(t?window.nfcTagAliases[o]=t:delete window.nfcTagAliases[o],_normalizeNfcTagId(window.lastNfcSerialNumber)===o&&(window.lastNfcAlias=t,window.lastNfcMessage&&(window.lastNfcMessage.alias=t)),t):(console.warn("p5-phone: setNfcTagAlias() needs an NFC serial number"),"")}function getNfcTagAlias(e=window.lastNfcSerialNumber){const n=_normalizeNfcTagId(e);return n&&window.nfcTagAliases[n]||""}function isNfcTag(e,n=window.lastNfcSerialNumber){const o=_normalizeNfcTagId(n),t=_normalizeNfcText(e);return!(!o||!t)&&(o===_normalizeNfcTagId(t)||_nfcTextMatches(getNfcTagAlias(n),t))}async function _requestMotionPermissionsCore(){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}catch(e){console.error("Motion sensor permission error:",e),_debugVisible&&debugError("Motion sensor permission error:",e),window.sensorsEnabled=!0}}async function _requestMicrophonePermissionsCore(){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();")}catch(e){console.error("Microphone permission error:",e),_debugVisible&&debugError("Microphone permission error:",e)}}async function _requestSoundOutputCore(){try{"undefined"!=typeof userStartAudio&&await userStartAudio(),window.soundEnabled=!0}catch(e){console.error("Sound output error:",e),_debugVisible&&debugError("Sound output error:",e),window.soundEnabled=!0}}async function _requestSpeechPermissionCore(){try{"undefined"!=typeof userStartAudio&&await userStartAudio(),window.speechEnabled=!0}catch(e){console.error("Speech permission error:",e),_debugVisible&&debugError("Speech permission error:",e)}}async function _requestVibrationPermissionCore(){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"),void(window.vibrationEnabled=!1);navigator.vibrate(1)?(window.vibrationEnabled=!0,console.log("✅ Vibration enabled")):(console.warn("⚠️ Vibration API available but vibration failed"),window.vibrationEnabled=!1)}catch(e){console.error("Vibration permission error:",e),_debugVisible&&debugError("Vibration permission error:",e),window.vibrationEnabled=!1}}async function _requestNfcPermissionCore(){try{return!(!window.nfcEnabled||!_nfcReader)||(window.nfcError="",window.nfcStatus="starting","NDEFReader"in window?(window.nfcStatus="requesting-permission",_nfcAbortController=new AbortController,_nfcReader=new NDEFReader,_nfcReader.onreading=e=>{const n=e.serialNumber||"",o=new TextDecoder,t=[];for(const n of e.message.records){const e={recordType:n.recordType,mediaType:n.mediaType||null,id:n.id||null,data:null,raw:n.data};if("text"===n.recordType||"url"===n.recordType)e.data=o.decode(n.data);else if("mime"===n.recordType&&n.mediaType)try{const t=o.decode(n.data);n.mediaType.includes("json")?e.data=JSON.parse(t):e.data=t}catch(o){e.data=n.data}else e.data=n.data;t.push(e)}const a=getNfcTagAlias(n),i={serialNumber:n,alias:a,records:t};window.lastNfcMessage=i,window.lastNfcSerialNumber=n,window.lastNfcAlias=a,window.nfcStatus="tag-read",window.nfcError="","function"==typeof nfcRead&&nfcRead(i,n),console.log("NFC tag read — serial:",n,"records:",t.length),_debugVisible&&debug("NFC tag read: "+n)},_nfcReader.onreadingerror=e=>{console.warn("⚠️ NFC read error — tag may be incompatible or out of range"),window.nfcError="NFC read error. Make sure the tag is NDEF formatted and hold it near the phone NFC antenna.",_debugVisible&&debugWarn("NFC read error — tag incompatible or out of range")},await _nfcReader.scan({signal:_nfcAbortController.signal}),window.nfcEnabled=!0,window.nfcStatus="scanning",console.log("✅ NFC scanning active"),!0):(console.warn("⚠️ Web NFC API not supported on this device/browser (Android Chrome 89+ required)"),_debugVisible&&debugWarn("Web NFC not supported on this device/browser"),window.nfcEnabled=!1,window.nfcStatus="unsupported",window.nfcError=!1===window.isSecureContext?"NFC requires HTTPS. Serve this sketch from an HTTPS URL, not plain HTTP.":"Web NFC is not supported in this browser. Use Android Chrome 89+ over HTTPS.",!1))}catch(e){return"NotAllowedError"===e.name?(console.warn("⚠️ NFC permission denied by user"),window.nfcStatus="permission-denied",window.nfcError="NFC permission was denied. Reload and tap Allow if Chrome asks.",_debugVisible&&debugWarn("NFC permission denied")):"NotSupportedError"===e.name?(console.warn("⚠️ NFC not supported on this device"),window.nfcStatus="unsupported",window.nfcError="NFC is not supported on this device/browser, or this page is not using HTTPS.",_debugVisible&&debugWarn("NFC not supported on this device")):"SecurityError"===e.name?(console.warn("⚠️ NFC requires a secure HTTPS context"),window.nfcStatus="secure-context-required",window.nfcError="NFC requires HTTPS. Serve this sketch from an HTTPS URL, not plain HTTP.",_debugVisible&&debugWarn("NFC requires HTTPS")):(console.error("NFC permission error:",e),window.nfcStatus="error",window.nfcError=e&&e.message?e.message:"NFC could not start.",_debugVisible&&debugError("NFC error: "+e.message)),window.nfcEnabled=!1,_nfcReader=null,_nfcAbortController=null,!1}}async function _requestMotionPermissions(){await _requestMotionPermissionsCore(),_notifySketchReady()}async function _requestMicrophonePermissions(){await _requestMicrophonePermissionsCore(),_notifySketchReady()}async function _requestSoundOutput(){await _requestSoundOutputCore(),_notifySketchReady()}async function _requestSpeechPermission(){await _requestSpeechPermissionCore(),_notifySketchReady()}async function _requestVibrationPermission(){await _requestVibrationPermissionCore(),_notifySketchReady()}async function _requestNfcPermission(){const e=await _requestNfcPermissionCore();return _notifySketchReady(),e}function _notifySketchReady(){"function"==typeof userSetupComplete&&userSetupComplete(),window.dispatchEvent(new CustomEvent("permissionsReady",{detail:{sensors:window.sensorsEnabled,microphone:window.micEnabled,sound:window.soundEnabled,speech:window.speechEnabled,vibration:window.vibrationEnabled,nfc:window.nfcEnabled,gestures:window.gesturesLocked}}))}function _createPermissionButton(e,n,o){_removeExistingUI();let t=!1;const a=document.createElement("button");a.id="permissionButton",a.textContent=e,a.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 ",a.addEventListener("mouseenter",()=>{a.style.transform="translate(-50%, -50%) scale(1.05)"}),a.addEventListener("mouseleave",()=>{a.style.transform="translate(-50%, -50%) scale(1)"});const r=async()=>{!t&&a.parentNode&&(t=!0,a.style.display="none",i.style.display="block",await o(),i.style.display="none",_removeExistingUI())};a.addEventListener("click",r),a.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),r()}),a.addEventListener("pointerup",function(e){e.preventDefault(),e.stopPropagation(),r()}),document.body.appendChild(a),document.body.appendChild(i)}function _createTapToEnable(e,n){_removeExistingUI();let o=!1;const t=document.createElement("div");t.id="tapOverlay",t.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 a=document.createElement("div");a.textContent=e,a.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 ",t.appendChild(a);const i=async()=>{!o&&t.parentNode&&(o=!0,a.textContent="Enabling...",await n(),t.parentNode&&document.body.removeChild(t))};t.addEventListener("click",i),t.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),i()}),t.addEventListener("pointerup",function(e){e.preventDefault(),e.stopPropagation(),i()}),document.body.appendChild(t)}function _removeExistingUI(){const e=document.getElementById("permissionButton"),n=document.getElementById("permissionStatus"),o=document.getElementById("tapOverlay"),t=document.getElementById("permissionBanner");e&&e.remove(),n&&n.remove(),o&&o.remove(),t&&t.remove()}function _createCanvasToEnable(e,n){_removeExistingUI();let o=!1,t=null;e&&(t=setInterval(()=>{const n=document.querySelector("canvas");n&&"function"==typeof push&&(push(),fill(255,255,255,200),noStroke(),textAlign(CENTER,CENTER),textSize(.04*Math.min(n.width,n.height)),text(e,("undefined"!=typeof width?width:n.width)/2,.9*("undefined"!=typeof height?height:n.height)),pop())},50));const a=async e=>{o||(o=!0,t&&(clearInterval(t),t=null),document.removeEventListener("touchstart",a,!0),document.removeEventListener("mousedown",a,!0),await n())},i=()=>{const e=document.querySelector("canvas");e?(e.addEventListener("touchstart",a,{once:!0,capture:!0}),e.addEventListener("mousedown",a,{once:!0,capture:!0})):setTimeout(i,50)};i()}function _createBannerToEnable(e,n,o){_removeExistingUI();let t=!1;const a=document.createElement("div");a.id="permissionBanner";const i="top"===n;a.style.cssText=`\n position: fixed;\n ${i?"top: 0;":"bottom: 0;"}\n left: 0;\n width: 100%;\n padding: 16px 20px;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n font-size: 16px;\n font-weight: 600;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n text-align: center;\n z-index: 999999;\n cursor: pointer;\n touch-action: manipulation;\n box-shadow: ${i?"0 2px 10px rgba(0,0,0,0.3)":"0 -2px 10px rgba(0,0,0,0.3)"};\n transition: opacity 0.3s ease, transform 0.3s ease;\n transform: translateY(${i?"-100%":"100%"});\n `,a.textContent=e,document.body.appendChild(a),requestAnimationFrame(()=>{requestAnimationFrame(()=>{a.style.transform="translateY(0)"})});const r=async()=>{!t&&a.parentNode&&(t=!0,a.textContent="Enabling...",a.style.pointerEvents="none",await o(),a.style.transform=`translateY(${i?"-100%":"100%"})`,a.style.opacity="0",setTimeout(()=>{a.parentNode&&a.remove()},300))};a.addEventListener("click",r),a.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),r()}),a.addEventListener("pointerup",function(e){e.preventDefault(),e.stopPropagation(),r()})}function _bindPermissionTo(e,n){let o=!1;const t=()=>{const a=document.querySelector(e);if(!a)return console.warn(`p5-phone: Element "${e}" not found. Retrying...`),void setTimeout(t,100);const i=async()=>{o||(o=!0,await n())};a.addEventListener("click",i),a.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),i()})};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",t):t()}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,a=o.touches[0].clientY,i=t-e,r=a-n;(e<20&&i>0||e>window.innerWidth-20&&i<0)&&(o.preventDefault(),o.stopPropagation()),0===window.pageYOffset&&r>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(){let e=0;const n=()=>{e++;"undefined"!=typeof p5&&p5.instance||document.querySelector("canvas")||"function"==typeof window.setup&&"function"==typeof window.draw?_overrideP5Touch():e<50?setTimeout(n,100):console.warn("p5-phone: Could not detect p5.js setup completion. Touch overrides not applied.")};setTimeout(n,100)}function _overrideP5Touch(){const e=window.mousePressed||function(){},n=window.mouseDragged||function(){},o=window.mouseReleased||function(){};if(!_isP5v2){const e=window.touchStarted||function(){},n=window.touchMoved||function(){},o=window.touchEnded||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(n){return e(n),!1},window.mouseDragged=function(e){return n(e),!1},window.mouseReleased=function(e){return o(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,"function"==typeof console.error&&(window._originalConsoleError=console.error),"function"==typeof console.warn&&(window._originalConsoleWarn=console.warn),console.error=function(...e){try{window._originalConsoleError.apply(console,e)}catch(e){}_debugVisible&&debugError(...e)},console.warn=function(...e){try{window._originalConsoleWarn.apply(console,e)}catch(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.enableSensorTap=enableGyroTap,window.enableSensorButton=enableGyroButton,window.enableMicTap=enableMicTap,window.enableMicButton=enableMicButton,window.enableSoundTap=enableSoundTap,window.enableSoundButton=enableSoundButton,window.enableSpeechTap=enableSpeechTap,window.enableSpeechButton=enableSpeechButton,window.enableVibrationTap=enableVibrationTap,window.enableVibrationButton=enableVibrationButton,window.vibrate=vibrate,window.stopVibration=stopVibration,window.enableNfcTap=enableNfcTap,window.enableNfcButton=enableNfcButton,window.stopNfc=stopNfc,window.setNfcTagAlias=setNfcTagAlias,window.getNfcTagAlias=getNfcTagAlias,window.isNfcTag=isNfcTag,window.enableAllTap=enableAllTap,window.enableAllButton=enableAllButton,window.enableGyroCanvas=enableGyroCanvas,window.enableSensorCanvas=enableGyroCanvas,window.enableMicCanvas=enableMicCanvas,window.enableSoundCanvas=enableSoundCanvas,window.enableSpeechCanvas=enableSpeechCanvas,window.enableVibrationCanvas=enableVibrationCanvas,window.enableNfcCanvas=enableNfcCanvas,window.enableAllCanvas=enableAllCanvas,window.enableCameraCanvas=enableCameraCanvas,window.enableGyroBanner=enableGyroBanner,window.enableSensorBanner=enableGyroBanner,window.enableMicBanner=enableMicBanner,window.enableSoundBanner=enableSoundBanner,window.enableSpeechBanner=enableSpeechBanner,window.enableVibrationBanner=enableVibrationBanner,window.enableNfcBanner=enableNfcBanner,window.enableAllBanner=enableAllBanner,window.enableCameraBanner=enableCameraBanner,window.enableGyroOn=enableGyroOn,window.enableSensorOn=enableGyroOn,window.enableMicOn=enableMicOn,window.enableSoundOn=enableSoundOn,window.enableSpeechOn=enableSpeechOn,window.enableVibrationOn=enableVibrationOn,window.enableNfcOn=enableNfcOn,window.enableAllOn=enableAllOn,window.enableCameraOn=enableCameraOn;class PhoneCamera{constructor(e="user",n=!0,o="fitHeight"){this._active=e,this._mirror=n,this._mode=o,this._fixedWidth=640,this._fixedHeight=480,this._video=null,this._ready=!1,this._p5Instance=window,this._onReadyCallback=null,this._createCaptureRef=null,window._phoneCameras||(window._phoneCameras=[]),window._phoneCameras.push(this)}get ready(){return this._ready}get video(){return this._video}get videoElement(){return this._video?this._video.elt:null}get width(){if(!this._ready)return 0;return this.getDimensions().width}get height(){if(!this._ready)return 0;return this.getDimensions().height}get active(){return this._active}set active(e){"user"===e||"environment"===e?this._active!==e&&(this._active=e,this._ready&&this._switchCamera()):console.error('PhoneCamera: active must be "user" or "environment"')}get mirror(){return this._mirror}set mirror(e){this._mirror=!!e}get mode(){return this._mode}set mode(e){const n=["fitWidth","fitHeight","cover","contain","fixed"];n.includes(e)?this._mode=e:console.error("PhoneCamera: mode must be one of:",n.join(", "))}get fixedWidth(){return this._fixedWidth}set fixedWidth(e){this._fixedWidth=Math.max(1,e)}get fixedHeight(){return this._fixedHeight}set fixedHeight(e){this._fixedHeight=Math.max(1,e)}onReady(e){this._onReadyCallback=e,this._ready&&this._video&&this._video.elt&&this._video.elt.readyState>=2?e():this._video&&this._checkVideoReady()}_initializeCamera(){if(this._ready||this._video)return;const e={video:{facingMode:this._active},audio:!1};this._video=createCapture(e,()=>{this._ready=!0,this._video.hide(),console.log("✅ PhoneCamera ready"),this._checkVideoReady()}),this._video&&this._video.elt&&this._video.elt.addEventListener("loadeddata",()=>{this._ready=!0,this._checkVideoReady()})}_checkVideoReady(e=0){if(this._video&&this._video.elt&&this._video.elt.readyState>=2){if(this._onReadyCallback){const e=this._onReadyCallback;this._onReadyCallback=null,e()}}else e<100?setTimeout(()=>this._checkVideoReady(e+1),100):(console.warn("PhoneCamera: Video failed to reach ready state after 10 seconds"),_debugVisible&&debugWarn("PhoneCamera: Video not ready after timeout. Check camera permissions."))}_switchCamera(){if(!this._video)return;this._ready;this._ready=!1,this._video.remove();const e={video:{facingMode:this._active},audio:!1};this._video=createCapture(e,()=>{this._ready=!0,this._video.hide(),console.log(`✅ PhoneCamera switched to ${this._active} camera`)}),this._video&&this._video.elt&&this._video.elt.addEventListener("loadeddata",()=>{this._ready=!0})}remove(){this._video&&(this._video.remove(),this._video=null),this._ready=!1}getDimensions(){if(!this._ready||!this._video)return{x:0,y:0,width:0,height:0,scaleX:1,scaleY:1};const e=this.videoElement,n=e&&e.videoWidth||this._video.width,o=e&&e.videoHeight||this._video.height;if(!n||!o)return{x:0,y:0,width:0,height:0,scaleX:1,scaleY:1};const t="undefined"!=typeof width?width:window.innerWidth,a="undefined"!=typeof height?height:window.innerHeight;let i,r,s,l;if("fixed"===this._mode)i=this._fixedWidth,r=this._fixedHeight,s=(t-i)/2,l=(a-r)/2;else if("fitWidth"===this._mode)i=t,r=o/n*i,s=0,l=(a-r)/2;else if("fitHeight"===this._mode)r=a,i=n/o*r,s=(t-i)/2,l=0;else if("cover"===this._mode){const e=Math.max(t/n,a/o);i=n*e,r=o*e,s=(t-i)/2,l=(a-r)/2}else if("contain"===this._mode){const e=Math.min(t/n,a/o);i=n*e,r=o*e,s=(t-i)/2,l=(a-r)/2}return{x:s,y:l,width:i,height:r,scaleX:i/n,scaleY:r/o}}mapPoint(e,n){const o=this.getDimensions();let t=e*o.scaleX;const a=n*o.scaleY;this._mirror&&(t=o.width-t);return{x:t+o.x,y:a+o.y}}mapKeypoint(e){if(!e||void 0===e.x||void 0===e.y)return console.warn("PhoneCamera.mapKeypoint: invalid keypoint",e),e;const n=this.mapPoint(e.x,e.y);return{...e,x:n.x,y:n.y}}mapKeypoints(e){return Array.isArray(e)?e.map(e=>this.mapKeypoint(e)):(console.warn("PhoneCamera.mapKeypoints: expected array, got",typeof e),e)}mapBox(e){if(!e)return console.warn("PhoneCamera.mapBox: invalid box",e),e;const n=void 0!==e.x?e.x:e.xMin,o=void 0!==e.y?e.y:e.yMin,t=void 0!==e.width?e.width:e.xMax-e.xMin,a=void 0!==e.height?e.height:e.yMax-e.yMin,i=Number(n),r=Number(o),s=Number(t),l=Number(a);if(!(Number.isFinite(i)&&Number.isFinite(r)&&Number.isFinite(s)&&Number.isFinite(l)))return console.warn("PhoneCamera.mapBox: invalid box",e),e;const d=this.mapPoint(i,r),c=this.mapPoint(i+s,r+l),u=Math.min(d.x,c.x),p=Math.min(d.y,c.y),b=Math.abs(c.x-d.x),h=Math.abs(c.y-d.y);return{...e,x:u,y:p,width:b,height:h,xMin:u,yMin:p,xMax:u+b,yMax:p+h}}mapBoxes(e){return Array.isArray(e)?e.map(e=>this.mapBox(e)):(console.warn("PhoneCamera.mapBoxes: expected array, got",typeof e),e)}_draw(e=null){const n=this.videoElement;if(!this._ready||!this._video||!n||n.readyState<2)return;const o=this.getDimensions(),t="undefined"!=typeof width?width:window.innerWidth;this._drawDebugLogged||(console.log("_draw() params:",{x:o.x,y:o.y,width:o.width,height:o.height,canvasWidth:t,mirror:this._mirror}),this._drawDebugLogged=!0);const a=e&&e.drawingContext||("undefined"!=typeof drawingContext?drawingContext:null);if(!a||"function"!=typeof a.drawImage)return;const i=e&&e.push?e.push.bind(e):"function"==typeof push?push:null,r=e&&e.pop?e.pop.bind(e):"function"==typeof pop?pop:null,s=e&&e.translate?e.translate.bind(e):"function"==typeof translate?translate:null,l=e&&e.scale?e.scale.bind(e):"function"==typeof scale?scale:null;i&&r&&s&&l&&(i(),this._mirror?(s(t,0),l(-1,1),a.drawImage(n,o.x,o.y,o.width,o.height)):a.drawImage(n,o.x,o.y,o.width,o.height),r())}}function createPhoneCamera(e="user",n=!0,o="fitHeight"){return new PhoneCamera(e,n,o)}function enableCameraButton(e="ENABLE CAMERA",n="Starting camera..."){_createPermissionButton(e,n,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via button")})}function enableCameraTap(e="Tap screen to enable camera"){navigator.permissions&&navigator.permissions.query?navigator.permissions.query({name:"camera"}).then(n=>{"granted"===n.state?(console.log("✅ Camera permission already granted - auto-starting"),_requestCameraPermission()):_createTapToEnable(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via tap")})}).catch(()=>{_createTapToEnable(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via tap")})}):_createTapToEnable(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via tap")})}async function _requestCameraPermission(){try{if(void 0!==window._phoneCameras&&Array.isArray(window._phoneCameras))for(let e of window._phoneCameras)!e||e._ready||e._video||e._initializeCamera();"function"==typeof userCameraReady&&userCameraReady(),_notifySketchReady()}catch(e){console.error("Camera permission error:",e),_debugVisible&&debugError("Camera permission error:",e),_notifySketchReady()}}if(window.createPhoneCamera=createPhoneCamera,window.enableCameraButton=enableCameraButton,window.enableCameraTap=enableCameraTap,"undefined"!=typeof p5&&p5.prototype){const e=p5.prototype.image;p5.prototype.image=function(...n){if(n[0]instanceof PhoneCamera){n[0]._draw(this)}else e.apply(this,n)}}"undefined"!=typeof p5&&p5.prototype&&"function"!=typeof p5.registerAddon&&(p5.prototype.lockGestures=lockGestures,p5.prototype.enableGyroTap=enableGyroTap,p5.prototype.enableGyroButton=enableGyroButton,p5.prototype.enableSensorTap=enableGyroTap,p5.prototype.enableSensorButton=enableGyroButton,p5.prototype.enableMicTap=enableMicTap,p5.prototype.enableMicButton=enableMicButton,p5.prototype.enableSoundTap=enableSoundTap,p5.prototype.enableSoundButton=enableSoundButton,p5.prototype.enableSpeechTap=enableSpeechTap,p5.prototype.enableSpeechButton=enableSpeechButton,p5.prototype.enableVibrationTap=enableVibrationTap,p5.prototype.enableVibrationButton=enableVibrationButton,p5.prototype.vibrate=vibrate,p5.prototype.stopVibration=stopVibration,p5.prototype.enableNfcTap=enableNfcTap,p5.prototype.enableNfcButton=enableNfcButton,p5.prototype.stopNfc=stopNfc,p5.prototype.setNfcTagAlias=setNfcTagAlias,p5.prototype.getNfcTagAlias=getNfcTagAlias,p5.prototype.isNfcTag=isNfcTag,p5.prototype.enableAllTap=enableAllTap,p5.prototype.enableAllButton=enableAllButton,p5.prototype.enableGyroCanvas=enableGyroCanvas,p5.prototype.enableSensorCanvas=enableGyroCanvas,p5.prototype.enableMicCanvas=enableMicCanvas,p5.prototype.enableSoundCanvas=enableSoundCanvas,p5.prototype.enableSpeechCanvas=enableSpeechCanvas,p5.prototype.enableVibrationCanvas=enableVibrationCanvas,p5.prototype.enableNfcCanvas=enableNfcCanvas,p5.prototype.enableAllCanvas=enableAllCanvas,p5.prototype.enableCameraCanvas=enableCameraCanvas,p5.prototype.enableGyroBanner=enableGyroBanner,p5.prototype.enableSensorBanner=enableGyroBanner,p5.prototype.enableMicBanner=enableMicBanner,p5.prototype.enableSoundBanner=enableSoundBanner,p5.prototype.enableSpeechBanner=enableSpeechBanner,p5.prototype.enableVibrationBanner=enableVibrationBanner,p5.prototype.enableNfcBanner=enableNfcBanner,p5.prototype.enableAllBanner=enableAllBanner,p5.prototype.enableCameraBanner=enableCameraBanner,p5.prototype.enableGyroOn=enableGyroOn,p5.prototype.enableSensorOn=enableGyroOn,p5.prototype.enableMicOn=enableMicOn,p5.prototype.enableSoundOn=enableSoundOn,p5.prototype.enableSpeechOn=enableSpeechOn,p5.prototype.enableVibrationOn=enableVibrationOn,p5.prototype.enableNfcOn=enableNfcOn,p5.prototype.enableAllOn=enableAllOn,p5.prototype.enableCameraOn=enableCameraOn,p5.prototype.createPhoneCamera=createPhoneCamera,p5.prototype.enableCameraButton=enableCameraButton,p5.prototype.enableCameraTap=enableCameraTap,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")),"undefined"!=typeof p5&&"function"==typeof p5.registerAddon&&p5.registerAddon(function(e,n,o){o.presetup=function(){this.lockGestures=lockGestures,this.enableGyroTap=enableGyroTap,this.enableGyroButton=enableGyroButton,this.enableSensorTap=enableGyroTap,this.enableSensorButton=enableGyroButton,this.enableMicTap=enableMicTap,this.enableMicButton=enableMicButton,this.enableSoundTap=enableSoundTap,this.enableSoundButton=enableSoundButton,this.enableSpeechTap=enableSpeechTap,this.enableSpeechButton=enableSpeechButton,this.enableVibrationTap=enableVibrationTap,this.enableVibrationButton=enableVibrationButton,this.vibrate=vibrate,this.stopVibration=stopVibration,this.enableNfcTap=enableNfcTap,this.enableNfcButton=enableNfcButton,this.stopNfc=stopNfc,this.setNfcTagAlias=setNfcTagAlias,this.getNfcTagAlias=getNfcTagAlias,this.isNfcTag=isNfcTag,this.enableAllTap=enableAllTap,this.enableAllButton=enableAllButton,this.enableGyroCanvas=enableGyroCanvas,this.enableSensorCanvas=enableGyroCanvas,this.enableMicCanvas=enableMicCanvas,this.enableSoundCanvas=enableSoundCanvas,this.enableSpeechCanvas=enableSpeechCanvas,this.enableVibrationCanvas=enableVibrationCanvas,this.enableNfcCanvas=enableNfcCanvas,this.enableAllCanvas=enableAllCanvas,this.enableCameraCanvas=enableCameraCanvas,this.enableGyroBanner=enableGyroBanner,this.enableSensorBanner=enableGyroBanner,this.enableMicBanner=enableMicBanner,this.enableSoundBanner=enableSoundBanner,this.enableSpeechBanner=enableSpeechBanner,this.enableVibrationBanner=enableVibrationBanner,this.enableNfcBanner=enableNfcBanner,this.enableAllBanner=enableAllBanner,this.enableCameraBanner=enableCameraBanner,this.enableGyroOn=enableGyroOn,this.enableSensorOn=enableGyroOn,this.enableMicOn=enableMicOn,this.enableSoundOn=enableSoundOn,this.enableSpeechOn=enableSpeechOn,this.enableVibrationOn=enableVibrationOn,this.enableNfcOn=enableNfcOn,this.enableAllOn=enableAllOn,this.enableCameraOn=enableCameraOn,this.createPhoneCamera=createPhoneCamera,this.enableCameraButton=enableCameraButton,this.enableCameraTap=enableCameraTap,this.showDebug=showDebug,this.hideDebug=hideDebug,this.toggleDebug=toggleDebug,this.debug=debug,this.debugError=debugError,this.debugWarn=debugWarn},console.log("✅ Mobile p5.js Permissions: registered as p5.js 2.0 addon")});
@@ -5,7 +5,7 @@
5
5
  <script src="https://cdn.jsdelivr.net/npm/p5@2.2.3/lib/p5.js"></script>
6
6
  <script src="https://cdn.jsdelivr.net/npm/p5.js-compatibility@0.2.0/src/preload.js"></script>
7
7
  <script src="https://cdn.jsdelivr.net/npm/p5.sound@0.3.0/dist/p5.sound.min.js"></script>
8
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.1/dist/p5-phone.min.js"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.3/dist/p5-phone.min.js"></script>
9
9
  <style>
10
10
  body {
11
11
  margin: 0;
@@ -15,6 +15,6 @@
15
15
  </style>
16
16
  </head>
17
17
  <body>
18
- <script src="sketch.js"></script>
18
+ <script src="sketch.js?v=20260602"></script>
19
19
  </body>
20
20
  </html>
@@ -4,6 +4,7 @@
4
4
 
5
5
  // Global variables - easy to adjust for different use cases
6
6
  let mic;
7
+ let micAmplitude;
7
8
  let micLevel = 0;
8
9
  let micMultiplier = 5; // Multiplier to amplify mic sensitivity
9
10
  let threshold = 0.3; // Threshold for background color change (0.0 - 1.0)
@@ -22,6 +23,8 @@ function setup()
22
23
 
23
24
  // Create microphone input (global variable for library to use)
24
25
  mic = new p5.AudioIn();
26
+ micAmplitude = new p5.Amplitude();
27
+ routeMicToAnalyzer();
25
28
 
26
29
  textAlign(CENTER, CENTER);
27
30
  textSize(16);
@@ -40,8 +43,10 @@ function draw()
40
43
  // Check if microphone is available
41
44
  if (window.micEnabled)
42
45
  {
46
+ routeMicToAnalyzer();
47
+
43
48
  // Get current microphone level (0.0 to 1.0) and apply multiplier
44
- micLevel = mic.getLevel() * micMultiplier;
49
+ micLevel = micAmplitude.getLevel() * micMultiplier;
45
50
 
46
51
  // Check if level exceeds threshold and change background
47
52
  if (micLevel > threshold)
@@ -114,4 +119,13 @@ function mouseReleased()
114
119
  {
115
120
  // Touch positions will be updated in draw() function
116
121
  return false;
122
+ }
123
+
124
+ function routeMicToAnalyzer()
125
+ {
126
+ if (mic && mic.disconnect && micAmplitude)
127
+ {
128
+ mic.disconnect();
129
+ mic.connect(micAmplitude);
130
+ }
117
131
  }
@@ -6,7 +6,7 @@
6
6
  <script src="https://cdn.jsdelivr.net/npm/p5.js-compatibility@0.2.0/src/preload.js"></script>
7
7
  <script src="https://cdn.jsdelivr.net/npm/p5.sound@0.3.0/dist/p5.sound.min.js"></script>
8
8
  <script src="https://cdn.jsdelivr.net/gh/IDMNYU/p5.js-speech@0.0.3/lib/p5.speech.js"></script>
9
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.1/dist/p5-phone.min.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.3/dist/p5-phone.min.js"></script>
10
10
  <style>
11
11
  body {
12
12
  margin: 0;
@@ -19,7 +19,7 @@
19
19
  <script src="https://cdn.jsdelivr.net/npm/p5.js-compatibility@0.2.0/src/preload.js"></script>
20
20
 
21
21
  <!-- Load the mobile p5.js permissions library -->
22
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.1/dist/p5-phone.js"></script>
22
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.3/dist/p5-phone.js"></script>
23
23
 
24
24
  </head>
25
25
  <body>
@@ -19,7 +19,7 @@
19
19
  <script src="https://cdn.jsdelivr.net/npm/p5.js-compatibility@0.2.0/src/preload.js"></script>
20
20
 
21
21
  <!-- Load the mobile p5.js permissions library -->
22
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.1/dist/p5-phone.js"></script>
22
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.3/dist/p5-phone.js"></script>
23
23
 
24
24
  </head>
25
25
  <body>
@@ -19,7 +19,7 @@
19
19
  <script src="https://cdn.jsdelivr.net/npm/p5.js-compatibility@0.2.0/src/preload.js"></script>
20
20
 
21
21
  <!-- Load the mobile p5.js permissions library -->
22
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.1/dist/p5-phone.js"></script>
22
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.3/dist/p5-phone.js"></script>
23
23
 
24
24
  </head>
25
25
  <body>
@@ -7,6 +7,7 @@ let backgroundColor;
7
7
  function setup()
8
8
  {
9
9
  createCanvas(windowWidth, windowHeight);
10
+ lockGestures();
10
11
  backgroundColor = color(50, 50, 50);
11
12
  textAlign(CENTER, CENTER);
12
13
  textSize(16);
@@ -15,7 +15,7 @@
15
15
 
16
16
  <script src="https://cdn.jsdelivr.net/npm/p5@2.2.3/lib/p5.js"></script>
17
17
  <script src="https://cdn.jsdelivr.net/npm/p5.js-compatibility@0.2.0/src/preload.js"></script>
18
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.1/dist/p5-phone.js"></script>
18
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.3/dist/p5-phone.js"></script>
19
19
  </head>
20
20
  <body>
21
21
  <script src="sketch.js"></script>
@@ -5,10 +5,12 @@
5
5
  // Use setShakeThreshold() to control how strong a shake must be.
6
6
 
7
7
  let shakeCount = 0;
8
- let shakeThreshold = 30;
8
+ let shakeThreshold = 60;
9
9
  let lastShakeTime = 0;
10
10
  let flashColor;
11
11
 
12
+ const shakeDebounceMs = 700;
13
+
12
14
  function setup()
13
15
  {
14
16
  createCanvas(windowWidth, windowHeight);
@@ -98,16 +100,18 @@ function drawButton(centerX, centerY, buttonWidth, buttonHeight, label)
98
100
 
99
101
  function applyShakeThreshold()
100
102
  {
101
- shakeThreshold = constrain(shakeThreshold, 5, 100);
103
+ shakeThreshold = constrain(shakeThreshold, 20, 150);
102
104
  setShakeThreshold(shakeThreshold);
103
105
  }
104
106
 
105
107
  function deviceShaken()
106
108
  {
107
- if (window.sensorsEnabled)
109
+ let now = millis();
110
+
111
+ if (window.sensorsEnabled && (lastShakeTime === 0 || now - lastShakeTime > shakeDebounceMs))
108
112
  {
109
113
  shakeCount++;
110
- lastShakeTime = millis();
114
+ lastShakeTime = now;
111
115
  }
112
116
  }
113
117
 
@@ -119,12 +123,12 @@ function mousePressed()
119
123
 
120
124
  if (isInsideButton(width / 2 - 64, buttonY, 96, 44))
121
125
  {
122
- shakeThreshold -= 5;
126
+ shakeThreshold -= 10;
123
127
  applyShakeThreshold();
124
128
  }
125
129
  else if (isInsideButton(width / 2 + 64, buttonY, 96, 44))
126
130
  {
127
- shakeThreshold += 5;
131
+ shakeThreshold += 10;
128
132
  applyShakeThreshold();
129
133
  }
130
134
  }
@@ -15,7 +15,7 @@
15
15
 
16
16
  <script src="https://cdn.jsdelivr.net/npm/p5@2.2.3/lib/p5.js"></script>
17
17
  <script src="https://cdn.jsdelivr.net/npm/p5.js-compatibility@0.2.0/src/preload.js"></script>
18
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.1/dist/p5-phone.js"></script>
18
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.9.3/dist/p5-phone.js"></script>
19
19
  </head>
20
20
  <body>
21
21
  <script src="sketch.js"></script>