@hustle-together/api-dev-tools 2.0.7 → 3.0.0

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 (37) hide show
  1. package/README.md +283 -478
  2. package/bin/cli.js +55 -11
  3. package/commands/README.md +124 -251
  4. package/commands/api-create.md +318 -136
  5. package/commands/api-interview.md +252 -256
  6. package/commands/api-research.md +209 -234
  7. package/commands/api-verify.md +231 -0
  8. package/demo/audio/generate-all-narrations.js +516 -0
  9. package/demo/audio/generate-voice-previews.js +140 -0
  10. package/demo/audio/narration-adam-timing.json +3666 -0
  11. package/demo/audio/narration-adam.mp3 +0 -0
  12. package/demo/audio/narration-creature-timing.json +3666 -0
  13. package/demo/audio/narration-creature.mp3 +0 -0
  14. package/demo/audio/narration-gaming-timing.json +3666 -0
  15. package/demo/audio/narration-gaming.mp3 +0 -0
  16. package/demo/audio/narration-hope-timing.json +3666 -0
  17. package/demo/audio/narration-hope.mp3 +0 -0
  18. package/demo/audio/narration-mark-timing.json +3666 -0
  19. package/demo/audio/narration-mark.mp3 +0 -0
  20. package/demo/audio/previews/manifest.json +30 -0
  21. package/demo/audio/previews/preview-creature.mp3 +0 -0
  22. package/demo/audio/previews/preview-gaming.mp3 +0 -0
  23. package/demo/audio/previews/preview-hope.mp3 +0 -0
  24. package/demo/audio/previews/preview-mark.mp3 +0 -0
  25. package/demo/audio/voices-manifest.json +50 -0
  26. package/demo/hustle-together/blog/gemini-vs-claude-widgets.html +30 -28
  27. package/demo/hustle-together/blog/interview-driven-api-development.html +37 -23
  28. package/demo/hustle-together/index.html +142 -109
  29. package/demo/workflow-demo.html +1023 -558
  30. package/hooks/periodic-reground.py +154 -0
  31. package/hooks/session-startup.py +151 -0
  32. package/hooks/track-tool-use.py +109 -17
  33. package/hooks/verify-after-green.py +152 -0
  34. package/package.json +2 -2
  35. package/templates/api-dev-state.json +42 -7
  36. package/templates/research-index.json +6 -0
  37. package/templates/settings.json +23 -0
@@ -12,8 +12,8 @@
12
12
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/TextPlugin.min.js"></script>
13
13
  <style>
14
14
  /* ============================================
15
- 90s RETRO TERMINAL AESTHETIC
16
- Black & White, Monospace, Dashed Borders
15
+ DARK THEME WITH RED ACCENTS & ANIMATED GRID
16
+ 90s terminal aesthetic with glow effects
17
17
  ============================================ */
18
18
 
19
19
  * {
@@ -23,25 +23,28 @@
23
23
  }
24
24
 
25
25
  :root {
26
- --white: #e0e0e0;
27
- --grey: #888;
28
- --dark-grey: #444;
26
+ /* Dark theme colors */
29
27
  --black: #0a0a0a;
30
- --glow: rgba(255, 255, 255, 0.3);
31
- --accent-red: #BA0C2F;
28
+ --darker-grey: #111;
29
+ --dark-grey: #1a1a1a;
30
+ --card-bg: #0d0d0d;
31
+ --border-color: #333;
32
+ --grey: #888;
33
+ --white: #f0f0f0;
34
+ --accent-red: #ba0c2f;
32
35
  --accent-red-glow: rgba(186, 12, 47, 0.4);
33
36
  }
34
37
 
35
38
  body {
36
39
  background: var(--black);
37
40
  color: var(--white);
38
- font-family: 'Courier New', Courier, monospace;
39
- line-height: 1.7;
41
+ font-family: 'Courier New', monospace;
42
+ line-height: 1.6;
40
43
  overflow-x: hidden;
41
44
  }
42
45
 
43
46
  /* ============================================
44
- ANIMATED BACKGROUND
47
+ ANIMATED BACKGROUND GRID
45
48
  ============================================ */
46
49
  .background-pattern {
47
50
  position: fixed;
@@ -49,21 +52,20 @@
49
52
  left: 0;
50
53
  width: 100%;
51
54
  height: 100%;
52
- pointer-events: none;
53
55
  z-index: -1;
54
- overflow: hidden;
56
+ opacity: 0.4;
55
57
  }
56
58
 
57
59
  .grid-svg {
58
60
  width: 100%;
59
61
  height: 100%;
60
- opacity: 0.15;
61
62
  }
62
63
 
63
64
  .grid-line {
64
- stroke: var(--grey);
65
+ stroke: var(--accent-red);
65
66
  stroke-width: 0.5;
66
67
  fill: none;
68
+ opacity: 0.4;
67
69
  }
68
70
 
69
71
  /* Floating particles */
@@ -73,130 +75,101 @@
73
75
  left: 0;
74
76
  width: 100%;
75
77
  height: 100%;
76
- pointer-events: none;
77
78
  z-index: -1;
79
+ pointer-events: none;
78
80
  }
79
81
 
80
82
  .particle {
81
83
  position: absolute;
82
84
  width: 4px;
83
85
  height: 4px;
84
- background: var(--grey);
85
- opacity: 0.3;
86
+ background: var(--accent-red);
86
87
  border-radius: 50%;
88
+ opacity: 0;
89
+ box-shadow: 0 0 6px var(--accent-red-glow);
87
90
  }
88
91
 
92
+ /* Particle shape variations */
89
93
  .particle.square {
90
94
  border-radius: 0;
91
- border: 1px solid var(--grey);
92
- background: transparent;
93
- width: 8px;
94
- height: 8px;
95
+ width: 6px;
96
+ height: 6px;
95
97
  }
96
98
 
97
99
  .particle.plus {
100
+ width: 8px;
101
+ height: 2px;
102
+ background: var(--accent-red);
98
103
  border-radius: 0;
99
- background: transparent;
100
- width: 12px;
101
- height: 12px;
102
104
  }
103
105
 
