@hustle-together/api-dev-tools 2.0.6 → 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 +1054 -544
  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,448 +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
- box-shadow: 0 0 30px var(--accent-red-glow), 0 0 60px var(--accent-red-glow) !important;
587
- border-color: var(--accent-red) !important;
588
- transform: scale(1.02);
546
+ outline: 2px solid var(--accent-red) !important;
547
+ outline-offset: 4px;
589
548
  transition: all 0.3s ease !important;
549
+ box-shadow: 0 0 20px var(--accent-red-glow) !important;
550
+ }
551
+
552
+ .audio-highlighted.solution-card,
553
+ .audio-highlighted.phase-box,
554
+ .audio-highlighted.gap-item,
555
+ .audio-highlighted.phase-node {
556
+ outline: none !important;
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;
590
560
  }
591
561
 
592
562
  /* Progress indicator */
593
563
  .progress-bar {
594
564
  position: fixed;
595
- top: 0;
565
+ top: 61px;
596
566
  left: 0;
597
- height: 3px;
567
+ height: 2px;
598
568
  background: var(--accent-red);
599
569
  width: 0%;
600
570
  z-index: 1001;
571
+ box-shadow: 0 0 10px var(--accent-red-glow);
601
572
  }
602
573
 
603
- /* Section base */
574
+ /* ============================================
575
+ SECTIONS & CONTENT
576
+ ============================================ */
604
577
  section {
605
578
  min-height: 100vh;
606
579
  display: flex;
607
580
  flex-direction: column;
608
581
  justify-content: center;
609
582
  align-items: center;
610
- padding: 80px 40px;
583
+ padding: 120px 40px 80px;
611
584
  position: relative;
612
585
  }
613
586
 
614
- /* ASCII border decoration */
615
587
  .ascii-border {
616
- border: 1px dashed var(--grey);
588
+ border: 1px dashed var(--border-color);
617
589
  padding: 50px;
618
590
  position: relative;
619
- max-width: 1000px;
591
+ max-width: 900px;
620
592
  width: 100%;
621
- /* Blurred glass backdrop so boxes don't interfere with background animation */
622
- background: rgba(10, 10, 10, 0.85);
623
- backdrop-filter: blur(12px);
624
- -webkit-backdrop-filter: blur(12px);
593
+ background: rgba(10, 10, 10, 0.8);
625
594
  }
626
595
 
627
- .ascii-border::before {
596
+ .ascii-border::before,
597
+ .ascii-border::after {
628
598
  content: '+';
629
599
  position: absolute;
600
+ color: var(--border-color);
601
+ font-size: 1rem;
602
+ }
603
+
604
+ .ascii-border::before {
630
605
  top: -8px;
631
606
  left: -8px;
632
- color: var(--grey);
633
607
  }
634
608
 
635
609
  .ascii-border::after {
636
- content: '+';
637
- position: absolute;
638
610
  bottom: -8px;
639
611
  right: -8px;
640
- color: var(--grey);
641
612
  }
642
613
 
643
614
  /* Typography */
644
615
  h1 {
645
- font-size: 2.5rem;
646
- font-weight: normal;
647
- letter-spacing: 4px;
616
+ font-family: 'Fredoka', sans-serif;
617
+ font-size: 2.8rem;
618
+ font-weight: 700;
648
619
  margin-bottom: 20px;
620
+ color: var(--white);
649
621
  }
650
622
 
651
623
  h2 {
652
- font-size: 1.8rem;
653
- font-weight: normal;
654
- letter-spacing: 2px;
655
- 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);
656
629
  }
657
630
 
658
631
  h3 {
659
- font-size: 1.2rem;
632
+ font-size: 1rem;
660
633
  margin-bottom: 15px;
661
634
  text-transform: uppercase;
662
635
  letter-spacing: 1px;
663
636
  color: var(--grey);
664
637
  }
665
638
 
666
- /* Explanation blocks - THE KEY EDUCATIONAL ELEMENT */
639
+ /* Explanation blocks */
667
640
  .explanation {
668
- background: rgba(255, 255, 255, 0.03);
669
- 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);
670
644
  padding: 25px 30px;
671
645
  margin: 30px 0;
672
646
  font-size: 1rem;
673
- line-height: 1.9;
647
+ line-height: 1.8;
674
648
  opacity: 0;
675
649
  }
676
650
 
677
651
  .explanation-title {
678
- font-size: 0.85rem;
652
+ font-size: 0.75rem;
679
653
  text-transform: uppercase;
680
- letter-spacing: 3px;
681
- color: var(--grey);
654
+ letter-spacing: 2px;
655
+ color: var(--accent-red);
682
656
  margin-bottom: 15px;
683
- display: flex;
684
- align-items: center;
685
- gap: 10px;
657
+ font-weight: 600;
686
658
  }
687
659
 
688
660
  .explanation-title::before {
689
- content: '?';
690
- width: 24px;
691
- height: 24px;
692
- border: 1px dashed var(--grey);
693
- display: flex;
694
- align-items: center;
695
- justify-content: center;
696
- font-size: 0.9rem;
661
+ content: '// ';
662
+ color: var(--grey);
697
663
  }
698
664
 
699
665
  .explanation p {
700
666
  margin-bottom: 15px;
667
+ color: var(--grey);
701
668
  }
702
669
 
