@hustle-together/api-dev-tools 2.0.5 → 2.0.6
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.
- package/demo/workflow-demo.html +279 -254
- package/package.json +1 -1
package/demo/workflow-demo.html
CHANGED
|
@@ -506,6 +506,19 @@
|
|
|
506
506
|
border-color: var(--accent-red);
|
|
507
507
|
}
|
|
508
508
|
|
|
509
|
+
.nav-btn.disabled {
|
|
510
|
+
opacity: 0.5;
|
|
511
|
+
cursor: not-allowed;
|
|
512
|
+
text-decoration: line-through;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.nav-btn.disabled:hover {
|
|
516
|
+
background: transparent;
|
|
517
|
+
color: var(--grey);
|
|
518
|
+
border-color: var(--grey);
|
|
519
|
+
box-shadow: none;
|
|
520
|
+
}
|
|
521
|
+
|
|
509
522
|
/* ============================================
|
|
510
523
|
AUDIO NARRATION PLAYER
|
|
511
524
|
============================================ */
|
|
@@ -1665,9 +1678,9 @@
|
|
|
1665
1678
|
|
|
1666
1679
|
<!-- Navigation -->
|
|
1667
1680
|
<nav class="nav">
|
|
1668
|
-
<button class="nav-btn" id="
|
|
1669
|
-
<button class="nav-btn" id="
|
|
1670
|
-
<button class="nav-btn" id="resetBtn"
|
|
1681
|
+
<button class="nav-btn" id="playBtn">▶ AUTO PLAY</button>
|
|
1682
|
+
<button class="nav-btn" id="audioToggleBtn" title="Toggle narration audio">🔊 WITH AUDIO</button>
|
|
1683
|
+
<button class="nav-btn" id="resetBtn">↺ RESTART</button>
|
|
1671
1684
|
</nav>
|
|
1672
1685
|
|
|
1673
1686
|
<!-- Audio Narration Player (hidden) -->
|
|
@@ -1700,7 +1713,7 @@
|
|
|
1700
1713
|
<span class="hustle-word hustle-highlight">API-DEV-TOOLS</span>
|
|
1701
1714
|
</div>
|
|
1702
1715
|
<div class="package-name" id="packageName">@hustle-together/api-dev-tools</div>
|
|
1703
|
-
<div class="version" id="versionText">v2.0.
|
|
1716
|
+
<div class="version" id="versionText">v2.0.6</div>
|
|
1704
1717
|
<p class="tagline">"Hustle together. Share resources. Build stronger."<span class="cursor"></span></p>
|
|
1705
1718
|
|
|
1706
1719
|
<div class="intro-text">
|
|
@@ -2758,32 +2771,272 @@
|
|
|
2758
2771
|
.to('.made-with', { opacity: 1, duration: 0.5 });
|
|
2759
2772
|
|
|
2760
2773
|
// ============================================
|
|
2761
|
-
// AUTO-PLAY
|
|
2774
|
+
// UNIFIED AUTO-PLAY + AUDIO SYSTEM
|
|
2762
2775
|
// ============================================
|
|
2763
|
-
document.getElementById('playBtn')
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2776
|
+
const playBtn = document.getElementById('playBtn');
|
|
2777
|
+
const audioToggleBtn = document.getElementById('audioToggleBtn');
|
|
2778
|
+
const narrationAudio = document.getElementById('narrationAudio');
|
|
2779
|
+
const audioProgressContainer = document.getElementById('audioProgressContainer');
|
|
2780
|
+
const audioProgressBar = document.getElementById('audioProgressBar');
|
|
2781
|
+
const audioProgressFill = document.getElementById('audioProgressFill');
|
|
2782
|
+
const audioCurrentTime = document.getElementById('audioCurrentTime');
|
|
2783
|
+
const audioTotalTime = document.getElementById('audioTotalTime');
|
|
2784
|
+
const audioSectionMarkers = document.getElementById('audioSectionMarkers');
|
|
2785
|
+
|
|
2786
|
+
let audioEnabled = true; // Audio on by default
|
|
2787
|
+
let timingData = null;
|
|
2788
|
+
let currentHighlight = null;
|
|
2789
|
+
let highlightIndex = 0;
|
|
2790
|
+
let currentSectionIdx = 0;
|
|
2791
|
+
let autoPlayInterval = null;
|
|
2792
|
+
|
|
2793
|
+
// Format seconds to MM:SS
|
|
2794
|
+
function formatTime(seconds) {
|
|
2795
|
+
const mins = Math.floor(seconds / 60);
|
|
2796
|
+
const secs = Math.floor(seconds % 60);
|
|
2797
|
+
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
// Load timing data
|
|
2801
|
+
async function loadTimingData() {
|
|
2802
|
+
if (timingData) return timingData;
|
|
2803
|
+
try {
|
|
2804
|
+
const response = await fetch('audio/narration-timing.json');
|
|
2805
|
+
if (!response.ok) throw new Error('Failed to load timing data');
|
|
2806
|
+
timingData = await response.json();
|
|
2807
|
+
|
|
2808
|
+
// Update total time display
|
|
2809
|
+
if (audioTotalTime) {
|
|
2810
|
+
audioTotalTime.textContent = formatTime(timingData.duration);
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
// Create section markers on progress bar
|
|
2814
|
+
if (audioSectionMarkers && timingData.sections) {
|
|
2815
|
+
audioSectionMarkers.innerHTML = '';
|
|
2816
|
+
timingData.sections.forEach((section, i) => {
|
|
2817
|
+
if (i === 0) return;
|
|
2818
|
+
const marker = document.createElement('div');
|
|
2819
|
+
marker.className = 'audio-section-marker';
|
|
2820
|
+
marker.style.left = `${(section.timestamp / timingData.duration) * 100}%`;
|
|
2821
|
+
marker.title = section.id.toUpperCase();
|
|
2822
|
+
audioSectionMarkers.appendChild(marker);
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
console.log('Timing data loaded:', timingData.sections.length, 'sections,', timingData.highlights.length, 'highlights');
|
|
2827
|
+
return timingData;
|
|
2828
|
+
} catch (error) {
|
|
2829
|
+
console.error('Failed to load timing data:', error);
|
|
2830
|
+
return null;
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
// Apply highlight to an element
|
|
2835
|
+
function applyHighlight(selector) {
|
|
2836
|
+
removeHighlight();
|
|
2837
|
+
try {
|
|
2838
|
+
const el = document.querySelector(selector);
|
|
2839
|
+
if (el) {
|
|
2840
|
+
currentHighlight = el;
|
|
2841
|
+
el.classList.add('audio-highlighted');
|
|
2842
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
2843
|
+
gsap.fromTo(el,
|
|
2844
|
+
{ boxShadow: '0 0 0px var(--accent-red-glow)' },
|
|
2845
|
+
{ boxShadow: '0 0 40px var(--accent-red-glow), 0 0 80px var(--accent-red-glow)', duration: 0.4, ease: 'power2.out' }
|
|
2846
|
+
);
|
|
2847
|
+
}
|
|
2848
|
+
} catch (e) {
|
|
2849
|
+
console.warn('Could not highlight:', selector, e);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
// Remove current highlight
|
|
2854
|
+
function removeHighlight() {
|
|
2855
|
+
if (currentHighlight) {
|
|
2856
|
+
currentHighlight.classList.remove('audio-highlighted');
|
|
2857
|
+
gsap.to(currentHighlight, { boxShadow: 'none', duration: 0.3 });
|
|
2858
|
+
currentHighlight = null;
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
// Handle audio time update (syncs highlights with narration)
|
|
2863
|
+
function onAudioTimeUpdate() {
|
|
2864
|
+
if (!timingData || !audioEnabled) return;
|
|
2865
|
+
|
|
2866
|
+
const currentTime = narrationAudio.currentTime;
|
|
2867
|
+
|
|
2868
|
+
// Update progress bar
|
|
2869
|
+
const progress = (currentTime / timingData.duration) * 100;
|
|
2870
|
+
audioProgressFill.style.width = `${progress}%`;
|
|
2871
|
+
|
|
2872
|
+
// Update time display
|
|
2873
|
+
if (audioCurrentTime) {
|
|
2874
|
+
audioCurrentTime.textContent = formatTime(currentTime);
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
// Check for highlight triggers
|
|
2878
|
+
for (let i = highlightIndex; i < timingData.highlights.length; i++) {
|
|
2879
|
+
const highlight = timingData.highlights[i];
|
|
2880
|
+
if (currentTime >= highlight.timestamp) {
|
|
2881
|
+
if (i !== highlightIndex || !currentHighlight) {
|
|
2882
|
+
highlightIndex = i;
|
|
2883
|
+
applyHighlight(highlight.selector);
|
|
2884
|
+
}
|
|
2885
|
+
} else {
|
|
2886
|
+
break;
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
// Handle seek on progress bar click
|
|
2892
|
+
function onProgressBarClick(e) {
|
|
2893
|
+
if (!timingData || !narrationAudio.duration) return;
|
|
2894
|
+
|
|
2895
|
+
const rect = audioProgressBar.getBoundingClientRect();
|
|
2896
|
+
const clickX = e.clientX - rect.left;
|
|
2897
|
+
const percentage = clickX / rect.width;
|
|
2898
|
+
const seekTime = percentage * timingData.duration;
|
|
2899
|
+
|
|
2900
|
+
narrationAudio.currentTime = seekTime;
|
|
2901
|
+
|
|
2902
|
+
// Reset highlight tracking
|
|
2903
|
+
highlightIndex = 0;
|
|
2904
|
+
for (let i = 0; i < timingData.highlights.length; i++) {
|
|
2905
|
+
if (timingData.highlights[i].timestamp <= seekTime) {
|
|
2906
|
+
highlightIndex = i;
|
|
2907
|
+
} else {
|
|
2908
|
+
break;
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
// Silent auto-play (no audio, timed sections)
|
|
2914
|
+
function startSilentAutoPlay() {
|
|
2915
|
+
let sectionIdx = 0;
|
|
2916
|
+
autoPlayInterval = setInterval(() => {
|
|
2917
|
+
if (sectionIdx < sections.length) {
|
|
2918
|
+
document.getElementById(sections[sectionIdx]).scrollIntoView({ behavior: 'smooth' });
|
|
2919
|
+
sectionIdx++;
|
|
2920
|
+
} else {
|
|
2921
|
+
stopAutoPlay();
|
|
2922
|
+
}
|
|
2923
|
+
}, 5000);
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
// Start auto-play
|
|
2927
|
+
async function startAutoPlay() {
|
|
2928
|
+
if (isPlaying) return;
|
|
2929
|
+
|
|
2930
|
+
isPlaying = true;
|
|
2931
|
+
playBtn.classList.add('active');
|
|
2932
|
+
playBtn.textContent = '⏸ STOP';
|
|
2933
|
+
|
|
2934
|
+
// Scroll to top first
|
|
2935
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
2936
|
+
|
|
2937
|
+
await loadTimingData();
|
|
2938
|
+
|
|
2939
|
+
if (audioEnabled && timingData) {
|
|
2940
|
+
// Try to play audio
|
|
2941
|
+
audioProgressContainer.style.display = 'block';
|
|
2942
|
+
|
|
2943
|
+
try {
|
|
2944
|
+
await narrationAudio.play();
|
|
2945
|
+
console.log('Audio playing');
|
|
2946
|
+
} catch (err) {
|
|
2947
|
+
console.error('Audio playback failed:', err);
|
|
2948
|
+
// Fall back to silent mode
|
|
2949
|
+
audioEnabled = false;
|
|
2950
|
+
audioToggleBtn.textContent = '🔇 NO AUDIO';
|
|
2951
|
+
audioToggleBtn.classList.add('disabled');
|
|
2952
|
+
audioProgressContainer.style.display = 'none';
|
|
2953
|
+
startSilentAutoPlay();
|
|
2954
|
+
}
|
|
2955
|
+
} else {
|
|
2956
|
+
// Silent auto-play
|
|
2957
|
+
startSilentAutoPlay();
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
// Stop auto-play
|
|
2962
|
+
function stopAutoPlay() {
|
|
2963
|
+
isPlaying = false;
|
|
2964
|
+
playBtn.classList.remove('active');
|
|
2965
|
+
playBtn.textContent = '▶ AUTO PLAY';
|
|
2966
|
+
|
|
2967
|
+
if (autoPlayInterval) {
|
|
2968
|
+
clearInterval(autoPlayInterval);
|
|
2969
|
+
autoPlayInterval = null;
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
narrationAudio.pause();
|
|
2973
|
+
narrationAudio.currentTime = 0;
|
|
2974
|
+
audioProgressContainer.style.display = 'none';
|
|
2975
|
+
removeHighlight();
|
|
2976
|
+
highlightIndex = 0;
|
|
2977
|
+
currentSectionIdx = 0;
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
// Audio ended
|
|
2981
|
+
function onAudioEnded() {
|
|
2982
|
+
stopAutoPlay();
|
|
2983
|
+
setTimeout(() => {
|
|
2984
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
2985
|
+
}, 500);
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
// Toggle audio on/off
|
|
2989
|
+
function toggleAudio() {
|
|
2990
|
+
audioEnabled = !audioEnabled;
|
|
2991
|
+
if (audioEnabled) {
|
|
2992
|
+
audioToggleBtn.textContent = '🔊 WITH AUDIO';
|
|
2993
|
+
audioToggleBtn.classList.remove('disabled');
|
|
2994
|
+
} else {
|
|
2995
|
+
audioToggleBtn.textContent = '🔇 SILENT';
|
|
2996
|
+
audioToggleBtn.classList.add('disabled');
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
// If currently playing, adjust
|
|
3000
|
+
if (isPlaying) {
|
|
3001
|
+
if (audioEnabled) {
|
|
3002
|
+
narrationAudio.play().catch(() => {});
|
|
3003
|
+
audioProgressContainer.style.display = 'block';
|
|
3004
|
+
if (autoPlayInterval) {
|
|
3005
|
+
clearInterval(autoPlayInterval);
|
|
3006
|
+
autoPlayInterval = null;
|
|
2783
3007
|
}
|
|
2784
|
-
}
|
|
3008
|
+
} else {
|
|
3009
|
+
narrationAudio.pause();
|
|
3010
|
+
audioProgressContainer.style.display = 'none';
|
|
3011
|
+
startSilentAutoPlay();
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
// Event listeners
|
|
3017
|
+
playBtn.addEventListener('click', () => {
|
|
3018
|
+
if (isPlaying) {
|
|
3019
|
+
stopAutoPlay();
|
|
3020
|
+
} else {
|
|
3021
|
+
startAutoPlay();
|
|
2785
3022
|
}
|
|
2786
3023
|
});
|
|
3024
|
+
audioToggleBtn.addEventListener('click', toggleAudio);
|
|
3025
|
+
narrationAudio.addEventListener('timeupdate', onAudioTimeUpdate);
|
|
3026
|
+
narrationAudio.addEventListener('ended', onAudioEnded);
|
|
3027
|
+
audioProgressBar.addEventListener('click', onProgressBarClick);
|
|
3028
|
+
|
|
3029
|
+
// Check if audio file is accessible
|
|
3030
|
+
narrationAudio.addEventListener('error', (e) => {
|
|
3031
|
+
console.error('Audio error:', e);
|
|
3032
|
+
audioEnabled = false;
|
|
3033
|
+
audioToggleBtn.textContent = '🔇 NO AUDIO';
|
|
3034
|
+
audioToggleBtn.classList.add('disabled');
|
|
3035
|
+
audioToggleBtn.title = 'Audio file not accessible (run from a web server)';
|
|
3036
|
+
});
|
|
3037
|
+
|
|
3038
|
+
// Preload timing data
|
|
3039
|
+
loadTimingData();
|
|
2787
3040
|
|
|
2788
3041
|
// ============================================
|
|
2789
3042
|
// RESET BUTTON
|
|
@@ -2984,234 +3237,6 @@
|
|
|
2984
3237
|
});
|
|
2985
3238
|
});
|
|
2986
3239
|
|
|
2987
|
-
// ============================================
|
|
2988
|
-
// AUDIO NARRATION CONTROLLER
|
|
2989
|
-
// ============================================
|
|
2990
|
-
const narrateBtn = document.getElementById('narrateBtn');
|
|
2991
|
-
const narrationAudio = document.getElementById('narrationAudio');
|
|
2992
|
-
const audioProgressContainer = document.getElementById('audioProgressContainer');
|
|
2993
|
-
const audioProgressBar = document.getElementById('audioProgressBar');
|
|
2994
|
-
const audioProgressFill = document.getElementById('audioProgressFill');
|
|
2995
|
-
const audioCurrentTime = document.getElementById('audioCurrentTime');
|
|
2996
|
-
const audioTotalTime = document.getElementById('audioTotalTime');
|
|
2997
|
-
const audioSectionMarkers = document.getElementById('audioSectionMarkers');
|
|
2998
|
-
|
|
2999
|
-
let isNarrating = false;
|
|
3000
|
-
let timingData = null;
|
|
3001
|
-
let currentHighlight = null;
|
|
3002
|
-
let highlightIndex = 0;
|
|
3003
|
-
let sectionIndex = 0;
|
|
3004
|
-
|
|
3005
|
-
// Format seconds to MM:SS
|
|
3006
|
-
function formatTime(seconds) {
|
|
3007
|
-
const mins = Math.floor(seconds / 60);
|
|
3008
|
-
const secs = Math.floor(seconds % 60);
|
|
3009
|
-
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
3010
|
-
}
|
|
3011
|
-
|
|
3012
|
-
// Load timing data
|
|
3013
|
-
async function loadTimingData() {
|
|
3014
|
-
if (timingData) return timingData;
|
|
3015
|
-
try {
|
|
3016
|
-
const response = await fetch('audio/narration-timing.json');
|
|
3017
|
-
timingData = await response.json();
|
|
3018
|
-
|
|
3019
|
-
// Update total time display
|
|
3020
|
-
if (audioTotalTime) {
|
|
3021
|
-
audioTotalTime.textContent = formatTime(timingData.duration);
|
|
3022
|
-
}
|
|
3023
|
-
|
|
3024
|
-
// Create section markers on progress bar
|
|
3025
|
-
if (audioSectionMarkers && timingData.sections) {
|
|
3026
|
-
timingData.sections.forEach((section, i) => {
|
|
3027
|
-
if (i === 0) return; // Skip first section (intro at 0)
|
|
3028
|
-
const marker = document.createElement('div');
|
|
3029
|
-
marker.className = 'audio-section-marker';
|
|
3030
|
-
marker.style.left = `${(section.timestamp / timingData.duration) * 100}%`;
|
|
3031
|
-
marker.title = section.id.toUpperCase();
|
|
3032
|
-
audioSectionMarkers.appendChild(marker);
|
|
3033
|
-
});
|
|
3034
|
-
}
|
|
3035
|
-
|
|
3036
|
-
console.log('Timing data loaded:', timingData.sections.length, 'sections,', timingData.highlights.length, 'highlights');
|
|
3037
|
-
return timingData;
|
|
3038
|
-
} catch (error) {
|
|
3039
|
-
console.error('Failed to load timing data:', error);
|
|
3040
|
-
return null;
|
|
3041
|
-
}
|
|
3042
|
-
}
|
|
3043
|
-
|
|
3044
|
-
// Apply highlight to an element
|
|
3045
|
-
function applyHighlight(selector) {
|
|
3046
|
-
// Remove previous highlight
|
|
3047
|
-
removeHighlight();
|
|
3048
|
-
|
|
3049
|
-
try {
|
|
3050
|
-
const el = document.querySelector(selector);
|
|
3051
|
-
if (el) {
|
|
3052
|
-
currentHighlight = el;
|
|
3053
|
-
el.classList.add('audio-highlighted');
|
|
3054
|
-
|
|
3055
|
-
// Scroll element into view
|
|
3056
|
-
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
3057
|
-
|
|
3058
|
-
// GSAP enhancement
|
|
3059
|
-
gsap.fromTo(el,
|
|
3060
|
-
{ boxShadow: '0 0 0px var(--accent-red-glow)' },
|
|
3061
|
-
{
|
|
3062
|
-
boxShadow: '0 0 40px var(--accent-red-glow), 0 0 80px var(--accent-red-glow)',
|
|
3063
|
-
duration: 0.4,
|
|
3064
|
-
ease: 'power2.out'
|
|
3065
|
-
}
|
|
3066
|
-
);
|
|
3067
|
-
}
|
|
3068
|
-
} catch (e) {
|
|
3069
|
-
console.warn('Could not highlight:', selector, e);
|
|
3070
|
-
}
|
|
3071
|
-
}
|
|
3072
|
-
|
|
3073
|
-
// Remove current highlight
|
|
3074
|
-
function removeHighlight() {
|
|
3075
|
-
if (currentHighlight) {
|
|
3076
|
-
currentHighlight.classList.remove('audio-highlighted');
|
|
3077
|
-
gsap.to(currentHighlight, {
|
|
3078
|
-
boxShadow: 'none',
|
|
3079
|
-
duration: 0.3
|
|
3080
|
-
});
|
|
3081
|
-
currentHighlight = null;
|
|
3082
|
-
}
|
|
3083
|
-
}
|
|
3084
|
-
|
|
3085
|
-
// Scroll to section
|
|
3086
|
-
function scrollToSection(sectionId) {
|
|
3087
|
-
const el = document.getElementById(sectionId);
|
|
3088
|
-
if (el) {
|
|
3089
|
-
el.scrollIntoView({ behavior: 'smooth' });
|
|
3090
|
-
}
|
|
3091
|
-
}
|
|
3092
|
-
|
|
3093
|
-
// Handle audio time update
|
|
3094
|
-
function onTimeUpdate() {
|
|
3095
|
-
if (!timingData) return;
|
|
3096
|
-
|
|
3097
|
-
const currentTime = narrationAudio.currentTime;
|
|
3098
|
-
|
|
3099
|
-
// Update progress bar
|
|
3100
|
-
const progress = (currentTime / timingData.duration) * 100;
|
|
3101
|
-
audioProgressFill.style.width = `${progress}%`;
|
|
3102
|
-
|
|
3103
|
-
// Update time display
|
|
3104
|
-
if (audioCurrentTime) {
|
|
3105
|
-
audioCurrentTime.textContent = formatTime(currentTime);
|
|
3106
|
-
}
|
|
3107
|
-
|
|
3108
|
-
// Check for section changes
|
|
3109
|
-
for (let i = timingData.sections.length - 1; i >= 0; i--) {
|
|
3110
|
-
if (currentTime >= timingData.sections[i].timestamp) {
|
|
3111
|
-
if (i !== sectionIndex) {
|
|
3112
|
-
sectionIndex = i;
|
|
3113
|
-
const section = timingData.sections[i];
|
|
3114
|
-
console.log('Section:', section.id);
|
|
3115
|
-
// Optionally scroll to section (only on major section changes)
|
|
3116
|
-
// scrollToSection(section.id);
|
|
3117
|
-
}
|
|
3118
|
-
break;
|
|
3119
|
-
}
|
|
3120
|
-
}
|
|
3121
|
-
|
|
3122
|
-
// Check for highlight triggers
|
|
3123
|
-
for (let i = highlightIndex; i < timingData.highlights.length; i++) {
|
|
3124
|
-
const highlight = timingData.highlights[i];
|
|
3125
|
-
if (currentTime >= highlight.timestamp) {
|
|
3126
|
-
if (i !== highlightIndex || !currentHighlight) {
|
|
3127
|
-
highlightIndex = i;
|
|
3128
|
-
applyHighlight(highlight.selector);
|
|
3129
|
-
}
|
|
3130
|
-
} else {
|
|
3131
|
-
break;
|
|
3132
|
-
}
|
|
3133
|
-
}
|
|
3134
|
-
}
|
|
3135
|
-
|
|
3136
|
-
// Handle seek on progress bar click
|
|
3137
|
-
function onProgressBarClick(e) {
|
|
3138
|
-
if (!timingData || !narrationAudio.duration) return;
|
|
3139
|
-
|
|
3140
|
-
const rect = audioProgressBar.getBoundingClientRect();
|
|
3141
|
-
const clickX = e.clientX - rect.left;
|
|
3142
|
-
const percentage = clickX / rect.width;
|
|
3143
|
-
const seekTime = percentage * timingData.duration;
|
|
3144
|
-
|
|
3145
|
-
narrationAudio.currentTime = seekTime;
|
|
3146
|
-
|
|
3147
|
-
// Reset highlight tracking
|
|
3148
|
-
highlightIndex = 0;
|
|
3149
|
-
for (let i = 0; i < timingData.highlights.length; i++) {
|
|
3150
|
-
if (timingData.highlights[i].timestamp <= seekTime) {
|
|
3151
|
-
highlightIndex = i;
|
|
3152
|
-
} else {
|
|
3153
|
-
break;
|
|
3154
|
-
}
|
|
3155
|
-
}
|
|
3156
|
-
}
|
|
3157
|
-
|
|
3158
|
-
// Start/Stop narration
|
|
3159
|
-
async function toggleNarration() {
|
|
3160
|
-
if (!isNarrating) {
|
|
3161
|
-
// Start narration
|
|
3162
|
-
await loadTimingData();
|
|
3163
|
-
|
|
3164
|
-
narrationAudio.play().then(() => {
|
|
3165
|
-
isNarrating = true;
|
|
3166
|
-
narrateBtn.classList.add('active');
|
|
3167
|
-
narrateBtn.textContent = '⏸ STOP';
|
|
3168
|
-
audioProgressContainer.style.display = 'block';
|
|
3169
|
-
|
|
3170
|
-
// Scroll to top for intro
|
|
3171
|
-
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
3172
|
-
}).catch(err => {
|
|
3173
|
-
console.error('Failed to play audio:', err);
|
|
3174
|
-
alert('Could not play audio. Make sure narration.mp3 exists in the audio folder.');
|
|
3175
|
-
});
|
|
3176
|
-
} else {
|
|
3177
|
-
// Stop narration
|
|
3178
|
-
narrationAudio.pause();
|
|
3179
|
-
narrationAudio.currentTime = 0;
|
|
3180
|
-
isNarrating = false;
|
|
3181
|
-
narrateBtn.classList.remove('active');
|
|
3182
|
-
narrateBtn.textContent = '🔊 NARRATE';
|
|
3183
|
-
audioProgressContainer.style.display = 'none';
|
|
3184
|
-
removeHighlight();
|
|
3185
|
-
highlightIndex = 0;
|
|
3186
|
-
sectionIndex = 0;
|
|
3187
|
-
}
|
|
3188
|
-
}
|
|
3189
|
-
|
|
3190
|
-
// Audio ended
|
|
3191
|
-
function onAudioEnded() {
|
|
3192
|
-
isNarrating = false;
|
|
3193
|
-
narrateBtn.classList.remove('active');
|
|
3194
|
-
narrateBtn.textContent = '🔊 NARRATE';
|
|
3195
|
-
audioProgressContainer.style.display = 'none';
|
|
3196
|
-
removeHighlight();
|
|
3197
|
-
highlightIndex = 0;
|
|
3198
|
-
sectionIndex = 0;
|
|
3199
|
-
|
|
3200
|
-
// Scroll back to top
|
|
3201
|
-
setTimeout(() => {
|
|
3202
|
-
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
3203
|
-
}, 500);
|
|
3204
|
-
}
|
|
3205
|
-
|
|
3206
|
-
// Event listeners
|
|
3207
|
-
narrateBtn.addEventListener('click', toggleNarration);
|
|
3208
|
-
narrationAudio.addEventListener('timeupdate', onTimeUpdate);
|
|
3209
|
-
narrationAudio.addEventListener('ended', onAudioEnded);
|
|
3210
|
-
audioProgressBar.addEventListener('click', onProgressBarClick);
|
|
3211
|
-
|
|
3212
|
-
// Preload timing data on page load
|
|
3213
|
-
loadTimingData();
|
|
3214
|
-
|
|
3215
3240
|
</script>
|
|
3216
3241
|
|
|
3217
3242
|
</body>
|
package/package.json
CHANGED