104
- .particle.plus::before,
105
- .particle.plus::after {
106
+ .particle.plus::before {
106
107
  content: '';
107
108
  position: absolute;
108
- background: var(--grey);
109
- }
110
-
111
- .particle.plus::before {
112
- width: 12px;
113
- height: 1px;
114
- top: 5.5px;
115
- left: 0;
116
- }
117
-
118
- .particle.plus::after {
119
- width: 1px;
120
- height: 12px;
121
- left: 5.5px;
122
- top: 0;
109
+ width: 2px;
110
+ height: 8px;
111
+ background: var(--accent-red);
112
+ left: 3px;
113
+ top: -3px;
123
114
  }
124
115
 
125
- /* X shape particle */
126
116
  .particle.x-shape {
117
+ width: 8px;
118
+ height: 2px;
119
+ background: var(--accent-red);
127
120
  border-radius: 0;
128
- background: transparent;
129
- width: 14px;
130
- height: 14px;
121
+ transform: rotate(45deg);
131
122
  }
132
123
 
133
- .particle.x-shape::before,
134
- .particle.x-shape::after {
124
+ .particle.x-shape::before {
135
125
  content: '';
136
126
  position: absolute;
137
- background: var(--grey);
138
- }
139
-
140
- .particle.x-shape::before {
141
- width: 14px;
142
- height: 1px;
143
- top: 6.5px;
144
- left: 0;
145
- transform: rotate(45deg);
146
- }
147
-
148
- .particle.x-shape::after {
149
- width: 14px;
150
- height: 1px;
151
- top: 6.5px;
152
- left: 0;
153
- transform: rotate(-45deg);
127
+ width: 8px;
128
+ height: 2px;
129
+ background: var(--accent-red);
130
+ transform: rotate(90deg);
154
131
  }
155
132
 
156
- /* Line/dash particle */
157
133
  .particle.line {
158
- border-radius: 0;
159
- width: 20px;
134
+ width: 12px;
160
135
  height: 1px;
161
- background: var(--grey);
136
+ border-radius: 0;
162
137
  }
163
138
 
164
139
  .particle.line-v {
165
- border-radius: 0;
166
140
  width: 1px;
167
- height: 20px;
168
- background: var(--grey);
141
+ height: 12px;
142
+ border-radius: 0;
169
143
  }
170
144
 
171
- /* Circle outline particle */
172
145
  .particle.circle-outline {
173
- width: 16px;
174
- height: 16px;
175
- border-radius: 50%;
176
146
  background: transparent;
177
- border: 1px solid var(--grey);
147
+ border: 1px solid var(--accent-red);
148
+ width: 8px;
149
+ height: 8px;
178
150
  }
179
151
 
180
- /* Triangle particle */
181
152
  .particle.triangle {
182
153
  width: 0;
183
154
  height: 0;
184
155
  background: transparent;
185
- border-left: 6px solid transparent;
186
- border-right: 6px solid transparent;
187
- border-bottom: 10px solid var(--grey);
156
+ border-left: 4px solid transparent;
157
+ border-right: 4px solid transparent;
158
+ border-bottom: 7px solid var(--accent-red);
159
+ border-radius: 0;
188
160
  }
189
161
 
190
162
  /* Cursor glow effect */
191
163
  .cursor-glow {
192
164
  position: fixed;
193
- width: 300px;
194
- height: 300px;
195
- background: radial-gradient(circle, rgba(255,255,255,0.08) 0%, transparent 70%);
165
+ width: 500px;
166
+ height: 500px;
167
+ border-radius: 50%;
168
+ background: radial-gradient(circle, var(--accent-red-glow) 0%, rgba(186, 12, 47, 0.15) 30%, transparent 70%);
196
169
  pointer-events: none;
197
- z-index: 0;
170
+ z-index: -1;
198
171
  transform: translate(-50%, -50%);
199
- transition: opacity 0.3s;
172
+ opacity: 0.7;
200
173
  }
201
174
 
202
175
  /* Scanlines overlay */
@@ -206,26 +179,27 @@
206
179
  left: 0;
207
180
  width: 100%;
208
181
  height: 100%;
209
- pointer-events: none;
210
- z-index: 9999;
211
182
  background: repeating-linear-gradient(
212
183
  0deg,
213
184
  transparent,
214
185
  transparent 2px,
215
- rgba(0, 0, 0, 0.1) 2px,
216
- rgba(0, 0, 0, 0.1) 4px
186
+ rgba(0, 0, 0, 0.05) 2px,
187
+ rgba(0, 0, 0, 0.05) 4px
217
188
  );
218
- opacity: 0.3;
189
+ pointer-events: none;
190
+ z-index: 9999;
191
+ opacity: 0.2;
219
192
  }
220
193
 
221
- /* Animated corner decorations */
194
+ /* Corner decorations */
222
195
  .corner-decoration {
223
196
  position: fixed;
224
- width: 60px;
225
- height: 60px;
226
- border: 1px dashed var(--dark-grey);
227
- opacity: 0.4;
197
+ width: 100px;
198
+ height: 100px;
199
+ border: 1px dashed var(--accent-red);
228
200
  z-index: 1;
201
+ pointer-events: none;
202
+ opacity: 0.4;
229
203
  }
230
204
 
231
205
  .corner-decoration.top-left {
@@ -237,7 +211,7 @@
237
211
 
238
212
  .corner-decoration.top-right {
239
213
  top: 20px;
240
- right: 80px;
214
+ right: 20px;
241
215
  border-left: none;
242
216
  border-bottom: none;
243
217
  }
@@ -256,468 +230,441 @@
256
230
  border-top: none;
257
231
  }
258
232
 
259
- /* Visual flow lines between sections */
260
- .flow-connector-line {
261
- position: absolute;
262
- width: 2px;
263
- background: linear-gradient(to bottom, transparent, var(--dark-grey), transparent);
264
- left: 50%;
265
- bottom: -60px;
266
- height: 60px;
267
- opacity: 0.3;
233
+ /* ============================================
234
+ NAVIGATION
235
+ ============================================ */
236
+ .nav {
237
+ position: fixed;
238
+ top: 0;
239
+ left: 0;
240
+ right: 0;
241
+ z-index: 1000;
242
+ display: flex;
243
+ justify-content: flex-end;
244
+ gap: 10px;
245
+ padding: 20px 40px;
246
+ background: rgba(10, 10, 10, 0.9);
247
+ border-bottom: 1px dashed var(--border-color);
268
248
  }
269
249
 
270
- /* Status badge animations */
271
- .status-badge {
272
- display: inline-flex;
273
- align-items: center;
274
- gap: 8px;
275
- padding: 6px 14px;
276
- border: 1px dashed var(--grey);
250
+ .nav-btn {
251
+ background: transparent;
252
+ border: 1px solid var(--border-color);
253
+ color: var(--grey);
254
+ padding: 8px 16px;
255
+ font-family: inherit;
277
256
  font-size: 0.75rem;
257
+ cursor: pointer;
258
+ transition: all 0.2s;
278
259
  text-transform: uppercase;
279
- letter-spacing: 2px;
260
+ letter-spacing: 1px;
280
261
  }
281
262
 
282
- .status-badge .pulse {
283
- width: 8px;
284
- height: 8px;
285
- border-radius: 50%;
286
- background: var(--grey);
287
- animation: pulse 2s infinite;
263
+ .nav-btn:hover {
264
+ border-color: var(--accent-red);
265
+ color: var(--accent-red);
266
+ box-shadow: 0 0 10px var(--accent-red-glow);
288
267
  }
289
268
 
290
- .status-badge.active .pulse {
269
+ .nav-btn.active {
291
270
  background: var(--accent-red);
292
- box-shadow: 0 0 10px var(--accent-red);
293
- }
294
-
295
- @keyframes pulse {
296
- 0%, 100% { transform: scale(1); opacity: 1; }
297
- 50% { transform: scale(1.2); opacity: 0.5; }
298
- }
299
-
300
- /* Visual data flow diagram */
301
- .data-flow {
302
- display: flex;
303
- align-items: center;
304
- justify-content: center;
305
- gap: 20px;
306
- margin: 30px 0;
307
- flex-wrap: wrap;
308
- }
309
-
310
- .data-node {
311
- border: 1px dashed var(--grey);
312
- padding: 15px 25px;
313
- text-align: center;
314
- position: relative;
315
- min-width: 120px;
316
- }
317
-
318
- .data-node-icon {
319
- font-size: 1.5rem;
320
- margin-bottom: 8px;
321
- display: block;
271
+ border-color: var(--accent-red);
272
+ color: var(--white);
322
273
  }
323
274
 
324
- .data-node-label {
325
- font-size: 0.75rem;
326
- text-transform: uppercase;
327
- letter-spacing: 1px;
328
- color: var(--grey);
275
+ .nav-btn.disabled {
276
+ opacity: 0.4;
277
+ cursor: not-allowed;
329
278
  }
330
279
 
331
- .data-arrow {
280
+ .nav-btn.disabled:hover {
281
+ border-color: var(--border-color);
332
282
  color: var(--grey);
333
- font-size: 1.5rem;
334
- animation: arrowPulse 1.5s infinite;
283
+ box-shadow: none;
335
284
  }
336
285
 
337
- @keyframes arrowPulse {
338
- 0%, 100% { opacity: 0.3; transform: translateX(0); }
339
- 50% { opacity: 1; transform: translateX(5px); }
286
+ /* ============================================
287
+ AUDIO PLAYER
288
+ ============================================ */
289
+ .audio-progress-container {
290
+ position: fixed;
291
+ bottom: 0;
292
+ left: 0;
293
+ right: 0;
294
+ background: rgba(10, 10, 10, 0.95);
295
+ border-top: 1px dashed var(--border-color);
296
+ padding: 15px 40px;
297
+ z-index: 1001;
340
298
  }
341
299
 
342
- /* Visual checkmarks for completed items */
343
- .visual-check {
344
- display: inline-flex;
345
- align-items: center;
346
- justify-content: center;
347
- width: 24px;
348
- height: 24px;
349
- border: 2px solid var(--grey);
350
- margin-right: 12px;
300
+ .audio-time-display {
301
+ display: flex;
302
+ justify-content: space-between;
351
303
  font-size: 0.8rem;
352
- transition: all 0.3s;
353
- }
354
-
355
- .visual-check.checked {
356
- border-color: var(--accent-red);
357
- background: rgba(186, 12, 47, 0.2);
358
- }
359
-
360
- .visual-check.checked::after {
361
- content: '✓';
362
- color: var(--accent-red);
304
+ color: var(--grey);
305
+ margin-bottom: 8px;
363
306
  }
364
307
 
365
- /* Animated underline effect */
366
- .highlight-underline {
308
+ .audio-progress-bar {
367
309
  position: relative;
368
- display: inline-block;
310
+ height: 12px;
311
+ background: var(--darker-grey);
312
+ border: 1px solid var(--border-color);
313
+ cursor: pointer;
369
314
  }
370
315
 
371
- .highlight-underline::after {
372
- content: '';
373
- position: absolute;
374
- bottom: -4px;
375
- left: 0;
376
- width: 0;
377
- height: 2px;
316
+ .audio-progress-fill {
317
+ height: 100%;
378
318
  background: var(--accent-red);
379
- transition: width 0.3s ease;
380
- }
381
-
382
- .highlight-underline:hover::after {
383
- width: 100%;
319
+ width: 0%;
320
+ transition: width 0.1s linear;
321
+ pointer-events: none;
322
+ box-shadow: 0 0 10px var(--accent-red-glow);
384
323
  }
385
324
 
386
- /* Visual timeline indicator */
387
- .timeline-indicator {
325
+ .audio-section-markers {
388
326
  position: absolute;
389
- left: -40px;
390
327
  top: 0;
391
- bottom: 0;
392
- width: 2px;
393
- background: var(--dark-grey);
328
+ left: 0;
329
+ width: 100%;
330
+ height: 100%;
331
+ pointer-events: none;
394
332
  }
395
333
 
396
- .timeline-indicator::before {
397
- content: '';
334
+ .audio-section-marker {
398
335
  position: absolute;
399
- top: 50%;
400
- left: -4px;
401
- width: 10px;
402
- height: 10px;
403
- border: 2px solid var(--grey);
404
- border-radius: 50%;
405
- background: var(--black);
406
- transform: translateY(-50%);
336
+ top: -2px;
337
+ width: 3px;
338
+ height: 16px;
339
+ background: var(--white);
340
+ opacity: 0.5;
341
+ pointer-events: auto;
342
+ cursor: pointer;
343
+ margin-left: -1px;
407
344
  }
408
345
 
409
- .timeline-indicator.active::before {
410
- border-color: var(--accent-red);
411
- box-shadow: 0 0 10px var(--accent-red-glow);
346
+ .audio-section-marker:hover {
347
+ opacity: 1;
348
+ background: var(--accent-red);
349
+ box-shadow: 0 0 8px var(--accent-red-glow);
412
350
  }
413
351
 
414
- /* Animated typing indicator */
415
- .typing-indicator {
416
- display: inline-flex;
417
- gap: 4px;
418
- padding: 10px 15px;
419
- background: rgba(255,255,255,0.03);
352
+ /* Chapter buttons */
353
+ .chapter-buttons {
354
+ display: flex;
355
+ gap: 8px;
356
+ margin-bottom: 12px;
357
+ flex-wrap: wrap;
358
+ justify-content: center;
420
359
  }
421
360
 
422
- .typing-indicator span {
423
- width: 6px;
424
- height: 6px;
425
- background: var(--grey);
426
- border-radius: 50%;
427
- animation: typing 1.4s infinite both;
361
+ .chapter-btn {
362
+ padding: 6px 12px;
363
+ font-size: 0.65rem;
364
+ background: transparent;
365
+ border: 1px solid var(--border-color);
366
+ color: var(--grey);
367
+ cursor: pointer;
368
+ text-transform: uppercase;
369
+ letter-spacing: 1px;
370
+ transition: all 0.2s;
428
371
  }
429
372
 
430
- .typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
431
- .typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
432
-
433
- @keyframes typing {
434
- 0%, 100% { transform: translateY(0); opacity: 0.3; }
435
- 50% { transform: translateY(-8px); opacity: 1; }
373
+ .chapter-btn:hover {
374
+ border-color: var(--accent-red);
375
+ color: var(--accent-red);
436
376
  }
437
377
 
438
- /* Phase connection arrows */
439
- .phase-arrow {
440
- display: none;
441
- position: absolute;
442
- right: -22px;
443
- top: 50%;
444
- transform: translateY(-50%);
445
- color: var(--dark-grey);
446
- font-size: 1rem;
447
- z-index: 2;
378
+ .chapter-btn.active {
379
+ background: var(--accent-red);
380
+ border-color: var(--accent-red);
381
+ color: var(--white);
382
+ box-shadow: 0 0 10px var(--accent-red-glow);
448
383
  }
449
384
 
450
- .phase-box:not(:nth-child(5n)) .phase-arrow {
451
- display: block;
385
+ /* Voice Modal */
386
+ .voice-modal-overlay {
387
+ position: fixed;
388
+ top: 0;
389
+ left: 0;
390
+ width: 100%;
391
+ height: 100%;
392
+ background: rgba(0, 0, 0, 0.8);
393
+ z-index: 10000;
394
+ display: none;
395
+ align-items: center;
396
+ justify-content: center;
452
397
  }
453
398
 
454
- /* Glowing border effect for active elements */
455
- .glow-border {
456
- position: relative;
399
+ .voice-modal-overlay.active {
400
+ display: flex;
457
401
  }
458
402
 
459
- .glow-border::before {
460
- content: '';
461
- position: absolute;
462
- top: -2px;
463
- left: -2px;
464
- right: -2px;
465
- bottom: -2px;
466
- background: linear-gradient(45deg, transparent, var(--white), transparent);
467
- opacity: 0;
468
- transition: opacity 0.3s;
469
- z-index: -1;
403
+ .voice-modal {
404
+ background: var(--darker-grey);
405
+ border: 2px solid var(--border-color);
406
+ padding: 30px;
407
+ max-width: 500px;
408
+ width: 90%;
470
409
  }
471
410
 
472
- .glow-border:hover::before {
473
- opacity: 0.1;
411
+ .voice-modal-header {
412
+ display: flex;
413
+ justify-content: space-between;
414
+ align-items: center;
415
+ margin-bottom: 25px;
416
+ padding-bottom: 15px;
417
+ border-bottom: 1px dashed var(--border-color);
474
418
  }
475
419
 
476
- /* Navigation */
477
- .nav {
478
- position: fixed;
479
- top: 20px;
480
- right: 20px;
481
- z-index: 1000;
482
- display: flex;
483
- gap: 10px;
420
+ .voice-modal-title {
421
+ font-size: 1.2rem;
422
+ font-weight: 600;
423
+ color: var(--white);
484
424
  }
485
425
 
486
- .nav-btn {
426
+ .voice-modal-close {
487
427
  background: transparent;
488
- border: 1px dashed var(--grey);
489
- color: var(--white);
490
- padding: 8px 16px;
491
- font-family: inherit;
428
+ border: 1px solid var(--border-color);
429
+ color: var(--grey);
430
+ width: 32px;
431
+ height: 32px;
492
432
  cursor: pointer;
493
- transition: all 0.3s;
433
+ font-size: 1.2rem;
434
+ display: flex;
435
+ align-items: center;
436
+ justify-content: center;
494
437
  }
495
438
 
496
- .nav-btn:hover {
439
+ .voice-modal-close:hover {
497
440
  background: var(--accent-red);
498
- color: #fff;
499
441
  border-color: var(--accent-red);
500
- box-shadow: 0 0 15px var(--accent-red-glow);
442
+ color: var(--white);
501
443
  }
502
444
 
503
- .nav-btn.active {
504
- background: var(--accent-red);
505
- color: #fff;
445
+ .voice-option {
446
+ display: flex;
447
+ align-items: center;
448
+ gap: 15px;
449
+ padding: 15px;
450
+ margin-bottom: 10px;
451
+ border: 1px solid var(--border-color);
452
+ background: var(--card-bg);
453
+ cursor: pointer;
454
+ transition: all 0.2s;
455
+ }
456
+
457
+ .voice-option:hover {
506
458
  border-color: var(--accent-red);
507
459
  }
508
460
 
509
- .nav-btn.disabled {
510
- opacity: 0.5;
511
- cursor: not-allowed;
512
- text-decoration: line-through;
461
+ .voice-option.selected {
462
+ border-color: var(--accent-red);
463
+ background: rgba(186, 12, 47, 0.1);
464
+ box-shadow: 0 0 15px var(--accent-red-glow);
513
465
  }
514
466
 
515
- .nav-btn.disabled:hover {
467
+ .voice-play-btn {
468
+ width: 40px;
469
+ height: 40px;
470
+ border: 1px solid var(--border-color);
516
471
  background: transparent;
517
472
  color: var(--grey);
518
- border-color: var(--grey);
519
- box-shadow: none;
473
+ cursor: pointer;
474
+ display: flex;
475
+ align-items: center;
476
+ justify-content: center;
477
+ font-size: 1rem;
478
+ flex-shrink: 0;
520
479
  }
521
480
 
522
- /* ============================================
523
- AUDIO NARRATION PLAYER
524
- ============================================ */
525
- .audio-progress-container {
526
- position: fixed;
527
- bottom: 60px;
528
- left: 50%;
529
- transform: translateX(-50%);
530
- width: 80%;
531
- max-width: 600px;
532
- background: rgba(0, 0, 0, 0.9);
533
- border: 1px dashed var(--grey);
534
- padding: 15px 20px;
535
- z-index: 1001;
536
- border-radius: 0;
481
+ .voice-play-btn:hover {
482
+ background: var(--accent-red);
483
+ border-color: var(--accent-red);
484
+ color: var(--white);
537
485
  }
538
486
 
539
- .audio-time-display {
540
- display: flex;
541
- justify-content: space-between;
542
- font-size: 0.75rem;
487
+ .voice-info { flex: 1; }
488
+
489
+ .voice-name {
490
+ font-size: 0.95rem;
491
+ font-weight: 600;
492
+ margin-bottom: 4px;
493
+ color: var(--white);
494
+ }
495
+
496
+ .voice-desc {
497
+ font-size: 0.8rem;
543
498
  color: var(--grey);
544
- margin-bottom: 10px;
545
- font-family: 'Courier New', monospace;
546
499
  }
547
500
 
548
- .audio-progress-bar {
549
- position: relative;
550
- height: 6px;
551
- background: var(--dark-grey);
501
+ .voice-select-btn {
502
+ background: transparent;
503
+ border: 1px solid var(--border-color);
504
+ color: var(--grey);
505
+ padding: 6px 12px;
506
+ font-size: 0.75rem;
552
507
  cursor: pointer;
553
508
  }
554
509
 
555
- .audio-progress-fill {
556
- height: 100%;
510
+ .voice-option.selected .voice-select-btn {
557
511
  background: var(--accent-red);
558
- width: 0%;
559
- transition: width 0.1s linear;
512
+ border-color: var(--accent-red);
513
+ color: var(--white);
560
514
  }
561
515
 
562
- .audio-section-markers {
563
- position: absolute;
564
- top: 0;
565
- left: 0;
566
- width: 100%;
567
- height: 100%;
568
- pointer-events: none;
516
+ .voice-modal-footer {
517
+ margin-top: 20px;
518
+ padding-top: 15px;
519
+ border-top: 1px dashed var(--border-color);
520
+ text-align: center;
569
521
  }
570
522
 
571
- .audio-section-marker {
572
- position: absolute;
573
- top: -3px;
574
- width: 2px;
575
- height: 12px;
576
- background: var(--white);
577
- opacity: 0.5;
523
+ .voice-modal-note {
524
+ font-size: 0.8rem;
525
+ color: var(--grey);
526
+ margin-bottom: 15px;
578
527
  }
579
528
 
580
- .audio-section-marker:hover {
581
- opacity: 1;
529
+ .voice-confirm-btn {
530
+ background: var(--accent-red);
531
+ border: 1px solid var(--accent-red);
532
+ color: var(--white);
533
+ padding: 12px 30px;
534
+ font-size: 0.85rem;
535
+ cursor: pointer;
536
+ transition: all 0.2s;
582
537
  }
583
538
 
584
- /* Audio highlight effect for elements being narrated */
539
+ .voice-confirm-btn:hover {
540
+ background: transparent;
541
+ box-shadow: 0 0 15px var(--accent-red-glow);
542
+ }
543
+
544
+ /* Audio highlight effect */
585
545
  .audio-highlighted {
586
546
  outline: 2px solid var(--accent-red) !important;
587
547
  outline-offset: 4px;
588
- border-color: var(--accent-red) !important;
589
548
  transition: all 0.3s ease !important;
549
+ box-shadow: 0 0 20px var(--accent-red-glow) !important;
590
550
  }
591
551
 
592
- /* For container elements (cards, boxes), use glow effect */
593
552
  .audio-highlighted.solution-card,
594
553
  .audio-highlighted.phase-box,
595
554
  .audio-highlighted.gap-item,
596
555
  .audio-highlighted.phase-node {
597
- box-shadow: 0 0 25px var(--accent-red-glow) !important;
598
- outline: none !important;
599
- transform: scale(1.02);
600
- }
601
-
602
- /* For text elements (headings, brand), use subtle underline/border */
603
- .audio-highlighted h2,
604
- h2.audio-highlighted,
605
- .hustle-brand.audio-highlighted {
606
- box-shadow: none !important;
607
556
  outline: none !important;
608
- border-bottom: 3px solid var(--accent-red) !important;
609
- padding-bottom: 8px;
557
+ border-color: var(--accent-red) !important;
558
+ background: rgba(186, 12, 47, 0.15) !important;
559
+ box-shadow: 0 0 25px var(--accent-red-glow) !important;
610
560
  }
611
561
 
612
562
  /* Progress indicator */
613
563
  .progress-bar {
614
564
  position: fixed;
615
- top: 0;
565
+ top: 61px;
616
566
  left: 0;
617
- height: 3px;
567
+ height: 2px;
618
568
  background: var(--accent-red);
619
569
  width: 0%;
620
570
  z-index: 1001;
571
+ box-shadow: 0 0 10px var(--accent-red-glow);
621
572
  }
622
573
 
623
- /* Section base */
574
+ /* ============================================
575
+ SECTIONS & CONTENT
576
+ ============================================ */
624
577
  section {
625
578
  min-height: 100vh;
626
579
  display: flex;
627
580
  flex-direction: column;
628
581
  justify-content: center;
629
582
  align-items: center;
630
- padding: 80px 40px;
583
+ padding: 120px 40px 80px;
631
584
  position: relative;
632
585
  }
633
586
 
634
- /* ASCII border decoration */
635
587
  .ascii-border {
636
- border: 1px dashed var(--grey);
588
+ border: 1px dashed var(--border-color);
637
589
  padding: 50px;
638
590
  position: relative;
639
- max-width: 1000px;
591
+ max-width: 900px;
640
592
  width: 100%;
641
- /* Blurred glass backdrop so boxes don't interfere with background animation */
642
- background: rgba(10, 10, 10, 0.85);
643
- backdrop-filter: blur(12px);
644
- -webkit-backdrop-filter: blur(12px);
593
+ background: rgba(10, 10, 10, 0.8);
645
594
  }
646
595
 
647
- .ascii-border::before {
596
+ .ascii-border::before,
597
+ .ascii-border::after {
648
598
  content: '+';
649
599
  position: absolute;
600
+ color: var(--border-color);
601
+ font-size: 1rem;
602
+ }
603
+
604
+ .ascii-border::before {
650
605
  top: -8px;
651
606
  left: -8px;
652
- color: var(--grey);
653
607
  }
654
608
 
655
609
  .ascii-border::after {
656
- content: '+';
657
- position: absolute;
658
610
  bottom: -8px;
659
611
  right: -8px;
660
- color: var(--grey);
661
612
  }
662
613
 
663
614
  /* Typography */
664
615
  h1 {
665
- font-size: 2.5rem;
666
- font-weight: normal;
667
- letter-spacing: 4px;
616
+ font-family: 'Fredoka', sans-serif;
617
+ font-size: 2.8rem;
618
+ font-weight: 700;
668
619
  margin-bottom: 20px;
620
+ color: var(--white);
669
621
  }
670
622
 
671
623
  h2 {
672
- font-size: 1.8rem;
673
- font-weight: normal;
674
- letter-spacing: 2px;
675
- margin-bottom: 10px;
624
+ font-family: 'Fredoka', sans-serif;
625
+ font-size: 2rem;
626
+ font-weight: 600;
627
+ margin-bottom: 15px;
628
+ color: var(--white);
676
629
  }
677
630
 
678
631
  h3 {
679
- font-size: 1.2rem;
632
+ font-size: 1rem;
680
633
  margin-bottom: 15px;
681
634
  text-transform: uppercase;
682
635
  letter-spacing: 1px;
683
636
  color: var(--grey);
684
637
  }
685
638
 
686
- /* Explanation blocks - THE KEY EDUCATIONAL ELEMENT */
639
+ /* Explanation blocks */
687
640
  .explanation {
688
- background: rgba(255, 255, 255, 0.03);
689
- border-left: 3px solid var(--grey);
641
+ background: var(--darker-grey);
642
+ border: 1px dashed var(--border-color);
643
+ border-left: 3px solid var(--accent-red);
690
644
  padding: 25px 30px;
691
645
  margin: 30px 0;
692
646
  font-size: 1rem;
693
- line-height: 1.9;
647
+ line-height: 1.8;
694
648
  opacity: 0;
695
649
  }
696
650
 
697
651
  .explanation-title {
698
- font-size: 0.85rem;
652
+ font-size: 0.75rem;
699
653
  text-transform: uppercase;
700
- letter-spacing: 3px;
701
- color: var(--grey);
654
+ letter-spacing: 2px;
655
+ color: var(--accent-red);
702
656
  margin-bottom: 15px;
703
- display: flex;
704
- align-items: center;
705
- gap: 10px;
657
+ font-weight: 600;
706
658
  }
707
659
 
708
660
  .explanation-title::before {
709
- content: '?';
710
- width: 24px;
711
- height: 24px;
712
- border: 1px dashed var(--grey);
713
- display: flex;
714
- align-items: center;
715
- justify-content: center;
716
- font-size: 0.9rem;
661
+ content: '// ';
662
+ color: var(--grey);
717
663
  }
718
664
 
719
665
  .explanation p {
720
666
  margin-bottom: 15px;
667
+ color: var(--grey);
721
668
  }
722
669
 
723
670
  .explanation p:last-child {
@@ -726,14 +673,13 @@
726
673
 
727
674
  .explanation strong {
728
675
  color: var(--white);
729
- font-weight: normal;
730
- border-bottom: 1px dashed var(--grey);
676
+ font-weight: 600;
731
677
  }
732
678
 
733
679
  /* Real example callout */
734
680
  .real-example {
735
- background: rgba(255, 255, 255, 0.05);
736
- border: 1px dashed var(--white);
681
+ background: var(--darker-grey);
682
+ border: 2px solid var(--border-color);
737
683
  padding: 20px 25px;
738
684
  margin: 20px 0;
739
685
  position: relative;
@@ -744,11 +690,12 @@
744
690
  position: absolute;
745
691
  top: -10px;
746
692
  left: 20px;
747
- background: var(--black);
693
+ background: var(--card-bg);
748
694
  padding: 0 10px;
749
- font-size: 0.75rem;
695
+ font-size: 0.7rem;
750
696
  letter-spacing: 2px;
751
- color: var(--grey);
697
+ color: var(--accent-red);
698
+ border: 1px solid var(--border-color);
752
699
  }
753
700
 
754
701
  .real-example-content {
@@ -760,7 +707,7 @@
760
707
  display: inline-block;
761
708
  width: 10px;
762
709
  height: 20px;
763
- background: var(--white);
710
+ background: var(--accent-red);
764
711
  animation: blink 1s infinite;
765
712
  vertical-align: middle;
766
713
  margin-left: 5px;
@@ -778,10 +725,11 @@
778
725
  justify-content: center;
779
726
  width: 50px;
780
727
  height: 50px;
781
- border: 2px solid var(--white);
728
+ border: 2px solid var(--accent-red);
782
729
  font-size: 1.5rem;
783
730
  margin-right: 20px;
784
731
  flex-shrink: 0;
732
+ color: var(--accent-red);
785
733
  }
786
734
 
787
735
  .step-header {
@@ -848,11 +796,12 @@
848
796
 
849
797
  .hustle-highlight {
850
798
  color: var(--accent-red);
851
- text-shadow: 0 0 30px var(--accent-red-glow), 0 0 60px var(--accent-red-glow);
799
+ text-shadow: 0 0 20px var(--accent-red-glow);
852
800
  }
853
801
 
854
802
  .text-red {
855
803
  color: var(--accent-red);
804
+ font-weight: 600;
856
805
  }
857
806
 
858
807
  /* Intro section centering */
@@ -883,13 +832,13 @@
883
832
  }
884
833
 
885
834
  /* ============================================
886
- PHASE FLOW - LIGHTING UP SEQUENCE
835
+ PHASE FLOW - LIGHTING UP SEQUENCE (boxy)
887
836
  ============================================ */
888
837
  .phase-flow {
889
838
  display: flex;
890
839
  align-items: center;
891
840
  justify-content: center;
892
- gap: 15px;
841
+ gap: 12px;
893
842
  margin-top: 50px;
894
843
  flex-wrap: wrap;
895
844
  opacity: 0;
@@ -897,30 +846,31 @@
897
846
 
898
847
  .phase-node {
899
848
  position: relative;
900
- border: 2px solid var(--dark-grey);
849
+ border: 2px solid var(--border-color);
850
+ background: var(--darker-grey);
901
851
  padding: 20px 25px;
902
852
  text-align: center;
903
853
  min-width: 100px;
904
- transition: all 0.4s ease;
854
+ transition: all 0.3s ease;
905
855
  opacity: 0;
906
856
  transform: translateY(20px);
907
857
  }
908
858
 
909
859
  .phase-node .phase-glow {
910
860
  position: absolute;
911
- top: -2px;
912
- left: -2px;
913
- right: -2px;
914
- bottom: -2px;
861
+ top: 0;
862
+ left: 0;
863
+ right: 0;
864
+ bottom: 0;
915
865
  border: 2px solid var(--accent-red);
916
866
  opacity: 0;
917
- box-shadow: 0 0 20px var(--accent-red-glow), inset 0 0 20px var(--accent-red-glow);
918
- transition: opacity 0.4s ease;
867
+ transition: opacity 0.3s ease;
919
868
  }
920
869
 
921
870
  .phase-node.active {
922
871
  border-color: var(--accent-red);
923
- background: rgba(186, 12, 47, 0.1);
872
+ background: rgba(186, 12, 47, 0.2);
873
+ box-shadow: 0 0 20px var(--accent-red-glow);
924
874
  }
925
875
 
926
876
  .phase-node.active .phase-glow {
@@ -929,7 +879,6 @@
929
879
 
930
880
  .phase-node.active .phase-icon {
931
881
  color: var(--accent-red);
932
- text-shadow: 0 0 10px var(--accent-red);
933
882
  }
934
883
 
935
884
  .phase-node.active .phase-label {
@@ -938,31 +887,31 @@
938
887
 
939
888
  .phase-icon {
940
889
  display: block;
941
- font-size: 1.8rem;
890
+ font-size: 1.6rem;
942
891
  font-weight: bold;
943
892
  margin-bottom: 8px;
944
893
  color: var(--grey);
945
- transition: all 0.4s ease;
894
+ transition: all 0.3s ease;
946
895
  }
947
896
 
948
897
  .phase-label {
949
- font-size: 0.7rem;
898
+ font-size: 0.65rem;
950
899
  text-transform: uppercase;
951
900
  letter-spacing: 2px;
952
- color: var(--dark-grey);
953
- transition: all 0.4s ease;
901
+ color: var(--grey);
902
+ transition: all 0.3s ease;
903
+ font-weight: bold;
954
904
  }
955
905
 
956
906
  .phase-connector-arrow {
957
- color: var(--dark-grey);
958
- font-size: 1.5rem;
907
+ color: var(--border-color);
908
+ font-size: 1.2rem;
959
909
  opacity: 0;
960
910
  transition: all 0.3s ease;
961
911
  }
962
912
 
963
913
  .phase-connector-arrow.active {
964
914
  color: var(--accent-red);
965
- text-shadow: 0 0 10px var(--accent-red-glow);
966
915
  }
967
916
 
968
917
  @media (max-width: 768px) {
@@ -1012,62 +961,67 @@
1012
961
  }
1013
962
 
1014
963
  /* ============================================
1015
- SECTION 2: THE PROBLEM - GAPS
964
+ SECTION 2: THE PROBLEM - GAPS - boxy cards
1016
965
  ============================================ */
1017
966
  .gap-list {
1018
967
  list-style: none;
1019
968
  width: 100%;
969
+ display: flex;
970
+ flex-direction: column;
971
+ gap: 15px;
1020
972
  }
1021
973
 
1022
974
  .gap-item {
1023
- border: 1px dashed var(--dark-grey);
1024
- padding: 25px 30px;
1025
- margin-bottom: 20px;
975
+ border: 2px solid var(--border-color);
976
+ background: var(--darker-grey);
977
+ padding: 25px 30px 25px 50px;
1026
978
  opacity: 0;
1027
979
  transform: translateX(-30px);
1028
- transition: all 0.3s;
980
+ transition: all 0.2s;
1029
981
  position: relative;
1030
982
  }
1031
983
 
1032
984
  .gap-item:hover {
1033
985
  border-color: var(--accent-red);
1034
- box-shadow: 0 0 25px var(--accent-red-glow);
986
+ transform: translateX(5px);
1035
987
  }
1036
988
 
1037
989
  .gap-item::before {
1038
- content: 'X';
990
+ content: '';
1039
991
  position: absolute;
1040
- left: -35px;
1041
- top: 50%;
1042
- transform: translateY(-50%);
992
+ left: 18px;
993
+ top: 26px;
1043
994
  color: var(--accent-red);
1044
- font-size: 1.2rem;
995
+ font-size: 1rem;
996
+ font-weight: bold;
1045
997
  }
1046
998
 
1047
999
  .gap-number {
1048
- color: var(--grey);
1049
- font-size: 0.8rem;
1000
+ color: var(--accent-red);
1001
+ font-size: 0.75rem;
1050
1002
  text-transform: uppercase;
1051
1003
  letter-spacing: 2px;
1004
+ font-weight: bold;
1052
1005
  }
1053
1006
 
1054
1007
  .gap-title {
1055
- font-size: 1.15rem;
1008
+ font-size: 1.1rem;
1056
1009
  margin: 8px 0;
1057
1010
  }
1058
1011
 
1059
1012
  .gap-desc {
1060
1013
  color: var(--grey);
1061
- font-size: 0.95rem;
1014
+ font-size: 0.9rem;
1062
1015
  margin-bottom: 10px;
1063
1016
  }
1064
1017
 
1065
1018
  .gap-example {
1066
1019
  font-size: 0.85rem;
1067
1020
  padding: 12px 15px;
1068
- background: rgba(255,255,255,0.03);
1069
- border-left: 2px solid var(--dark-grey);
1070
- margin-top: 10px;
1021
+ background: var(--card-bg);
1022
+ border: 1px solid var(--border-color);
1023
+ border-left: 3px solid var(--accent-red);
1024
+ margin-top: 12px;
1071
1025
  }
1072
1026
 
1073
1027
  .gap-example .bad {
@@ -1076,44 +1030,50 @@
1076
1030
  }
1077
1031
 
1078
1032
  .gap-example .good {
1079
- color: var(--white);
1033
+ color: var(--accent-red);
1034
+ font-weight: bold;
1080
1035
  }
1081
1036
 
1082
1037
  /* ============================================
1083
- SECTION 3: SOLUTION OVERVIEW
1038
+ SECTION 3: SOLUTION OVERVIEW - boxy grid
1084
1039
  ============================================ */
1085
1040
  .solution-grid {
1086
1041
  display: grid;
1087
1042
  grid-template-columns: repeat(3, 1fr);
1088
- gap: 25px;
1043
+ gap: 20px;
1089
1044
  margin-top: 30px;
1090
1045
  }
1091
1046
 
1092
1047
  .solution-card {
1093
- border: 1px dashed var(--dark-grey);
1094
- padding: 30px 25px;
1048
+ border: 2px solid var(--border-color);
1049
+ background: var(--darker-grey);
1050
+ padding: 35px 25px;
1095
1051
  text-align: center;
1096
1052
  opacity: 0;
1097
1053
  transform: translateY(20px);
1098
- transition: all 0.3s;
1054
+ transition: all 0.2s;
1099
1055
  }
1100
1056
 
1101
1057
  .solution-card:hover {
1102
1058
  border-color: var(--accent-red);
1103
- box-shadow: 0 0 25px var(--accent-red-glow);
1059
+ transform: translateY(-3px);
1104
1060
  }
1105
1061
 
1106
1062
  .solution-icon {
1107
- font-size: 2rem;
1063
+ font-size: 1.8rem;
1108
1064
  margin-bottom: 15px;
1109
1065
  display: block;
1066
+ color: var(--accent-red);
1067
+ font-weight: bold;
1110
1068
  }
1111
1069
 
1112
1070
  .solution-title {
1113
- font-size: 1rem;
1114
- margin-bottom: 10px;
1071
+ font-size: 0.95rem;
1072
+ margin-bottom: 12px;
1115
1073
  text-transform: uppercase;
1116
1074
  letter-spacing: 1px;
1075
+ color: var(--white);
1076
+ font-weight: 600;
1117
1077
  }
1118
1078
 
1119
1079
  .solution-desc {
@@ -1123,7 +1083,7 @@
1123
1083
  }
1124
1084
 
1125
1085
  /* ============================================
1126
- SECTION 4: HOOK SYSTEM
1086
+ SECTION 4: HOOK SYSTEM - clean boxy flow
1127
1087
  ============================================ */
1128
1088
  .hook-diagram {
1129
1089
  width: 100%;
@@ -1131,49 +1091,53 @@
1131
1091
  }
1132
1092
 
1133
1093
  .flow-box {
1134
- border: 1px dashed var(--grey);
1094
+ border: 2px solid var(--border-color);
1095
+ background: var(--darker-grey);
1135
1096
  padding: 18px 30px;
1136
1097
  margin: 12px 0;
1137
1098
  text-align: center;
1138
1099
  opacity: 0;
1139
- transition: all 0.3s;
1100
+ transition: all 0.2s;
1140
1101
  }
1141
1102
 
1142
1103
  .flow-box:hover {
1143
- background: rgba(186, 12, 47, 0.1);
1144
- box-shadow: 0 0 20px var(--accent-red-glow);
1145
1104
  border-color: var(--accent-red);
1105
+ transform: translateX(5px);
1146
1106
  }
1147
1107
 
1148
1108
  .flow-arrow {
1149
1109
  text-align: center;
1150
- color: var(--grey);
1110
+ color: var(--accent-red);
1151
1111
  font-size: 1.5rem;
1152
1112
  opacity: 0;
1153
1113
  }
1154
1114
 
1155
1115
  .hook-group {
1156
- border: 2px dashed var(--white);
1116
+ border: 2px dashed var(--border-color);
1117
+ background: var(--darker-grey);
1157
1118
  padding: 25px;
1158
1119
  margin: 25px 0;
1159
1120
  opacity: 0;
1160
1121
  }
1161
1122
 
1162
1123
  .hook-group h4 {
1163
- color: var(--grey);
1164
- font-size: 0.85rem;
1124
+ color: var(--accent-red);
1125
+ font-size: 0.8rem;
1165
1126
  text-transform: uppercase;
1166
1127
  letter-spacing: 2px;
1167
1128
  margin-bottom: 15px;
1129
+ font-weight: bold;
1168
1130
  }
1169
1131
 
1170
1132
  .hook-file {
1171
1133
  padding: 12px 20px;
1172
1134
  margin: 8px 0;
1173
- border-left: 2px solid var(--grey);
1135
+ border: 1px solid var(--border-color);
1136
+ border-left: 3px solid var(--grey);
1137
+ background: var(--card-bg);
1174
1138
  font-size: 0.9rem;
1175
1139
  opacity: 0;
1176
- transition: all 0.3s;
1140
+ transition: all 0.2s;
1177
1141
  display: flex;
1178
1142
  justify-content: space-between;
1179
1143
  align-items: center;
@@ -1181,22 +1145,23 @@
1181
1145
 
1182
1146
  .hook-file:hover {
1183
1147
  border-left-color: var(--accent-red);
1184
- background: rgba(186, 12, 47, 0.1);
1185
- padding-left: 25px;
1148
+ border-color: var(--accent-red);
1186
1149
  }
1187
1150
 
1188
1151
  .hook-file code {
1189
1152
  color: var(--white);
1153
+ font-weight: 500;
1190
1154
  }
1191
1155
 
1192
1156
  .hook-purpose {
1193
1157
  color: var(--grey);
1194
- font-size: 0.8rem;
1158
+ font-size: 0.75rem;
1159
+ text-transform: uppercase;
1195
1160
  }
1196
1161
 
1197
1162
  .result-box {
1198
1163
  display: flex;
1199
- gap: 40px;
1164
+ gap: 30px;
1200
1165
  justify-content: center;
1201
1166
  margin-top: 25px;
1202
1167
  }
@@ -1204,12 +1169,15 @@
1204
1169
  .result-allowed, .result-blocked {
1205
1170
  padding: 18px 35px;
1206
1171
  border: 2px solid;
1172
+ background: var(--card-bg);
1207
1173
  opacity: 0;
1208
- transition: all 0.3s;
1174
+ transition: all 0.2s;
1175
+ font-weight: bold;
1209
1176
  }
1210
1177
 
1211
1178
  .result-allowed {
1212
1179
  border-color: var(--grey);
1180
+ color: var(--grey);
1213
1181
  }
1214
1182
 
1215
1183
  .result-blocked {
@@ -1218,62 +1186,65 @@
1218
1186
  }
1219
1187
 
1220
1188
  .result-allowed:hover, .result-blocked:hover {
1221
- transform: scale(1.05);
1189
+ transform: scale(1.03);
1222
1190
  }
1223
1191
 
1224
1192
  /* ============================================
1225
- SECTION 5: 10-PHASE WORKFLOW
1193
+ SECTION 5: 10-PHASE WORKFLOW - boxy grid
1226
1194
  ============================================ */
1227
1195
  .phase-grid {
1228
1196
  display: grid;
1229
1197
  grid-template-columns: repeat(5, 1fr);
1230
- gap: 18px;
1198
+ gap: 15px;
1231
1199
  width: 100%;
1232
1200
  margin-top: 30px;
1233
1201
  }
1234
1202
 
1235
1203
  .phase-box {
1236
- border: 1px dashed var(--dark-grey);
1204
+ border: 2px solid var(--border-color);
1205
+ background: var(--darker-grey);
1237
1206
  padding: 25px 15px;
1238
1207
  text-align: center;
1239
1208
  opacity: 0;
1240
1209
  transform: scale(0.9);
1241
- transition: all 0.3s;
1210
+ transition: all 0.2s;
1242
1211
  position: relative;
1243
1212
  }
1244
1213
 
1245
1214
  .phase-box:hover {
1246
1215
  border-color: var(--accent-red);
1247
- box-shadow: 0 0 25px var(--accent-red-glow);
1248
1216
  transform: scale(1.02);
1249
1217
  }
1250
1218
 
1251
1219
  .phase-box.active {
1252
1220
  border-color: var(--accent-red);
1253
- background: rgba(186, 12, 47, 0.1);
1221
+ background: rgba(186, 12, 47, 0.2);
1222
+ box-shadow: 0 0 20px var(--accent-red-glow);
1254
1223
  }
1255
1224
 
1256
1225
  .phase-number {
1257
- font-size: 2.2rem;
1258
- color: var(--dark-grey);
1226
+ font-size: 2rem;
1227
+ color: var(--grey);
1259
1228
  margin-bottom: 10px;
1229
+ font-weight: bold;
1260
1230
  }
1261
1231
 
1262
1232
  .phase-box.active .phase-number {
1263
- color: var(--white);
1233
+ color: var(--accent-red);
1264
1234
  }
1265
1235
 
1266
1236
  .phase-name {
1267
- font-size: 0.75rem;
1237
+ font-size: 0.7rem;
1268
1238
  text-transform: uppercase;
1269
1239
  letter-spacing: 1px;
1240
+ font-weight: bold;
1270
1241
  }
1271
1242
 
1272
1243
  .phase-status {
1273
1244
  position: absolute;
1274
1245
  top: 8px;
1275
1246
  right: 8px;
1276
- font-size: 0.7rem;
1247
+ font-size: 0.65rem;
1277
1248
  color: var(--grey);
1278
1249
  }
1279
1250
 
@@ -1287,26 +1258,26 @@
1287
1258
  }
1288
1259
 
1289
1260
  .phase-desc {
1290
- font-size: 0.7rem;
1261
+ font-size: 0.65rem;
1291
1262
  color: var(--grey);
1292
1263
  margin-top: 8px;
1293
1264
  line-height: 1.4;
1294
1265
  }
1295
1266
 
1296
1267
  /* ============================================
1297
- SECTION 6: REAL WALKTHROUGH
1268
+ SECTION 6: REAL WALKTHROUGH - boxy steps
1298
1269
  ============================================ */
1299
1270
  .walkthrough-step {
1300
- border: 1px dashed var(--dark-grey);
1271
+ border: 2px solid var(--border-color);
1272
+ background: var(--darker-grey);
1301
1273
  padding: 30px;
1302
- margin-bottom: 25px;
1274
+ margin-bottom: 20px;
1303
1275
  opacity: 0;
1304
1276
  transform: translateY(20px);
1305
1277
  }
1306
1278
 
1307
1279
  .walkthrough-step.active {
1308
1280
  border-color: var(--accent-red);
1309
- box-shadow: 0 0 20px var(--accent-red-glow);
1310
1281
  }
1311
1282
 
1312
1283
  .walkthrough-header {
@@ -1319,16 +1290,19 @@
1319
1290
  .walkthrough-num {
1320
1291
  width: 45px;
1321
1292
  height: 45px;
1322
- border: 2px solid var(--white);
1293
+ border: 2px solid var(--accent-red);
1294
+ background: transparent;
1323
1295
  display: flex;
1324
1296
  align-items: center;
1325
1297
  justify-content: center;
1326
- font-size: 1.3rem;
1298
+ font-size: 1.2rem;
1327
1299
  flex-shrink: 0;
1300
+ color: var(--accent-red);
1301
+ font-weight: bold;
1328
1302
  }
1329
1303
 
1330
1304
  .walkthrough-title {
1331
- font-size: 1.1rem;
1305
+ font-size: 1rem;
1332
1306
  text-transform: uppercase;
1333
1307
  letter-spacing: 1px;
1334
1308
  }
@@ -1344,26 +1318,28 @@
1344
1318
  }
1345
1319
 
1346
1320
  .walkthrough-example {
1347
- background: #111;
1321
+ background: var(--card-bg);
1322
+ border: 1px solid var(--border-color);
1348
1323
  padding: 15px 20px;
1349
1324
  font-size: 0.85rem;
1350
- border-left: 3px solid var(--grey);
1325
+ border-left: 3px solid var(--accent-red);
1351
1326
  }
1352
1327
 
1353
1328
  .walkthrough-example .label {
1354
- color: var(--grey);
1355
- font-size: 0.75rem;
1329
+ color: var(--accent-red);
1330
+ font-size: 0.7rem;
1356
1331
  text-transform: uppercase;
1357
1332
  letter-spacing: 1px;
1358
1333
  margin-bottom: 8px;
1334
+ font-weight: bold;
1359
1335
  }
1360
1336
 
1361
1337
  /* ============================================
1362
- SECTION 7: LIVE DEMO TERMINAL
1338
+ SECTION 7: LIVE DEMO TERMINAL - boxy style
1363
1339
  ============================================ */
1364
1340
  .terminal {
1365
- background: #0d0d0d;
1366
- border: 1px solid var(--grey);
1341
+ background: var(--card-bg);
1342
+ border: 2px solid var(--border-color);
1367
1343
  padding: 25px;
1368
1344
  width: 100%;
1369
1345
  font-size: 0.85rem;
@@ -1375,20 +1351,27 @@
1375
1351
  gap: 8px;
1376
1352
  margin-bottom: 20px;
1377
1353
  padding-bottom: 15px;
1378
- border-bottom: 1px dashed var(--dark-grey);
1354
+ border-bottom: 1px dashed var(--border-color);
1379
1355
  }
1380
1356
 
1381
1357
  .terminal-dot {
1382
1358
  width: 12px;
1383
1359
  height: 12px;
1384
- border-radius: 50%;
1385
- border: 1px solid var(--grey);
1360
+ border-radius: 0;
1361
+ border: 2px solid var(--border-color);
1362
+ }
1363
+
1364
+ .terminal-dot:first-child {
1365
+ border-color: var(--accent-red);
1366
+ background: var(--accent-red);
1386
1367
  }
1387
1368
 
1388
1369
  .terminal-title {
1389
1370
  margin-left: auto;
1390
1371
  color: var(--grey);
1391
- font-size: 0.8rem;
1372
+ font-size: 0.75rem;
1373
+ text-transform: uppercase;
1374
+ letter-spacing: 1px;
1392
1375
  }
1393
1376
 
1394
1377
  .terminal-line {
@@ -1400,8 +1383,9 @@
1400
1383
  }
1401
1384
 
1402
1385
  .terminal-prompt {
1403
- color: var(--grey);
1386
+ color: var(--accent-red);
1404
1387
  flex-shrink: 0;
1388
+ font-weight: bold;
1405
1389
  }
1406
1390
 
1407
1391
  .terminal-command {
@@ -1420,8 +1404,8 @@
1420
1404
  }
1421
1405
 
1422
1406
  .terminal-allowed {
1423
- color: var(--grey);
1424
- border-left: 3px solid var(--grey);
1407
+ color: #22c55e;
1408
+ border-left: 3px solid #22c55e;
1425
1409
  padding-left: 15px;
1426
1410
  }
1427
1411
 
@@ -1431,18 +1415,18 @@
1431
1415
  }
1432
1416
 
1433
1417
  .terminal-comment {
1434
- color: var(--dark-grey);
1435
- font-size: 0.8rem;
1418
+ color: var(--grey);
1419
+ font-size: 0.75rem;
1436
1420
  padding-left: 25px;
1437
1421
  margin-bottom: 5px;
1438
1422
  }
1439
1423
 
1440
1424
  /* ============================================
1441
- SECTION 8: STATE FILE
1425
+ SECTION 8: STATE FILE - boxy JSON viewer
1442
1426
  ============================================ */
1443
1427
  .json-viewer {
1444
- background: #0d0d0d;
1445
- border: 1px solid var(--grey);
1428
+ background: var(--card-bg);
1429
+ border: 2px solid var(--border-color);
1446
1430
  padding: 25px;
1447
1431
  font-size: 0.85rem;
1448
1432
  width: 100%;
@@ -1450,7 +1434,8 @@
1450
1434
  }
1451
1435
 
1452
1436
  .json-key {
1453
- color: var(--grey);
1437
+ color: var(--accent-red);
1438
+ font-weight: 600;
1454
1439
  }
1455
1440
 
1456
1441
  .json-value {
@@ -1467,33 +1452,36 @@
1467
1452
  }
1468
1453
 
1469
1454
  .json-line.highlight {
1470
- background: rgba(186, 12, 47, 0.15);
1455
+ background: rgba(186, 12, 47, 0.1);
1471
1456
  margin: 0 -25px;
1472
1457
  padding-left: 25px;
1473
1458
  padding-right: 25px;
1474
- border-left: 2px solid var(--accent-red);
1459
+ border-left: 3px solid var(--accent-red);
1475
1460
  }
1476
1461
 
1477
1462
  .json-comment {
1478
- color: var(--dark-grey);
1479
- font-size: 0.75rem;
1463
+ color: var(--grey);
1464
+ font-size: 0.7rem;
1480
1465
  margin-left: 20px;
1466
+ text-transform: uppercase;
1481
1467
  }
1482
1468
 
1483
1469
  /* ============================================
1484
- SECTION 9: INSTALLATION
1470
+ SECTION 9: INSTALLATION - boxy style
1485
1471
  ============================================ */
1486
1472
  .install-command {
1487
- background: #0d0d0d;
1488
- border: 1px solid var(--grey);
1489
- padding: 20px 30px;
1473
+ background: var(--darker-grey);
1474
+ border: 2px solid var(--accent-red);
1475
+ padding: 25px 35px;
1490
1476
  font-size: 1.1rem;
1491
1477
  margin: 30px 0;
1492
1478
  text-align: center;
1479
+ box-shadow: 0 0 20px var(--accent-red-glow);
1493
1480
  }
1494
1481
 
1495
1482
  .install-command code {
1496
- color: var(--white);
1483
+ color: var(--accent-red);
1484
+ font-weight: bold;
1497
1485
  }
1498
1486
 
1499
1487
  .install-flow {
@@ -1504,25 +1492,29 @@
1504
1492
  display: flex;
1505
1493
  align-items: center;
1506
1494
  gap: 25px;
1507
- padding: 20px 0;
1508
- border-bottom: 1px dashed var(--dark-grey);
1495
+ padding: 20px;
1496
+ margin-bottom: 15px;
1497
+ border: 2px solid var(--border-color);
1498
+ background: var(--darker-grey);
1509
1499
  opacity: 0;
1510
1500
  transform: translateY(20px);
1511
1501
  }
1512
1502
 
1513
1503
  .install-step:last-child {
1514
- border-bottom: none;
1504
+ margin-bottom: 0;
1515
1505
  }
1516
1506
 
1517
1507
  .install-icon {
1518
1508
  width: 50px;
1519
1509
  height: 50px;
1520
- border: 1px dashed var(--grey);
1510
+ border: 2px solid var(--accent-red);
1511
+ background: transparent;
1521
1512
  display: flex;
1522
1513
  align-items: center;
1523
1514
  justify-content: center;
1524
1515
  flex-shrink: 0;
1525
1516
  font-size: 1.2rem;
1517
+ color: var(--accent-red);
1526
1518
  }
1527
1519
 
1528
1520
  .install-content {
@@ -1531,28 +1523,30 @@
1531
1523
 
1532
1524
  .install-from {
1533
1525
  color: var(--grey);
1534
- font-size: 0.9rem;
1526
+ font-size: 0.85rem;
1535
1527
  }
1536
1528
 
1537
1529
  .install-arrow {
1538
- color: var(--grey);
1530
+ color: var(--accent-red);
1539
1531
  flex-shrink: 0;
1540
1532
  font-size: 1.2rem;
1533
+ font-weight: bold;
1541
1534
  }
1542
1535
 
1543
1536
  .install-to {
1544
1537
  color: var(--white);
1545
- font-size: 0.95rem;
1538
+ font-size: 0.9rem;
1539
+ font-weight: 500;
1546
1540
  }
1547
1541
 
1548
1542
  .install-note {
1549
1543
  color: var(--grey);
1550
- font-size: 0.8rem;
1544
+ font-size: 0.75rem;
1551
1545
  margin-top: 5px;
1552
1546
  }
1553
1547
 
1554
1548
  /* ============================================
1555
- SECTION 10: CREDITS
1549
+ SECTION 10: CREDITS - boxy style
1556
1550
  ============================================ */
1557
1551
  #credits {
1558
1552
  text-align: center;
@@ -1560,64 +1554,69 @@
1560
1554
 
1561
1555
  .credit-links {
1562
1556
  display: flex;
1563
- gap: 30px;
1557
+ gap: 20px;
1564
1558
  margin-top: 40px;
1565
1559
  flex-wrap: wrap;
1566
1560
  justify-content: center;
1567
1561
  }
1568
1562
 
1569
1563
  .credit-link {
1570
- border: 1px dashed var(--grey);
1564
+ border: 2px solid var(--border-color);
1565
+ background: var(--darker-grey);
1571
1566
  padding: 18px 35px;
1572
1567
  text-decoration: none;
1573
1568
  color: var(--white);
1574
- transition: all 0.3s;
1569
+ transition: all 0.2s;
1575
1570
  opacity: 0;
1571
+ text-transform: uppercase;
1572
+ letter-spacing: 1px;
1573
+ font-size: 0.85rem;
1576
1574
  }
1577
1575
 
1578
1576
  .credit-link:hover {
1579
1577
  background: var(--accent-red);
1580
- color: #fff;
1578
+ color: var(--white);
1581
1579
  border-color: var(--accent-red);
1582
- box-shadow: 0 0 25px var(--accent-red-glow);
1580
+ box-shadow: 0 0 15px var(--accent-red-glow);
1583
1581
  }
1584
1582
 
1585
1583
  .made-with {
1586
1584
  margin-top: 50px;
1587
1585
  color: var(--grey);
1588
- font-size: 0.95rem;
1586
+ font-size: 0.9rem;
1589
1587
  opacity: 0;
1590
1588
  line-height: 1.8;
1591
1589
  }
1592
1590
 
1593
1591
  /* ============================================
1594
- SECTION INDICATORS
1592
+ SECTION INDICATORS - square boxy dots
1595
1593
  ============================================ */
1596
1594
  .section-indicator {
1597
1595
  position: fixed;
1598
- right: 25px;
1596
+ right: 20px;
1599
1597
  top: 50%;
1600
1598
  transform: translateY(-50%);
1601
1599
  display: flex;
1602
1600
  flex-direction: column;
1603
- gap: 12px;
1601
+ gap: 10px;
1604
1602
  z-index: 1000;
1605
1603
  }
1606
1604
 
1607
1605
  .section-dot {
1608
1606
  width: 10px;
1609
1607
  height: 10px;
1610
- border: 1px solid var(--grey);
1611
- border-radius: 50%;
1608
+ border: 2px solid var(--border-color);
1609
+ border-radius: 0;
1612
1610
  cursor: pointer;
1613
- transition: all 0.3s;
1611
+ transition: all 0.2s;
1612
+ background: var(--card-bg);
1614
1613
  }
1615
1614
 
1616
1615
  .section-dot:hover,
1617
1616
  .section-dot.active {
1618
1617
  background: var(--accent-red);
1619
1618
  border-color: var(--accent-red);
1620
- box-shadow: 0 0 12px var(--accent-red-glow);
1619
+ box-shadow: 0 0 8px var(--accent-red-glow);
1621
1620
  }
1622
1621
 
1623
1622
  /* ============================================
@@ -1700,16 +1699,82 @@
1700
1699
  <nav class="nav">
1701
1700
  <button class="nav-btn" id="playBtn">▶ AUTO PLAY</button>
1702
1701
  <button class="nav-btn" id="audioToggleBtn" title="Toggle narration audio">🔊 WITH AUDIO</button>
1702
+ <button class="nav-btn" id="voiceSelectBtn" title="Choose narrator voice">🎙 VOICE</button>
1703
1703
  <button class="nav-btn" id="resetBtn">↺ RESTART</button>
1704
1704
  </nav>
1705
1705
 
1706
+ <!-- Voice Preview Modal -->
1707
+ <div class="voice-modal-overlay" id="voiceModalOverlay">
1708
+ <div class="voice-modal">
1709
+ <div class="voice-modal-header">
1710
+ <div class="voice-modal-title">Choose Narrator Voice</div>
1711
+ <button class="voice-modal-close" id="voiceModalClose">×</button>
1712
+ </div>
1713
+
1714
+ <div class="voice-options">
1715
+ <div class="voice-option selected" data-voice="adam" data-file="audio/narration-adam.mp3" data-timing="audio/narration-adam-timing.json" data-duration="370.8">
1716
+ <button class="voice-play-btn" data-preview="audio/narration-adam.mp3">▶</button>
1717
+ <div class="voice-info">
1718
+ <div class="voice-name">Adam</div>
1719
+ <div class="voice-desc">Deep, professional (~6 min)</div>
1720
+ </div>
1721
+ <button class="voice-select-btn">Selected</button>
1722
+ </div>
1723
+
1724
+ <div class="voice-option" data-voice="mark" data-file="audio/narration-mark.mp3" data-timing="audio/narration-mark-timing.json" data-duration="338.7">
1725
+ <button class="voice-play-btn" data-preview="audio/previews/preview-mark.mp3">▶</button>
1726
+ <div class="voice-info">
1727
+ <div class="voice-name">Mark</div>
1728
+ <div class="voice-desc">Warm, conversational (~5.5 min)</div>
1729
+ </div>
1730
+ <button class="voice-select-btn">Select</button>
1731
+ </div>
1732
+
1733
+ <div class="voice-option" data-voice="hope" data-file="audio/narration-hope.mp3" data-timing="audio/narration-hope-timing.json" data-duration="305.2">
1734
+ <button class="voice-play-btn" data-preview="audio/previews/preview-hope.mp3">▶</button>
1735
+ <div class="voice-info">
1736
+ <div class="voice-name">Hope</div>
1737
+ <div class="voice-desc">Bright, energetic (~5 min)</div>
1738
+ </div>
1739
+ <button class="voice-select-btn">Select</button>
1740
+ </div>
1741
+
1742
+ <div class="voice-option" data-voice="creature" data-file="audio/narration-creature.mp3" data-timing="audio/narration-creature-timing.json" data-duration="466.6">
1743
+ <button class="voice-play-btn" data-preview="audio/previews/preview-creature.mp3">▶</button>
1744
+ <div class="voice-info">
1745
+ <div class="voice-name">Creature</div>
1746
+ <div class="voice-desc">Unique, character voice (~7.5 min)</div>
1747
+ </div>
1748
+ <button class="voice-select-btn">Select</button>
1749
+ </div>
1750
+
1751
+ <div class="voice-option" data-voice="gaming" data-file="audio/narration-gaming.mp3" data-timing="audio/narration-gaming-timing.json" data-duration="644.1">
1752
+ <button class="voice-play-btn" data-preview="audio/previews/preview-gaming.mp3">▶</button>
1753
+ <div class="voice-info">
1754
+ <div class="voice-name">Gaming</div>
1755
+ <div class="voice-desc">Dynamic, enthusiastic (~10.5 min)</div>
1756
+ </div>
1757
+ <button class="voice-select-btn">Select</button>
1758
+ </div>
1759
+ </div>
1760
+
1761
+ <div class="voice-modal-footer">
1762
+ <div class="voice-modal-note">All voices have full narration with synchronized highlights.</div>
1763
+ <button class="voice-confirm-btn" id="voiceConfirmBtn">Confirm Selection</button>
1764
+ </div>
1765
+ </div>
1766
+ </div>
1767
+
1706
1768
  <!-- Audio Narration Player (hidden) -->
1707
1769
  <audio id="narrationAudio" preload="auto">
1708
- <source src="audio/narration.mp3" type="audio/mpeg">
1770
+ <source src="audio/narration-adam.mp3" type="audio/mpeg">
1709
1771
  </audio>
1710
1772
 
1711
1773
  <!-- Audio Progress Bar -->
1712
1774
  <div class="audio-progress-container" id="audioProgressContainer" style="display: none;">
1775
+ <div class="chapter-buttons" id="chapterButtons">
1776
+ <!-- Chapter buttons populated dynamically from timing data -->
1777
+ </div>
1713
1778
  <div class="audio-time-display">
1714
1779
  <span id="audioCurrentTime">0:00</span>
1715
1780
  <span id="audioTotalTime">0:00</span>
@@ -1733,7 +1798,7 @@
1733
1798
  <span class="hustle-word hustle-highlight">API-DEV-TOOLS</span>
1734
1799
  </div>
1735
1800
  <div class="package-name" id="packageName">@hustle-together/api-dev-tools</div>
1736
- <div class="version" id="versionText">v2.0.7</div>
1801
+ <div class="version" id="versionText">v2.0.8</div>
1737
1802
  <p class="tagline">"Hustle together. Share resources. Build stronger."<span class="cursor"></span></p>
1738
1803
 
1739
1804
  <div class="intro-text">
@@ -2804,6 +2869,7 @@
2804
2869
  const audioCurrentTime = document.getElementById('audioCurrentTime');
2805
2870
  const audioTotalTime = document.getElementById('audioTotalTime');
2806
2871
  const audioSectionMarkers = document.getElementById('audioSectionMarkers');
2872
+ const chapterButtons = document.getElementById('chapterButtons');
2807
2873
 
2808
2874
  let audioEnabled = true; // Audio on by default
2809
2875
  let timingData = null;
@@ -2819,11 +2885,15 @@
2819
2885
  return `${mins}:${secs.toString().padStart(2, '0')}`;
2820
2886
  }
2821
2887
 
2822
- // Load timing data
2823
- async function loadTimingData() {
2824
- if (timingData) return timingData;
2888
+ // Current timing URL (can be changed when voice changes)
2889
+ let currentTimingUrl = 'audio/narration-adam-timing.json';
2890
+
2891
+ // Load timing data (pass URL to force reload with different voice)
2892
+ async function loadTimingData(url = null, forceReload = false) {
2893
+ if (url) currentTimingUrl = url;
2894
+ if (timingData && !forceReload) return timingData;
2825
2895
  try {
2826
- const response = await fetch('audio/narration-timing.json');
2896
+ const response = await fetch(currentTimingUrl);
2827
2897
  if (!response.ok) throw new Error('Failed to load timing data');
2828
2898
  timingData = await response.json();
2829
2899
 
@@ -2845,6 +2915,116 @@
2845
2915
  });
2846
2916
  }
2847
2917
 
2918
+ // Create chapter buttons for navigation
2919
+ if (chapterButtons && timingData.sections) {
2920
+ chapterButtons.innerHTML = '';
2921
+ timingData.sections.forEach((section, i) => {
2922
+ const btn = document.createElement('button');
2923
+ btn.className = 'chapter-btn';
2924
+ btn.textContent = section.id.toUpperCase();
2925
+ btn.dataset.timestamp = section.timestamp;
2926
+ btn.dataset.sectionId = section.id;
2927
+ btn.dataset.index = i;
2928
+
2929
+ // Click handler: seek audio to timestamp + scroll to section
2930
+ btn.addEventListener('click', (e) => {
2931
+ e.stopPropagation(); // Prevent bubbling to progress bar
2932
+
2933
+ const seekTime = section.timestamp;
2934
+ console.log(`Chapter click: seeking to ${section.id} at ${seekTime.toFixed(1)}s`);
2935
+
2936
+ // Function to perform the seek after audio is ready
2937
+ const doSeek = () => {
2938
+ console.log(`Performing seek to ${seekTime.toFixed(1)}s, audio readyState: ${narrationAudio.readyState}`);
2939
+ narrationAudio.currentTime = seekTime;
2940
+ console.log(`After setting currentTime: ${narrationAudio.currentTime.toFixed(1)}s`);
2941
+ };
2942
+
2943
+ // If not playing, start first then seek
2944
+ if (!isPlaying) {
2945
+ isPlaying = true;
2946
+ playBtn.classList.add('active');
2947
+ playBtn.textContent = '⏸ STOP';
2948
+
2949
+ if (window.introLightingTL) {
2950
+ window.introLightingTL.kill();
2951
+ window.introLightingTL = null;
2952
+ document.querySelectorAll('#introFlow .phase-node').forEach(n => n.classList.remove('active'));
2953
+ document.querySelectorAll('#introFlow .phase-connector-arrow').forEach(a => a.classList.remove('active'));
2954
+ }
2955
+
2956
+ audioProgressContainer.style.display = 'block';
2957
+
2958
+ // Wait for audio to be ready enough to seek
2959
+ if (narrationAudio.readyState >= 1) {
2960
+ doSeek();
2961
+ narrationAudio.play().catch(err => console.error('Playback failed:', err));
2962
+ } else {
2963
+ // Audio not ready yet, wait for loadedmetadata
2964
+ narrationAudio.addEventListener('loadedmetadata', function onLoaded() {
2965
+ narrationAudio.removeEventListener('loadedmetadata', onLoaded);
2966
+ doSeek();
2967
+ narrationAudio.play().catch(err => console.error('Playback failed:', err));
2968
+ });
2969
+ narrationAudio.load(); // Trigger loading
2970
+ }
2971
+ } else {
2972
+ // Already playing - pause, seek, then play
2973
+ narrationAudio.pause();
2974
+
2975
+ const performSeekAndPlay = () => {
2976
+ doSeek();
2977
+ narrationAudio.play().catch(err => console.error('Playback failed:', err));
2978
+ };
2979
+
2980
+ // Use timingData.duration as indicator that we have valid timing for this voice
2981
+ // Also check readyState >= 2 (HAVE_CURRENT_DATA) for reliable seeking
2982
+ if (timingData && timingData.duration > 0 && narrationAudio.readyState >= 2) {
2983
+ performSeekAndPlay();
2984
+ } else {
2985
+ // Audio not ready (e.g. after voice switch), wait for canplay
2986
+ console.log('Audio not ready for seek, waiting for canplay...');
2987
+ narrationAudio.addEventListener('canplay', function onReady() {
2988
+ narrationAudio.removeEventListener('canplay', onReady);
2989
+ performSeekAndPlay();
2990
+ }, { once: true });
2991
+ // Also try loadedmetadata as backup
2992
+ narrationAudio.addEventListener('loadedmetadata', function onMeta() {
2993
+ narrationAudio.removeEventListener('loadedmetadata', onMeta);
2994
+ if (narrationAudio.readyState >= 2) {
2995
+ performSeekAndPlay();
2996
+ }
2997
+ }, { once: true });
2998
+ narrationAudio.load();
2999
+ }
3000
+ }
3001
+
3002
+ // Update highlight tracking for new position
3003
+ highlightIndex = 0;
3004
+ if (timingData && timingData.highlights) {
3005
+ for (let j = 0; j < timingData.highlights.length; j++) {
3006
+ if (timingData.highlights[j].timestamp <= seekTime) {
3007
+ highlightIndex = j;
3008
+ } else {
3009
+ break;
3010
+ }
3011
+ }
3012
+ }
3013
+
3014
+ // Scroll to section
3015
+ const sectionEl = document.getElementById(section.id);
3016
+ if (sectionEl) {
3017
+ sectionEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
3018
+ }
3019
+
3020
+ // Update active state
3021
+ updateActiveChapter(i);
3022
+ });
3023
+
3024
+ chapterButtons.appendChild(btn);
3025
+ });
3026
+ }
3027
+
2848
3028
  console.log('Timing data loaded:', timingData.sections.length, 'sections,', timingData.highlights.length, 'highlights');
2849
3029
  return timingData;
2850
3030
  } catch (error) {
@@ -2872,8 +3052,8 @@
2872
3052
 
2873
3053
  if (isContainer) {
2874
3054
  gsap.fromTo(el,
2875
- { boxShadow: '0 0 0px var(--accent-red-glow)' },
2876
- { boxShadow: '0 0 30px var(--accent-red-glow)', duration: 0.4, ease: 'power2.out' }
3055
+ { boxShadow: '0 4px 6px rgba(0,0,0,0.05)' },
3056
+ { boxShadow: '0 8px 25px rgba(0,0,0,0.15)', duration: 0.4, ease: 'power2.out' }
2877
3057
  );
2878
3058
  }
2879
3059
  }
@@ -2891,6 +3071,29 @@
2891
3071
  }
2892
3072
  }
2893
3073
 
3074
+ // Update active chapter button based on index
3075
+ function updateActiveChapter(index) {
3076
+ if (!chapterButtons) return;
3077
+ const buttons = chapterButtons.querySelectorAll('.chapter-btn');
3078
+ buttons.forEach((btn, i) => {
3079
+ btn.classList.toggle('active', i === index);
3080
+ });
3081
+ }
3082
+
3083
+ // Find current section index based on audio time
3084
+ function getCurrentSectionIndex(currentTime) {
3085
+ if (!timingData || !timingData.sections) return 0;
3086
+ let idx = 0;
3087
+ for (let i = 0; i < timingData.sections.length; i++) {
3088
+ if (currentTime >= timingData.sections[i].timestamp) {
3089
+ idx = i;
3090
+ } else {
3091
+ break;
3092
+ }
3093
+ }
3094
+ return idx;
3095
+ }
3096
+
2894
3097
  // Handle audio time update (syncs highlights with narration)
2895
3098
  function onAudioTimeUpdate() {
2896
3099
  if (!timingData || !audioEnabled) return;
@@ -2906,6 +3109,13 @@
2906
3109
  audioCurrentTime.textContent = formatTime(currentTime);
2907
3110
  }
2908
3111
 
3112
+ // Update active chapter button
3113
+ const sectionIdx = getCurrentSectionIndex(currentTime);
3114
+ if (sectionIdx !== currentSectionIdx) {
3115
+ currentSectionIdx = sectionIdx;
3116
+ updateActiveChapter(sectionIdx);
3117
+ }
3118
+
2909
3119
  // Check for highlight triggers
2910
3120
  for (let i = highlightIndex; i < timingData.highlights.length; i++) {
2911
3121
  const highlight = timingData.highlights[i];
@@ -2921,23 +3131,73 @@
2921
3131
  }
2922
3132
 
2923
3133
  // Handle seek on progress bar click
2924
- function onProgressBarClick(e) {
2925
- if (!timingData || !narrationAudio.duration) return;
3134
+ async function onProgressBarClick(e) {
3135
+ e.stopPropagation(); // Prevent event bubbling
2926
3136
 
3137
+ // Use the progress bar's rect for calculation
2927
3138
  const rect = audioProgressBar.getBoundingClientRect();
2928
3139
  const clickX = e.clientX - rect.left;
2929
- const percentage = clickX / rect.width;
2930
- const seekTime = percentage * timingData.duration;
3140
+ const percentage = Math.max(0, Math.min(1, clickX / rect.width));
3141
+
3142
+ // ALWAYS prefer timingData.duration (most reliable after voice switch)
3143
+ // Only fall back to narrationAudio.duration if timing data unavailable
3144
+ let duration = (timingData && timingData.duration > 0) ? timingData.duration : narrationAudio.duration;
3145
+
3146
+ if (!duration || duration === 0 || isNaN(duration)) {
3147
+ console.warn('No duration available, waiting for audio to load...');
3148
+ // Wait for audio to load
3149
+ await new Promise((resolve) => {
3150
+ let resolved = false;
3151
+ const tryResolve = () => {
3152
+ if (!resolved && narrationAudio.duration > 0) {
3153
+ resolved = true;
3154
+ resolve();
3155
+ }
3156
+ };
3157
+ narrationAudio.addEventListener('loadedmetadata', tryResolve, { once: true });
3158
+ narrationAudio.addEventListener('canplay', tryResolve, { once: true });
3159
+ narrationAudio.load();
3160
+ setTimeout(() => { if (!resolved) { resolved = true; resolve(); } }, 2000);
3161
+ });
3162
+ duration = narrationAudio.duration;
3163
+ if (!duration || duration === 0 || isNaN(duration)) {
3164
+ console.error('Still no duration available after waiting');
3165
+ return;
3166
+ }
3167
+ }
3168
+
3169
+ const seekTime = percentage * duration;
3170
+ console.log(`Progress bar click: seeking to ${seekTime.toFixed(1)}s (${(percentage * 100).toFixed(1)}%) using duration ${duration.toFixed(1)}s`);
3171
+
3172
+ // Pause, seek, then play (most reliable method)
3173
+ const wasPlaying = !narrationAudio.paused;
3174
+ narrationAudio.pause();
3175
+
3176
+ // Ensure audio is ready to seek (especially important after voice switch)
3177
+ if (narrationAudio.readyState < 2) {
3178
+ console.log('Waiting for audio to be ready for seeking...');
3179
+ await new Promise((resolve) => {
3180
+ narrationAudio.addEventListener('canplay', resolve, { once: true });
3181
+ setTimeout(resolve, 1000);
3182
+ });
3183
+ }
2931
3184
 
2932
3185
  narrationAudio.currentTime = seekTime;
3186
+ console.log(`After setting currentTime: ${narrationAudio.currentTime.toFixed(1)}s`);
3187
+
3188
+ if (wasPlaying) {
3189
+ narrationAudio.play().catch(err => console.error('Playback failed:', err));
3190
+ }
2933
3191
 
2934
3192
  // Reset highlight tracking
2935
3193
  highlightIndex = 0;
2936
- for (let i = 0; i < timingData.highlights.length; i++) {
2937
- if (timingData.highlights[i].timestamp <= seekTime) {
2938
- highlightIndex = i;
2939
- } else {
2940
- break;
3194
+ if (timingData && timingData.highlights) {
3195
+ for (let i = 0; i < timingData.highlights.length; i++) {
3196
+ if (timingData.highlights[i].timestamp <= seekTime) {
3197
+ highlightIndex = i;
3198
+ } else {
3199
+ break;
3200
+ }
2941
3201
  }
2942
3202
  }
2943
3203
  }
@@ -2963,9 +3223,11 @@
2963
3223
  playBtn.classList.add('active');
2964
3224
  playBtn.textContent = '⏸ STOP';
2965
3225
 
2966
- // Pause the intro lighting animation to avoid visual conflict
3226
+ // KILL the intro lighting animation completely to avoid visual conflict
3227
+ // (Don't just pause - kill it so it won't restart)
2967
3228
  if (window.introLightingTL) {
2968
- window.introLightingTL.pause();
3229
+ window.introLightingTL.kill();
3230
+ window.introLightingTL = null;
2969
3231
  // Reset the phase nodes to clean state
2970
3232
  document.querySelectorAll('#introFlow .phase-node').forEach(n => n.classList.remove('active'));
2971
3233
  document.querySelectorAll('#introFlow .phase-connector-arrow').forEach(a => a.classList.remove('active'));
@@ -3016,10 +3278,8 @@
3016
3278
  highlightIndex = 0;
3017
3279
  currentSectionIdx = 0;
3018
3280
 
3019
- // Resume the intro lighting animation
3020
- if (window.introLightingTL) {
3021
- window.introLightingTL.restart();
3022
- }
3281
+ // Note: We killed the intro lighting animation, so don't try to restart it
3282
+ // The animation was distracting during/after narration playback
3023
3283
  }
3024
3284
 
3025
3285
  // Audio ended
@@ -3069,6 +3329,31 @@
3069
3329
  audioToggleBtn.addEventListener('click', toggleAudio);
3070
3330
  narrationAudio.addEventListener('timeupdate', onAudioTimeUpdate);
3071
3331
  narrationAudio.addEventListener('ended', onAudioEnded);
3332
+
3333
+ // DEBUG: Global click handler to see what's being clicked anywhere on page
3334
+ document.addEventListener('click', (e) => {
3335
+ // Only log clicks in the bottom 200px of the screen (where the audio player is)
3336
+ if (e.clientY > window.innerHeight - 200) {
3337
+ console.log('%c CLICK DETECTED ', 'background: red; color: white; font-size: 14px;', {
3338
+ element: e.target.tagName,
3339
+ id: e.target.id || '(no id)',
3340
+ class: e.target.className || '(no class)',
3341
+ x: e.clientX,
3342
+ y: e.clientY,
3343
+ audioCurrentTime: narrationAudio.currentTime.toFixed(1),
3344
+ audioDuration: narrationAudio.duration ? narrationAudio.duration.toFixed(1) : 'not loaded'
3345
+ });
3346
+ }
3347
+ }, true); // Use capture phase to see ALL clicks before they're handled
3348
+
3349
+ // Debug: track seeking events
3350
+ narrationAudio.addEventListener('seeking', () => {
3351
+ console.log('%c AUDIO SEEKING ', 'background: blue; color: white;', 'to:', narrationAudio.currentTime.toFixed(1));
3352
+ });
3353
+ narrationAudio.addEventListener('seeked', () => {
3354
+ console.log('%c AUDIO SEEKED ', 'background: green; color: white;', 'to:', narrationAudio.currentTime.toFixed(1));
3355
+ });
3356
+
3072
3357
  audioProgressBar.addEventListener('click', onProgressBarClick);
3073
3358
 
3074
3359
  // Check if audio file is accessible
@@ -3087,6 +3372,10 @@
3087
3372
  // RESET BUTTON
3088
3373
  // ============================================
3089
3374
  document.getElementById('resetBtn').addEventListener('click', () => {
3375
+ // Stop audio and auto-play first
3376
+ stopAutoPlay();
3377
+
3378
+ // Scroll to top
3090
3379
  window.scrollTo({ top: 0, behavior: 'smooth' });
3091
3380
 
3092
3381
  // Reset animations
@@ -3104,11 +3393,20 @@
3104
3393
  gsap.set('.credit-link, .made-with', { opacity: 0 });
3105
3394
  gsap.set('.intro-text, .scroll-hint', { opacity: 0 });
3106
3395
 
3396
+ // Reset phase boxes
3107
3397
  document.querySelectorAll('.phase-box').forEach(box => {
3108
3398
  box.classList.remove('active');
3109
3399
  box.querySelector('.phase-status').textContent = '';
3110
3400
  });
3111
3401
 
3402
+ // Reset intro phase nodes
3403
+ document.querySelectorAll('.phase-node').forEach(node => {
3404
+ node.classList.remove('active');
3405
+ });
3406
+ document.querySelectorAll('.phase-connector-arrow').forEach(arrow => {
3407
+ arrow.classList.remove('active');
3408
+ });
3409
+
3112
3410
  ScrollTrigger.refresh();
3113
3411
  }, 500);
3114
3412
  });
@@ -3282,6 +3580,173 @@
3282
3580
  });
3283
3581
  });
3284
3582
 
3583
+ // ============================================
3584
+ // VOICE PREVIEW MODAL
3585
+ // ============================================
3586
+ const voiceSelectBtn = document.getElementById('voiceSelectBtn');
3587
+ const voiceModalOverlay = document.getElementById('voiceModalOverlay');
3588
+ const voiceModalClose = document.getElementById('voiceModalClose');
3589
+ const voiceConfirmBtn = document.getElementById('voiceConfirmBtn');
3590
+ const voiceOptions = document.querySelectorAll('.voice-option');
3591
+ const voicePlayBtns = document.querySelectorAll('.voice-play-btn');
3592
+
3593
+ let previewAudio = null;
3594
+ let currentlyPlayingBtn = null;
3595
+ let selectedVoice = 'adam';
3596
+
3597
+ // Open modal
3598
+ voiceSelectBtn.addEventListener('click', () => {
3599
+ voiceModalOverlay.classList.add('active');
3600
+ });
3601
+
3602
+ // Close modal
3603
+ function closeVoiceModal() {
3604
+ voiceModalOverlay.classList.remove('active');
3605
+ // Stop any playing preview
3606
+ if (previewAudio) {
3607
+ previewAudio.pause();
3608
+ previewAudio = null;
3609
+ }
3610
+ if (currentlyPlayingBtn) {
3611
+ currentlyPlayingBtn.textContent = '▶';
3612
+ currentlyPlayingBtn.classList.remove('playing');
3613
+ currentlyPlayingBtn = null;
3614
+ }
3615
+ }
3616
+
3617
+ voiceModalClose.addEventListener('click', closeVoiceModal);
3618
+ voiceModalOverlay.addEventListener('click', (e) => {
3619
+ if (e.target === voiceModalOverlay) {
3620
+ closeVoiceModal();
3621
+ }
3622
+ });
3623
+
3624
+ // Play preview
3625
+ voicePlayBtns.forEach(btn => {
3626
+ btn.addEventListener('click', (e) => {
3627
+ e.stopPropagation();
3628
+ const previewSrc = btn.dataset.preview;
3629
+
3630
+ // If clicking the same button that's playing, stop it
3631
+ if (currentlyPlayingBtn === btn && previewAudio && !previewAudio.paused) {
3632
+ previewAudio.pause();
3633
+ btn.textContent = '▶';
3634
+ btn.classList.remove('playing');
3635
+ currentlyPlayingBtn = null;
3636
+ return;
3637
+ }
3638
+
3639
+ // Stop any currently playing preview
3640
+ if (previewAudio) {
3641
+ previewAudio.pause();
3642
+ }
3643
+ if (currentlyPlayingBtn) {
3644
+ currentlyPlayingBtn.textContent = '▶';
3645
+ currentlyPlayingBtn.classList.remove('playing');
3646
+ }
3647
+
3648
+ // Play new preview
3649
+ previewAudio = new Audio(previewSrc);
3650
+ previewAudio.play().catch(err => {
3651
+ console.error('Audio play failed:', err);
3652
+ btn.textContent = '⚠';
3653
+ setTimeout(() => { btn.textContent = '▶'; }, 2000);
3654
+ });
3655
+
3656
+ btn.textContent = '⏸';
3657
+ btn.classList.add('playing');
3658
+ currentlyPlayingBtn = btn;
3659
+
3660
+ previewAudio.addEventListener('ended', () => {
3661
+ btn.textContent = '▶';
3662
+ btn.classList.remove('playing');
3663
+ currentlyPlayingBtn = null;
3664
+ });
3665
+ });
3666
+ });
3667
+
3668
+ // Select voice
3669
+ voiceOptions.forEach(option => {
3670
+ option.addEventListener('click', () => {
3671
+ // Remove selected from all
3672
+ voiceOptions.forEach(opt => {
3673
+ opt.classList.remove('selected');
3674
+ opt.querySelector('.voice-select-btn').textContent = 'Select';
3675
+ });
3676
+
3677
+ // Add selected to clicked
3678
+ option.classList.add('selected');
3679
+ option.querySelector('.voice-select-btn').textContent = 'Selected';
3680
+ selectedVoice = option.dataset.voice;
3681
+ });
3682
+ });
3683
+
3684
+ // Confirm selection
3685
+ voiceConfirmBtn.addEventListener('click', async () => {
3686
+ const selectedOption = document.querySelector('.voice-option.selected');
3687
+ if (selectedOption) {
3688
+ const voiceFile = selectedOption.dataset.file;
3689
+ const timingFile = selectedOption.dataset.timing;
3690
+ const voiceName = selectedOption.querySelector('.voice-name').textContent;
3691
+
3692
+ // Stop current playback if any
3693
+ if (isPlaying) {
3694
+ stopAutoPlay();
3695
+ }
3696
+
3697
+ // Update audio source
3698
+ narrationAudio.src = voiceFile;
3699
+
3700
+ // Force load the new audio and wait for it to be ready
3701
+ narrationAudio.load();
3702
+
3703
+ // Wait for the audio to load metadata AND have valid duration (don't trust stale readyState)
3704
+ await new Promise((resolve) => {
3705
+ let resolved = false;
3706
+ const tryResolve = () => {
3707
+ if (!resolved && narrationAudio.duration > 0 && !isNaN(narrationAudio.duration)) {
3708
+ resolved = true;
3709
+ resolve();
3710
+ }
3711
+ };
3712
+ // Listen for multiple events to ensure we catch when audio is truly ready
3713
+ narrationAudio.addEventListener('loadedmetadata', tryResolve, { once: true });
3714
+ narrationAudio.addEventListener('canplay', tryResolve, { once: true });
3715
+ narrationAudio.addEventListener('durationchange', tryResolve, { once: true });
3716
+ // Timeout fallback in case load fails
3717
+ setTimeout(() => {
3718
+ if (!resolved) {
3719
+ console.warn('Audio load timeout, proceeding anyway');
3720
+ resolved = true;
3721
+ resolve();
3722
+ }
3723
+ }, 3000);
3724
+ });
3725
+
3726
+ // Reset audio position and clear old state
3727
+ narrationAudio.currentTime = 0;
3728
+ timingData = null;
3729
+ highlightIndex = 0;
3730
+ currentSectionIdx = 0;
3731
+ await loadTimingData(timingFile, true);
3732
+
3733
+ // Update voice button to show selected voice
3734
+ voiceSelectBtn.textContent = `🎙 ${voiceName.toUpperCase()}`;
3735
+
3736
+ console.log(`Voice changed to: ${voiceName} (${voiceFile})`);
3737
+ console.log(`Audio duration: ${narrationAudio.duration}s, readyState: ${narrationAudio.readyState}`);
3738
+ console.log(`Timing data loaded: ${timingFile}`);
3739
+ }
3740
+ closeVoiceModal();
3741
+ });
3742
+
3743
+ // Keyboard close (Escape)
3744
+ document.addEventListener('keydown', (e) => {
3745
+ if (e.key === 'Escape' && voiceModalOverlay.classList.contains('active')) {
3746
+ closeVoiceModal();
3747
+ }
3748
+ });
3749
+
3285
3750
  </script>
3286
3751
 
3287
3752
  </body>