703
670
  .explanation p:last-child {
@@ -706,14 +673,13 @@
706
673
 
707
674
  .explanation strong {
708
675
  color: var(--white);
709
- font-weight: normal;
710
- border-bottom: 1px dashed var(--grey);
676
+ font-weight: 600;
711
677
  }
712
678
 
713
679
  /* Real example callout */
714
680
  .real-example {
715
- background: rgba(255, 255, 255, 0.05);
716
- border: 1px dashed var(--white);
681
+ background: var(--darker-grey);
682
+ border: 2px solid var(--border-color);
717
683
  padding: 20px 25px;
718
684
  margin: 20px 0;
719
685
  position: relative;
@@ -724,11 +690,12 @@
724
690
  position: absolute;
725
691
  top: -10px;
726
692
  left: 20px;
727
- background: var(--black);
693
+ background: var(--card-bg);
728
694
  padding: 0 10px;
729
- font-size: 0.75rem;
695
+ font-size: 0.7rem;
730
696
  letter-spacing: 2px;
731
- color: var(--grey);
697
+ color: var(--accent-red);
698
+ border: 1px solid var(--border-color);
732
699
  }
733
700
 
734
701
  .real-example-content {
@@ -740,7 +707,7 @@
740
707
  display: inline-block;
741
708
  width: 10px;
742
709
  height: 20px;
743
- background: var(--white);
710
+ background: var(--accent-red);
744
711
  animation: blink 1s infinite;
745
712
  vertical-align: middle;
746
713
  margin-left: 5px;
@@ -758,10 +725,11 @@
758
725
  justify-content: center;
759
726
  width: 50px;
760
727
  height: 50px;
761
- border: 2px solid var(--white);
728
+ border: 2px solid var(--accent-red);
762
729
  font-size: 1.5rem;
763
730
  margin-right: 20px;
764
731
  flex-shrink: 0;
732
+ color: var(--accent-red);
765
733
  }
766
734
 
767
735
  .step-header {
@@ -828,11 +796,12 @@
828
796
 
829
797
  .hustle-highlight {
830
798
  color: var(--accent-red);
831
- 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);
832
800
  }
833
801
 
834
802
  .text-red {
835
803
  color: var(--accent-red);
804
+ font-weight: 600;
836
805
  }
837
806
 
838
807
  /* Intro section centering */
@@ -863,13 +832,13 @@
863
832
  }
864
833
 
865
834
  /* ============================================
866
- PHASE FLOW - LIGHTING UP SEQUENCE
835
+ PHASE FLOW - LIGHTING UP SEQUENCE (boxy)
867
836
  ============================================ */
868
837
  .phase-flow {
869
838
  display: flex;
870
839
  align-items: center;
871
840
  justify-content: center;
872
- gap: 15px;
841
+ gap: 12px;
873
842
  margin-top: 50px;
874
843
  flex-wrap: wrap;
875
844
  opacity: 0;
@@ -877,30 +846,31 @@
877
846
 
878
847
  .phase-node {
879
848
  position: relative;
880
- border: 2px solid var(--dark-grey);
849
+ border: 2px solid var(--border-color);
850
+ background: var(--darker-grey);
881
851
  padding: 20px 25px;
882
852
  text-align: center;
883
853
  min-width: 100px;
884
- transition: all 0.4s ease;
854
+ transition: all 0.3s ease;
885
855
  opacity: 0;
886
856
  transform: translateY(20px);
887
857
  }
888
858
 
889
859
  .phase-node .phase-glow {
890
860
  position: absolute;
891
- top: -2px;
892
- left: -2px;
893
- right: -2px;
894
- bottom: -2px;
861
+ top: 0;
862
+ left: 0;
863
+ right: 0;
864
+ bottom: 0;
895
865
  border: 2px solid var(--accent-red);
896
866
  opacity: 0;
897
- box-shadow: 0 0 20px var(--accent-red-glow), inset 0 0 20px var(--accent-red-glow);
898
- transition: opacity 0.4s ease;
867
+ transition: opacity 0.3s ease;
899
868
  }
900
869
 
901
870
  .phase-node.active {
902
871
  border-color: var(--accent-red);
903
- 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);
904
874
  }
905
875
 
