-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
2439 lines (2063 loc) · 101 KB
/
Copy pathscript.js
File metadata and controls
2439 lines (2063 loc) · 101 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
document.addEventListener('DOMContentLoaded', () => {
console.log("DOM fully loaded - initializing Smart Shoe application");
// DOM Elements
const startBtn = document.getElementById('startBtn');
const continueBtn = document.getElementById('continueBtn');
const projectInfo = document.getElementById('project-info');
const instructions = document.getElementById('instructions');
const introScreensContainer = document.querySelector('.intro-screens-container');
const screens = document.querySelectorAll('.screen');
const shoeImage = document.getElementById('shoeImage');
const tiltIndicator = document.getElementById('tiltIndicator');
const tiltArrow = document.getElementById('tiltArrow');
const voiceFeedback = document.getElementById('voiceFeedback');
const voiceText = document.getElementById('voiceText');
const floorProjection = document.querySelector('.floor-projection');
const projectionArea = document.querySelector('.projection-area');
const projectionBeam = document.getElementById('projectionBeam');
const tryAgainBtn = document.getElementById('tryAgainBtn');
// Verify we can find the sync button
const syncButton = document.querySelector('.sync-button');
if (syncButton) {
console.log("Found sync button:", syncButton);
} else {
console.error("Could not find sync button in DOM");
}
// Log all available screens for debugging
console.log("Available screens:", screens.length);
screens.forEach((screen, index) => {
console.log(`Screen ${index}: id=${screen.id}, classes=${screen.className}`);
});
const endScreen = document.getElementById('end-screen');
if (endScreen) {
console.log("Found end-screen element:", endScreen);
} else {
console.error("Could not find end-screen element");
}
// Add flag to track back button hover state
let isBackButtonHovered = false;
// On-screen control buttons
const leftBtn = document.getElementById('leftBtn');
const selectBtn = document.getElementById('selectBtn');
const rightBtn = document.getElementById('rightBtn');
// Workout type selection elements
const workoutOptions = document.querySelectorAll('.workout-option');
// Start workout buttons
const startWorkoutBtns = document.querySelectorAll('.start-workout-btn');
// Goal selection elements
const goalTabs = document.querySelectorAll('.goal-tab');
const goalValue = document.getElementById('goalValue');
const goalUnit = document.getElementById('goalUnit');
const sliderHandle = document.querySelector('.slider-handle');
const sliderFill = document.querySelector('.slider-fill');
// Workout screen elements
const heartRateEl = document.getElementById('heartRate');
const caloriesEl = document.getElementById('caloriesBurned');
const tempEl = document.getElementById('temp');
const stepCountEl = document.getElementById('stepCount');
const distanceEl = document.getElementById('distance');
const timeElapsedEl = document.getElementById('timeElapsed');
const timeRemainingEl = document.getElementById('timeRemaining');
// State variables
let currentScreenIndex = 0;
let selectedWorkoutOption = 1; // Default to "Walk" (middle option)
let selectedGoalTab = 0; // Default to "Distance"
let sliderValue = 60; // Default slider position (0-100)
let workoutActive = false;
let workoutPaused = false;
let workoutStartTime = null;
let workoutElapsedTime = 0;
let workoutInterval = null;
let heartRate = 80;
let calories = 0;
let steps = 0;
let distance = 0;
let mouseTiltActive = true; // Set mouse control active by default
let lastMouseTilt = null;
let mouseControlTimeout = null;
let lastMilestoneStep = 0;
// Add demo mode flag and settings
let demoMode = true; // Always use demo mode for this prototype
let demoCompletionTime = 20000; // Complete demo in 20 seconds
let demoProgressPercentage = 0;
let demoTargetSteps = 5000;
let demoTargetCalories = 300;
let demoMaxHeartRate = 145;
// Set cursor tracking as active by default
let cursorTrackingActive = true;
// Add a flag to control UI interactivity
let uiActive = false;
// Sample data for quicker testing - no longer needed with demo mode
const sampleData = {
workoutTime: 300000, // 5 minutes in milliseconds
steps: 500,
heartRate: 125,
calories: 75
};
// Goal settings
const goalSettings = {
distance: {
min: 1,
max: 10,
unit: 'km',
step: 0.5,
format: (val) => val.toFixed(1)
},
calories: {
min: 100,
max: 1000,
unit: 'kcal',
step: 50,
format: (val) => val.toString()
},
duration: {
min: 10,
max: 120,
unit: 'min',
step: 5,
format: (val) => val.toString()
}
};
let currentGoalType = 'distance';
// Smart goal value arrays
const distanceSteps = [0.5, 1, 2, 5, 10, 20, 30, 50, 100];
const calorieSteps = [50, 100, 200, 300, 500, 750, 1000, 1500, 2000];
const durationSteps = [5, 10, 15, 20, 30, 45, 60, 90, 120];
// Helper to get current index in array
function getCurrentGoalIndex(goalType, value) {
let arr = [];
if (goalType === 'distance') arr = distanceSteps;
else if (goalType === 'calories') arr = calorieSteps;
else if (goalType === 'duration') arr = durationSteps;
// Find closest value (in case of floating point imprecision)
let idx = arr.findIndex(v => Math.abs(v - value) < 0.01);
if (idx === -1) {
// If not found, clamp to nearest
let minDiff = Infinity, minIdx = 0;
arr.forEach((v, i) => { if (Math.abs(v - value) < minDiff) { minDiff = Math.abs(v - value); minIdx = i; } });
idx = minIdx;
}
return idx;
}
// Helper to get smart step array for current goal type
function getCurrentStepArray() {
if (currentGoalType === 'distance') return distanceSteps;
if (currentGoalType === 'calories') return calorieSteps;
if (currentGoalType === 'duration') return durationSteps;
return [];
}
// Store the current index in the smart step array
let currentStepIndex = 0;
// Set default value to first smart step for current goal type
function setDefaultGoalValue() {
const arr = getCurrentStepArray();
currentStepIndex = 0;
updateSlider();
}
// On load, set default goal value
setDefaultGoalValue();
// Initialize intro screens container
function initIntroScreens() {
// Show only the project info screen initially
projectInfo.style.display = 'flex';
instructions.style.display = 'none'; // Hide instructions initially
// Reset any transform
introScreensContainer.style.transform = '';
introScreensContainer.style.opacity = '1';
// Reset container position
introScreensContainer.style.position = 'fixed';
introScreensContainer.style.top = '0';
introScreensContainer.style.left = '0';
introScreensContainer.style.width = '100%';
introScreensContainer.style.height = '100%';
console.log("Initialized intro screens - project info visible, instructions hidden");
}
// Call initialization
initIntroScreens();
// Continue button click event - completely redesigned for reliability
continueBtn.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
// Create a ripple effect for feedback
createRippleEffect('forward', true);
// Simply hide project info and show instructions
// Skip the sliding animation since it's causing issues
projectInfo.style.display = 'none';
instructions.style.display = 'flex';
console.log("Continue button clicked - showing instructions screen");
// Show voice feedback to confirm transition
showVoiceFeedback('Learn how to use the interface');
});
// Start button click event - updated to hide entire intro container
startBtn.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
// Create ripple effect
createRippleEffect('forward', true);
// Hide the entire intro screens container with a fade out
introScreensContainer.style.opacity = '0';
setTimeout(() => {
introScreensContainer.style.display = 'none';
showScreen(0); // Show workout type selection
showVoiceFeedback('Select a workout type');
preloadAssets();
uiActive = true; // Enable UI interactivity
updateBackButton();
}, 500);
});
// Keyboard controls
document.addEventListener('keydown', (event) => {
if (!uiActive) return;
switch (event.key) {
case 'ArrowLeft':
handleTilt('left');
break;
case 'ArrowRight':
handleTilt('right');
break;
case 'Enter':
handleTilt('forward');
break;
case 'p':
case 'P':
if (workoutActive) pauseWorkout();
break;
case 'r':
case 'R':
if (workoutActive && workoutPaused) resumeWorkout();
break;
case 'e':
case 'E':
if (workoutActive) {
endWorkout();
showScreen(4); // Show completion screen
}
break;
}
});
// On-screen controls
leftBtn.addEventListener('click', () => handleTilt('left'));
rightBtn.addEventListener('click', () => handleTilt('right'));
selectBtn.addEventListener('click', () => handleTilt('forward'));
// Mouse control for shoe movement
floorProjection.addEventListener('mousemove', (e) => {
if (!uiActive || !mouseTiltActive) return;
const rect = floorProjection.getBoundingClientRect();
const x = e.clientX - rect.left;
const width = rect.width;
const centerThreshold = width * 0.1; // 10% of width for center area
// Get projection area dimensions to limit beam width
const projectionAreaRect = projectionArea.getBoundingClientRect();
const projectionAreaWidth = projectionAreaRect.width;
// Clear any pending timeouts
if (mouseControlTimeout) {
clearTimeout(mouseControlTimeout);
}
if (x < (width / 2) - centerThreshold) {
// Left side of screen
if (lastMouseTilt !== 'left') {
shoeImage.classList.remove('tilt-right', 'tilt-forward');
shoeImage.classList.add('tilt-left');
// Synchronize projection area tilt
projectionArea.classList.remove('tilt-right');
projectionArea.classList.add('tilt-left');
// Synchronize projection beam tilt
projectionBeam.classList.remove('tilt-right');
projectionBeam.classList.add('tilt-left');
lastMouseTilt = 'left';
// Auto-reset after a delay
mouseControlTimeout = setTimeout(() => {
shoeImage.classList.remove('tilt-left');
projectionArea.classList.remove('tilt-left');
projectionBeam.classList.remove('tilt-left');
lastMouseTilt = null;
}, 1000);
}
} else if (x > (width / 2) + centerThreshold) {
// Right side of screen
if (lastMouseTilt !== 'right') {
shoeImage.classList.remove('tilt-left', 'tilt-forward');
shoeImage.classList.add('tilt-right');
// Synchronize projection area tilt
projectionArea.classList.remove('tilt-left');
projectionArea.classList.add('tilt-right');
// Synchronize projection beam tilt
projectionBeam.classList.remove('tilt-left');
projectionBeam.classList.add('tilt-right');
lastMouseTilt = 'right';
// Auto-reset after a delay
mouseControlTimeout = setTimeout(() => {
shoeImage.classList.remove('tilt-right');
projectionArea.classList.remove('tilt-right');
projectionBeam.classList.remove('tilt-right');
lastMouseTilt = null;
}, 1000);
}
} else {
// Center area
if (lastMouseTilt !== null) {
shoeImage.classList.remove('tilt-left', 'tilt-right', 'tilt-forward');
projectionArea.classList.remove('tilt-left', 'tilt-right');
projectionBeam.classList.remove('tilt-left', 'tilt-right');
lastMouseTilt = null;
}
}
});
// Mouse click to perform selected tilt action
floorProjection.addEventListener('click', (e) => {
if (!uiActive || !mouseTiltActive) return;
// Check if the click originated from value buttons
const clickedValueButton = e.target.closest('.value-btn');
if (clickedValueButton) {
return; // Don't process tilt actions for value button clicks
}
if (lastMouseTilt === 'left') {
handleTilt('left');
} else if (lastMouseTilt === 'right') {
handleTilt('right');
} else {
handleTilt('forward');
}
});
// Toggle mouse control
document.addEventListener('keydown', (e) => {
if (e.key === 'm' || e.key === 'M') {
mouseTiltActive = !mouseTiltActive;
showVoiceFeedback(mouseTiltActive ? 'Mouse control activated' : 'Mouse control deactivated');
}
});
// Cursor position tracking for shoe tilt
let cursorTimeout = null;
// Enhanced Interactive Elements Registry - refreshed on screen changes
const refreshInteractiveElements = () => {
console.log("Refreshing interactive elements");
return {
workoutOptions: document.querySelectorAll('.workout-option'),
goalTabs: document.querySelectorAll('.goal-tab'),
valueControls: document.querySelectorAll('.value-btn'),
sliderHandle: document.querySelector('.slider-handle'),
moodOptions: document.querySelectorAll('.mood-option'),
syncButton: document.querySelector('.sync-button'),
startWorkoutButtons: document.querySelectorAll('.start-workout-btn'),
startButton: document.querySelector('.start-button')
};
};
// Initialize interactive elements
let interactiveElements = refreshInteractiveElements();
// Current hovered element
let currentHoveredElement = null;
// Debug feedback - show what element is being hovered
const showDebugFeedback = (message) => {
console.log(message);
};
// Track interactive elements for hover effects with improved detection
document.addEventListener('mousemove', (e) => {
if (!uiActive || !cursorTrackingActive) return;
// Skip mousemove tilt handling if back button is being hovered
if (isBackButtonHovered) return;
// Calculate screen sections with a narrower center region
const screenWidth = window.innerWidth;
const centerWidth = screenWidth * 0.10; // 10% of screen width for center
const centerLeft = (screenWidth - centerWidth) / 2;
const centerRight = centerLeft + centerWidth;
// Get cursor position
const xPosition = e.clientX;
// Get shoe tip light element and projection beam
const shoeTipLight = document.getElementById('shoeTipLight');
const projectionBeam = document.getElementById('projectionBeam');
// Get projection area dimensions to limit beam width
const projectionAreaRect = projectionArea.getBoundingClientRect();
const projectionAreaWidth = projectionAreaRect.width;
// Remove any existing tilt classes
shoeImage.classList.remove('tilt-left', 'tilt-right', 'tilt-forward', 'tilt-left-tap', 'tilt-right-tap');
projectionArea.classList.remove('tilt-left', 'tilt-right');
projectionBeam.classList.remove('tilt-left', 'tilt-right');
shoeTipLight.classList.remove('tilt-left', 'tilt-right');
// Calculate position for the shoe tip light based on cursor position
if (xPosition < centerLeft) {
// Left zone - tilt left
shoeImage.classList.add('tilt-left');
projectionArea.classList.add('tilt-left');
projectionBeam.classList.add('tilt-left');
shoeTipLight.classList.add('tilt-left');
// Calculate more precise positioning for the light based on cursor position
const leftPercent = Math.max(0, Math.min(1, (centerLeft - xPosition) / centerLeft));
const leftOffset = 70 + (leftPercent * 10); // 70px base offset + up to 10px more based on cursor
// Apply calculated position to light
shoeTipLight.style.left = `calc(50% - ${leftOffset}px)`;
shoeTipLight.style.bottom = `${235 - (leftPercent * 5)}px`;
// Position the projection beam to match the light's position EXACTLY
projectionBeam.style.left = shoeTipLight.style.left;
projectionBeam.style.bottom = shoeTipLight.style.bottom;
lastMouseTilt = 'left';
} else if (xPosition > centerRight) {
// Right zone - tilt right
shoeImage.classList.add('tilt-right');
projectionArea.classList.add('tilt-right');
projectionBeam.classList.add('tilt-right');
shoeTipLight.classList.add('tilt-right');
// Calculate more precise positioning for the light based on cursor position
const rightEdge = screenWidth;
const rightPercent = Math.max(0, Math.min(1, (xPosition - centerRight) / (rightEdge - centerRight)));
const rightOffset = 70 + (rightPercent * 10); // 70px base offset + up to 10px more based on cursor
// Apply calculated position to light
shoeTipLight.style.left = `calc(50% + ${rightOffset}px)`;
shoeTipLight.style.bottom = `${235 - (rightPercent * 5)}px`;
// Position the projection beam to match the light's position EXACTLY
projectionBeam.style.left = shoeTipLight.style.left;
projectionBeam.style.bottom = shoeTipLight.style.bottom;
lastMouseTilt = 'right';
} else {
// Center zone - no tilt
lastMouseTilt = null;
// Reset shoe tip light position for center position
shoeTipLight.style.left = '50%';
shoeTipLight.style.bottom = '242px'; // Exact match with CSS value
shoeTipLight.style.transform = 'translateX(-50%)';
// Reset projection beam position to match light
projectionBeam.style.left = '50%';
projectionBeam.style.bottom = '242px'; // Exact match with CSS value
projectionBeam.style.transform = 'translateX(-50%)';
}
// Check if cursor is over any interactive elements and apply hover effect
checkElementHover(e);
});
// Check if mouse is over any interactive elements and apply hover effect
function checkElementHover(e) {
// Remove previous hover effects
if (currentHoveredElement) {
currentHoveredElement.classList.remove('hover-effect');
currentHoveredElement = null;
}
// Helper function to check if element is under cursor
function isElementUnderCursor(element) {
if (!element) return false;
const rect = element.getBoundingClientRect();
return (
e.clientX >= rect.left &&
e.clientX <= rect.right &&
e.clientY >= rect.top &&
e.clientY <= rect.bottom
);
}
// Check all interactive elements
// Check goal tabs
const goalTabs = document.querySelectorAll('.goal-tab');
if (goalTabs && goalTabs.length) {
for (const tab of goalTabs) {
if (isElementUnderCursor(tab)) {
tab.classList.add('hover-effect');
currentHoveredElement = tab;
// Position-based tilt will be applied by the mousemove handler
return;
}
}
}
// Check workout options
const workoutOptions = document.querySelectorAll('.workout-option');
if (workoutOptions && workoutOptions.length) {
for (const option of workoutOptions) {
if (isElementUnderCursor(option)) {
option.classList.add('hover-effect');
currentHoveredElement = option;
// Position-based tilt will be applied by the mousemove handler
return;
}
}
}
// Check value buttons
const valueControls = document.querySelectorAll('.value-btn');
if (valueControls && valueControls.length) {
for (const btn of valueControls) {
if (isElementUnderCursor(btn)) {
btn.classList.add('hover-effect');
currentHoveredElement = btn;
// Position-based tilt will be applied by the mousemove handler
return;
}
}
}
// Check mood options
const moodOptions = document.querySelectorAll('.mood-option');
if (moodOptions && moodOptions.length) {
for (const option of moodOptions) {
if (isElementUnderCursor(option)) {
option.classList.add('hover-effect');
currentHoveredElement = option;
// Position-based tilt will be applied by the mousemove handler
return;
}
}
}
// Check other specific UI elements (buttons, etc.)
const otherElements = [
document.querySelector('.slider-handle'),
document.querySelector('.sync-button'),
...document.querySelectorAll('.start-workout-btn'),
document.querySelector('.start-button')
];
for (const element of otherElements) {
if (element && isElementUnderCursor(element)) {
element.classList.add('hover-effect');
currentHoveredElement = element;
// Position-based tilt will be applied by the mousemove handler
return;
}
}
}
// Track the last screen index to detect changes
let lastScreenIndex = currentScreenIndex;
// Function to show a specific screen - with interactive elements refresh
function showScreen(index) {
console.log(`showScreen called with index: ${index}`);
// Verify the index is valid
if (index < 0 || index >= screens.length) {
console.error(`Invalid screen index: ${index}, max: ${screens.length - 1}`);
return;
}
// Get the screen element for logging
const targetScreen = screens[index];
console.log(`Showing screen: ${targetScreen.id}`);
screens.forEach((screen, i) => {
const wasActive = screen.classList.contains('active');
screen.classList.toggle('active', i === index);
const isActive = screen.classList.contains('active');
console.log(`Screen ${i} (${screen.id}): was ${wasActive ? 'active' : 'inactive'}, now ${isActive ? 'active' : 'inactive'}`);
});
currentScreenIndex = index;
// Refresh interactive elements when screen changes
interactiveElements = refreshInteractiveElements();
lastScreenIndex = index;
// Create a small floor ripple to show the transition
createRippleEffect('forward');
// Special screen initialization
if (index === 3) { // Workout screen
if (!workoutActive) {
startWorkout();
// Show demo mode indicator
const indicator = document.querySelector('#workout-screen .indicator-text');
if (indicator) {
indicator.innerHTML = `
Tilt <span class="forward-indicator">forward ▲</span> to pause workout |
Press <span class="key" style="font-size: 0.8rem; padding: 0.1rem 0.3rem;">E</span> to end workout |
Press <span class="key" style="font-size: 0.8rem; padding: 0.1rem 0.3rem;">D</span> to show demo info
`;
}
// Add demo mode info key
document.addEventListener('keydown', (e) => {
if ((e.key === 'd' || e.key === 'D') && workoutActive) {
showVoiceFeedback(`Demo mode active: workout will complete in ${demoCompletionTime/1000} seconds`);
}
});
}
} else if (index === 4) { // Complete screen
if (workoutActive) {
endWorkout();
celebrateCompletion();
}
}
updateBackButton();
}
// Handle clicks on interactive elements - simplified for reliability
document.addEventListener('click', (e) => {
if (!uiActive) return;
console.log("Click detected on:", e.target);
// Find the clicked element or its closest interactive parent
let target = e.target;
let isInteractive = false;
// Check if this is an interactive element or a child of one
// Define element types for clarity
const WORKOUT_OPTION = 'workout-option';
const GOAL_TAB = 'goal-tab';
const VALUE_BTN = 'value-btn';
const SLIDER_HANDLE = 'slider-handle';
const MOOD_OPTION = 'mood-option';
const SYNC_BUTTON = 'sync-button';
const START_WORKOUT_BTN = 'start-workout-btn';
const START_BUTTON = 'start-button';
// Debugging - log current screen
console.log("Current screen index:", currentScreenIndex);
// Find the exact interactive element that was clicked
const workoutOption = target.closest('.' + WORKOUT_OPTION);
const goalTab = target.closest('.' + GOAL_TAB);
const valueBtn = target.closest('.' + VALUE_BTN);
const sliderHandle = target.closest('.' + SLIDER_HANDLE);
const moodOption = target.closest('.' + MOOD_OPTION);
const syncButtonEl = target.closest('.' + SYNC_BUTTON);
const startWorkoutBtn = target.closest('.' + START_WORKOUT_BTN);
const startButton = target.closest('.' + START_BUTTON);
// Determine which element was clicked (if any)
if (workoutOption) {
console.log("Workout option clicked");
// Update selected workout option
selectedWorkoutOption = Array.from(document.querySelectorAll('.workout-option')).indexOf(workoutOption);
updateSelectedWorkoutOption();
showVoiceFeedback(getWorkoutTypeName(selectedWorkoutOption));
// If on the workout selection screen, proceed to goal selection
if (currentScreenIndex === 0) {
setTimeout(() => {
showScreen(1);
showVoiceFeedback(`Select your ${currentGoalType} goal`);
}, 100); // Small delay for animation
}
e.stopPropagation();
return;
}
else if (goalTab) {
console.log("Goal tab clicked");
// Update selected goal tab - ONLY SELECT, DON'T PROCEED
selectedGoalTab = Array.from(document.querySelectorAll('.' + GOAL_TAB)).indexOf(goalTab);
updateSelectedGoalTab();
// Prevent any other actions
e.stopPropagation();
return;
}
else if (valueBtn) {
console.log("Value button clicked");
// Handle value increase/decrease
if (valueBtn.classList.contains('decrease')) {
decreaseSliderValue();
} else if (valueBtn.classList.contains('increase')) {
increaseSliderValue();
}
// Create beam effect
createShoeBeamEffect();
// Reset lastMouseTilt to prevent unintended navigation
lastMouseTilt = null;
// Prevent any other actions
e.stopPropagation();
e.preventDefault();
return;
}
else if (sliderHandle) {
console.log("Slider handle clicked");
// Focus slider handle
sliderHandle.focus();
showVoiceFeedback('Adjust goal value with left and right tilts');
// Prevent any other actions
e.stopPropagation();
return;
}
else if (moodOption) {
console.log("Mood option clicked");
// Update selected mood
const moodOptions = document.querySelectorAll('.' + MOOD_OPTION);
moodOptions.forEach(option => option.classList.remove('selected'));
moodOption.classList.add('selected');
showVoiceFeedback(`Mood: ${moodOption.getAttribute('data-mood')}`);
// Prevent any other actions
e.stopPropagation();
return;
}
else if (target.closest('.sync-button')) {
console.log("Sync button detected in click handler - handled by dedicated listener");
e.stopPropagation();
return;
}
else if (startWorkoutBtn) {
console.log("Start workout button clicked");
// This is the ONLY element that should trigger workout start or screen changes
handleTilt('forward'); // Apply animation
// Small delay to allow animation to be visible
setTimeout(() => {
if (currentScreenIndex === 0) {
// From workout type selection, go to goal selection
console.log("Moving to goal selection screen");
showScreen(1);
showVoiceFeedback(`Select your ${currentGoalType} goal`);
} else if (currentScreenIndex === 1) {
// From goal selection, go to countdown
console.log("Moving to countdown screen");
showScreen(2);
startCountdown();
}
}, 100);
// Prevent any other actions
e.stopPropagation();
return;
}
else if (startButton) {
console.log("Start button clicked (welcome screen)");
// Welcome screen start button
handleTilt('forward'); // Apply animation
// Small delay to allow animation to be visible
setTimeout(() => {
// Hide the entire intro screens container properly
introScreensContainer.style.opacity = '0';
setTimeout(() => {
introScreensContainer.style.display = 'none';
showScreen(0); // Show workout type selection screen
showVoiceFeedback('Select a workout type');
// Preload assets for smooth UX
preloadAssets();
uiActive = true; // Enable UI interactivity
updateBackButton();
}, 500);
}, 100);
// Prevent any other actions
e.stopPropagation();
return;
}
// If we got here, no interactive element was clicked
console.log("No interactive element clicked");
});
// Add value button event listeners
const increaseValueBtn = document.getElementById('increaseValue');
const decreaseValueBtn = document.getElementById('decreaseValue');
if (increaseValueBtn) {
increaseValueBtn.addEventListener('mouseover', () => {
// Clear existing tilt classes
shoeImage.classList.remove('tilt-left', 'tilt-forward');
// Apply right tilt
shoeImage.classList.add('tilt-right');
lastMouseTilt = 'right';
});
increaseValueBtn.addEventListener('click', (e) => {
e.stopPropagation(); // Prevent event bubbling to the floorProjection click handler
e.preventDefault(); // Prevent default behavior
// First remove animation classes to ensure new animation plays
shoeImage.classList.remove('tilt-right-tap', 'tilt-left-tap');
// Force a reflow to ensure the browser recognizes the removal
void shoeImage.offsetWidth;
// Apply right tap animation
shoeImage.classList.add('tilt-right-tap');
// Value change and effects
increaseSliderValue();
createShoeBeamEffect();
// Create ripple effect directly under the shoe
createRippleEffect('right', true);
// Reset lastMouseTilt to prevent unintended navigation
setTimeout(() => {
lastMouseTilt = null;
}, 10);
});
}
if (decreaseValueBtn) {
decreaseValueBtn.addEventListener('mouseover', () => {
// Clear existing tilt classes
shoeImage.classList.remove('tilt-right', 'tilt-forward');
// Apply left tilt
shoeImage.classList.add('tilt-left');
lastMouseTilt = 'left';
});
decreaseValueBtn.addEventListener('click', (e) => {
e.stopPropagation(); // Prevent event bubbling to the floorProjection click handler
e.preventDefault(); // Prevent default behavior
// First remove animation classes to ensure new animation plays
shoeImage.classList.remove('tilt-right-tap', 'tilt-left-tap');
// Force a reflow to ensure the browser recognizes the removal
void shoeImage.offsetWidth;
// Apply left tap animation
shoeImage.classList.add('tilt-left-tap');
// Value change and effects
decreaseSliderValue();
createShoeBeamEffect();
// Create ripple effect directly under the shoe
createRippleEffect('left', true);
// Reset lastMouseTilt to prevent unintended navigation
setTimeout(() => {
lastMouseTilt = null;
}, 10);
});
}
// Toggle cursor tracking with 'T' key
document.addEventListener('keydown', (e) => {
if (e.key === 't' || e.key === 'T') {
cursorTrackingActive = !cursorTrackingActive;
showVoiceFeedback(cursorTrackingActive ? 'Top cursor tracking activated' : 'Top cursor tracking deactivated');
// Reset shoe position when disabling
if (!cursorTrackingActive) {
shoeImage.classList.remove('tilt-left', 'tilt-right');
lastMouseTilt = null;
}
}
});
// Load sample data button - added to the workout screen
const loadSampleData = () => {
// This function is no longer needed with the new demo mode
if (!workoutActive) return;
showVoiceFeedback('Using demo mode: workout will complete in 20 seconds');
};
// Handle foot tilt (left, right, forward)
function handleTilt(direction) {
// Store current tilt state before removing classes
const currentlyTiltedLeft = shoeImage.classList.contains('tilt-left');
const currentlyTiltedRight = shoeImage.classList.contains('tilt-right');
// Ensure previous states are reset
shoeImage.classList.remove('tilt-left', 'tilt-right', 'tilt-forward', 'tilt-left-tap', 'tilt-right-tap', 'tilt-back-btn');
// Get projection beam and shoe tip light elements
const projectionBeam = document.getElementById('projectionBeam');
const shoeTipLight = document.getElementById('shoeTipLight');
// Remove tilt classes from projection components
projectionBeam.classList.remove('tilt-left', 'tilt-right');
shoeTipLight.classList.remove('tilt-left', 'tilt-right');
projectionArea.classList.remove('tilt-left', 'tilt-right');
// Reset inline styles from mousemove handler
shoeTipLight.style.left = '';
shoeTipLight.style.bottom = '';
shoeTipLight.style.transform = '';
// Reset projection beam styles
projectionBeam.style.width = '';
projectionBeam.style.left = '';
projectionBeam.style.bottom = '';
projectionBeam.style.height = '';
projectionBeam.style.transform = '';
projectionBeam.style.clipPath = '';
// Force browser to recognize the removal before adding
setTimeout(() => {
// Create ripple effect on floor (projection simulation) - position under shoe
createRippleEffect(direction, true);
// Show tilt indicator
tiltIndicator.style.opacity = '1';
// Adjust tilt indicator direction
if (direction === 'left') {
tiltArrow.style.transform = 'rotate(-90deg)';
} else if (direction === 'right') {
tiltArrow.style.transform = 'rotate(90deg)';
} else { // forward
tiltArrow.style.transform = 'rotate(0)';
}
// Simulate haptic feedback
simulateHapticFeedback(direction);
// Create beam effect for directional tilts
if (direction === 'forward') {
createShoeBeamEffect();
}
// Apply the appropriate animation class based on direction
if (direction === 'forward') {
shoeImage.classList.add('tilt-forward');
// Position shoe tip light for forward tilt
shoeTipLight.style.transform = 'translateX(-50%) scale(1.2)';
shoeTipLight.style.filter = 'blur(7px)';
shoeTipLight.style.boxShadow = '0 0 35px 12px rgba(0, 168, 107, 0.9)';
// Position projection beam for forward tilt - narrower triangle pointing up
projectionBeam.style.height = '430px';
projectionBeam.style.transform = 'translateX(-50%) scaleY(1.05)';
projectionBeam.style.opacity = '1';
projectionBeam.style.clipPath = 'polygon(50% 100%, 20% 0%, 80% 0%)';
// Delay the screen action to match the visual effect for tap animation
setTimeout(() => {
processScreenAction(direction);
}, 250);
// For arrow key navigation, reset the tilt after a delay
setTimeout(() => {
shoeImage.classList.remove('tilt-forward');