p5-phone 1.4.4

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 (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +509 -0
  3. package/dist/p5-phone.js +1062 -0
  4. package/dist/p5-phone.min.js +10 -0
  5. package/examples/Phone Sensor Examples/microphone/01_mic_level/index.html +19 -0
  6. package/examples/Phone Sensor Examples/microphone/01_mic_level/sketch.js +117 -0
  7. package/examples/Phone Sensor Examples/movement/01_orientation_basic/index.html +28 -0
  8. package/examples/Phone Sensor Examples/movement/01_orientation_basic/sketch.js +123 -0
  9. package/examples/Phone Sensor Examples/movement/02_rotational_velocity/index.html +28 -0
  10. package/examples/Phone Sensor Examples/movement/02_rotational_velocity/sketch.js +144 -0
  11. package/examples/Phone Sensor Examples/movement/03_acceleration/index.html +28 -0
  12. package/examples/Phone Sensor Examples/movement/03_acceleration/sketch.js +87 -0
  13. package/examples/Phone Sensor Examples/sound/01_dual_audio/index.html +31 -0
  14. package/examples/Phone Sensor Examples/sound/01_dual_audio/sketch.js +225 -0
  15. package/examples/Phone Sensor Examples/sound/01_dual_audio/tracks/audio1.mp3 +0 -0
  16. package/examples/Phone Sensor Examples/sound/01_dual_audio/tracks/audio2.mp3 +0 -0
  17. package/examples/Phone Sensor Examples/sound/02_volume_touches/index.html +31 -0
  18. package/examples/Phone Sensor Examples/sound/02_volume_touches/sketch.js +269 -0
  19. package/examples/Phone Sensor Examples/sound/02_volume_touches/tracks/audio1.mp3 +0 -0
  20. package/examples/Phone Sensor Examples/touch/01_touch_basic/index.html +28 -0
  21. package/examples/Phone Sensor Examples/touch/01_touch_basic/sketch.js +94 -0
  22. package/examples/Phone Sensor Examples/touch/02_touch_zones/index.html +28 -0
  23. package/examples/Phone Sensor Examples/touch/02_touch_zones/sketch.js +220 -0
  24. package/examples/Phone Sensor Examples/touch/03_touch_count/index.html +28 -0
  25. package/examples/Phone Sensor Examples/touch/03_touch_count/sketch.js +93 -0
  26. package/examples/Phone Sensor Examples/touch/04_touch_distance/index.html +28 -0
  27. package/examples/Phone Sensor Examples/touch/04_touch_distance/sketch.js +120 -0
  28. package/examples/Phone Sensor Examples/touch/05_touch_angle/index.html +28 -0
  29. package/examples/Phone Sensor Examples/touch/05_touch_angle/sketch.js +117 -0
  30. package/examples/Phone Sensor Examples - Minimal/microphone/01_mic_level/index.html +19 -0
  31. package/examples/Phone Sensor Examples - Minimal/microphone/01_mic_level/sketch.js +72 -0
  32. package/examples/Phone Sensor Examples - Minimal/movement/01_orientation_basic/index.html +18 -0
  33. package/examples/Phone Sensor Examples - Minimal/movement/01_orientation_basic/sketch.js +51 -0
  34. package/examples/Phone Sensor Examples - Minimal/movement/02_rotational_velocity/index.html +18 -0
  35. package/examples/Phone Sensor Examples - Minimal/movement/02_rotational_velocity/sketch.js +70 -0
  36. package/examples/Phone Sensor Examples - Minimal/movement/03_acceleration/index.html +18 -0
  37. package/examples/Phone Sensor Examples - Minimal/movement/03_acceleration/sketch.js +83 -0
  38. package/examples/Phone Sensor Examples - Minimal/sound/01_sound_basic/index.html +19 -0
  39. package/examples/Phone Sensor Examples - Minimal/sound/01_sound_basic/sketch.js +96 -0
  40. package/examples/Phone Sensor Examples - Minimal/sound/01_sound_basic/tracks/audio1.mp3 +0 -0
  41. package/examples/Phone Sensor Examples - Minimal/sound/02_sound_amplitude/index.html +19 -0
  42. package/examples/Phone Sensor Examples - Minimal/sound/02_sound_amplitude/sketch.js +118 -0
  43. package/examples/Phone Sensor Examples - Minimal/sound/02_sound_amplitude/tracks/audio1.mp3 +0 -0
  44. package/examples/Phone Sensor Examples - Minimal/touch/01_touch_basic/index.html +18 -0
  45. package/examples/Phone Sensor Examples - Minimal/touch/01_touch_basic/sketch.js +58 -0
  46. package/examples/Phone Sensor Examples - Minimal/touch/02_touch_zones/index.html +18 -0
  47. package/examples/Phone Sensor Examples - Minimal/touch/02_touch_zones/sketch.js +78 -0
  48. package/examples/Phone Sensor Examples - Minimal/touch/03_touch_count/index.html +18 -0
  49. package/examples/Phone Sensor Examples - Minimal/touch/03_touch_count/sketch.js +64 -0
  50. package/examples/Phone Sensor Examples - Minimal/touch/04_touch_distance/index.html +18 -0
  51. package/examples/Phone Sensor Examples - Minimal/touch/04_touch_distance/sketch.js +69 -0
  52. package/examples/Phone Sensor Examples - Minimal/touch/05_touch_angle/index.html +18 -0
  53. package/examples/Phone Sensor Examples - Minimal/touch/05_touch_angle/sketch.js +85 -0
  54. package/examples/Phone and Gif/collision/README.md +31 -0
  55. Gif/collision/gifs/spaceSuit2.png +0 -0
  56. package/examples/Phone and Gif/collision/index.html +19 -0
  57. package/examples/Phone and Gif/collision/sketch.js +226 -0
  58. Gif/fetch/gifs/corgiswimflip.gif +0 -0
  59. package/examples/Phone and Gif/fetch/index.html +18 -0
  60. package/examples/Phone and Gif/fetch/sketch.js +139 -0
  61. Gif/fly/gifs/comparison.gif +0 -0
  62. package/examples/Phone and Gif/fly/index.html +18 -0
  63. package/examples/Phone and Gif/fly/sketch.js +103 -0
  64. Gif/roll/gifs/how-penciles-are-made.gif +0 -0
  65. package/examples/Phone and Gif/roll/index.html +18 -0
  66. package/examples/Phone and Gif/roll/sketch.js +121 -0
  67. package/examples/UXcompare/button-vs-movement/index.html +45 -0
  68. package/examples/UXcompare/button-vs-movement/sketch.js +355 -0
  69. package/examples/UXcompare/button-vs-orientation/index.html +25 -0
  70. package/examples/UXcompare/button-vs-orientation/sketch.js +317 -0
  71. package/examples/UXcompare/button-vs-shake/index.html +45 -0
  72. package/examples/UXcompare/button-vs-shake/sketch.js +320 -0
  73. package/examples/UXcompare/gyroscope-demo/index.html +78 -0
  74. package/examples/UXcompare/gyroscope-demo/sketch.js +166 -0
  75. package/examples/UXcompare/index.html +419 -0
  76. package/examples/UXcompare/microphone-demo/index.html +79 -0
  77. package/examples/UXcompare/microphone-demo/sketch.js +210 -0
  78. package/examples/UXcompare/slider-vs-angle/index.html +25 -0
  79. package/examples/UXcompare/slider-vs-angle/sketch.js +429 -0
  80. package/examples/UXcompare/slider-vs-distance/index.html +25 -0
  81. package/examples/UXcompare/slider-vs-distance/sketch.js +401 -0
  82. package/examples/UXcompare/slider-vs-microphone/index.html +26 -0
  83. package/examples/UXcompare/slider-vs-microphone/sketch.js +336 -0
  84. package/examples/UXcompare/slider-vs-touches/index.html +25 -0
  85. package/examples/UXcompare/slider-vs-touches/sketch.js +376 -0
  86. package/examples/UXcompare/sliders-vs-acceleration/index.html +25 -0
  87. package/examples/UXcompare/sliders-vs-acceleration/sketch.js +361 -0
  88. package/examples/UXcompare/sliders-vs-rotation/index.html +25 -0
  89. package/examples/UXcompare/sliders-vs-rotation/sketch.js +375 -0
  90. package/examples/blankTemplate/index.html +31 -0
  91. package/examples/blankTemplate/sketch.js +55 -0
  92. package/examples/homepage/index.html +506 -0
  93. package/package.json +73 -0
  94. package/src/p5-phone.js +1062 -0
  95. package/src/permissionMic.js +240 -0
  96. package/src/permissionsGesture.js +213 -0
  97. package/src/permissionsGyro.js +246 -0
@@ -0,0 +1,1062 @@
1
+ /*!
2
+ * p5-phone v1.4.4
3
+ * Simplified mobile hardware access for p5.js - handle sensors, microphone, touch, and browser gestures with ease
4
+ * https://github.com/DigitalFuturesOCADU/p5-phone
5
+ *
6
+ * Copyright (c) 2025 Nick Puckett
7
+ * Released under the MIT License
8
+ * https://opensource.org/licenses/MIT
9
+ */
10
+
11
+ // =============================================
12
+ // P5-PHONE - Mobile Hardware Access for p5.js
13
+ // Clean API for enabling permissions in p5.js sketches
14
+ // =============================================
15
+
16
+ // Set up global error handling immediately when script loads
17
+ (function() {
18
+ // Store original console methods before any overrides
19
+ window._originalConsoleError = console.error;
20
+ window._originalConsoleWarn = console.warn;
21
+
22
+ // Only set up once
23
+ if (window._debugErrorHandlersSet) return;
24
+ window._debugErrorHandlersSet = true;
25
+
26
+ // Initialize early error storage
27
+ window._earlyErrors = window._earlyErrors || [];
28
+
29
+ // Global error handler for JavaScript errors
30
+ window.addEventListener('error', function(event) {
31
+ const errorMsg = event.error?.message || event.message || 'Unknown error';
32
+ const fileName = event.filename ? event.filename.split('/').pop() : 'unknown file';
33
+ const line = event.lineno || 'unknown line';
34
+
35
+ const fullError = `${errorMsg} (${fileName}:${line})`;
36
+
37
+ console.error('🚨 Error caught:', fullError);
38
+ if (event.error?.stack) {
39
+ console.error('Stack:', event.error.stack);
40
+ }
41
+
42
+ // Store error for debug panel
43
+ window._earlyErrors.push({
44
+ type: 'error',
45
+ message: 'JavaScript Error: ' + fullError,
46
+ stack: event.error?.stack
47
+ });
48
+
49
+ // Auto-show debug panel when an error occurs (if SHOW_DEBUG is true)
50
+ if (window.SHOW_DEBUG !== false && !window._debugVisible) {
51
+ // Try to show debug panel automatically
52
+ if (typeof showDebug === 'function') {
53
+ showDebug();
54
+ }
55
+ }
56
+
57
+ // If debug panel is already visible, show immediately
58
+ if (window._debugVisible && typeof debugError === 'function') {
59
+ debugError('JavaScript Error:', fullError);
60
+ if (event.error?.stack) {
61
+ debugError('Stack trace:', event.error.stack);
62
+ }
63
+ }
64
+ });
65
+
66
+ // Global handler for unhandled promise rejections
67
+ window.addEventListener('unhandledrejection', function(event) {
68
+ const errorMsg = event.reason?.message || event.reason || 'Unknown promise rejection';
69
+
70
+ console.error('🚨 Promise rejection caught:', errorMsg);
71
+
72
+ window._earlyErrors.push({
73
+ type: 'error',
74
+ message: 'Unhandled Promise Rejection: ' + errorMsg
75
+ });
76
+
77
+ if (window._debugVisible && typeof debugError === 'function') {
78
+ debugError('Unhandled Promise Rejection:', errorMsg);
79
+ }
80
+ });
81
+ })();
82
+
83
+ // Global state flags
84
+ window.sensorsEnabled = false;
85
+ window.micEnabled = false;
86
+ window.soundEnabled = false;
87
+ window.gesturesLocked = false;
88
+
89
+ // Internal state
90
+ let _permissionsInitialized = false;
91
+ let _micInstance = null;
92
+
93
+ // =========================================
94
+ // PUBLIC API - CALL THESE FROM YOUR P5 SKETCH
95
+ // =========================================
96
+
97
+ /**
98
+ * Lock mobile gestures to prevent browser interference
99
+ * Call this in your setup() function
100
+ */
101
+ function lockGestures() {
102
+ if (window.gesturesLocked) return;
103
+
104
+ console.log('🔒 Locking mobile gestures...');
105
+ _initializeGestureBlocking();
106
+ _initializeP5TouchOverrides();
107
+ window.gesturesLocked = true;
108
+ console.log('✅ Mobile gestures locked');
109
+ }
110
+
111
+ /**
112
+ * Enable gyroscope with a button interface
113
+ * Creates a start button that user must click
114
+ */
115
+ function enableGyroButton(buttonText = 'ENABLE MOTION SENSORS', statusText = 'Requesting motion sensors...') {
116
+ _createPermissionButton(buttonText, statusText, async () => {
117
+ await _requestMotionPermissions();
118
+ console.log('✅ Gyroscope enabled via button');
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Enable gyroscope with tap-to-start
124
+ * User taps anywhere on screen to enable
125
+ */
126
+ function enableGyroTap(message = 'Tap screen to enable motion sensors') {
127
+ _createTapToEnable(message, async () => {
128
+ await _requestMotionPermissions();
129
+ console.log('✅ Gyroscope enabled via tap');
130
+ });
131
+ }
132
+
133
+ /**
134
+ * Enable microphone with a button interface
135
+ * Creates a start button that user must click
136
+ */
137
+ function enableMicButton(buttonText = 'ENABLE MICROPHONE', statusText = 'Requesting microphone access...') {
138
+ _createPermissionButton(buttonText, statusText, async () => {
139
+ await _requestMicrophonePermissions();
140
+ console.log('✅ Microphone enabled via button');
141
+ });
142
+ }
143
+
144
+ /**
145
+ * Enable microphone with tap-to-start
146
+ * User taps anywhere on screen to enable
147
+ */
148
+ function enableMicTap(message = 'Tap screen to enable microphone') {
149
+ _createTapToEnable(message, async () => {
150
+ await _requestMicrophonePermissions();
151
+ console.log('✅ Microphone enabled via tap');
152
+ });
153
+ }
154
+
155
+ /**
156
+ * Enable sound output with a button interface
157
+ * Creates a start button that user must click
158
+ * Use this for playing sounds without needing microphone input
159
+ */
160
+ function enableSoundButton(buttonText = 'ENABLE SOUND', statusText = 'Enabling audio...') {
161
+ _createPermissionButton(buttonText, statusText, async () => {
162
+ await _requestSoundOutput();
163
+ console.log('✅ Sound output enabled via button');
164
+ });
165
+ }
166
+
167
+ /**
168
+ * Enable sound output with tap-to-start
169
+ * User taps anywhere on screen to enable
170
+ * Use this for playing sounds without needing microphone input
171
+ */
172
+ function enableSoundTap(message = 'Tap screen to enable sound') {
173
+ _createTapToEnable(message, async () => {
174
+ await _requestSoundOutput();
175
+ console.log('✅ Sound output enabled via tap');
176
+ });
177
+ }
178
+
179
+ /**
180
+ * Enable both motion sensors and microphone with a button interface
181
+ * Creates a start button that user must click to enable both
182
+ */
183
+ function enableAllButton(buttonText = 'ENABLE MOTION & MICROPHONE', statusText = 'Requesting permissions...') {
184
+ _createPermissionButton(buttonText, statusText, async () => {
185
+ await _requestMotionPermissions();
186
+ await _requestMicrophonePermissions();
187
+ console.log('✅ Motion sensors and microphone enabled via button');
188
+ });
189
+ }
190
+
191
+ /**
192
+ * Enable both motion sensors and microphone with tap-to-start
193
+ * User taps anywhere on screen to enable both
194
+ */
195
+ function enableAllTap(message = 'Tap screen to enable motion sensors & microphone') {
196
+ _createTapToEnable(message, async () => {
197
+ await _requestMotionPermissions();
198
+ await _requestMicrophonePermissions();
199
+ console.log('✅ Motion sensors and microphone enabled via tap');
200
+ });
201
+ }
202
+
203
+ // =========================================
204
+ // INTERNAL PERMISSION HANDLERS
205
+ // =========================================
206
+
207
+ async function _requestMotionPermissions() {
208
+ try {
209
+ // Request motion sensor permissions (iOS 13+)
210
+ if (typeof DeviceOrientationEvent !== 'undefined' &&
211
+ typeof DeviceOrientationEvent.requestPermission === 'function') {
212
+
213
+ const orientationPermission = await DeviceOrientationEvent.requestPermission();
214
+ console.log('Orientation permission:', orientationPermission);
215
+
216
+ if (typeof DeviceMotionEvent !== 'undefined' &&
217
+ typeof DeviceMotionEvent.requestPermission === 'function') {
218
+ const motionPermission = await DeviceMotionEvent.requestPermission();
219
+ console.log('Motion permission:', motionPermission);
220
+ }
221
+ }
222
+
223
+ window.sensorsEnabled = true;
224
+ _notifySketchReady();
225
+
226
+ } catch (error) {
227
+ console.error('Motion sensor permission error:', error);
228
+ if (_debugVisible) {
229
+ debugError('Motion sensor permission error:', error);
230
+ }
231
+ // Enable anyway for non-iOS devices
232
+ window.sensorsEnabled = true;
233
+ _notifySketchReady();
234
+ }
235
+ }
236
+
237
+ async function _requestMicrophonePermissions() {
238
+ try {
239
+ // Start audio context for p5.sound
240
+ if (typeof userStartAudio !== 'undefined') {
241
+ await userStartAudio();
242
+ }
243
+
244
+ // If there's a global mic object, start it
245
+ if (typeof mic !== 'undefined' && mic && mic.start) {
246
+ mic.start();
247
+ _micInstance = mic;
248
+ window.micEnabled = true;
249
+ } else {
250
+ console.warn('No microphone object found. Create one with: mic = new p5.AudioIn();');
251
+ }
252
+
253
+ _notifySketchReady();
254
+
255
+ } catch (error) {
256
+ console.error('Microphone permission error:', error);
257
+ if (_debugVisible) {
258
+ debugError('Microphone permission error:', error);
259
+ }
260
+ _notifySketchReady();
261
+ }
262
+ }
263
+
264
+ async function _requestSoundOutput() {
265
+ try {
266
+ // Start audio context for p5.sound (enables sound playback)
267
+ if (typeof userStartAudio !== 'undefined') {
268
+ await userStartAudio();
269
+ }
270
+
271
+ window.soundEnabled = true;
272
+ _notifySketchReady();
273
+
274
+ } catch (error) {
275
+ console.error('Sound output error:', error);
276
+ if (_debugVisible) {
277
+ debugError('Sound output error:', error);
278
+ }
279
+ window.soundEnabled = true; // Enable anyway since no permission needed
280
+ _notifySketchReady();
281
+ }
282
+ }
283
+
284
+ function _notifySketchReady() {
285
+ // Call userSetupComplete if it exists
286
+ if (typeof userSetupComplete === 'function') {
287
+ userSetupComplete();
288
+ }
289
+
290
+ // Trigger a custom event for more advanced use cases
291
+ window.dispatchEvent(new CustomEvent('permissionsReady', {
292
+ detail: {
293
+ sensors: window.sensorsEnabled,
294
+ microphone: window.micEnabled,
295
+ sound: window.soundEnabled,
296
+ gestures: window.gesturesLocked
297
+ }
298
+ }));
299
+ }
300
+
301
+ // =========================================
302
+ // UI CREATION HELPERS
303
+ // =========================================
304
+
305
+ function _createPermissionButton(buttonText, statusText, onClickHandler) {
306
+ // Remove existing button if present
307
+ _removeExistingUI();
308
+
309
+ // Create button
310
+ const button = document.createElement('button');
311
+ button.id = 'permissionButton';
312
+ button.textContent = buttonText;
313
+ button.style.cssText = `
314
+ position: fixed;
315
+ top: 50%;
316
+ left: 50%;
317
+ transform: translate(-50%, -50%);
318
+ padding: 20px 40px;
319
+ font-size: 18px;
320
+ font-weight: bold;
321
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
322
+ color: white;
323
+ border: none;
324
+ border-radius: 12px;
325
+ cursor: pointer;
326
+ z-index: 999999;
327
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
328
+ box-shadow: 0 4px 15px rgba(0,0,0,0.2);
329
+ transition: transform 0.2s ease;
330
+ touch-action: manipulation;
331
+ `;
332
+
333
+ // Create status text
334
+ const status = document.createElement('div');
335
+ status.id = 'permissionStatus';
336
+ status.textContent = statusText;
337
+ status.style.cssText = `
338
+ position: fixed;
339
+ top: 60%;
340
+ left: 50%;
341
+ transform: translate(-50%, 0);
342
+ color: white;
343
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
344
+ text-align: center;
345
+ z-index: 999998;
346
+ display: none;
347
+ `;
348
+
349
+ // Add hover effect
350
+ button.addEventListener('mouseenter', () => {
351
+ button.style.transform = 'translate(-50%, -50%) scale(1.05)';
352
+ });
353
+
354
+ button.addEventListener('mouseleave', () => {
355
+ button.style.transform = 'translate(-50%, -50%) scale(1)';
356
+ });
357
+
358
+ // Add multiple event handlers to ensure responsiveness
359
+ const handleButtonClick = async () => {
360
+ if (button.parentNode) {
361
+ button.style.display = 'none';
362
+ status.style.display = 'block';
363
+
364
+ await onClickHandler();
365
+
366
+ status.style.display = 'none';
367
+ _removeExistingUI();
368
+ }
369
+ };
370
+
371
+ // Add click, touch, and pointer handlers
372
+ button.addEventListener('click', handleButtonClick);
373
+ button.addEventListener('touchend', function(e) {
374
+ e.preventDefault();
375
+ e.stopPropagation();
376
+ handleButtonClick();
377
+ });
378
+ button.addEventListener('pointerup', function(e) {
379
+ e.preventDefault();
380
+ e.stopPropagation();
381
+ handleButtonClick();
382
+ });
383
+
384
+ document.body.appendChild(button);
385
+ document.body.appendChild(status);
386
+ }
387
+
388
+ function _createTapToEnable(message, onTapHandler) {
389
+ // Remove existing UI if present
390
+ _removeExistingUI();
391
+
392
+ // Create overlay
393
+ const overlay = document.createElement('div');
394
+ overlay.id = 'tapOverlay';
395
+ overlay.style.cssText = `
396
+ position: fixed;
397
+ top: 0;
398
+ left: 0;
399
+ width: 100%;
400
+ height: 100%;
401
+ background: rgba(0, 0, 0, 0.8);
402
+ display: flex;
403
+ align-items: center;
404
+ justify-content: center;
405
+ z-index: 999999;
406
+ cursor: pointer;
407
+ touch-action: manipulation;
408
+ `;
409
+
410
+ // Create message
411
+ const messageDiv = document.createElement('div');
412
+ messageDiv.textContent = message;
413
+ messageDiv.style.cssText = `
414
+ color: white;
415
+ font-size: 24px;
416
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
417
+ text-align: center;
418
+ padding: 40px;
419
+ border: 2px solid rgba(255, 255, 255, 0.3);
420
+ border-radius: 12px;
421
+ background: rgba(255, 255, 255, 0.1);
422
+ backdrop-filter: blur(10px);
423
+ `;
424
+
425
+ overlay.appendChild(messageDiv);
426
+
427
+ // Add multiple event handlers to ensure responsiveness
428
+ const handleActivation = async () => {
429
+ if (overlay.parentNode) {
430
+ messageDiv.textContent = 'Enabling...';
431
+ await onTapHandler();
432
+ if (overlay.parentNode) {
433
+ document.body.removeChild(overlay);
434
+ }
435
+ }
436
+ };
437
+
438
+ // Add both click and touch handlers
439
+ overlay.addEventListener('click', handleActivation);
440
+ overlay.addEventListener('touchend', function(e) {
441
+ e.preventDefault();
442
+ e.stopPropagation();
443
+ handleActivation();
444
+ });
445
+
446
+ // Also add pointer events for wider compatibility
447
+ overlay.addEventListener('pointerup', function(e) {
448
+ e.preventDefault();
449
+ e.stopPropagation();
450
+ handleActivation();
451
+ });
452
+
453
+ document.body.appendChild(overlay);
454
+ }
455
+
456
+ function _removeExistingUI() {
457
+ const button = document.getElementById('permissionButton');
458
+ const status = document.getElementById('permissionStatus');
459
+ const overlay = document.getElementById('tapOverlay');
460
+
461
+ if (button) button.remove();
462
+ if (status) status.remove();
463
+ if (overlay) overlay.remove();
464
+ }
465
+
466
+ // =========================================
467
+ // GESTURE BLOCKING IMPLEMENTATION
468
+ // =========================================
469
+
470
+ function _initializeGestureBlocking() {
471
+ // Prevent back navigation
472
+ window.history.pushState(null, '', window.location.href);
473
+ window.onpopstate = function() {
474
+ window.history.pushState(null, '', window.location.href);
475
+ };
476
+
477
+ // Warn before leaving
478
+ window.addEventListener('beforeunload', function(e) {
479
+ e.preventDefault();
480
+ e.returnValue = '';
481
+ });
482
+
483
+ _initializeEdgeSwipePrevention();
484
+ _initializeOtherGesturePrevention();
485
+ }
486
+
487
+ function _initializeEdgeSwipePrevention() {
488
+ let touchStartX = 0;
489
+ let touchStartY = 0;
490
+ const edgeThreshold = 20;
491
+
492
+ document.addEventListener('touchstart', function(e) {
493
+ if (e.touches && e.touches.length > 0) {
494
+ touchStartX = e.touches[0].clientX;
495
+ touchStartY = e.touches[0].clientY;
496
+
497
+ // Prevent edge swipes
498
+ if (touchStartX < edgeThreshold ||
499
+ touchStartX > window.innerWidth - edgeThreshold) {
500
+ e.preventDefault();
501
+ }
502
+ }
503
+ }, { passive: false, capture: true });
504
+
505
+ document.addEventListener('touchmove', function(e) {
506
+ if (!e.touches || e.touches.length === 0) return;
507
+
508
+ let currentX = e.touches[0].clientX;
509
+ let currentY = e.touches[0].clientY;
510
+ let deltaX = currentX - touchStartX;
511
+ let deltaY = currentY - touchStartY;
512
+
513
+ // Prevent horizontal edge swipes (back/forward)
514
+ if ((touchStartX < edgeThreshold && deltaX > 0) ||
515
+ (touchStartX > window.innerWidth - edgeThreshold && deltaX < 0)) {
516
+ e.preventDefault();
517
+ e.stopPropagation();
518
+ }
519
+
520
+ // Prevent pull-to-refresh
521
+ if (window.pageYOffset === 0 && deltaY > 0) {
522
+ e.preventDefault();
523
+ }
524
+
525
+ // Prevent canvas touches but not on permission UI
526
+ if (e.target && e.target.tagName === 'CANVAS' &&
527
+ !document.getElementById('tapOverlay') &&
528
+ !document.getElementById('permissionButton')) {
529
+ e.preventDefault();
530
+ }
531
+ }, { passive: false, capture: true });
532
+ }
533
+
534
+ function _initializeOtherGesturePrevention() {
535
+ // Prevent pinch zoom
536
+ document.addEventListener('gesturestart', function(e) {
537
+ e.preventDefault();
538
+ });
539
+
540
+ document.addEventListener('gesturechange', function(e) {
541
+ e.preventDefault();
542
+ });
543
+
544
+ document.addEventListener('gestureend', function(e) {
545
+ e.preventDefault();
546
+ });
547
+
548
+ // Prevent double-tap zoom
549
+ let lastTouchEnd = 0;
550
+ document.addEventListener('touchend', function(e) {
551
+ // Don't prevent clicks on permission UI elements
552
+ if (e.target && (
553
+ e.target.id === 'tapOverlay' ||
554
+ e.target.closest('#tapOverlay') ||
555
+ e.target.id === 'permissionButton' ||
556
+ e.target.id === 'permissionStatus' ||
557
+ e.target.closest('#permissionButton') ||
558
+ e.target.closest('#permissionStatus')
559
+ )) {
560
+ return; // Allow clicks on permission UI
561
+ }
562
+
563
+ const now = Date.now();
564
+ if (now - lastTouchEnd <= 300) {
565
+ e.preventDefault();
566
+ }
567
+ lastTouchEnd = now;
568
+ }, false);
569
+
570
+ // Prevent long-press context menu
571
+ window.oncontextmenu = function(e) {
572
+ e.preventDefault();
573
+ e.stopPropagation();
574
+ return false;
575
+ };
576
+ }
577
+
578
+ function _initializeP5TouchOverrides() {
579
+ // Wait for p5 to be ready
580
+ setTimeout(() => {
581
+ if (window._setupDone) {
582
+ _overrideP5Touch();
583
+ } else {
584
+ // Try again after setup
585
+ const checkP5 = setInterval(() => {
586
+ if (window._setupDone) {
587
+ _overrideP5Touch();
588
+ clearInterval(checkP5);
589
+ }
590
+ }, 100);
591
+ }
592
+ }, 100);
593
+ }
594
+
595
+ function _overrideP5Touch() {
596
+ const origTouchStarted = window.touchStarted || function() {};
597
+ const origTouchMoved = window.touchMoved || function() {};
598
+ const origTouchEnded = window.touchEnded || function() {};
599
+ const origMousePressed = window.mousePressed || function() {};
600
+ const origMouseDragged = window.mouseDragged || function() {};
601
+ const origMouseReleased = window.mouseReleased || function() {};
602
+
603
+ // Ensure all touch functions return false to prevent default behaviors
604
+ window.touchStarted = function(e) {
605
+ origTouchStarted(e);
606
+ return false;
607
+ };
608
+
609
+ window.touchMoved = function(e) {
610
+ origTouchMoved(e);
611
+ return false;
612
+ };
613
+
614
+ window.touchEnded = function(e) {
615
+ origTouchEnded(e);
616
+ return false;
617
+ };
618
+
619
+ window.mousePressed = function(e) {
620
+ origMousePressed(e);
621
+ return false;
622
+ };
623
+
624
+ window.mouseDragged = function(e) {
625
+ origMouseDragged(e);
626
+ return false;
627
+ };
628
+
629
+ window.mouseReleased = function(e) {
630
+ origMouseReleased(e);
631
+ return false;
632
+ };
633
+ }
634
+
635
+ // =========================================
636
+ // LEGACY COMPATIBILITY
637
+ // =========================================
638
+
639
+ // Initialize gesture blocking on DOM load for backward compatibility
640
+ document.addEventListener('DOMContentLoaded', function() {
641
+ // Check for old-style HTML elements
642
+ const startButton = document.getElementById('startButton');
643
+ const statusText = document.getElementById('statusText');
644
+
645
+ if (startButton && statusText) {
646
+ console.warn('⚠️ Legacy HTML elements detected. Consider using the new API functions instead.');
647
+ // Maintain backward compatibility
648
+ startButton.addEventListener('click', async () => {
649
+ startButton.classList.add('hidden');
650
+ statusText.classList.remove('hidden');
651
+ statusText.textContent = 'Requesting permissions...';
652
+
653
+ await _requestMotionPermissions();
654
+ await _requestMicrophonePermissions();
655
+
656
+ statusText.classList.add('hidden');
657
+ });
658
+
659
+ lockGestures(); // Auto-lock gestures for legacy mode
660
+ }
661
+ });
662
+
663
+ // =========================================
664
+ // DEBUG SYSTEM - ON-SCREEN CONSOLE
665
+ // =========================================
666
+
667
+ // Debug system state
668
+ let _debugPanel = null;
669
+ let _debugVisible = false;
670
+ let _debugMessages = [];
671
+ const MAX_DEBUG_MESSAGES = 20;
672
+
673
+ /**
674
+ * Show the on-screen debug panel
675
+ */
676
+ function showDebug() {
677
+ _createDebugPanel();
678
+ _debugPanel.style.display = 'block';
679
+ _debugVisible = true;
680
+ window._debugVisible = true; // Global flag
681
+
682
+ // Set up console overrides for future calls
683
+ _setupConsoleOverrides();
684
+
685
+ // Immediately show any early errors that might have been caught
686
+ _displayEarlyErrors();
687
+ }
688
+
689
+ /**
690
+ * Hide the on-screen debug panel
691
+ */
692
+ function hideDebug() {
693
+ if (_debugPanel) {
694
+ _debugPanel.style.display = 'none';
695
+ _debugVisible = false;
696
+ }
697
+ }
698
+
699
+ /**
700
+ * Toggle the debug panel visibility
701
+ */
702
+ function toggleDebug() {
703
+ if (_debugVisible) {
704
+ hideDebug();
705
+ } else {
706
+ showDebug();
707
+ }
708
+ }
709
+
710
+ /**
711
+ * Debug function - works like console.log but shows on screen
712
+ * Also logs to browser console
713
+ */
714
+ function debug(...args) {
715
+ // Also log to browser console
716
+ console.log(...args);
717
+
718
+ // Format arguments like console.log does
719
+ const message = args.map(arg => {
720
+ if (typeof arg === 'object' && arg !== null) {
721
+ try {
722
+ return JSON.stringify(arg, null, 2);
723
+ } catch (e) {
724
+ return String(arg);
725
+ }
726
+ }
727
+ return String(arg);
728
+ }).join(' ');
729
+
730
+ _addDebugMessage(message, 'log');
731
+ }
732
+
733
+ /**
734
+ * Error function - shows errors on screen with red styling
735
+ * Also logs to browser console as error
736
+ */
737
+ function debugError(...args) {
738
+ // Use original console.error to avoid infinite loop
739
+ const originalError = window._originalConsoleError || console.error;
740
+ originalError.apply(console, args);
741
+
742
+ // Format arguments like console.log does
743
+ const message = args.map(arg => {
744
+ if (typeof arg === 'object' && arg !== null) {
745
+ try {
746
+ return JSON.stringify(arg, null, 2);
747
+ } catch (e) {
748
+ return String(arg);
749
+ }
750
+ }
751
+ return String(arg);
752
+ }).join(' ');
753
+
754
+ _addDebugMessage(`❌ ERROR: ${message}`, 'error');
755
+ }
756
+
757
+ /**
758
+ * Warning function - shows warnings on screen with yellow styling
759
+ * Also logs to browser console as warning
760
+ */
761
+ function debugWarn(...args) {
762
+ // Use original console.warn to avoid infinite loop
763
+ const originalWarn = window._originalConsoleWarn || console.warn;
764
+ originalWarn.apply(console, args);
765
+
766
+ // Format arguments like console.log does
767
+ const message = args.map(arg => {
768
+ if (typeof arg === 'object' && arg !== null) {
769
+ try {
770
+ return JSON.stringify(arg, null, 2);
771
+ } catch (e) {
772
+ return String(arg);
773
+ }
774
+ }
775
+ return String(arg);
776
+ }).join(' ');
777
+
778
+ _addDebugMessage(`⚠️ WARNING: ${message}`, 'warning');
779
+ }
780
+
781
+ /**
782
+ * Internal function to add messages to debug panel
783
+ */
784
+ function _addDebugMessage(message, type = 'log') {
785
+ // Add timestamp
786
+ const timestamp = new Date().toLocaleTimeString('en-US', {
787
+ hour12: false,
788
+ hour: '2-digit',
789
+ minute: '2-digit',
790
+ second: '2-digit',
791
+ fractionalSecondDigits: 3
792
+ });
793
+
794
+ const timestampedMessage = {
795
+ text: `[${timestamp}] ${message}`,
796
+ type: type
797
+ };
798
+
799
+ // Add to message history
800
+ _debugMessages.push(timestampedMessage);
801
+ if (_debugMessages.length > MAX_DEBUG_MESSAGES) {
802
+ _debugMessages.shift();
803
+ }
804
+
805
+ // Update display if panel exists
806
+ if (_debugPanel) {
807
+ _updateDebugDisplay();
808
+ }
809
+ }
810
+
811
+ /**
812
+ * Clear all debug messages
813
+ */
814
+ debug.clear = function() {
815
+ _debugMessages = [];
816
+ if (_debugPanel) {
817
+ _updateDebugDisplay();
818
+ }
819
+ console.clear();
820
+ };
821
+
822
+ // Make debug functions globally accessible
823
+ window.debug = debug;
824
+ window.debugError = debugError;
825
+ window.debugWarn = debugWarn;
826
+ window.showDebug = showDebug;
827
+ window.hideDebug = hideDebug;
828
+ window.toggleDebug = toggleDebug;
829
+
830
+ // Make permission functions globally accessible
831
+ window.lockGestures = lockGestures;
832
+ window.enableGyroTap = enableGyroTap;
833
+ window.enableGyroButton = enableGyroButton;
834
+ window.enableMicTap = enableMicTap;
835
+ window.enableMicButton = enableMicButton;
836
+ window.enableSoundTap = enableSoundTap;
837
+ window.enableSoundButton = enableSoundButton;
838
+ window.enableAllTap = enableAllTap;
839
+ window.enableAllButton = enableAllButton;
840
+
841
+ /**
842
+ * Set up console overrides to capture console.error and console.warn
843
+ */
844
+ function _setupConsoleOverrides() {
845
+ // Only override once
846
+ if (window._consoleOverrideSet) return;
847
+ window._consoleOverrideSet = true;
848
+
849
+ // Store original console methods for debug functions to use
850
+ window._originalConsoleError = console.error;
851
+ window._originalConsoleWarn = console.warn;
852
+
853
+ // Override console.error to also show in debug panel
854
+ console.error = function(...args) {
855
+ window._originalConsoleError.apply(console, args);
856
+ if (_debugVisible) {
857
+ debugError(...args);
858
+ }
859
+ };
860
+
861
+ // Override console.warn to also show in debug panel
862
+ console.warn = function(...args) {
863
+ window._originalConsoleWarn.apply(console, args);
864
+ if (_debugVisible) {
865
+ debugWarn(...args);
866
+ }
867
+ };
868
+ }
869
+
870
+ /**
871
+ * Display any errors that were caught before the debug panel was ready
872
+ */
873
+ function _displayEarlyErrors() {
874
+ if (window._earlyErrors && window._earlyErrors.length > 0) {
875
+ debugError(`🚨 Found ${window._earlyErrors.length} early error(s):`);
876
+ window._earlyErrors.forEach(error => {
877
+ debugError(error.message);
878
+ if (error.stack) {
879
+ debugError('Stack trace:', error.stack);
880
+ }
881
+ });
882
+ window._earlyErrors = []; // Clear after displaying
883
+ }
884
+ }
885
+
886
+ /**
887
+ * Create the debug panel DOM element
888
+ */
889
+ function _createDebugPanel() {
890
+ if (_debugPanel) return;
891
+
892
+ _debugPanel = document.createElement('div');
893
+ _debugPanel.id = 'mobile-debug-panel';
894
+ _debugPanel.innerHTML = `
895
+ <div id="mobile-debug-header">
896
+ <span>Debug</span>
897
+ <button id="mobile-debug-close">×</button>
898
+ </div>
899
+ <div id="mobile-debug-content"></div>
900
+ `;
901
+
902
+ // Add styles
903
+ const style = document.createElement('style');
904
+ style.textContent = `
905
+ #mobile-debug-panel {
906
+ position: fixed;
907
+ top: 20px;
908
+ right: 20px;
909
+ width: 350px;
910
+ max-width: calc(100vw - 40px);
911
+ max-height: 400px;
912
+ background: rgba(0, 0, 0, 0.9);
913
+ color: #ffffff;
914
+ font-family: 'Courier New', monospace;
915
+ font-size: 12px;
916
+ border-radius: 8px;
917
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
918
+ z-index: 10000;
919
+ display: none;
920
+ }
921
+
922
+ #mobile-debug-header {
923
+ background: rgba(255, 255, 255, 0.1);
924
+ padding: 8px 12px;
925
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
926
+ display: flex;
927
+ justify-content: space-between;
928
+ align-items: center;
929
+ border-radius: 8px 8px 0 0;
930
+ }
931
+
932
+ #mobile-debug-header span {
933
+ font-weight: bold;
934
+ font-size: 13px;
935
+ }
936
+
937
+ #mobile-debug-close {
938
+ background: none;
939
+ border: none;
940
+ color: #ffffff;
941
+ font-size: 18px;
942
+ cursor: pointer;
943
+ padding: 0;
944
+ width: 20px;
945
+ height: 20px;
946
+ display: flex;
947
+ align-items: center;
948
+ justify-content: center;
949
+ }
950
+
951
+ #mobile-debug-close:hover {
952
+ background: rgba(255, 255, 255, 0.1);
953
+ border-radius: 4px;
954
+ }
955
+
956
+ #mobile-debug-content {
957
+ padding: 12px;
958
+ max-height: 340px;
959
+ overflow-y: auto;
960
+ word-wrap: break-word;
961
+ line-height: 1.4;
962
+ }
963
+
964
+ .debug-message {
965
+ margin-bottom: 4px;
966
+ white-space: pre-wrap;
967
+ }
968
+
969
+ .debug-message.error {
970
+ color: #ff6b6b;
971
+ background: rgba(255, 107, 107, 0.1);
972
+ padding: 4px;
973
+ border-radius: 3px;
974
+ border-left: 3px solid #ff6b6b;
975
+ }
976
+
977
+ .debug-message.warning {
978
+ color: #ffd93d;
979
+ background: rgba(255, 217, 61, 0.1);
980
+ padding: 4px;
981
+ border-radius: 3px;
982
+ border-left: 3px solid #ffd93d;
983
+ }
984
+
985
+ .debug-timestamp {
986
+ color: #888;
987
+ font-size: 10px;
988
+ }
989
+
990
+ @media (max-width: 480px) {
991
+ #mobile-debug-panel {
992
+ width: calc(100vw - 20px);
993
+ right: 10px;
994
+ top: 10px;
995
+ }
996
+ }
997
+ `;
998
+
999
+ document.head.appendChild(style);
1000
+ document.body.appendChild(_debugPanel);
1001
+
1002
+ // Add close button functionality
1003
+ document.getElementById('mobile-debug-close').onclick = hideDebug;
1004
+
1005
+ // Update display with existing messages
1006
+ _updateDebugDisplay();
1007
+ }
1008
+
1009
+ /**
1010
+ * Update the debug panel display with current messages
1011
+ */
1012
+ function _updateDebugDisplay() {
1013
+ if (!_debugPanel) return;
1014
+
1015
+ const content = document.getElementById('mobile-debug-content');
1016
+ if (!content) return;
1017
+
1018
+ content.innerHTML = _debugMessages
1019
+ .map(msg => {
1020
+ // Handle both old string format and new object format
1021
+ if (typeof msg === 'string') {
1022
+ return `<div class="debug-message">${msg}</div>`;
1023
+ } else {
1024
+ return `<div class="debug-message ${msg.type}">${msg.text}</div>`;
1025
+ }
1026
+ })
1027
+ .join('');
1028
+
1029
+ // Auto-scroll to bottom
1030
+ content.scrollTop = content.scrollHeight;
1031
+ }
1032
+
1033
+ // =========================================
1034
+ // P5.JS NAMESPACE SUPPORT
1035
+ // =========================================
1036
+
1037
+ /**
1038
+ * Add functions to p5.js prototype for namespace support
1039
+ * This allows using both global functions and p5.prototype.functionName
1040
+ */
1041
+ if (typeof p5 !== 'undefined' && p5.prototype) {
1042
+ // Core permission functions
1043
+ p5.prototype.lockGestures = lockGestures;
1044
+ p5.prototype.enableGyroTap = enableGyroTap;
1045
+ p5.prototype.enableGyroButton = enableGyroButton;
1046
+ p5.prototype.enableMicTap = enableMicTap;
1047
+ p5.prototype.enableMicButton = enableMicButton;
1048
+ p5.prototype.enableSoundTap = enableSoundTap;
1049
+ p5.prototype.enableSoundButton = enableSoundButton;
1050
+ p5.prototype.enableAllTap = enableAllTap;
1051
+ p5.prototype.enableAllButton = enableAllButton;
1052
+
1053
+ // Debug functions
1054
+ p5.prototype.showDebug = showDebug;
1055
+ p5.prototype.hideDebug = hideDebug;
1056
+ p5.prototype.toggleDebug = toggleDebug;
1057
+ p5.prototype.debug = debug;
1058
+ p5.prototype.debugError = debugError;
1059
+ p5.prototype.debugWarn = debugWarn;
1060
+
1061
+ console.log('✅ Mobile p5.js Permissions: p5.prototype functions registered');
1062
+ }