906
876
  .phase-node.active .phase-glow {
@@ -909,7 +879,6 @@
909
879
 
910
880
  .phase-node.active .phase-icon {
911
881
  color: var(--accent-red);
912
- text-shadow: 0 0 10px var(--accent-red);
913
882
  }
914
883
 
915
884
  .phase-node.active .phase-label {
@@ -918,31 +887,31 @@
918
887
 
919
888
  .phase-icon {
920
889
  display: block;
921
- font-size: 1.8rem;
890
+ font-size: 1.6rem;
922
891
  font-weight: bold;
923
892
  margin-bottom: 8px;
924
893
  color: var(--grey);
925
- transition: all 0.4s ease;
894
+ transition: all 0.3s ease;
926
895
  }
927
896
 
928
897
  .phase-label {
929
- font-size: 0.7rem;
898
+ font-size: 0.65rem;
930
899
  text-transform: uppercase;
931
900
  letter-spacing: 2px;
932
- color: var(--dark-grey);
933
- transition: all 0.4s ease;
901
+ color: var(--grey);
902
+ transition: all 0.3s ease;
903
+ font-weight: bold;
934
904
  }
935
905
 
936
906
  .phase-connector-arrow {
937
- color: var(--dark-grey);
938
- font-size: 1.5rem;
907
+ color: var(--border-color);
908
+ font-size: 1.2rem;
939
909
  opacity: 0;
940
910
  transition: all 0.3s ease;
941
911
  }
942
912
 
943
913
  .phase-connector-arrow.active {
944
914
  color: var(--accent-red);
945
- text-shadow: 0 0 10px var(--accent-red-glow);
946
915
  }
947
916
 
948
917
  @media (max-width: 768px) {
@@ -992,62 +961,67 @@
992
961
  }
993
962
 
994
963
  /* ============================================
995
- SECTION 2: THE PROBLEM - GAPS
964
+ SECTION 2: THE PROBLEM - GAPS - boxy cards
996
965
  ============================================ */
997
966
  .gap-list {
998
967
  list-style: none;
999
968
  width: 100%;
969
+ display: flex;
970
+ flex-direction: column;
971
+ gap: 15px;
1000
972
  }
1001
973
 
1002
974
  .gap-item {
1003
- border: 1px dashed var(--dark-grey);
1004
- padding: 25px 30px;
1005
- margin-bottom: 20px;
975
+ border: 2px solid var(--border-color);
976
+ background: var(--darker-grey);
977
+ padding: 25px 30px 25px 50px;
1006
978
  opacity: 0;
1007
979
  transform: translateX(-30px);
1008
- transition: all 0.3s;
980
+ transition: all 0.2s;
1009
981
  position: relative;
1010
982
  }
1011
983
 
1012
984
  .gap-item:hover {
1013
985
  border-color: var(--accent-red);
1014
- box-shadow: 0 0 25px var(--accent-red-glow);
986
+ transform: translateX(5px);
1015
987
  }
1016
988
 
1017
989
  .gap-item::before {
1018
- content: 'X';
990
+ content: '';
1019
991
  position: absolute;
1020
- left: -35px;
1021
- top: 50%;
1022
- transform: translateY(-50%);
992
+ left: 18px;
993
+ top: 26px;
1023
994
  color: var(--accent-red);
1024
- font-size: 1.2rem;
995
+ font-size: 1rem;
996
+ font-weight: bold;
1025
997
  }
1026
998
 
1027
999
  .gap-number {
1028
- color: var(--grey);
1029
- font-size: 0.8rem;
1000
+ color: var(--accent-red);
1001
+ font-size: 0.75rem;
1030
1002
  text-transform: uppercase;
1031
1003
  letter-spacing: 2px;
1004
+ font-weight: bold;
1032
1005
  }
1033
1006
 
1034
1007
  .gap-title {
1035
- font-size: 1.15rem;
1008
+ font-size: 1.1rem;
1036
1009
  margin: 8px 0;
1037
1010
  }
1038
1011
 
1039
1012
  .gap-desc {
1040
1013
  color: var(--grey);
1041
- font-size: 0.95rem;
1014
+ font-size: 0.9rem;
1042
1015
  margin-bottom: 10px;
1043
1016
  }
1044
1017
 
1045
1018
  .gap-example {
1046
1019
  font-size: 0.85rem;
1047
1020
  padding: 12px 15px;
1048
- background: rgba(255,255,255,0.03);
1049
- border-left: 2px solid var(--dark-grey);
1050
- 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;
1051
1025
  }
1052
1026
 
1053
1027
  .gap-example .bad {
@@ -1056,44 +1030,50 @@
1056
1030
  }
1057
1031
 
1058
1032
  .gap-example .good {
1059
- color: var(--white);
1033
+ color: var(--accent-red);
1034
+ font-weight: bold;
1060
1035
  }
1061
1036
 
1062
1037
  /* ============================================
1063
- SECTION 3: SOLUTION OVERVIEW
1038
+ SECTION 3: SOLUTION OVERVIEW - boxy grid
1064
1039
  ============================================ */
1065
1040
  .solution-grid {
1066
1041
  display: grid;
1067
1042
  grid-template-columns: repeat(3, 1fr);
1068
- gap: 25px;
1043
+ gap: 20px;
1069
1044
  margin-top: 30px;
1070
1045
  }
1071
1046
 
1072
1047
  .solution-card {
1073
- border: 1px dashed var(--dark-grey);
1074
- padding: 30px 25px;
1048
+ border: 2px solid var(--border-color);
1049
+ background: var(--darker-grey);
1050
+ padding: 35px 25px;
1075
1051
  text-align: center;
1076
1052
  opacity: 0;
1077
1053
  transform: translateY(20px);
1078
- transition: all 0.3s;
1054
+ transition: all 0.2s;
1079
1055
  }
1080
1056
 
1081
1057
  .solution-card:hover {
1082
1058
  border-color: var(--accent-red);
1083
- box-shadow: 0 0 25px var(--accent-red-glow);
1059
+ transform: translateY(-3px);
1084
1060
  }
1085
1061
 
1086
1062
  .solution-icon {
1087
- font-size: 2rem;
1063
+ font-size: 1.8rem;
1088
1064
  margin-bottom: 15px;
1089
1065
  display: block;
1066
+ color: var(--accent-red);
1067
+ font-weight: bold;
1090
1068
  }
1091
1069
 
1092
1070
  .solution-title {
1093
- font-size: 1rem;
1094
- margin-bottom: 10px;
1071
+ font-size: 0.95rem;
1072
+ margin-bottom: 12px;
1095
1073
  text-transform: uppercase;
1096
1074
  letter-spacing: 1px;
1075
+ color: var(--white);
1076
+ font-weight: 600;
1097
1077
  }
1098
1078
 
1099
1079
  .solution-desc {
@@ -1103,7 +1083,7 @@
1103
1083
  }
1104
1084
 
1105
1085
  /* ============================================
1106
- SECTION 4: HOOK SYSTEM
1086
+ SECTION 4: HOOK SYSTEM - clean boxy flow
1107
1087
  ============================================ */
1108
1088
  .hook-diagram {
1109
1089
  width: 100%;
@@ -1111,49 +1091,53 @@
1111
1091
  }
1112
1092
 
1113
1093
  .flow-box {
1114
- border: 1px dashed var(--grey);
1094
+ border: 2px solid var(--border-color);
1095
+ background: var(--darker-grey);
1115
1096
  padding: 18px 30px;
1116
1097
  margin: 12px 0;
1117
1098
  text-align: center;
1118
1099
  opacity: 0;
1119
- transition: all 0.3s;
1100
+ transition: all 0.2s;
1120
1101
  }
1121
1102
 
1122
1103
  .flow-box:hover {
1123
- background: rgba(186, 12, 47, 0.1);
1124
- box-shadow: 0 0 20px var(--accent-red-glow);
1125
1104
  border-color: var(--accent-red);
1105
+ transform: translateX(5px);
1126
1106
  }
1127
1107
 
1128
1108
  .flow-arrow {
1129
1109
  text-align: center;
1130
- color: var(--grey);
1110
+ color: var(--accent-red);
1131
1111
  font-size: 1.5rem;
1132
1112
  opacity: 0;
1133
1113
  }
1134
1114
 
1135
1115
  .hook-group {
1136
- border: 2px dashed var(--white);
1116
+ border: 2px dashed var(--border-color);
1117
+ background: var(--darker-grey);
1137
1118
  padding: 25px;
1138
1119
  margin: 25px 0;
1139
1120
  opacity: 0;
1140
1121
  }
1141
1122
 
1142
1123
  .hook-group h4 {
1143
- color: var(--grey);
1144
- font-size: 0.85rem;
1124
+ color: var(--accent-red);
1125
+ font-size: 0.8rem;
1145
1126
  text-transform: uppercase;
1146
1127
  letter-spacing: 2px;
1147
1128
  margin-bottom: 15px;
1129
+ font-weight: bold;
1148
1130
  }
1149
1131
 
1150
1132
  .hook-file {
1151
1133
  padding: 12px 20px;
1152
1134
  margin: 8px 0;
1153
- 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);
1154
1138
  font-size: 0.9rem;
1155
1139
  opacity: 0;
1156
- transition: all 0.3s;
1140
+ transition: all 0.2s;
1157
1141
  display: flex;
1158
1142
  justify-content: space-between;
1159
1143
  align-items: center;
@@ -1161,22 +1145,23 @@
1161
1145
 
1162
1146
  .hook-file:hover {
1163
1147
  border-left-color: var(--accent-red);
1164
- background: rgba(186, 12, 47, 0.1);
1165
- padding-left: 25px;
1148
+ border-color: var(--accent-red);
1166
1149
  }
1167
1150
 
1168
1151
  .hook-file code {
1169
1152
  color: var(--white);
1153
+ font-weight: 500;
1170
1154
  }
1171
1155
 
1172
1156
  .hook-purpose {
1173
1157
  color: var(--grey);
1174
- font-size: 0.8rem;
1158
+ font-size: 0.75rem;
1159
+ text-transform: uppercase;
1175
1160
  }
1176
1161
 
1177
1162
  .result-box {
1178
1163
  display: flex;
1179
- gap: 40px;
1164
+ gap: 30px;
1180
1165
  justify-content: center;
1181
1166
  margin-top: 25px;
1182
1167
  }
@@ -1184,12 +1169,15 @@
1184
1169
  .result-allowed, .result-blocked {
1185
1170
  padding: 18px 35px;
1186
1171
  border: 2px solid;
1172
+ background: var(--card-bg);
1187
1173
  opacity: 0;
1188
- transition: all 0.3s;
1174
+ transition: all 0.2s;
1175
+ font-weight: bold;
1189
1176
  }
1190
1177
 
1191
1178
  .result-allowed {
1192
1179
  border-color: var(--grey);
1180
+ color: var(--grey);
1193
1181
  }
1194
1182
 
1195
1183
  .result-blocked {
@@ -1198,62 +1186,65 @@
1198
1186
  }
1199
1187
 
1200
1188
  .result-allowed:hover, .result-blocked:hover {
1201
- transform: scale(1.05);
1189
+ transform: scale(1.03);
1202
1190
  }
1203
1191
 
1204
1192
  /* ============================================
1205
- SECTION 5: 10-PHASE WORKFLOW
1193
+ SECTION 5: 10-PHASE WORKFLOW - boxy grid
1206
1194
  ============================================ */
1207
1195
  .phase-grid {
1208
1196
  display: grid;
1209
1197
  grid-template-columns: repeat(5, 1fr);
1210
- gap: 18px;
1198
+ gap: 15px;
1211
1199
  width: 100%;
1212
1200
  margin-top: 30px;
1213
1201
  }
1214
1202
 
1215
1203
  .phase-box {
1216
- border: 1px dashed var(--dark-grey);
1204
+ border: 2px solid var(--border-color);
1205
+ background: var(--darker-grey);
1217
1206
  padding: 25px 15px;
1218
1207
  text-align: center;
1219
1208
  opacity: 0;
1220
1209
  transform: scale(0.9);
1221
- transition: all 0.3s;
1210
+ transition: all 0.2s;
1222
1211
  position: relative;
1223
1212
  }
1224
1213
 
1225
1214
  .phase-box:hover {
1226
1215
  border-color: var(--accent-red);
1227
- box-shadow: 0 0 25px var(--accent-red-glow);
1228
1216
  transform: scale(1.02);
1229
1217
  }
1230
1218
 
1231
1219
  .phase-box.active {
1232
1220
  border-color: var(--accent-red);
1233
- 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);
1234
1223
  }
1235
1224
 
1236
1225
  .phase-number {
1237
- font-size: 2.2rem;
1238
- color: var(--dark-grey);
1226
+ font-size: 2rem;
1227
+ color: var(--grey);
1239
1228
  margin-bottom: 10px;
1229
+ font-weight: bold;
1240
1230
  }
1241
1231
 
1242
1232
  .phase-box.active .phase-number {
1243
- color: var(--white);
1233
+ color: var(--accent-red);
1244
1234
  }
1245
1235
 
1246
1236
  .phase-name {
1247
- font-size: 0.75rem;
1237
+ font-size: 0.7rem;
1248
1238
  text-transform: uppercase;
1249
1239
  letter-spacing: 1px;
1240
+ font-weight: bold;
1250
1241
  }
1251
1242
 
1252
1243
  .phase-status {
1253
1244
  position: absolute;
1254
1245
  top: 8px;
1255
1246
  right: 8px;
1256
- font-size: 0.7rem;
1247
+ font-size: 0.65rem;
1257
1248
  color: var(--grey);
1258
1249
  }
1259
1250
 
@@ -1267,26 +1258,26 @@
1267
1258
  }
