@hustle-together/api-dev-tools 2.0.3 → 2.0.5

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.
@@ -0,0 +1,48 @@
1
+ {
2
+ "generated": "2025-12-08T00:00:00.000Z",
3
+ "duration": 300,
4
+ "wordCount": 850,
5
+ "sections": [
6
+ { "id": "intro", "timestamp": 0 },
7
+ { "id": "problems", "timestamp": 35 },
8
+ { "id": "solution", "timestamp": 95 },
9
+ { "id": "workflow", "timestamp": 140 },
10
+ { "id": "installation", "timestamp": 220 },
11
+ { "id": "credits", "timestamp": 260 },
12
+ { "id": "outro", "timestamp": 285 }
13
+ ],
14
+ "highlights": [
15
+ { "selector": "#hustleBrand", "section": "intro", "timestamp": 2 },
16
+ { "selector": "[data-phase='research']", "section": "intro", "timestamp": 8 },
17
+ { "selector": "[data-phase='interview']", "section": "intro", "timestamp": 14 },
18
+ { "selector": "[data-phase='test']", "section": "intro", "timestamp": 20 },
19
+ { "selector": "[data-phase='code']", "section": "intro", "timestamp": 26 },
20
+ { "selector": "[data-phase='docs']", "section": "intro", "timestamp": 32 },
21
+ { "selector": "#problems h2", "section": "problems", "timestamp": 36 },
22
+ { "selector": ".gap-item:nth-child(1)", "section": "problems", "timestamp": 42 },
23
+ { "selector": ".gap-item:nth-child(2)", "section": "problems", "timestamp": 54 },
24
+ { "selector": ".gap-item:nth-child(3)", "section": "problems", "timestamp": 66 },
25
+ { "selector": ".gap-item:nth-child(4)", "section": "problems", "timestamp": 78 },
26
+ { "selector": ".gap-item:nth-child(5)", "section": "problems", "timestamp": 88 },
27
+ { "selector": "#solution h2", "section": "solution", "timestamp": 96 },
28
+ { "selector": ".hook-box:nth-child(1)", "section": "solution", "timestamp": 104 },
29
+ { "selector": ".hook-box:nth-child(2)", "section": "solution", "timestamp": 118 },
30
+ { "selector": ".hook-box:nth-child(3)", "section": "solution", "timestamp": 130 },
31
+ { "selector": "#workflow h2", "section": "workflow", "timestamp": 141 },
32
+ { "selector": ".workflow-phase:nth-child(1)", "section": "workflow", "timestamp": 148 },
33
+ { "selector": ".workflow-phase:nth-child(2)", "section": "workflow", "timestamp": 155 },
34
+ { "selector": ".workflow-phase:nth-child(3)", "section": "workflow", "timestamp": 162 },
35
+ { "selector": ".workflow-phase:nth-child(4)", "section": "workflow", "timestamp": 172 },
36
+ { "selector": ".workflow-phase:nth-child(5)", "section": "workflow", "timestamp": 180 },
37
+ { "selector": ".workflow-phase:nth-child(6)", "section": "workflow", "timestamp": 188 },
38
+ { "selector": ".workflow-phase:nth-child(7)", "section": "workflow", "timestamp": 196 },
39
+ { "selector": ".workflow-phase:nth-child(8)", "section": "workflow", "timestamp": 204 },
40
+ { "selector": ".workflow-phase:nth-child(9)", "section": "workflow", "timestamp": 211 },
41
+ { "selector": ".workflow-phase:nth-child(10)", "section": "workflow", "timestamp": 218 },
42
+ { "selector": "#installation h2", "section": "installation", "timestamp": 221 },
43
+ { "selector": ".install-command", "section": "installation", "timestamp": 228 },
44
+ { "selector": "#credits h2", "section": "credits", "timestamp": 261 },
45
+ { "selector": "#intro", "section": "outro", "timestamp": 286 }
46
+ ],
47
+ "words": []
48
+ }
Binary file
@@ -881,11 +881,11 @@
881
881
  <span class="word" data-text="HUSTLE"></span>
882
882
  <span class="word highlight" data-text="TOGETHER"></span>
883
883
  </h1>
884
- <p class="hero-tagline">Tools for builders who like to try things</p>
884
+ <p class="hero-tagline">Build together. Share resources. Grow stronger.</p>
885
885
  <p class="hero-desc">
886
- We're a small team obsessed with building tools that actually work.
887
- We experiment, we iterate, and we share what we learn.
888
- No corporate BS - just useful stuff for developers and creators.
886
+ We hustle hard to create tools worth sharing. When we build together
887
+ and share what we learn, everyone gets stronger. No gatekeeping - just
888
+ useful stuff for developers who believe in community over competition.
889
889
  </p>
890
890
  <div class="hero-cta">
891
891
  <a href="#tools" class="btn btn-primary">VIEW TOOLS</a>
