jubaerahmed20 commited on
Commit
0b9577a
·
verified ·
1 Parent(s): df9e6dd

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1145 -935
index.html CHANGED
@@ -1,960 +1,1170 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>VibeVoice - Realtime 0.5B</title>
7
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
- <style>
9
- :root {
10
- --primary-color: #6c5ce7;
11
- --secondary-color: #a29bfe;
12
- --accent-color: #fd79a8;
13
- --dark-color: #2d3436;
14
- --light-color: #f5f6fa;
15
- --success-color: #00b894;
16
- --warning-color: #fdcb6e;
17
- --danger-color: #d63031;
18
- --shadow: 0 10px 30px -5px rgba(108, 92, 231, 0.3);
19
- --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
20
- }
21
-
22
- * {
23
- margin: 0;
24
- padding: 0;
25
- box-sizing: border-box;
26
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
27
- }
28
-
29
- body {
30
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
31
- min-height: 100vh;
32
- display: flex;
33
- flex-direction: column;
34
- color: var(--dark-color);
35
- line-height: 1.6;
36
- }
37
-
38
- header {
39
- background: white;
40
- box-shadow: var(--shadow);
41
- padding: 1rem 2rem;
42
- display: flex;
43
- justify-content: space-between;
44
- align-items: center;
45
- position: sticky;
46
- top: 0;
47
- z-index: 100;
48
- }
49
-
50
- .logo {
51
- display: flex;
52
- align-items: center;
53
- gap: 0.5rem;
54
- text-decoration: none;
55
- color: var(--primary-color);
56
- font-weight: 700;
57
- font-size: 1.5rem;
58
- }
59
-
60
- .logo-icon {
61
- font-size: 2rem;
62
- color: var(--accent-color);
63
- }
64
-
65
- .header-right {
66
- display: flex;
67
- align-items: center;
68
- gap: 1.5rem;
69
- }
70
-
71
- .anycoder-link {
72
- color: var(--primary-color);
73
- text-decoration: none;
74
- font-weight: 500;
75
- transition: var(--transition);
76
- }
77
-
78
- .anycoder-link:hover {
79
- color: var(--accent-color);
80
- text-decoration: underline;
81
- }
82
-
83
- .user-profile {
84
- display: flex;
85
- align-items: center;
86
- gap: 0.5rem;
87
- cursor: pointer;
88
- }
89
-
90
- .user-avatar {
91
- width: 40px;
92
- height: 40px;
93
- border-radius: 50%;
94
- background: var(--secondary-color);
95
- display: flex;
96
- align-items: center;
97
- justify-content: center;
98
- color: white;
99
- font-weight: bold;
100
- }
101
-
102
- main {
103
- flex: 1;
104
- padding: 2rem;
105
- display: flex;
106
- flex-direction: column;
107
- gap: 2rem;
108
- max-width: 1200px;
109
- margin: 0 auto;
110
- width: 100%;
111
- }
112
-
113
- .hero {
114
- text-align: center;
115
- padding: 2rem 0;
116
- background: white;
117
- border-radius: 20px;
118
- box-shadow: var(--shadow);
119
- }
120
-
121
- .hero h1 {
122
- font-size: 2.5rem;
123
- margin-bottom: 1rem;
124
- background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
125
- -webkit-background-clip: text;
126
- -webkit-text-fill-color: transparent;
127
- }
128
-
129
- .hero p {
130
- font-size: 1.1rem;
131
- color: #636e72;
132
- max-width: 700px;
133
- margin: 0 auto 2rem;
134
- }
135
-
136
- .voice-controls {
137
- display: flex;
138
- flex-direction: column;
139
- gap: 2rem;
140
- background: white;
141
- padding: 2rem;
142
- border-radius: 20px;
143
- box-shadow: var(--shadow);
144
- }
145
-
146
- .control-section {
147
- display: flex;
148
- flex-direction: column;
149
- gap: 1rem;
150
- }
151
-
152
- .control-header {
153
- display: flex;
154
- justify-content: space-between;
155
- align-items: center;
156
- }
157
-
158
- .control-title {
159
- font-size: 1.3rem;
160
- font-weight: 600;
161
- color: var(--primary-color);
162
- }
163
-
164
- .control-grid {
165
- display: grid;
166
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
167
- gap: 1.5rem;
168
- }
169
-
170
- .control-card {
171
- background: var(--light-color);
172
- padding: 1.5rem;
173
- border-radius: 15px;
174
- display: flex;
175
- flex-direction: column;
176
- gap: 1rem;
177
- transition: var(--transition);
178
- border: 2px solid transparent;
179
- }
180
-
181
- .control-card:hover {
182
- border-color: var(--secondary-color);
183
- transform: translateY(-5px);
184
- }
185
-
186
- .control-card h3 {
187
- color: var(--dark-color);
188
- font-size: 1.1rem;
189
- }
190
-
191
- .control-card p {
192
- color: #636e72;
193
- font-size: 0.9rem;
194
- }
195
-
196
- .voice-button {
197
- width: 100%;
198
- padding: 1rem;
199
- border: none;
200
- border-radius: 10px;
201
- font-size: 1rem;
202
- font-weight: 600;
203
- cursor: pointer;
204
- transition: var(--transition);
205
- display: flex;
206
- align-items: center;
207
- justify-content: center;
208
- gap: 0.5rem;
209
- }
210
-
211
- .btn-record {
212
- background: linear-gradient(135deg, var(--primary-color), #5649c0);
213
- color: white;
214
- }
215
-
216
- .btn-record:active {
217
- background: linear-gradient(135deg, #5649c0, var(--primary-color));
218
- transform: scale(0.98);
219
- }
220
-
221
- .btn-play {
222
- background: linear-gradient(135deg, var(--success-color), #009688);
223
- color: white;
224
- }
225
-
226
- .btn-play:active {
227
- background: linear-gradient(135deg, #009688, var(--success-color));
228
- transform: scale(0.98);
229
- }
230
-
231
- .btn-stop {
232
- background: linear-gradient(135deg, var(--danger-color), #b71540);
233
- color: white;
234
- }
235
-
236
- .btn-stop:active {
237
- background: linear-gradient(135deg, #b71540, var(--danger-color));
238
- transform: scale(0.98);
239
- }
240
-
241
- .visualizer {
242
- height: 100px;
243
- background: var(--light-color);
244
- border-radius: 10px;
245
- display: flex;
246
- align-items: center;
247
- justify-content: center;
248
- overflow: hidden;
249
- position: relative;
250
- }
251
-
252
- .visualizer-bars {
253
- display: flex;
254
- height: 100%;
255
- width: 100%;
256
- align-items: flex-end;
257
- justify-content: center;
258
- gap: 2px;
259
- }
260
-
261
- .bar {
262
- width: 8px;
263
- background: var(--primary-color);
264
- border-radius: 5px 5px 0 0;
265
- transition: height 0.1s ease;
266
- }
267
-
268
- .status-indicator {
269
- display: flex;
270
- align-items: center;
271
- gap: 0.5rem;
272
- padding: 0.5rem 1rem;
273
- border-radius: 20px;
274
- font-weight: 500;
275
- margin-top: 1rem;
276
- }
277
-
278
- .status-recording {
279
- background: rgba(240, 128, 128, 0.2);
280
- color: var(--danger-color);
281
- }
282
-
283
- .status-playing {
284
- background: rgba(76, 175, 80, 0.2);
285
- color: var(--success-color);
286
- }
287
-
288
- .status-idle {
289
- background: rgba(0, 0, 0, 0.05);
290
- color: #636e72;
291
- }
292
-
293
- .audio-list {
294
- display: flex;
295
- flex-direction: column;
296
- gap: 1rem;
297
- max-height: 300px;
298
- overflow-y: auto;
299
- padding-right: 0.5rem;
300
- }
301
-
302
- .audio-item {
303
- background: var(--light-color);
304
- padding: 1rem;
305
- border-radius: 10px;
306
- display: flex;
307
- justify-content: space-between;
308
- align-items: center;
309
- transition: var(--transition);
310
- }
311
-
312
- .audio-item:hover {
313
- background: #e8e8e8;
314
- }
315
-
316
- .audio-info {
317
- display: flex;
318
- flex-direction: column;
319
- gap: 0.2rem;
320
- }
321
-
322
- .audio-name {
323
- font-weight: 600;
324
- color: var(--dark-color);
325
- }
326
-
327
- .audio-duration {
328
- font-size: 0.8rem;
329
- color: #636e72;
330
- }
331
-
332
- .audio-actions {
333
- display: flex;
334
- gap: 0.5rem;
335
- }
336
-
337
- .action-btn {
338
- background: none;
339
- border: none;
340
- color: #636e72;
341
- cursor: pointer;
342
- padding: 0.3rem;
343
- border-radius: 50%;
344
- transition: var(--transition);
345
- }
346
-
347
- .action-btn:hover {
348
- background: rgba(0, 0, 0, 0.1);
349
- color: var(--primary-color);
350
- }
351
-
352
- .settings-panel {
353
- background: white;
354
- padding: 1.5rem;
355
- border-radius: 20px;
356
- box-shadow: var(--shadow);
357
- }
358
-
359
- .settings-header {
360
- display: flex;
361
- justify-content: space-between;
362
- align-items: center;
363
- margin-bottom: 1.5rem;
364
- }
365
-
366
- .settings-title {
367
- font-size: 1.3rem;
368
- font-weight: 600;
369
- color: var(--primary-color);
370
- }
371
-
372
- .settings-grid {
373
- display: grid;
374
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
375
- gap: 1.5rem;
376
- }
377
-
378
- .setting-item {
379
- display: flex;
380
- flex-direction: column;
381
- gap: 0.5rem;
382
- }
383
-
384
- .setting-label {
385
- font-weight: 500;
386
- color: var(--dark-color);
387
- }
388
-
389
- .setting-input {
390
- padding: 0.8rem;
391
- border: 2px solid #e0e0e0;
392
- border-radius: 10px;
393
- font-size: 1rem;
394
- transition: var(--transition);
395
- }
396
-
397
- .setting-input:focus {
398
- outline: none;
399
- border-color: var(--primary-color);
400
- }
401
 
402
- .toggle-switch {
403
- position: relative;
404
- display: inline-block;
405
- width: 60px;
406
- height: 30px;
407
- }
408
-
409
- .toggle-switch input {
410
- opacity: 0;
411
- width: 0;
412
- height: 0;
413
- }
414
-
415
- .slider {
416
- position: absolute;
417
- cursor: pointer;
418
- top: 0;
419
- left: 0;
420
- right: 0;
421
- bottom: 0;
422
- background-color: #ccc;
423
- transition: var(--transition);
424
- border-radius: 30px;
425
- }
426
-
427
- .slider:before {
428
- position: absolute;
429
- content: "";
430
- height: 22px;
431
- width: 22px;
432
- left: 4px;
433
- bottom: 4px;
434
- background-color: white;
435
- transition: var(--transition);
436
- border-radius: 50%;
437
- }
438
-
439
- input:checked + .slider {
440
- background-color: var(--primary-color);
441
- }
442
-
443
- input:checked + .slider:before {
444
- transform: translateX(30px);
445
- }
446
-
447
- footer {
448
- background: var(--dark-color);
449
- color: white;
450
- text-align: center;
451
- padding: 1.5rem;
452
- margin-top: auto;
453
- }
454
-
455
- .footer-links {
456
- display: flex;
457
- justify-content: center;
458
- gap: 1.5rem;
459
- margin-bottom: 1rem;
460
- }
461
-
462
- .footer-link {
463
- color: white;
464
- text-decoration: none;
465
- transition: var(--transition);
466
- }
467
-
468
- .footer-link:hover {
469
- color: var(--primary-color);
470
- }
471
-
472
- @media (max-width: 768px) {
473
- header {
474
- padding: 1rem;
475
- }
476
-
477
- .hero h1 {
478
- font-size: 2rem;
479
- }
480
-
481
- .hero p {
482
- font-size: 1rem;
483
- }
484
-
485
- .control-grid {
486
- grid-template-columns: 1fr;
487
- }
488
-
489
- .settings-grid {
490
- grid-template-columns: 1fr;
491
- }
492
-
493
- .header-right {
494
- gap: 1rem;
495
- }
496
-
497
- .user-profile {
498
- display: none;
499
- }
500
- }
501
-
502
- @media (max-width: 480px) {
503
- main {
504
- padding: 1rem;
505
- }
506
-
507
- .hero h1 {
508
- font-size: 1.5rem;
509
- }
510
-
511
- .footer-links {
512
- flex-direction: column;
513
- gap: 0.5rem;
514
- }
515
- }
516
-
517
- /* Loading animation */
518
- @keyframes pulse {
519
- 0%, 100% {
520
- opacity: 1;
521
- }
522
- 50% {
523
- opacity: 0.5;
524
- }
525
- }
526
-
527
- .loading {
528
- animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
529
- }
530
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
531
  </head>
 
532
  <body>
533
- <header>
534
- <a href="#" class="logo">
535
- <i class="fas fa-microphone-alt logo-icon"></i>
536
- <span>VibeVoice</span>
537
- </a>
538
- <div class="header-right">
539
- <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="anycoder-link" target="_blank">
540
- Built with anycoder
541
- </a>
542
- <div class="user-profile">
543
- <div class="user-avatar">JD</div>
544
- <span>John Doe</span>
545
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
546
  </div>
547
- </header>
548
-
549
- <main>
550
- <section class="hero">
551
- <h1>Real-time Voice Interaction</h1>
552
- <p>Experience seamless voice communication with our advanced 0.5B model. Record, play, and analyze voice in real-time with high-quality audio processing.</p>
553
- <div class="status-indicator status-idle" id="statusIndicator">
554
- <i class="fas fa-circle"></i>
555
- <span>Ready to record</span>
556
- </div>
557
- </section>
558
-
559
- <section class="voice-controls">
560
- <div class="control-section">
561
- <div class="control-header">
562
- <h2 class="control-title">Voice Controls</h2>
563
- </div>
564
- <div class="control-grid">
565
- <div class="control-card">
566
- <h3>Record Audio</h3>
567
- <p>Start recording your voice with high-quality capture</p>
568
- <button class="voice-button btn-record" id="recordButton">
569
- <i class="fas fa-microphone"></i>
570
- <span>Start Recording</span>
571
- </button>
572
- </div>
573
- <div class="control-card">
574
- <h3>Playback</h3>
575
- <p>Listen to your recorded audio with enhanced playback</p>
576
- <button class="voice-button btn-play" id="playButton" disabled>
577
- <i class="fas fa-play"></i>
578
- <span>Play Recording</span>
579
- </button>
580
- </div>
581
- <div class="control-card">
582
- <h3>Stop All</h3>
583
- <p>Stop all current audio operations</p>
584
- <button class="voice-button btn-stop" id="stopButton" disabled>
585
- <i class="fas fa-stop"></i>
586
- <span>Stop</span>
587
- </button>
588
- </div>
589
- </div>
590
- </div>
591
 
592
- <div class="control-section">
593
- <div class="control-header">
594
- <h2 class="control-title">Audio Visualizer</h2>
595
- </div>
596
- <div class="visualizer">
597
- <div class="visualizer-bars" id="visualizer">
598
- <!-- Bars will be generated by JavaScript -->
599
- </div>
600
- </div>
601
- </div>
602
 
603
- <div class="control-section">
604
- <div class="control-header">
605
- <h2 class="control-title">Your Recordings</h2>
606
- </div>
607
- <div class="audio-list" id="audioList">
608
- <!-- Recordings will appear here -->
609
- </div>
610
- </div>
611
- </section>
612
 
613
- <section class="settings-panel">
614
- <div class="settings-header">
615
- <h2 class="settings-title">Voice Settings</h2>
616
- </div>
617
- <div class="settings-grid">
618
- <div class="setting-item">
619
- <label class="setting-label">Audio Quality</label>
620
- <select class="setting-input" id="audioQuality">
621
- <option value="high">High (320kbps)</option>
622
- <option value="medium" selected>Medium (192kbps)</option>
623
- <option value="low">Low (128kbps)</option>
624
- </select>
625
- </div>
626
- <div class="setting-item">
627
- <label class="setting-label">Noise Reduction</label>
628
- <div class="toggle-switch">
629
- <input type="checkbox" id="noiseReduction" checked>
630
- <span class="slider"></span>
631
- </div>
632
- </div>
633
- <div class="setting-item">
634
- <label class="setting-label">Echo Cancellation</label>
635
- <div class="toggle-switch">
636
- <input type="checkbox" id="echoCancellation" checked>
637
- <span class="slider"></span>
638
- </div>
639
- </div>
640
- <div class="setting-item">
641
- <label class="setting-label">Auto Gain Control</label>
642
- <div class="toggle-switch">
643
- <input type="checkbox" id="autoGain" checked>
644
- <span class="slider"></span>
645
- </div>
646
- </div>
647
  </div>
648
- </section>
649
- </main>
650
-
651
- <footer>
652
- <div class="footer-links">
653
- <a href="#" class="footer-link">Privacy Policy</a>
654
- <a href="#" class="footer-link">Terms of Service</a>
655
- <a href="#" class="footer-link">Contact Support</a>
656
  </div>
657
- <p>&copy; 2023 VibeVoice. All rights reserved.</p>
658
- </footer>
659
-
660
- <script>
661
- // DOM Elements
662
- const recordButton = document.getElementById('recordButton');
663
- const playButton = document.getElementById('playButton');
664
- const stopButton = document.getElementById('stopButton');
665
- const statusIndicator = document.getElementById('statusIndicator');
666
- const visualizer = document.getElementById('visualizer');
667
- const audioList = document.getElementById('audioList');
668
-
669
- // Audio context and variables
670
- let audioContext;
671
- let mediaRecorder;
672
- let audioChunks = [];
673
- let audioBlob;
674
- let audioUrl;
675
- let audioElement;
676
- let analyser;
677
- let dataArray;
678
- let animationId;
679
- let isRecording = false;
680
- let isPlaying = false;
681
-
682
- // Initialize audio context
683
- function initAudioContext() {
684
- if (!audioContext) {
685
- audioContext = new (window.AudioContext || window.webkitAudioContext)();
686
- }
687
- }
688
-
689
- // Create visualizer bars
690
- function createVisualizer() {
691
- visualizer.innerHTML = '';
692
- for (let i = 0; i < 50; i++) {
693
- const bar = document.createElement('div');
694
- bar.className = 'bar';
695
- bar.style.height = '10%';
696
- visualizer.appendChild(bar);
697
- }
698
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
699
 
700
- // Update visualizer
701
- function updateVisualizer() {
702
- if (!analyser) return;
703
 
704
- analyser.getByteFrequencyData(dataArray);
 
705
 
706
- const bars = visualizer.querySelectorAll('.bar');
707
- const barCount = bars.length;
708
- const segmentWidth = Math.floor(dataArray.length / barCount);
 
 
 
709
 
710
- bars.forEach((bar, i) => {
711
- const segmentStart = i * segmentWidth;
712
- const segmentEnd = segmentStart + segmentWidth;
713
- const segment = dataArray.slice(segmentStart, segmentEnd);
714
 
715
- const average = segment.reduce((sum, value) => sum + value, 0) / segment.length;
716
- const height = (average / 255) * 100;
 
 
717
 
718
- bar.style.height = `${height}%`;
719
- });
720
 
721
- animationId = requestAnimationFrame(updateVisualizer);
722
- }
 
723
 
724
  // Start recording
725
- async function startRecording() {
726
- try {
727
- initAudioContext();
728
- createVisualizer();
729
-
730
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
731
-
732
- // Create media recorder
733
- mediaRecorder = new MediaRecorder(stream);
734
-
735
- // Create analyser for visualization
736
- const source = audioContext.createMediaStreamSource(stream);
737
- analyser = audioContext.createAnalyser();
738
- analyser.fftSize = 256;
739
- source.connect(analyser);
740
- dataArray = new Uint8Array(analyser.frequencyBinCount);
741
-
742
- // Set up media recorder events
743
- mediaRecorder.ondataavailable = (e) => {
744
- audioChunks.push(e.data);
745
- };
746
-
747
- mediaRecorder.onstop = () => {
748
- audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
749
- audioUrl = URL.createObjectURL(audioBlob);
750
- audioElement = new Audio(audioUrl);
751
-
752
- // Add to audio list
753
- addToAudioList();
754
-
755
- // Reset chunks
756
- audioChunks = [];
757
- };
758
-
759
- // Start recording
760
- mediaRecorder.start();
761
- updateVisualizer();
762
-
763
- // Update UI
764
- isRecording = true;
765
- recordButton.querySelector('i').className = 'fas fa-stop';
766
- recordButton.querySelector('span').textContent = 'Stop Recording';
767
- recordButton.classList.remove('btn-record');
768
- recordButton.classList.add('btn-stop');
769
-
770
- playButton.disabled = true;
771
- stopButton.disabled = false;
772
-
773
- statusIndicator.className = 'status-indicator status-recording';
774
- statusIndicator.querySelector('i').className = 'fas fa-circle';
775
- statusIndicator.querySelector('span').textContent = 'Recording...';
776
-
777
- } catch (error) {
778
- console.error('Error starting recording:', error);
779
- alert('Could not access microphone. Please check permissions.');
780
- }
781
- }
782
-
783
- // Stop recording
784
- function stopRecording() {
785
- if (mediaRecorder && mediaRecorder.state !== 'inactive') {
786
- mediaRecorder.stop();
787
- cancelAnimationFrame(animationId);
788
-
789
- // Update UI
790
- isRecording = false;
791
- recordButton.querySelector('i').className = 'fas fa-microphone';
792
- recordButton.querySelector('span').textContent = 'Start Recording';
793
- recordButton.classList.remove('btn-stop');
794
- recordButton.classList.add('btn-record');
795
-
796
- playButton.disabled = false;
797
- stopButton.disabled = true;
798
-
799
- statusIndicator.className = 'status-indicator status-idle';
800
- statusIndicator.querySelector('i').className = 'fas fa-circle';
801
- statusIndicator.querySelector('span').textContent = 'Recording stopped';
802
- }
803
- }
804
-
805
- // Play recording
806
- function playRecording() {
807
- if (audioElement) {
808
- audioElement.play();
809
- isPlaying = true;
810
-
811
- // Update UI
812
- playButton.querySelector('i').className = 'fas fa-pause';
813
- playButton.querySelector('span').textContent = 'Pause';
814
- playButton.classList.remove('btn-play');
815
- playButton.classList.add('btn-stop');
816
-
817
- stopButton.disabled = false;
818
-
819
- statusIndicator.className = 'status-indicator status-playing';
820
- statusIndicator.querySelector('i').className = 'fas fa-circle';
821
- statusIndicator.querySelector('span').textContent = 'Playing...';
822
-
823
- // Reset when finished
824
- audioElement.onended = () => {
825
- resetPlayback();
826
- };
827
- }
828
- }
829
-
830
- // Pause recording
831
- function pauseRecording() {
832
- if (audioElement && !audioElement.paused) {
833
- audioElement.pause();
834
- isPlaying = false;
835
-
836
- // Update UI
837
- playButton.querySelector('i').className = 'fas fa-play';
838
- playButton.querySelector('span').textContent = 'Play Recording';
839
- playButton.classList.remove('btn-stop');
840
- playButton.classList.add('btn-play');
841
-
842
- statusIndicator.className = 'status-indicator status-idle';
843
- statusIndicator.querySelector('i').className = 'fas fa-circle';
844
- statusIndicator.querySelector('span').textContent = 'Paused';
845
- }
846
- }
847
-
848
- // Reset playback UI
849
- function resetPlayback() {
850
- isPlaying = false;
851
- playButton.querySelector('i').className = 'fas fa-play';
852
- playButton.querySelector('span').textContent = 'Play Recording';
853
- playButton.classList.remove('btn-stop');
854
- playButton.classList.add('btn-play');
855
-
856
- stopButton.disabled = true;
857
-
858
- statusIndicator.className = 'status-indicator status-idle';
859
- statusIndicator.querySelector('i').className = 'fas fa-circle';
860
- statusIndicator.querySelector('span').textContent = 'Ready';
861
- }
862
-
863
- // Stop all audio
864
- function stopAll() {
865
- if (isRecording) {
866
- stopRecording();
867
- }
868
-
869
- if (isPlaying) {
870
- audioElement.pause();
871
- audioElement.currentTime = 0;
872
- resetPlayback();
873
- }
874
- }
875
-
876
- // Add recording to list
877
- function addToAudioList() {
878
- if (!audioBlob) return;
879
-
880
- const audioItem = document.createElement('div');
881
- audioItem.className = 'audio-item';
882
-
883
- const date = new Date();
884
- const name = `Recording ${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
885
-
886
- audioItem.innerHTML = `
887
- <div class="audio-info">
888
- <div class="audio-name">${name}</div>
889
- <div class="audio-duration">${formatTime(audioElement.duration)}</div>
890
- </div>
891
- <div class="audio-actions">
892
- <button class="action-btn play-audio" title="Play">
893
- <i class="fas fa-play"></i>
894
- </button>
895
- <button class="action-btn download-audio" title="Download">
896
- <i class="fas fa-download"></i>
897
- </button>
898
- <button class="action-btn delete-audio" title="Delete">
899
- <i class="fas fa-trash"></i>
900
- </button>
901
- </div>
902
- `;
903
-
904
- // Add event listeners
905
- audioItem.querySelector('.play-audio').addEventListener('click', () => {
906
- if (audioElement.paused) {
907
- audioElement.play();
908
- audioItem.querySelector('.play-audio i').className = 'fas fa-pause';
909
- } else {
910
- audioElement.pause();
911
- audioItem.querySelector('.play-audio i').className = 'fas fa-play';
912
- }
913
- });
914
-
915
- audioItem.querySelector('.download-audio').addEventListener('click', () => {
916
- const a = document.createElement('a');
917
- a.href = audioUrl;
918
- a.download = `${name}.wav`;
919
- a.click();
920
- });
921
-
922
- audioItem.querySelector('.delete-audio').addEventListener('click', () => {
923
- audioItem.remove();
924
- URL.revokeObjectURL(audioUrl);
925
- });
926
-
927
- audioList.prepend(audioItem);
928
- }
929
-
930
- // Format time (seconds to MM:SS)
931
- function formatTime(seconds) {
932
- const minutes = Math.floor(seconds / 60);
933
- const secs = Math.floor(seconds % 60);
934
- return `${minutes}:${secs < 10 ? '0' : ''}${secs}`;
935
- }
936
-
937
- // Event listeners
938
- recordButton.addEventListener('click', () => {
939
- if (isRecording) {
940
- stopRecording();
941
- } else {
942
- startRecording();
943
- }
944
- });
945
-
946
- playButton.addEventListener('click', () => {
947
- if (isPlaying) {
948
- pauseRecording();
949
- } else {
950
- playRecording();
951
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
952
  });
953
 
954
- stopButton.addEventListener('click', stopAll);
955
-
956
- // Initialize
957
- createVisualizer();
958
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
959
  </body>
 
960
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="en">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>VibeVoice - Realtime 0.5B</title>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ :root {
11
+ --primary-color: #6c5ce7;
12
+ --secondary-color: #a29bfe;
13
+ --accent-color: #fd79a8;
14
+ --dark-color: #2d3436;
15
+ --light-color: #f5f6fa;
16
+ --success-color: #00b894;
17
+ --warning-color: #fdcb6e;
18
+ --danger-color: #d63031;
19
+ --shadow: 0 10px 30px -5px rgba(108, 92, 231, 0.3);
20
+ --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
21
+ }
22
+
23
+ * {
24
+ margin: 0;
25
+ padding: 0;
26
+ box-sizing: border-box;
27
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
28
+ }
29
+
30
+ body {
31
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
32
+ min-height: 100vh;
33
+ display: flex;
34
+ flex-direction: column;
35
+ color: var(--dark-color);
36
+ line-height: 1.6;
37
+ }
38
+
39
+ header {
40
+ background: white;
41
+ box-shadow: var(--shadow);
42
+ padding: 1rem 2rem;
43
+ display: flex;
44
+ justify-content: space-between;
45
+ align-items: center;
46
+ position: sticky;
47
+ top: 0;
48
+ z-index: 100;
49
+ }
50
+
51
+ .logo {
52
+ display: flex;
53
+ align-items: center;
54
+ gap: 0.5rem;
55
+ text-decoration: none;
56
+ color: var(--primary-color);
57
+ font-weight: 700;
58
+ font-size: 1.5rem;
59
+ }
60
+
61
+ .logo-icon {
62
+ font-size: 2rem;
63
+ color: var(--accent-color);
64
+ }
65
+
66
+ .header-right {
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 1.5rem;
70
+ }
71
+
72
+ .anycoder-link {
73
+ color: var(--primary-color);
74
+ text-decoration: none;
75
+ font-weight: 500;
76
+ transition: var(--transition);
77
+ }
78
+
79
+ .anycoder-link:hover {
80
+ color: var(--accent-color);
81
+ text-decoration: underline;
82
+ }
83
+
84
+ .user-profile {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 0.5rem;
88
+ cursor: pointer;
89
+ }
90
+
91
+ .user-avatar {
92
+ width: 40px;
93
+ height: 40px;
94
+ border-radius: 50%;
95
+ background: var(--secondary-color);
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ color: white;
100
+ font-weight: bold;
101
+ }
102
+
103
+ main {
104
+ flex: 1;
105
+ padding: 2rem;
106
+ display: flex;
107
+ flex-direction: column;
108
+ gap: 2rem;
109
+ max-width: 1200px;
110
+ margin: 0 auto;
111
+ width: 100%;
112
+ }
113
+
114
+ .hero {
115
+ text-align: center;
116
+ padding: 2rem 0;
117
+ background: white;
118
+ border-radius: 20px;
119
+ box-shadow: var(--shadow);
120
+ }
121
+
122
+ .hero h1 {
123
+ font-size: 2.5rem;
124
+ margin-bottom: 1rem;
125
+ background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
126
+ -webkit-background-clip: text;
127
+ -webkit-text-fill-color: transparent;
128
+ }
129
+
130
+ .hero p {
131
+ font-size: 1.1rem;
132
+ color: #636e72;
133
+ max-width: 700px;
134
+ margin: 0 auto 2rem;
135
+ }
136
+
137
+ .voice-controls {
138
+ display: flex;
139
+ flex-direction: column;
140
+ gap: 2rem;
141
+ background: white;
142
+ padding: 2rem;
143
+ border-radius: 20px;
144
+ box-shadow: var(--shadow);
145
+ }
146
+
147
+ .control-section {
148
+ display: flex;
149
+ flex-direction: column;
150
+ gap: 1rem;
151
+ }
152
+
153
+ .control-header {
154
+ display: flex;
155
+ justify-content: space-between;
156
+ align-items: center;
157
+ }
158
+
159
+ .control-title {
160
+ font-size: 1.3rem;
161
+ font-weight: 600;
162
+ color: var(--primary-color);
163
+ }
164
+
165
+ .control-grid {
166
+ display: grid;
167
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
168
+ gap: 1.5rem;
169
+ }
170
+
171
+ .control-card {
172
+ background: var(--light-color);
173
+ padding: 1.5rem;
174
+ border-radius: 15px;
175
+ display: flex;
176
+ flex-direction: column;
177
+ gap: 1rem;
178
+ transition: var(--transition);
179
+ border: 2px solid transparent;
180
+ }
181
+
182
+ .control-card:hover {
183
+ border-color: var(--secondary-color);
184
+ transform: translateY(-5px);
185
+ }
186
+
187
+ .control-card h3 {
188
+ color: var(--dark-color);
189
+ font-size: 1.1rem;
190
+ }
191
+
192
+ .control-card p {
193
+ color: #636e72;
194
+ font-size: 0.9rem;
195
+ }
196
+
197
+ .voice-button {
198
+ width: 100%;
199
+ padding: 1rem;
200
+ border: none;
201
+ border-radius: 10px;
202
+ font-size: 1rem;
203
+ font-weight: 600;
204
+ cursor: pointer;
205
+ transition: var(--transition);
206
+ display: flex;
207
+ align-items: center;
208
+ justify-content: center;
209
+ gap: 0.5rem;
210
+ }
211
+
212
+ .btn-record {
213
+ background: linear-gradient(135deg, var(--primary-color), #5649c0);
214
+ color: white;
215
+ }
216
+
217
+ .btn-record:active {
218
+ background: linear-gradient(135deg, #5649c0, var(--primary-color));
219
+ transform: scale(0.98);
220
+ }
221
+
222
+ .btn-play {
223
+ background: linear-gradient(135deg, var(--success-color), #009688);
224
+ color: white;
225
+ }
226
+
227
+ .btn-play:active {
228
+ background: linear-gradient(135deg, #009688, var(--success-color));
229
+ transform: scale(0.98);
230
+ }
231
+
232
+ .btn-stop {
233
+ background: linear-gradient(135deg, var(--danger-color), #b71540);
234
+ color: white;
235
+ }
236
+
237
+ .btn-stop:active {
238
+ background: linear-gradient(135deg, #b71540, var(--danger-color));
239
+ transform: scale(0.98);
240
+ }
241
+
242
+ .btn-download {
243
+ background: linear-gradient(135deg, var(--warning-color), #fdcb6e);
244
+ color: white;
245
+ }
246
+
247
+ .btn-download:active {
248
+ background: linear-gradient(135deg, #fdcb6e, var(--warning-color));
249
+ transform: scale(0.98);
250
+ }
251
+
252
+ .visualizer {
253
+ height: 100px;
254
+ background: var(--light-color);
255
+ border-radius: 10px;
256
+ display: flex;
257
+ align-items: center;
258
+ justify-content: center;
259
+ overflow: hidden;
260
+ position: relative;
261
+ }
262
+
263
+ .visualizer-bars {
264
+ display: flex;
265
+ height: 100%;
266
+ width: 100%;
267
+ align-items: flex-end;
268
+ justify-content: center;
269
+ gap: 2px;
270
+ }
271
+
272
+ .bar {
273
+ width: 8px;
274
+ background: var(--primary-color);
275
+ border-radius: 5px 5px 0 0;
276
+ transition: height 0.1s ease;
277
+ }
278
+
279
+ .status-indicator {
280
+ display: flex;
281
+ align-items: center;
282
+ gap: 0.5rem;
283
+ padding: 0.5rem 1rem;
284
+ border-radius: 20px;
285
+ font-weight: 500;
286
+ margin-top: 1rem;
287
+ }
288
+
289
+ .status-recording {
290
+ background: rgba(240, 128, 128, 0.2);
291
+ color: var(--danger-color);
292
+ }
293
+
294
+ .status-playing {
295
+ background: rgba(76, 175, 80, 0.2);
296
+ color: var(--success-color);
297
+ }
298
+
299
+ .status-idle {
300
+ background: rgba(0, 0, 0, 0.05);
301
+ color: #636e72;
302
+ }
303
+
304
+ .audio-list {
305
+ display: flex;
306
+ flex-direction: column;
307
+ gap: 1rem;
308
+ max-height: 300px;
309
+ overflow-y: auto;
310
+ padding-right: 0.5rem;
311
+ }
312
+
313
+ .audio-item {
314
+ background: var(--light-color);
315
+ padding: 1rem;
316
+ border-radius: 10px;
317
+ display: flex;
318
+ justify-content: space-between;
319
+ align-items: center;
320
+ transition: var(--transition);
321
+ }
322
+
323
+ .audio-item:hover {
324
+ background: #e8e8e8;
325
+ }
326
+
327
+ .audio-info {
328
+ display: flex;
329
+ flex-direction: column;
330
+ gap: 0.2rem;
331
+ }
332
+
333
+ .audio-name {
334
+ font-weight: 600;
335
+ color: var(--dark-color);
336
+ }
337
+
338
+ .audio-duration {
339
+ font-size: 0.8rem;
340
+ color: #636e72;
341
+ }
342
+
343
+ .audio-actions {
344
+ display: flex;
345
+ gap: 0.5rem;
346
+ }
347
+
348
+ .action-btn {
349
+ background: none;
350
+ border: none;
351
+ color: #636e72;
352
+ cursor: pointer;
353
+ padding: 0.3rem;
354
+ border-radius: 50%;
355
+ transition: var(--transition);
356
+ }
357
+
358
+ .action-btn:hover {
359
+ background: rgba(0, 0, 0, 0.1);
360
+ color: var(--primary-color);
361
+ }
362
+
363
+ .settings-panel {
364
+ background: white;
365
+ padding: 1.5rem;
366
+ border-radius: 20px;
367
+ box-shadow: var(--shadow);
368
+ }
369
+
370
+ .settings-header {
371
+ display: flex;
372
+ justify-content: space-between;
373
+ align-items: center;
374
+ margin-bottom: 1.5rem;
375
+ }
376
+
377
+ .settings-title {
378
+ font-size: 1.3rem;
379
+ font-weight: 600;
380
+ color: var(--primary-color);
381
+ }
382
+
383
+ .settings-grid {
384
+ display: grid;
385
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
386
+ gap: 1.5rem;
387
+ }
388
+
389
+ .setting-item {
390
+ display: flex;
391
+ flex-direction: column;
392
+ gap: 0.5rem;
393
+ }
394
+
395
+ .setting-label {
396
+ font-weight: 500;
397
+ color: var(--dark-color);
398
+ }
399
+
400
+ .setting-input {
401
+ padding: 0.8rem;
402
+ border: 2px solid #e0e0e0;
403
+ border-radius: 10px;
404
+ font-size: 1rem;
405
+ transition: var(--transition);
406
+ }
407
+
408
+ .setting-input:focus {
409
+ outline: none;
410
+ border-color: var(--primary-color);
411
+ }
412
+
413
+ .toggle-switch {
414
+ position: relative;
415
+ display: inline-block;
416
+ width: 60px;
417
+ height: 30px;
418
+ }
419
+
420
+ .toggle-switch input {
421
+ opacity: 0;
422
+ width: 0;
423
+ height: 0;
424
+ }
425
+
426
+ .slider {
427
+ position: absolute;
428
+ cursor: pointer;
429
+ top: 0;
430
+ left: 0;
431
+ right: 0;
432
+ bottom: 0;
433
+ background-color: #ccc;
434
+ transition: var(--transition);
435
+ border-radius: 30px;
436
+ }
437
+
438
+ .slider:before {
439
+ position: absolute;
440
+ content: "";
441
+ height: 22px;
442
+ width: 22px;
443
+ left: 4px;
444
+ bottom: 4px;
445
+ background-color: white;
446
+ transition: var(--transition);
447
+ border-radius: 50%;
448
+ }
449
+
450
+ input:checked+.slider {
451
+ background-color: var(--primary-color);
452
+ }
453
+
454
+ input:checked+.slider:before {
455
+ transform: translateX(30px);
456
+ }
457
+
458
+ footer {
459
+ background: var(--dark-color);
460
+ color: white;
461
+ text-align: center;
462
+ padding: 1.5rem;
463
+ margin-top: auto;
464
+ }
465
+
466
+ .footer-links {
467
+ display: flex;
468
+ justify-content: center;
469
+ gap: 1.5rem;
470
+ margin-bottom: 1rem;
471
+ }
472
+
473
+ .footer-link {
474
+ color: white;
475
+ text-decoration: none;
476
+ transition: var(--transition);
477
+ }
478
+
479
+ .footer-link:hover {
480
+ color: var(--primary-color);
481
+ }
482
+
483
+ @media (max-width: 768px) {
484
+ header {
485
+ padding: 1rem;
486
+ }
487
+
488
+ .hero h1 {
489
+ font-size: 2rem;
490
+ }
491
+
492
+ .hero p {
493
+ font-size: 1rem;
494
+ }
495
+
496
+ .control-grid {
497
+ grid-template-columns: 1fr;
498
+ }
499
+
500
+ .settings-grid {
501
+ grid-template-columns: 1fr;
502
+ }
503
+
504
+ .header-right {
505
+ gap: 1rem;
506
+ }
507
+
508
+ .user-profile {
509
+ display: none;
510
+ }
511
+ }
512
+
513
+ @media (max-width: 480px) {
514
+ main {
515
+ padding: 1rem;
516
+ }
517
+
518
+ .hero h1 {
519
+ font-size: 1.5rem;
520
+ }
521
+
522
+ .footer-links {
523
+ flex-direction: column;
524
+ gap: 0.5rem;
525
+ }
526
+ }
527
+
528
+ /* Loading animation */
529
+ @keyframes pulse {
530
+ 0%, 100% {
531
+ opacity: 1;
532
+ }
533
+ 50% {
534
+ opacity: 0.5;
535
+ }
536
+ }
537
+
538
+ .loading {
539
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
540
+ }
541
+
542
+ /* Download button styles */
543
+ .download-section {
544
+ display: flex;
545
+ flex-direction: column;
546
+ gap: 1rem;
547
+ }
548
+
549
+ .download-card {
550
+ background: var(--light-color);
551
+ padding: 1.5rem;
552
+ border-radius: 15px;
553
+ display: flex;
554
+ flex-direction: column;
555
+ gap: 1rem;
556
+ transition: var(--transition);
557
+ border: 2px solid transparent;
558
+ }
559
+
560
+ .download-card:hover {
561
+ border-color: var(--secondary-color);
562
+ transform: translateY(-5px);
563
+ }
564
+
565
+ .download-options {
566
+ display: flex;
567
+ flex-direction: column;
568
+ gap: 0.5rem;
569
+ }
570
+
571
+ .download-format {
572
+ display: flex;
573
+ align-items: center;
574
+ gap: 0.5rem;
575
+ }
576
+
577
+ .format-label {
578
+ font-weight: 500;
579
+ }
580
+
581
+ .format-select {
582
+ padding: 0.5rem;
583
+ border: 2px solid #e0e0e0;
584
+ border-radius: 5px;
585
+ background: white;
586
+ cursor: pointer;
587
+ }
588
+
589
+ .download-all-btn {
590
+ background: linear-gradient(135deg, var(--primary-color), var(--accent-color));
591
+ color: white;
592
+ border: none;
593
+ padding: 0.8rem 1.5rem;
594
+ border-radius: 10px;
595
+ font-weight: 600;
596
+ cursor: pointer;
597
+ transition: var(--transition);
598
+ display: flex;
599
+ align-items: center;
600
+ justify-content: center;
601
+ gap: 0.5rem;
602
+ margin-top: 0.5rem;
603
+ }
604
+
605
+ .download-all-btn:hover {
606
+ transform: translateY(-2px);
607
+ box-shadow: 0 5px 15px rgba(108, 92, 231, 0.3);
608
+ }
609
+
610
+ .download-all-btn:active {
611
+ transform: translateY(0);
612
+ }
613
+
614
+ .download-all-btn i {
615
+ font-size: 1.1rem;
616
+ }
617
+ </style>
618
  </head>
619
+
620
  <body>
621
+ <header>
622
+ <a href="#" class="logo">
623
+ <i class="fas fa-microphone-alt logo-icon"></i>
624
+ <span>VibeVoice</span>
625
+ </a>
626
+ <div class="header-right">
627
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="anycoder-link" target="_blank">
628
+ Built with anycoder
629
+ </a>
630
+ <div class="user-profile">
631
+ <div class="user-avatar">JD</div>
632
+ <span>John Doe</span>
633
+ </div>
634
+ </div>
635
+ </header>
636
+
637
+ <main>
638
+ <section class="hero">
639
+ <h1>Real-time Voice Interaction</h1>
640
+ <p>Experience seamless voice communication with our advanced 0.5B model. Record, play, and analyze voice in
641
+ real-time with high-quality audio processing.</p>
642
+ <div class="status-indicator status-idle" id="statusIndicator">
643
+ <i class="fas fa-circle"></i>
644
+ <span>Ready to record</span>
645
+ </div>
646
+ </section>
647
+
648
+ <section class="voice-controls">
649
+ <div class="control-section">
650
+ <div class="control-header">
651
+ <h2 class="control-title">Voice Controls</h2>
652
  </div>
653
+ <div class="control-grid">
654
+ <div class="control-card">
655
+ <h3>Record Audio</h3>
656
+ <p>Start recording your voice with high-quality capture</p>
657
+ <button class="voice-button btn-record" id="recordButton">
658
+ <i class="fas fa-microphone"></i>
659
+ <span>Start Recording</span>
660
+ </button>
661
+ </div>
662
+ <div class="control-card">
663
+ <h3>Playback</h3>
664
+ <p>Listen to your recorded audio with enhanced playback</p>
665
+ <button class="voice-button btn-play" id="playButton" disabled>
666
+ <i class="fas fa-play"></i>
667
+ <span>Play Recording</span>
668
+ </button>
669
+ </div>
670
+ <div class="control-card">
671
+ <h3>Stop All</h3>
672
+ <p>Stop all current audio operations</p>
673
+ <button class="voice-button btn-stop" id="stopButton" disabled>
674
+ <i class="fas fa-stop"></i>
675
+ <span>Stop</span>
676
+ </button>
677
+ </div>
678
+ <div class="control-card">
679
+ <h3>Download All</h3>
680
+ <p>Download all your recordings at once</p>
681
+ <button class="voice-button btn-download" id="downloadAllButton" disabled>
682
+ <i class="fas fa-download"></i>
683
+ <span>Download All</span>
684
+ </button>
685
+ </div>
686
+ </div>
687
+ </div>
 
 
 
 
 
 
 
 
 
688
 
689
+ <div class="control-section">
690
+ <div class="control-header">
691
+ <h2 class="control-title">Audio Visualizer</h2>
692
+ </div>
693
+ <div class="visualizer">
694
+ <div class="visualizer-bars" id="visualizer">
695
+ <!-- Bars will be generated by JavaScript -->
696
+ </div>
697
+ </div>
698
+ </div>
699
 
700
+ <div class="control-section">
701
+ <div class="control-header">
702
+ <h2 class="control-title">Your Recordings</h2>
703
+ </div>
704
+ <div class="audio-list" id="audioList">
705
+ <!-- Recordings will appear here -->
706
+ </div>
707
+ </div>
 
708
 
709
+ <div class="control-section download-section">
710
+ <div class="control-header">
711
+ <h2 class="control-title">Download Options</h2>
712
+ </div>
713
+ <div class="download-card">
714
+ <div class="download-options">
715
+ <div class="download-format">
716
+ <span class="format-label">Format:</span>
717
+ <select class="format-select" id="downloadFormat">
718
+ <option value="wav">WAV (Uncompressed)</option>
719
+ <option value="mp3">MP3 (Compressed)</option>
720
+ </select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
721
  </div>
722
+ <button class="download-all-btn" id="downloadAllBtn">
723
+ <i class="fas fa-file-archive"></i>
724
+ <span>Download All Recordings</span>
725
+ </button>
726
+ </div>
 
 
 
727
  </div>
728
+ </div>
729
+ </section>
730
+
731
+ <section class="settings-panel">
732
+ <div class="settings-header">
733
+ <h2 class="settings-title">Voice Settings</h2>
734
+ </div>
735
+ <div class="settings-grid">
736
+ <div class="setting-item">
737
+ <label class="setting-label">Audio Quality</label>
738
+ <select class="setting-input" id="audioQuality">
739
+ <option value="high">High (320kbps)</option>
740
+ <option value="medium" selected>Medium (192kbps)</option>
741
+ <option value="low">Low (128kbps)</option>
742
+ </select>
743
+ </div>
744
+ <div class="setting-item">
745
+ <label class="setting-label">Noise Reduction</label>
746
+ <div class="toggle-switch">
747
+ <input type="checkbox" id="noiseReduction" checked>
748
+ <span class="slider"></span>
749
+ </div>
750
+ </div>
751
+ <div class="setting-item">
752
+ <label class="setting-label">Echo Cancellation</label>
753
+ <div class="toggle-switch">
754
+ <input type="checkbox" id="echoCancellation" checked>
755
+ <span class="slider"></span>
756
+ </div>
757
+ </div>
758
+ <div class="setting-item">
759
+ <label class="setting-label">Auto Gain Control</label>
760
+ <div class="toggle-switch">
761
+ <input type="checkbox" id="autoGain" checked>
762
+ <span class="slider"></span>
763
+ </div>
764
+ </div>
765
+ </div>
766
+ </section>
767
+ </main>
768
+
769
+ <footer>
770
+ <div class="footer-links">
771
+ <a href="#" class="footer-link">Privacy Policy</a>
772
+ <a href="#" class="footer-link">Terms of Service</a>
773
+ <a href="#" class="footer-link">Contact Support</a>
774
+ </div>
775
+ <p>&copy; 2023 VibeVoice. All rights reserved.</p>
776
+ </footer>
777
+
778
+ <script>
779
+ // DOM Elements
780
+ const recordButton = document.getElementById('recordButton');
781
+ const playButton = document.getElementById('playButton');
782
+ const stopButton = document.getElementById('stopButton');
783
+ const downloadAllButton = document.getElementById('downloadAllButton');
784
+ const downloadAllBtn = document.getElementById('downloadAllBtn');
785
+ const downloadFormat = document.getElementById('downloadFormat');
786
+ const statusIndicator = document.getElementById('statusIndicator');
787
+ const visualizer = document.getElementById('visualizer');
788
+ const audioList = document.getElementById('audioList');
789
+
790
+ // Audio context and variables
791
+ let audioContext;
792
+ let mediaRecorder;
793
+ let audioChunks = [];
794
+ let audioBlob;
795
+ let audioUrl;
796
+ let audioElement;
797
+ let analyser;
798
+ let dataArray;
799
+ let animationId;
800
+ let isRecording = false;
801
+ let isPlaying = false;
802
+ let recordings = [];
803
+
804
+ // Initialize audio context
805
+ function initAudioContext() {
806
+ if (!audioContext) {
807
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
808
+ }
809
+ }
810
+
811
+ // Create visualizer bars
812
+ function createVisualizer() {
813
+ visualizer.innerHTML = '';
814
+ for (let i = 0; i < 50; i++) {
815
+ const bar = document.createElement('div');
816
+ bar.className = 'bar';
817
+ bar.style.height = '10%';
818
+ visualizer.appendChild(bar);
819
+ }
820
+ }
821
+
822
+ // Update visualizer
823
+ function updateVisualizer() {
824
+ if (!analyser) return;
825
+
826
+ analyser.getByteFrequencyData(dataArray);
827
+
828
+ const bars = visualizer.querySelectorAll('.bar');
829
+ const barCount = bars.length;
830
+ const segmentWidth = Math.floor(dataArray.length / barCount);
831
+
832
+ bars.forEach((bar, i) => {
833
+ const segmentStart = i * segmentWidth;
834
+ const segmentEnd = segmentStart + segmentWidth;
835
+ const segment = dataArray.slice(segmentStart, segmentEnd);
836
+
837
+ const average = segment.reduce((sum, value) => sum + value, 0) / segment.length;
838
+ const height = (average / 255) * 100;
839
+
840
+ bar.style.height = `${height}%`;
841
+ });
842
+
843
+ animationId = requestAnimationFrame(updateVisualizer);
844
+ }
845
+
846
+ // Start recording
847
+ async function startRecording() {
848
+ try {
849
+ initAudioContext();
850
+ createVisualizer();
851
 
852
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
 
 
853
 
854
+ // Create media recorder
855
+ mediaRecorder = new MediaRecorder(stream);
856
 
857
+ // Create analyser for visualization
858
+ const source = audioContext.createMediaStreamSource(stream);
859
+ analyser = audioContext.createAnalyser();
860
+ analyser.fftSize = 256;
861
+ source.connect(analyser);
862
+ dataArray = new Uint8Array(analyser.frequencyBinCount);
863
 
864
+ // Set up media recorder events
865
+ mediaRecorder.ondataavailable = (e) => {
866
+ audioChunks.push(e.data);
867
+ };
868
 
869
+ mediaRecorder.onstop = () => {
870
+ audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
871
+ audioUrl = URL.createObjectURL(audioBlob);
872
+ audioElement = new Audio(audioUrl);
873
 
874
+ // Add to audio list
875
+ addToAudioList();
876
 
877
+ // Reset chunks
878
+ audioChunks = [];
879
+ };
880
 
881
  // Start recording
882
+ mediaRecorder.start();
883
+ updateVisualizer();
884
+
885
+ // Update UI
886
+ isRecording = true;
887
+ recordButton.querySelector('i').className = 'fas fa-stop';
888
+ recordButton.querySelector('span').textContent = 'Stop Recording';
889
+ recordButton.classList.remove('btn-record');
890
+ recordButton.classList.add('btn-stop');
891
+
892
+ playButton.disabled = true;
893
+ stopButton.disabled = false;
894
+ downloadAllButton.disabled = true;
895
+
896
+ statusIndicator.className = 'status-indicator status-recording';
897
+ statusIndicator.querySelector('i').className = 'fas fa-circle';
898
+ statusIndicator.querySelector('span').textContent = 'Recording...';
899
+
900
+ } catch (error) {
901
+ console.error('Error starting recording:', error);
902
+ alert('Could not access microphone. Please check permissions.');
903
+ }
904
+ }
905
+
906
+ // Stop recording
907
+ function stopRecording() {
908
+ if (mediaRecorder && mediaRecorder.state !== 'inactive') {
909
+ mediaRecorder.stop();
910
+ cancelAnimationFrame(animationId);
911
+
912
+ // Update UI
913
+ isRecording = false;
914
+ recordButton.querySelector('i').className = 'fas fa-microphone';
915
+ recordButton.querySelector('span').textContent = 'Start Recording';
916
+ recordButton.classList.remove('btn-stop');
917
+ recordButton.classList.add('btn-record');
918
+
919
+ playButton.disabled = false;
920
+ stopButton.disabled = true;
921
+
922
+ statusIndicator.className = 'status-indicator status-idle';
923
+ statusIndicator.querySelector('i').className = 'fas fa-circle';
924
+ statusIndicator.querySelector('span').textContent = 'Recording stopped';
925
+ }
926
+ }
927
+
928
+ // Play recording
929
+ function playRecording() {
930
+ if (audioElement) {
931
+ audioElement.play();
932
+ isPlaying = true;
933
+
934
+ // Update UI
935
+ playButton.querySelector('i').className = 'fas fa-pause';
936
+ playButton.querySelector('span').textContent = 'Pause';
937
+ playButton.classList.remove('btn-play');
938
+ playButton.classList.add('btn-stop');
939
+
940
+ stopButton.disabled = false;
941
+
942
+ statusIndicator.className = 'status-indicator status-playing';
943
+ statusIndicator.querySelector('i').className = 'fas fa-circle';
944
+ statusIndicator.querySelector('span').textContent = 'Playing...';
945
+
946
+ // Reset when finished
947
+ audioElement.onended = () => {
948
+ resetPlayback();
949
+ };
950
+ }
951
+ }
952
+
953
+ // Pause recording
954
+ function pauseRecording() {
955
+ if (audioElement && !audioElement.paused) {
956
+ audioElement.pause();
957
+ isPlaying = false;
958
+
959
+ // Update UI
960
+ playButton.querySelector('i').className = 'fas fa-play';
961
+ playButton.querySelector('span').textContent = 'Play Recording';
962
+ playButton.classList.remove('btn-stop');
963
+ playButton.classList.add('btn-play');
964
+
965
+ statusIndicator.className = 'status-indicator status-idle';
966
+ statusIndicator.querySelector('i').className = 'fas fa-circle';
967
+ statusIndicator.querySelector('span').textContent = 'Paused';
968
+ }
969
+ }
970
+
971
+ // Reset playback UI
972
+ function resetPlayback() {
973
+ isPlaying = false;
974
+ playButton.querySelector('i').className = 'fas fa-play';
975
+ playButton.querySelector('span').textContent = 'Play Recording';
976
+ playButton.classList.remove('btn-stop');
977
+ playButton.classList.add('btn-play');
978
+
979
+ stopButton.disabled = true;
980
+
981
+ statusIndicator.className = 'status-indicator status-idle';
982
+ statusIndicator.querySelector('i').className = 'fas fa-circle';
983
+ statusIndicator.querySelector('span').textContent = 'Ready';
984
+ }
985
+
986
+ // Stop all audio
987
+ function stopAll() {
988
+ if (isRecording) {
989
+ stopRecording();
990
+ }
991
+
992
+ if (isPlaying) {
993
+ audioElement.pause();
994
+ audioElement.currentTime = 0;
995
+ resetPlayback();
996
+ }
997
+ }
998
+
999
+ // Add recording to list
1000
+ function addToAudioList() {
1001
+ if (!audioBlob) return;
1002
+
1003
+ const date = new Date();
1004
+ const name = `Recording ${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
1005
+ const recording = {
1006
+ name: name,
1007
+ blob: audioBlob,
1008
+ url: audioUrl,
1009
+ element: audioElement,
1010
+ duration: audioElement.duration
1011
+ };
1012
+
1013
+ recordings.push(recording);
1014
+
1015
+ const audioItem = document.createElement('div');
1016
+ audioItem.className = 'audio-item';
1017
+ audioItem.dataset.index = recordings.length - 1;
1018
+
1019
+ audioItem.innerHTML = `
1020
+ <div class="audio-info">
1021
+ <div class="audio-name">${name}</div>
1022
+ <div class="audio-duration">${formatTime(audioElement.duration)}</div>
1023
+ </div>
1024
+ <div class="audio-actions">
1025
+ <button class="action-btn play-audio" title="Play">
1026
+ <i class="fas fa-play"></i>
1027
+ </button>
1028
+ <button class="action-btn download-audio" title="Download">
1029
+ <i class="fas fa-download"></i>
1030
+ </button>
1031
+ <button class="action-btn delete-audio" title="Delete">
1032
+ <i class="fas fa-trash"></i>
1033
+ </button>
1034
+ </div>
1035
+ `;
1036
+
1037
+ // Add event listeners
1038
+ audioItem.querySelector('.play-audio').addEventListener('click', () => {
1039
+ const index = parseInt(audioItem.dataset.index);
1040
+ const recording = recordings[index];
1041
+
1042
+ if (recording.element.paused) {
1043
+ // Pause all other recordings
1044
+ recordings.forEach((rec, i) => {
1045
+ if (i !== index && !rec.element.paused) {
1046
+ rec.element.pause();
1047
+ const otherItem = audioList.querySelector(`[data-index="${i}"]`);
1048
+ if (otherItem) {
1049
+ otherItem.querySelector('.play-audio i').className = 'fas fa-play';
1050
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1051
  }
1052
+ });
1053
+
1054
+ recording.element.play();
1055
+ audioItem.querySelector('.play-audio i').className = 'fas fa-pause';
1056
+ } else {
1057
+ recording.element.pause();
1058
+ audioItem.querySelector('.play-audio i').className = 'fas fa-play';
1059
+ }
1060
+ });
1061
+
1062
+ audioItem.querySelector('.download-audio').addEventListener('click', () => {
1063
+ const index = parseInt(audioItem.dataset.index);
1064
+ downloadRecording(recordings[index]);
1065
+ });
1066
+
1067
+ audioItem.querySelector('.delete-audio').addEventListener('click', () => {
1068
+ const index = parseInt(audioItem.dataset.index);
1069
+ audioItem.remove();
1070
+ URL.revokeObjectURL(recordings[index].url);
1071
+ recordings.splice(index, 1);
1072
+
1073
+ // Update remaining items' data-index
1074
+ const remainingItems = audioList.querySelectorAll('.audio-item');
1075
+ remainingItems.forEach((item, newIndex) => {
1076
+ item.dataset.index = newIndex;
1077
  });
1078
 
1079
+ // Update download all button state
1080
+ updateDownloadAllButtonState();
1081
+ });
1082
+
1083
+ audioList.prepend(audioItem);
1084
+
1085
+ // Enable download all button if we have recordings
1086
+ updateDownloadAllButtonState();
1087
+ }
1088
+
1089
+ // Update download all button state
1090
+ function updateDownloadAllButtonState() {
1091
+ downloadAllButton.disabled = recordings.length === 0;
1092
+ downloadAllBtn.disabled = recordings.length === 0;
1093
+ }
1094
+
1095
+ // Download single recording
1096
+ function downloadRecording(recording) {
1097
+ const format = downloadFormat.value;
1098
+ const extension = format === 'wav' ? 'wav' : 'mp3';
1099
+ const filename = `${recording.name}.${extension}`;
1100
+
1101
+ const a = document.createElement('a');
1102
+ a.href = recording.url;
1103
+ a.download = filename;
1104
+ a.click();
1105
+ }
1106
+
1107
+ // Download all recordings as zip
1108
+ async function downloadAllRecordings() {
1109
+ if (recordings.length === 0) return;
1110
+
1111
+ const format = downloadFormat.value;
1112
+ const extension = format === 'wav' ? 'wav' : 'mp3';
1113
+
1114
+ // Create a zip file using JSZip
1115
+ const JSZip = await import('https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js');
1116
+
1117
+ const zip = new JSZip();
1118
+
1119
+ recordings.forEach((recording, index) => {
1120
+ const filename = `${recording.name}.${extension}`;
1121
+ zip.file(filename, recording.blob);
1122
+ });
1123
+
1124
+ const content = await zip.generateAsync({ type: 'blob' });
1125
+ const zipFilename = `VibeVoice_Recordings_${new Date().toISOString().slice(0, 10)}.zip`;
1126
+
1127
+ const a = document.createElement('a');
1128
+ a.href = URL.createObjectURL(content);
1129
+ a.download = zipFilename;
1130
+ a.click();
1131
+
1132
+ // Clean up
1133
+ setTimeout(() => URL.revokeObjectURL(a.href), 100);
1134
+ }
1135
+
1136
+ // Format time (seconds to MM:SS)
1137
+ function formatTime(seconds) {
1138
+ const minutes = Math.floor(seconds / 60);
1139
+ const secs = Math.floor(seconds % 60);
1140
+ return `${minutes}:${secs < 10 ? '0' : ''}${secs}`;
1141
+ }
1142
+
1143
+ // Event listeners
1144
+ recordButton.addEventListener('click', () => {
1145
+ if (isRecording) {
1146
+ stopRecording();
1147
+ } else {
1148
+ startRecording();
1149
+ }
1150
+ });
1151
+
1152
+ playButton.addEventListener('click', () => {
1153
+ if (isPlaying) {
1154
+ pauseRecording();
1155
+ } else {
1156
+ playRecording();
1157
+ }
1158
+ });
1159
+
1160
+ stopButton.addEventListener('click', stopAll);
1161
+
1162
+ downloadAllButton.addEventListener('click', downloadAllRecordings);
1163
+ downloadAllBtn.addEventListener('click', downloadAllRecordings);
1164
+
1165
+ // Initialize
1166
+ createVisualizer();
1167
+ </script>
1168
  </body>
1169
+
1170
  </html>