1268
1259
 
1269
1260
  .phase-desc {
1270
- font-size: 0.7rem;
1261
+ font-size: 0.65rem;
1271
1262
  color: var(--grey);
1272
1263
  margin-top: 8px;
1273
1264
  line-height: 1.4;
1274
1265
  }
1275
1266
 
1276
1267
  /* ============================================
1277
- SECTION 6: REAL WALKTHROUGH
1268
+ SECTION 6: REAL WALKTHROUGH - boxy steps
1278
1269
  ============================================ */
1279
1270
  .walkthrough-step {
1280
- border: 1px dashed var(--dark-grey);
1271
+ border: 2px solid var(--border-color);
1272
+ background: var(--darker-grey);
1281
1273
  padding: 30px;
1282
- margin-bottom: 25px;
1274
+ margin-bottom: 20px;
1283
1275
  opacity: 0;
1284
1276
  transform: translateY(20px);
1285
1277
  }
1286
1278
 
1287
1279
  .walkthrough-step.active {
1288
1280
  border-color: var(--accent-red);
1289
- box-shadow: 0 0 20px var(--accent-red-glow);
1290
1281
  }
1291
1282
 
1292
1283
  .walkthrough-header {
@@ -1299,16 +1290,19 @@
1299
1290
  .walkthrough-num {
1300
1291
  width: 45px;
1301
1292
  height: 45px;
1302
- border: 2px solid var(--white);
1293
+ border: 2px solid var(--accent-red);
1294
+ background: transparent;
1303
1295
  display: flex;
1304
1296
  align-items: center;
1305
1297
  justify-content: center;
1306
- font-size: 1.3rem;
1298
+ font-size: 1.2rem;
1307
1299
  flex-shrink: 0;
1300
+ color: var(--accent-red);
1301
+ font-weight: bold;
1308
1302
  }
1309
1303
 
1310
1304
  .walkthrough-title {
1311
- font-size: 1.1rem;
1305
+ font-size: 1rem;
1312
1306
  text-transform: uppercase;
1313
1307
  letter-spacing: 1px;
1314
1308
  }
@@ -1324,26 +1318,28 @@
1324
1318
  }
1325
1319
 
1326
1320
  .walkthrough-example {
1327
- background: #111;
1321
+ background: var(--card-bg);
1322
+ border: 1px solid var(--border-color);
1328
1323
  padding: 15px 20px;
1329
1324
  font-size: 0.85rem;
1330
- border-left: 3px solid var(--grey);
1325
+ border-left: 3px solid var(--accent-red);
1331
1326
  }
1332
1327
 
1333
1328
  .walkthrough-example .label {
1334
- color: var(--grey);
1335
- font-size: 0.75rem;
1329
+ color: var(--accent-red);
1330
+ font-size: 0.7rem;
1336
1331
  text-transform: uppercase;
1337
1332
  letter-spacing: 1px;
1338
1333
  margin-bottom: 8px;
1334
+ font-weight: bold;
1339
1335
  }
1340
1336
 
1341
1337
  /* ============================================
1342
- SECTION 7: LIVE DEMO TERMINAL
1338
+ SECTION 7: LIVE DEMO TERMINAL - boxy style
1343
1339
  ============================================ */
1344
1340
  .terminal {
1345
- background: #0d0d0d;
1346
- border: 1px solid var(--grey);
1341
+ background: var(--card-bg);
1342
+ border: 2px solid var(--border-color);
1347
1343
  padding: 25px;
1348
1344
  width: 100%;
1349
1345
  font-size: 0.85rem;
@@ -1355,20 +1351,27 @@
1355
1351
  gap: 8px;
1356
1352
  margin-bottom: 20px;
1357
1353
  padding-bottom: 15px;
1358
- border-bottom: 1px dashed var(--dark-grey);
1354
+ border-bottom: 1px dashed var(--border-color);
1359
1355
  }
1360
1356
 
1361
1357
  .terminal-dot {
1362
1358
  width: 12px;
1363
1359
  height: 12px;
1364
- border-radius: 50%;
1365
- 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);
1366
1367
  }
1367
1368
 
1368
1369
  .terminal-title {
1369
1370
  margin-left: auto;
1370
1371
  color: var(--grey);
1371
- font-size: 0.8rem;
1372
+ font-size: 0.75rem;
1373
+ text-transform: uppercase;
1374
+ letter-spacing: 1px;
1372
1375
  }
1373
1376
 
1374
1377
  .terminal-line {
@@ -1380,8 +1383,9 @@
1380
1383
  }
1381
1384
 
1382
1385
  .terminal-prompt {
1383
- color: var(--grey);
1386
+ color: var(--accent-red);
1384
1387
  flex-shrink: 0;
1388
+ font-weight: bold;
1385
1389
  }
1386
1390
 
1387
1391
  .terminal-command {
@@ -1400,8 +1404,8 @@
1400
1404
  }
1401
1405
 
1402
1406
  .terminal-allowed {
1403
- color: var(--grey);
1404
- border-left: 3px solid var(--grey);
1407
+ color: #22c55e;
1408
+ border-left: 3px solid #22c55e;
1405
1409
  padding-left: 15px;
1406
1410
  }
1407
1411
 
@@ -1411,18 +1415,18 @@
1411
1415
  }
1412
1416
 
1413
1417
  .terminal-comment {
1414
- color: var(--dark-grey);
1415
- font-size: 0.8rem;
1418
+ color: var(--grey);
1419
+ font-size: 0.75rem;
1416
1420
  padding-left: 25px;
1417
1421
  margin-bottom: 5px;
1418
1422
  }
1419
1423
 
1420
1424
  /* ============================================
1421
- SECTION 8: STATE FILE
1425
+ SECTION 8: STATE FILE - boxy JSON viewer
1422
1426
  ============================================ */
1423
1427
  .json-viewer {
1424
- background: #0d0d0d;
1425
- border: 1px solid var(--grey);
1428
+ background: var(--card-bg);
1429
+ border: 2px solid var(--border-color);
1426
1430
  padding: 25px;
1427
1431
  font-size: 0.85rem;
1428
1432
  width: 100%;
@@ -1430,7 +1434,8 @@
1430
1434
  }
1431
1435
 
1432
1436
  .json-key {
1433
- color: var(--grey);
1437
+ color: var(--accent-red);
1438
+ font-weight: 600;
1434
1439
  }
1435
1440
 
1436
1441
  .json-value {
@@ -1447,33 +1452,36 @@
1447
1452
  }
1448
1453
 
1449
1454
  .json-line.highlight {
1450
- background: rgba(186, 12, 47, 0.15);
1455
+ background: rgba(186, 12, 47, 0.1);
1451
1456
  margin: 0 -25px;
1452
1457
  padding-left: 25px;
1453
1458
  padding-right: 25px;
1454
- border-left: 2px solid var(--accent-red);
1459
+ border-left: 3px solid var(--accent-red);
1455
1460
  }
1456
1461
 
1457
1462
  .json-comment {
1458
- color: var(--dark-grey);
1459
- font-size: 0.75rem;
1463
+ color: var(--grey);
1464
+ font-size: 0.7rem;
1460
1465
  margin-left: 20px;
1466
+ text-transform: uppercase;
1461
1467
  }
1462
1468
 
1463
1469
  /* ============================================
1464
- SECTION 9: INSTALLATION
1470
+ SECTION 9: INSTALLATION - boxy style
1465
1471
  ============================================ */
1466
1472
  .install-command {
1467
- background: #0d0d0d;
1468
- border: 1px solid var(--grey);
1469
- padding: 20px 30px;
1473
+ background: var(--darker-grey);
1474
+ border: 2px solid var(--accent-red);
1475
+ padding: 25px 35px;
1470
1476
  font-size: 1.1rem;
1471
1477
  margin: 30px 0;
1472
1478
  text-align: center;
1479
+ box-shadow: 0 0 20px var(--accent-red-glow);
1473
1480
  }
1474
1481
 
1475
1482
  .install-command code {
1476
- color: var(--white);
1483
+ color: var(--accent-red);
1484
+ font-weight: bold;
1477
1485
  }
1478
1486
 
1479
1487
  .install-flow {
@@ -1484,25 +1492,29 @@
1484
1492
  display: flex;
1485
1493
  align-items: center;
1486
1494
  gap: 25px;
1487
- padding: 20px 0;
1488
- 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);
1489
1499
  opacity: 0;
1490
1500
  transform: translateY(20px);
1491
1501
  }
1492
1502
 
1493
1503
  .install-step:last-child {
1494
- border-bottom: none;
1504
+ margin-bottom: 0;
1495
1505
  }
1496
1506
 
1497
1507
  .install-icon {
1498
1508
  width: 50px;
1499
1509
  height: 50px;
1500
- border: 1px dashed var(--grey);
1510
+ border: 2px solid var(--accent-red);
1511
+ background: transparent;
1501
1512
  display: flex;
1502
1513
  align-items: center;
1503
1514
  justify-content: center;
1504
1515
  flex-shrink: 0;
1505
1516
  font-size: 1.2rem;
1517
+ color: var(--accent-red);
1506
1518
  }
1507
1519
 
1508
1520
  .install-content {
@@ -1511,28 +1523,30 @@
1511
1523
 
1512
1524
  .install-from {
1513
1525
  color: var(--grey);
1514
- font-size: 0.9rem;
1526
+ font-size: 0.85rem;
1515
1527
  }
1516
1528
 
1517
1529
  .install-arrow {
1518
- color: var(--grey);
1530
+ color: var(--accent-red);
1519
1531
  flex-shrink: 0;
1520
1532
  font-size: 1.2rem;
1533
+ font-weight: bold;
1521
1534
  }
1522
1535
 
1523
1536
  .install-to {
1524
1537
  color: var(--white);
1525
- font-size: 0.95rem;
1538
+ font-size: 0.9rem;
1539
+ font-weight: 500;
1526
1540
  }
1527
1541
 
1528
1542
  .install-note {
1529
1543
  color: var(--grey);
1530
- font-size: 0.8rem;
1544
+ font-size: 0.75rem;
1531
1545
  margin-top: 5px;
1532
1546
  }
1533
1547
 
1534
1548
  /* ============================================
1535
- SECTION 10: CREDITS
1549
+ SECTION 10: CREDITS - boxy style
1536
1550
  ============================================ */
1537
1551
  #credits {
1538
1552
  text-align: center;
@@ -1540,64 +1554,69 @@
1540
1554
 
1541
1555
  .credit-links {
1542
1556
  display: flex;
1543
- gap: 30px;
1557
+ gap: 20px;
1544
1558
  margin-top: 40px;
1545
1559
  flex-wrap: wrap;
1546
1560
  justify-content: center;
1547
1561
  }
1548
1562
 
1549
1563
  .credit-link {
1550
- border: 1px dashed var(--grey);
1564
+ border: 2px solid var(--border-color);
1565
+ background: var(--darker-grey);
1551
1566
  padding: 18px 35px;
1552
1567
  text-decoration: none;
1553
1568
  color: var(--white);
1554
- transition: all 0.3s;
1569
+ transition: all 0.2s;
1555
1570
  opacity: 0;
1571
+ text-transform: uppercase;
1572
+ letter-spacing: 1px;
1573
+ font-size: 0.85rem;
1556
1574
  }
1557
1575
 
1558
1576
  .credit-link:hover {
1559
1577
  background: var(--accent-red);
1560
- color: #fff;
1578
+ color: var(--white);
1561
1579
  border-color: var(--accent-red);
1562
- box-shadow: 0 0 25px var(--accent-red-glow);
1580
+ box-shadow: 0 0 15px var(--accent-red-glow);
1563
1581
  }
1564
1582
 
1565
1583
  .made-with {
1566
1584
  margin-top: 50px;
1567
1585
  color: var(--grey);
1568
- font-size: 0.95rem;
1586
+ font-size: 0.9rem;
1569
1587
  opacity: 0;
1570
1588
  line-height: 1.8;
1571
1589
  }
1572
1590
 
1573
1591
  /* ============================================
1574
- SECTION INDICATORS
1592
+ SECTION INDICATORS - square boxy dots
1575
1593
  ============================================ */
1576
1594
  .section-indicator {
1577
1595
  position: fixed;
1578
- right: 25px;
1596
+ right: 20px;
1579
1597
  top: 50%;
1580
1598
  transform: translateY(-50%);
1581
1599
  display: flex;
1582
1600
  flex-direction: column;
1583
- gap: 12px;
1601
+ gap: 10px;
1584
1602
  z-index: 1000;
1585
1603
  }
1586
1604
 
1587
1605
  .section-dot {
1588
1606
  width: 10px;
1589
1607
  height: 10px;
1590
- border: 1px solid var(--grey);
1591
- border-radius: 50%;
1608
+ border: 2px solid var(--border-color);
1609
+ border-radius: 0;
1592
1610
  cursor: pointer;
1593
- transition: all 0.3s;
1611
+ transition: all 0.2s;
1612
+ background: var(--card-bg);
1594
1613
  }
1595
1614
 
1596
1615
  .section-dot:hover,
1597
1616
  .section-dot.active {
1598
1617
  background: var(--accent-red);
1599
1618
  border-color: var(--accent-red);
1600
- box-shadow: 0 0 12px var(--accent-red-glow);
1619
+ box-shadow: 0 0 8px var(--accent-red-glow);
1601
1620
  }
1602
1621
 
1603
1622
  /* ============================================
@@ -1680,16 +1699,82 @@
1680
1699
  <nav class="nav">
1681
1700
  <button class="nav-btn" id="playBtn">▶ AUTO PLAY</button>
1682
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>
1683
1703
  <button class="nav-btn" id="resetBtn">↺ RESTART</button>
1684
1704
  </nav>
1685
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
+
1686
1768
  <!-- Audio Narration Player (hidden) -->
1687
1769
  <audio id="narrationAudio" preload="auto">
1688
- <source src="audio/narration.mp3" type="audio/mpeg">
1770
+ <source src="audio/narration-adam.mp3" type="audio/mpeg">
1689
1771
  </audio>
1690
1772
 
1691
1773
  <!-- Audio Progress Bar -->
1692
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>
1693
1778
  <div class="audio-time-display">
1694
1779
  <span id="audioCurrentTime">0:00</span>
1695
1780
  <span id="audioTotalTime">0:00</span>
@@ -1713,7 +1798,7 @@
1713
1798
  <span class="hustle-word hustle-highlight">API-DEV-TOOLS</span>
1714
1799
  </div>
1715
1800
  <div class="package-name" id="packageName">@hustle-together/api-dev-tools</div>
1716
- <div class="version" id="versionText">v2.0.6</div>
1801
+ <div class="version" id="versionText">v2.0.8</div>
1717
1802
  <p class="tagline">"Hustle together. Share resources. Build stronger."<span class="cursor"></span></p>
1718
1803
 
1719
1804
  <div class="intro-text">
@@ -2546,11 +2631,13 @@
2546
2631
  const phaseArrows = document.querySelectorAll('#introFlow .phase-connector-arrow');
2547
2632
 
2548
2633
  // Create a separate timeline for the lighting sequence that loops
2549
- const lightingTL = gsap.timeline({
2634
+ // Store globally so we can pause during narration
2635
+ window.introLightingTL = gsap.timeline({
2550
2636
  repeat: -1,
2551
2637
  repeatDelay: 1,
2552
2638
  delay: 3 // Wait for intro animation to complete
2553
2639
  });
2640
+ const lightingTL = window.introLightingTL;
2554
2641
 
2555
2642
  phaseNodes.forEach((node, i) => {
2556
2643
  lightingTL
@@ -2782,6 +2869,7 @@
2782
2869
  const audioCurrentTime = document.getElementById('audioCurrentTime');
2783
2870
  const audioTotalTime = document.getElementById('audioTotalTime');
2784
2871
  const audioSectionMarkers = document.getElementById('audioSectionMarkers');
2872
+ const chapterButtons = document.getElementById('chapterButtons');
2785
2873
 
2786
2874
  let audioEnabled = true; // Audio on by default
2787
2875
  let timingData = null;
@@ -2797,11 +2885,15 @@
2797
2885
  return `${mins}:${secs.toString().padStart(2, '0')}`;
2798
2886
  }
2799
2887
 
2800
- // Load timing data
2801
- async function loadTimingData() {
2802
- 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;
2803
2895
  try {
2804
- const response = await fetch('audio/narration-timing.json');
2896
+ const response = await fetch(currentTimingUrl);
2805
2897
  if (!response.ok) throw new Error('Failed to load timing data');
2806
2898
  timingData = await response.json();
2807
2899
 
@@ -2823,6 +2915,116 @@
2823
2915
  });
2824
2916
  }
2825
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
+
2826
3028
  console.log('Timing data loaded:', timingData.sections.length, 'sections,', timingData.highlights.length, 'highlights');
2827
3029
  return timingData;
2828
3030
  } catch (error) {
@@ -2840,10 +3042,20 @@
2840
3042
  currentHighlight = el;
2841
3043
  el.classList.add('audio-highlighted');
2842
3044
  el.scrollIntoView({ behavior: 'smooth', block: 'center' });
2843
- gsap.fromTo(el,
2844
- { boxShadow: '0 0 0px var(--accent-red-glow)' },
2845
- { boxShadow: '0 0 40px var(--accent-red-glow), 0 0 80px var(--accent-red-glow)', duration: 0.4, ease: 'power2.out' }
2846
- );
3045
+
3046
+ // Only apply box-shadow GSAP animation to container elements, not text
3047
+ const isContainer = el.classList.contains('solution-card') ||
3048
+ el.classList.contains('phase-box') ||
3049
+ el.classList.contains('gap-item') ||
3050
+ el.classList.contains('phase-node') ||
3051
+ el.classList.contains('install-command');
3052
+
3053
+ if (isContainer) {
3054
+ gsap.fromTo(el,
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' }
3057
+ );
3058
+ }
2847
3059
  }
2848
3060
  } catch (e) {
2849
3061
  console.warn('Could not highlight:', selector, e);
@@ -2859,6 +3071,29 @@
2859
3071
  }
2860
3072
  }
2861
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
+
2862
3097
  // Handle audio time update (syncs highlights with narration)
2863
3098
  function onAudioTimeUpdate() {
2864
3099
  if (!timingData || !audioEnabled) return;
@@ -2874,6 +3109,13 @@
2874
3109
  audioCurrentTime.textContent = formatTime(currentTime);
2875
3110
  }
2876
3111
 
3112
+ // Update active chapter button
3113
+ const sectionIdx = getCurrentSectionIndex(currentTime);
3114
+ if (sectionIdx !== currentSectionIdx) {
3115
+ currentSectionIdx = sectionIdx;
3116
+ updateActiveChapter(sectionIdx);
3117
+ }
3118
+
2877
3119
  // Check for highlight triggers
2878
3120
  for (let i = highlightIndex; i < timingData.highlights.length; i++) {
2879
3121
  const highlight = timingData.highlights[i];
@@ -2889,23 +3131,73 @@
2889
3131
  }
2890
3132
 
2891
3133
  // Handle seek on progress bar click
2892
- function onProgressBarClick(e) {
2893
- if (!timingData || !narrationAudio.duration) return;
3134
+ async function onProgressBarClick(e) {
3135
+ e.stopPropagation(); // Prevent event bubbling
2894
3136
 
3137
+ // Use the progress bar's rect for calculation
2895
3138
  const rect = audioProgressBar.getBoundingClientRect();
2896
3139
  const clickX = e.clientX - rect.left;
2897
- const percentage = clickX / rect.width;
2898
- 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
+ }
2899
3184
 
2900
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
+ }
2901
3191
 
2902
3192
  // Reset highlight tracking
2903
3193
  highlightIndex = 0;
2904
- for (let i = 0; i < timingData.highlights.length; i++) {
2905
- if (timingData.highlights[i].timestamp <= seekTime) {
2906
- highlightIndex = i;
2907
- } else {
2908
- break;
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
+ }
2909
3201
  }
2910
3202
  }
2911
3203
  }
@@ -2931,6 +3223,16 @@
2931
3223
  playBtn.classList.add('active');
2932
3224
  playBtn.textContent = '⏸ STOP';
2933
3225
 
3226
+ // KILL the intro lighting animation completely to avoid visual conflict
3227
+ // (Don't just pause - kill it so it won't restart)
3228
+ if (window.introLightingTL) {
3229
+ window.introLightingTL.kill();
3230
+ window.introLightingTL = null;
3231
+ // Reset the phase nodes to clean state
3232
+ document.querySelectorAll('#introFlow .phase-node').forEach(n => n.classList.remove('active'));
3233
+ document.querySelectorAll('#introFlow .phase-connector-arrow').forEach(a => a.classList.remove('active'));
3234
+ }
3235
+
2934
3236
  // Scroll to top first
2935
3237
  window.scrollTo({ top: 0, behavior: 'smooth' });
2936
3238
 
@@ -2975,6 +3277,9 @@
2975
3277
  removeHighlight();
2976
3278
  highlightIndex = 0;
2977
3279
  currentSectionIdx = 0;
3280
+
3281
+ // Note: We killed the intro lighting animation, so don't try to restart it
3282
+ // The animation was distracting during/after narration playback
2978
3283
  }
2979
3284
 
2980
3285
  // Audio ended
@@ -3024,6 +3329,31 @@
3024
3329
  audioToggleBtn.addEventListener('click', toggleAudio);
3025
3330
  narrationAudio.addEventListener('timeupdate', onAudioTimeUpdate);
3026
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
+
3027
3357
  audioProgressBar.addEventListener('click', onProgressBarClick);
3028
3358
 
3029
3359
  // Check if audio file is accessible
@@ -3042,6 +3372,10 @@
3042
3372
  // RESET BUTTON
3043
3373
  // ============================================
3044
3374
  document.getElementById('resetBtn').addEventListener('click', () => {
3375
+ // Stop audio and auto-play first
3376
+ stopAutoPlay();
3377
+
3378
+ // Scroll to top
3045
3379
  window.scrollTo({ top: 0, behavior: 'smooth' });
3046
3380
 
3047
3381
  // Reset animations
@@ -3059,11 +3393,20 @@
3059
3393
  gsap.set('.credit-link, .made-with', { opacity: 0 });
3060
3394
  gsap.set('.intro-text, .scroll-hint', { opacity: 0 });
3061
3395
 
3396
+ // Reset phase boxes
3062
3397
  document.querySelectorAll('.phase-box').forEach(box => {
3063
3398
  box.classList.remove('active');
3064
3399
  box.querySelector('.phase-status').textContent = '';
3065
3400
  });
3066
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
+
3067
3410
  ScrollTrigger.refresh();
3068
3411
  }, 500);
3069
3412
  });
@@ -3237,6 +3580,173 @@
3237
3580
  });
3238
3581
  });
3239
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
+
3240
3750
  </script>
3241
3751
 
3242
3752
  </body>