@@ -500,6 +500,82 @@
500
500
  box-shadow: 0 0 15px var(--accent-red-glow);
501
501
  }
502
502
 
503
+ .nav-btn.active {
504
+ background: var(--accent-red);
505
+ color: #fff;
506
+ border-color: var(--accent-red);
507
+ }
508
+
509
+ /* ============================================
510
+ AUDIO NARRATION PLAYER
511
+ ============================================ */
512
+ .audio-progress-container {
513
+ position: fixed;
514
+ bottom: 60px;
515
+ left: 50%;
516
+ transform: translateX(-50%);
517
+ width: 80%;
518
+ max-width: 600px;
519
+ background: rgba(0, 0, 0, 0.9);
520
+ border: 1px dashed var(--grey);
521
+ padding: 15px 20px;
522
+ z-index: 1001;
523
+ border-radius: 0;
524
+ }
525
+
526
+ .audio-time-display {
527
+ display: flex;
528
+ justify-content: space-between;
529
+ font-size: 0.75rem;
530
+ color: var(--grey);
531
+ margin-bottom: 10px;
532
+ font-family: 'Courier New', monospace;
533
+ }
534
+
535
+ .audio-progress-bar {
536
+ position: relative;
537
+ height: 6px;
538
+ background: var(--dark-grey);
539
+ cursor: pointer;
540
+ }
541
+
542
+ .audio-progress-fill {
543
+ height: 100%;
544
+ background: var(--accent-red);
545
+ width: 0%;
546
+ transition: width 0.1s linear;
547
+ }
548
+
549
+ .audio-section-markers {
550
+ position: absolute;
551
+ top: 0;
552
+ left: 0;
553
+ width: 100%;
554
+ height: 100%;
555
+ pointer-events: none;
556
+ }
557
+
558
+ .audio-section-marker {
559
+ position: absolute;
560
+ top: -3px;
561
+ width: 2px;
562
+ height: 12px;
563
+ background: var(--white);
564
+ opacity: 0.5;
565
+ }
566
+
567
+ .audio-section-marker:hover {
568
+ opacity: 1;
569
+ }
570
+
571
+ /* Audio highlight effect for elements being narrated */
572
+ .audio-highlighted {
573
+ box-shadow: 0 0 30px var(--accent-red-glow), 0 0 60px var(--accent-red-glow) !important;
574
+ border-color: var(--accent-red) !important;
575
+ transform: scale(1.02);
576
+ transition: all 0.3s ease !important;
577
+ }
578
+
503
579
  /* Progress indicator */
504
580
  .progress-bar {
505
581
  position: fixed;
@@ -1589,10 +1665,28 @@
1589
1665
 
1590
1666
  <!-- Navigation -->
1591
1667
  <nav class="nav">
1668
+ <button class="nav-btn" id="narrateBtn">🔊 NARRATE</button>
1592
1669
  <button class="nav-btn" id="playBtn">AUTO PLAY</button>
1593
1670
  <button class="nav-btn" id="resetBtn">RESTART</button>
1594
1671
  </nav>
1595
1672
 
1673
+ <!-- Audio Narration Player (hidden) -->
1674
+ <audio id="narrationAudio" preload="auto">
1675
+ <source src="audio/narration.mp3" type="audio/mpeg">
1676
+ </audio>
1677
+
1678
+ <!-- Audio Progress Bar -->
1679
+ <div class="audio-progress-container" id="audioProgressContainer" style="display: none;">
1680
+ <div class="audio-time-display">
1681
+ <span id="audioCurrentTime">0:00</span>
1682
+ <span id="audioTotalTime">0:00</span>
1683
+ </div>
1684
+ <div class="audio-progress-bar" id="audioProgressBar">
1685
+ <div class="audio-progress-fill" id="audioProgressFill"></div>
1686
+ <div class="audio-section-markers" id="audioSectionMarkers"></div>
1687
+ </div>
1688
+ </div>
1689
+
1596
1690
  <!-- Section Indicators -->
1597
1691
  <div class="section-indicator" id="sectionIndicator"></div>
1598
1692
 
@@ -1606,7 +1700,7 @@
1606
1700
  <span class="hustle-word hustle-highlight">API-DEV-TOOLS</span>
1607
1701
  </div>
1608
1702
  <div class="package-name" id="packageName">@hustle-together/api-dev-tools</div>
1609
- <div class="version" id="versionText">v2.0.3</div>
1703
+ <div class="version" id="versionText">v2.0.5</div>
1610
1704
  <p class="tagline">"Hustle together. Share resources. Build stronger."<span class="cursor"></span></p>
1611
1705
 
1612
1706
  <div class="intro-text">
@@ -2889,6 +2983,235 @@
2889
2983
  }
2890
2984
  });
2891
2985
  });
2986
+
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
+
2892
3215
  </script>
2893
3216
 
2894
3217
  </body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hustle-together/api-dev-tools",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "Interview-driven API development workflow for Claude Code - Automates research, testing, and documentation",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {