HI7RAI commited on
Commit
3f1cc4b
·
verified ·
1 Parent(s): 681a08b

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1370 -19
index.html CHANGED
@@ -1,19 +1,1370 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Audio Recorder & Player</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
9
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
10
+ <style>
11
+ :root {
12
+ --bg: #0a0a0f;
13
+ --bg-secondary: #12121a;
14
+ --bg-tertiary: #1a1a25;
15
+ --fg: #e8e8ed;
16
+ --fg-muted: #6b6b7a;
17
+ --accent: #00d4aa;
18
+ --accent-glow: rgba(0, 212, 170, 0.3);
19
+ --accent-secondary: #ff6b6b;
20
+ --border: #2a2a3a;
21
+ --card: #15151f;
22
+ --danger: #ff4757;
23
+ --warning: #ffa502;
24
+ }
25
+
26
+ * {
27
+ box-sizing: border-box;
28
+ }
29
+
30
+ body {
31
+ font-family: 'Space Grotesk', sans-serif;
32
+ background: var(--bg);
33
+ color: var(--fg);
34
+ min-height: 100vh;
35
+ margin: 0;
36
+ overflow-x: hidden;
37
+ }
38
+
39
+ .mono {
40
+ font-family: 'JetBrains Mono', monospace;
41
+ }
42
+
43
+ /* Background atmosphere */
44
+ .bg-atmosphere {
45
+ position: fixed;
46
+ inset: 0;
47
+ pointer-events: none;
48
+ z-index: 0;
49
+ overflow: hidden;
50
+ }
51
+
52
+ .bg-atmosphere::before {
53
+ content: '';
54
+ position: absolute;
55
+ top: -50%;
56
+ left: -50%;
57
+ width: 200%;
58
+ height: 200%;
59
+ background:
60
+ radial-gradient(ellipse at 20% 20%, rgba(0, 212, 170, 0.08) 0%, transparent 50%),
61
+ radial-gradient(ellipse at 80% 80%, rgba(255, 107, 107, 0.05) 0%, transparent 50%),
62
+ radial-gradient(ellipse at 50% 50%, rgba(0, 212, 170, 0.03) 0%, transparent 70%);
63
+ animation: atmosphereRotate 60s linear infinite;
64
+ }
65
+
66
+ @keyframes atmosphereRotate {
67
+ from { transform: rotate(0deg); }
68
+ to { transform: rotate(360deg); }
69
+ }
70
+
71
+ .noise-overlay {
72
+ position: fixed;
73
+ inset: 0;
74
+ pointer-events: none;
75
+ z-index: 1;
76
+ opacity: 0.03;
77
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
78
+ }
79
+
80
+ /* Grid pattern */
81
+ .grid-pattern {
82
+ position: fixed;
83
+ inset: 0;
84
+ pointer-events: none;
85
+ z-index: 1;
86
+ background-image:
87
+ linear-gradient(rgba(0, 212, 170, 0.03) 1px, transparent 1px),
88
+ linear-gradient(90deg, rgba(0, 212, 170, 0.03) 1px, transparent 1px);
89
+ background-size: 50px 50px;
90
+ mask-image: radial-gradient(ellipse at center, black 0%, transparent 70%);
91
+ }
92
+
93
+ .main-container {
94
+ position: relative;
95
+ z-index: 10;
96
+ min-height: 100vh;
97
+ padding: 2rem;
98
+ }
99
+
100
+ /* Header */
101
+ .header {
102
+ display: flex;
103
+ align-items: center;
104
+ justify-content: space-between;
105
+ margin-bottom: 3rem;
106
+ padding-bottom: 1.5rem;
107
+ border-bottom: 1px solid var(--border);
108
+ }
109
+
110
+ .logo {
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 0.75rem;
114
+ }
115
+
116
+ .logo-icon {
117
+ width: 40px;
118
+ height: 40px;
119
+ background: linear-gradient(135deg, var(--accent), var(--accent-secondary));
120
+ border-radius: 12px;
121
+ display: flex;
122
+ align-items: center;
123
+ justify-content: center;
124
+ box-shadow: 0 4px 20px var(--accent-glow);
125
+ }
126
+
127
+ .logo-text {
128
+ font-size: 1.25rem;
129
+ font-weight: 600;
130
+ letter-spacing: -0.02em;
131
+ }
132
+
133
+ .built-with {
134
+ font-size: 0.75rem;
135
+ color: var(--fg-muted);
136
+ text-decoration: none;
137
+ transition: color 0.2s;
138
+ }
139
+
140
+ .built-with:hover {
141
+ color: var(--accent);
142
+ }
143
+
144
+ /* Audio Container */
145
+ .audio-wrapper {
146
+ max-width: 800px;
147
+ margin: 0 auto;
148
+ }
149
+
150
+ .block-label {
151
+ display: flex;
152
+ align-items: center;
153
+ gap: 0.5rem;
154
+ margin-bottom: 1rem;
155
+ color: var(--fg-muted);
156
+ font-size: 0.875rem;
157
+ font-weight: 500;
158
+ }
159
+
160
+ .block-label svg {
161
+ width: 18px;
162
+ height: 18px;
163
+ color: var(--accent);
164
+ }
165
+
166
+ .audio-container {
167
+ background: var(--card);
168
+ border: 1px solid var(--border);
169
+ border-radius: 20px;
170
+ padding: 1.5rem;
171
+ position: relative;
172
+ overflow: hidden;
173
+ transition: border-color 0.3s, box-shadow 0.3s;
174
+ }
175
+
176
+ .audio-container:hover {
177
+ border-color: rgba(0, 212, 170, 0.3);
178
+ }
179
+
180
+ .audio-container.dragging {
181
+ border-color: var(--accent);
182
+ box-shadow: 0 0 40px var(--accent-glow);
183
+ }
184
+
185
+ .audio-container.dragging .drop-zone {
186
+ opacity: 1;
187
+ visibility: visible;
188
+ }
189
+
190
+ /* Drop Zone */
191
+ .drop-zone {
192
+ position: absolute;
193
+ inset: 0;
194
+ background: rgba(0, 212, 170, 0.1);
195
+ backdrop-filter: blur(10px);
196
+ display: flex;
197
+ flex-direction: column;
198
+ align-items: center;
199
+ justify-content: center;
200
+ gap: 1rem;
201
+ opacity: 0;
202
+ visibility: hidden;
203
+ transition: all 0.3s;
204
+ z-index: 20;
205
+ border-radius: 20px;
206
+ }
207
+
208
+ .drop-zone-icon {
209
+ width: 60px;
210
+ height: 60px;
211
+ background: var(--accent);
212
+ border-radius: 50%;
213
+ display: flex;
214
+ align-items: center;
215
+ justify-content: center;
216
+ animation: dropPulse 1.5s ease-in-out infinite;
217
+ }
218
+
219
+ @keyframes dropPulse {
220
+ 0%, 100% { transform: scale(1); opacity: 1; }
221
+ 50% { transform: scale(1.1); opacity: 0.8; }
222
+ }
223
+
224
+ .drop-zone-text {
225
+ font-size: 1.125rem;
226
+ font-weight: 500;
227
+ color: var(--fg);
228
+ }
229
+
230
+ /* Source Selector */
231
+ .source-selector {
232
+ display: flex;
233
+ gap: 0.5rem;
234
+ margin-bottom: 1.5rem;
235
+ background: var(--bg-secondary);
236
+ padding: 0.375rem;
237
+ border-radius: 12px;
238
+ width: fit-content;
239
+ }
240
+
241
+ .source-btn {
242
+ padding: 0.625rem 1.25rem;
243
+ border-radius: 10px;
244
+ border: none;
245
+ background: transparent;
246
+ color: var(--fg-muted);
247
+ font-family: inherit;
248
+ font-size: 0.875rem;
249
+ font-weight: 500;
250
+ cursor: pointer;
251
+ transition: all 0.2s;
252
+ display: flex;
253
+ align-items: center;
254
+ gap: 0.5rem;
255
+ }
256
+
257
+ .source-btn:hover {
258
+ color: var(--fg);
259
+ }
260
+
261
+ .source-btn.active {
262
+ background: var(--accent);
263
+ color: var(--bg);
264
+ box-shadow: 0 4px 15px var(--accent-glow);
265
+ }
266
+
267
+ .source-btn svg {
268
+ width: 16px;
269
+ height: 16px;
270
+ }
271
+
272
+ /* Upload Area */
273
+ .upload-area {
274
+ border: 2px dashed var(--border);
275
+ border-radius: 16px;
276
+ padding: 3rem 2rem;
277
+ text-align: center;
278
+ transition: all 0.3s;
279
+ cursor: pointer;
280
+ }
281
+
282
+ .upload-area:hover {
283
+ border-color: var(--accent);
284
+ background: rgba(0, 212, 170, 0.03);
285
+ }
286
+
287
+ .upload-icon {
288
+ width: 64px;
289
+ height: 64px;
290
+ margin: 0 auto 1.5rem;
291
+ background: var(--bg-tertiary);
292
+ border-radius: 16px;
293
+ display: flex;
294
+ align-items: center;
295
+ justify-content: center;
296
+ color: var(--accent);
297
+ transition: transform 0.3s;
298
+ }
299
+
300
+ .upload-area:hover .upload-icon {
301
+ transform: translateY(-4px);
302
+ }
303
+
304
+ .upload-title {
305
+ font-size: 1.125rem;
306
+ font-weight: 600;
307
+ margin-bottom: 0.5rem;
308
+ }
309
+
310
+ .upload-subtitle {
311
+ color: var(--fg-muted);
312
+ font-size: 0.875rem;
313
+ }
314
+
315
+ .upload-formats {
316
+ margin-top: 1rem;
317
+ display: flex;
318
+ flex-wrap: wrap;
319
+ justify-content: center;
320
+ gap: 0.5rem;
321
+ }
322
+
323
+ .format-tag {
324
+ padding: 0.25rem 0.75rem;
325
+ background: var(--bg-secondary);
326
+ border-radius: 20px;
327
+ font-size: 0.75rem;
328
+ color: var(--fg-muted);
329
+ }
330
+
331
+ /* Recorder */
332
+ .recorder-container {
333
+ display: flex;
334
+ flex-direction: column;
335
+ align-items: center;
336
+ padding: 2rem;
337
+ }
338
+
339
+ .waveform-display {
340
+ width: 100%;
341
+ height: 120px;
342
+ background: var(--bg-secondary);
343
+ border-radius: 12px;
344
+ margin-bottom: 1.5rem;
345
+ overflow: hidden;
346
+ position: relative;
347
+ }
348
+
349
+ .waveform-canvas {
350
+ width: 100%;
351
+ height: 100%;
352
+ }
353
+
354
+ .recording-indicator {
355
+ position: absolute;
356
+ top: 1rem;
357
+ right: 1rem;
358
+ display: flex;
359
+ align-items: center;
360
+ gap: 0.5rem;
361
+ padding: 0.375rem 0.75rem;
362
+ background: rgba(255, 71, 87, 0.2);
363
+ border-radius: 20px;
364
+ font-size: 0.75rem;
365
+ color: var(--danger);
366
+ opacity: 0;
367
+ transition: opacity 0.3s;
368
+ }
369
+
370
+ .recording-indicator.active {
371
+ opacity: 1;
372
+ }
373
+
374
+ .recording-dot {
375
+ width: 8px;
376
+ height: 8px;
377
+ background: var(--danger);
378
+ border-radius: 50%;
379
+ animation: recordingPulse 1s ease-in-out infinite;
380
+ }
381
+
382
+ @keyframes recordingPulse {
383
+ 0%, 100% { opacity: 1; }
384
+ 50% { opacity: 0.3; }
385
+ }
386
+
387
+ .recorder-controls {
388
+ display: flex;
389
+ gap: 1rem;
390
+ align-items: center;
391
+ }
392
+
393
+ .record-btn {
394
+ width: 72px;
395
+ height: 72px;
396
+ border-radius: 50%;
397
+ border: 3px solid var(--accent);
398
+ background: transparent;
399
+ color: var(--accent);
400
+ cursor: pointer;
401
+ transition: all 0.3s;
402
+ display: flex;
403
+ align-items: center;
404
+ justify-content: center;
405
+ position: relative;
406
+ }
407
+
408
+ .record-btn::before {
409
+ content: '';
410
+ position: absolute;
411
+ inset: -8px;
412
+ border-radius: 50%;
413
+ border: 2px solid transparent;
414
+ transition: border-color 0.3s;
415
+ }
416
+
417
+ .record-btn:hover::before {
418
+ border-color: rgba(0, 212, 170, 0.3);
419
+ }
420
+
421
+ .record-btn.recording {
422
+ background: var(--danger);
423
+ border-color: var(--danger);
424
+ animation: recordBtnPulse 1.5s ease-in-out infinite;
425
+ }
426
+
427
+ @keyframes recordBtnPulse {
428
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(255, 71, 87, 0.4); }
429
+ 50% { box-shadow: 0 0 0 15px rgba(255, 71, 87, 0); }
430
+ }
431
+
432
+ .record-btn svg {
433
+ width: 28px;
434
+ height: 28px;
435
+ }
436
+
437
+ .control-btn {
438
+ width: 48px;
439
+ height: 48px;
440
+ border-radius: 12px;
441
+ border: 1px solid var(--border);
442
+ background: var(--bg-secondary);
443
+ color: var(--fg-muted);
444
+ cursor: pointer;
445
+ transition: all 0.2s;
446
+ display: flex;
447
+ align-items: center;
448
+ justify-content: center;
449
+ }
450
+
451
+ .control-btn:hover {
452
+ border-color: var(--accent);
453
+ color: var(--accent);
454
+ }
455
+
456
+ .control-btn:disabled {
457
+ opacity: 0.3;
458
+ cursor: not-allowed;
459
+ }
460
+
461
+ .control-btn svg {
462
+ width: 20px;
463
+ height: 20px;
464
+ }
465
+
466
+ .timer {
467
+ font-family: 'JetBrains Mono', monospace;
468
+ font-size: 1.5rem;
469
+ font-weight: 500;
470
+ color: var(--fg);
471
+ min-width: 100px;
472
+ text-align: center;
473
+ }
474
+
475
+ /* Audio Player */
476
+ .player-container {
477
+ display: none;
478
+ }
479
+
480
+ .player-container.active {
481
+ display: block;
482
+ }
483
+
484
+ .player-header {
485
+ display: flex;
486
+ align-items: center;
487
+ justify-content: space-between;
488
+ margin-bottom: 1rem;
489
+ }
490
+
491
+ .player-info {
492
+ display: flex;
493
+ align-items: center;
494
+ gap: 1rem;
495
+ }
496
+
497
+ .player-thumbnail {
498
+ width: 48px;
499
+ height: 48px;
500
+ background: linear-gradient(135deg, var(--accent), var(--accent-secondary));
501
+ border-radius: 10px;
502
+ display: flex;
503
+ align-items: center;
504
+ justify-content: center;
505
+ }
506
+
507
+ .player-name {
508
+ font-weight: 500;
509
+ margin-bottom: 0.25rem;
510
+ }
511
+
512
+ .player-meta {
513
+ font-size: 0.75rem;
514
+ color: var(--fg-muted);
515
+ }
516
+
517
+ .player-actions {
518
+ display: flex;
519
+ gap: 0.5rem;
520
+ }
521
+
522
+ .action-btn {
523
+ width: 36px;
524
+ height: 36px;
525
+ border-radius: 8px;
526
+ border: 1px solid var(--border);
527
+ background: transparent;
528
+ color: var(--fg-muted);
529
+ cursor: pointer;
530
+ transition: all 0.2s;
531
+ display: flex;
532
+ align-items: center;
533
+ justify-content: center;
534
+ }
535
+
536
+ .action-btn:hover {
537
+ border-color: var(--accent);
538
+ color: var(--accent);
539
+ }
540
+
541
+ .action-btn.danger:hover {
542
+ border-color: var(--danger);
543
+ color: var(--danger);
544
+ }
545
+
546
+ .action-btn svg {
547
+ width: 16px;
548
+ height: 16px;
549
+ }
550
+
551
+ .player-waveform {
552
+ height: 80px;
553
+ background: var(--bg-secondary);
554
+ border-radius: 12px;
555
+ margin-bottom: 1rem;
556
+ position: relative;
557
+ overflow: hidden;
558
+ cursor: pointer;
559
+ }
560
+
561
+ .waveform-bars {
562
+ display: flex;
563
+ align-items: center;
564
+ justify-content: center;
565
+ gap: 2px;
566
+ height: 100%;
567
+ padding: 0 1rem;
568
+ }
569
+
570
+ .waveform-bar {
571
+ width: 3px;
572
+ background: var(--accent);
573
+ border-radius: 2px;
574
+ opacity: 0.5;
575
+ transition: opacity 0.2s;
576
+ }
577
+
578
+ .waveform-bar.active {
579
+ opacity: 1;
580
+ }
581
+
582
+ .progress-overlay {
583
+ position: absolute;
584
+ top: 0;
585
+ left: 0;
586
+ height: 100%;
587
+ background: linear-gradient(90deg, rgba(0, 212, 170, 0.2), transparent);
588
+ pointer-events: none;
589
+ transition: width 0.1s;
590
+ }
591
+
592
+ .player-controls {
593
+ display: flex;
594
+ align-items: center;
595
+ gap: 1rem;
596
+ }
597
+
598
+ .play-btn {
599
+ width: 56px;
600
+ height: 56px;
601
+ border-radius: 50%;
602
+ border: none;
603
+ background: var(--accent);
604
+ color: var(--bg);
605
+ cursor: pointer;
606
+ transition: all 0.3s;
607
+ display: flex;
608
+ align-items: center;
609
+ justify-content: center;
610
+ box-shadow: 0 4px 20px var(--accent-glow);
611
+ }
612
+
613
+ .play-btn:hover {
614
+ transform: scale(1.05);
615
+ }
616
+
617
+ .play-btn svg {
618
+ width: 24px;
619
+ height: 24px;
620
+ }
621
+
622
+ .time-display {
623
+ font-family: 'JetBrains Mono', monospace;
624
+ font-size: 0.875rem;
625
+ color: var(--fg-muted);
626
+ }
627
+
628
+ .progress-bar {
629
+ flex: 1;
630
+ height: 4px;
631
+ background: var(--bg-tertiary);
632
+ border-radius: 2px;
633
+ overflow: hidden;
634
+ cursor: pointer;
635
+ }
636
+
637
+ .progress-fill {
638
+ height: 100%;
639
+ background: var(--accent);
640
+ border-radius: 2px;
641
+ transition: width 0.1s;
642
+ }
643
+
644
+ .volume-control {
645
+ display: flex;
646
+ align-items: center;
647
+ gap: 0.5rem;
648
+ }
649
+
650
+ .volume-slider {
651
+ width: 80px;
652
+ height: 4px;
653
+ -webkit-appearance: none;
654
+ appearance: none;
655
+ background: var(--bg-tertiary);
656
+ border-radius: 2px;
657
+ cursor: pointer;
658
+ }
659
+
660
+ .volume-slider::-webkit-slider-thumb {
661
+ -webkit-appearance: none;
662
+ width: 12px;
663
+ height: 12px;
664
+ background: var(--accent);
665
+ border-radius: 50%;
666
+ cursor: pointer;
667
+ }
668
+
669
+ /* Streaming Bar */
670
+ .streaming-bar {
671
+ height: 3px;
672
+ background: var(--bg-tertiary);
673
+ border-radius: 0 0 20px 20px;
674
+ margin: 0 -1.5rem -1.5rem;
675
+ overflow: hidden;
676
+ position: absolute;
677
+ bottom: 0;
678
+ left: 0;
679
+ right: 0;
680
+ }
681
+
682
+ .streaming-progress {
683
+ height: 100%;
684
+ background: linear-gradient(90deg, var(--accent), var(--accent-secondary));
685
+ width: 0%;
686
+ animation: streamingAnim 2s ease-in-out infinite;
687
+ }
688
+
689
+ @keyframes streamingAnim {
690
+ 0% { width: 0%; }
691
+ 50% { width: 70%; }
692
+ 100% { width: 0%; }
693
+ }
694
+
695
+ /* Status Messages */
696
+ .status-message {
697
+ padding: 0.75rem 1rem;
698
+ border-radius: 10px;
699
+ font-size: 0.875rem;
700
+ margin-top: 1rem;
701
+ display: flex;
702
+ align-items: center;
703
+ gap: 0.5rem;
704
+ }
705
+
706
+ .status-message.error {
707
+ background: rgba(255, 71, 87, 0.1);
708
+ color: var(--danger);
709
+ border: 1px solid rgba(255, 71, 87, 0.2);
710
+ }
711
+
712
+ .status-message.success {
713
+ background: rgba(0, 212, 170, 0.1);
714
+ color: var(--accent);
715
+ border: 1px solid rgba(0, 212, 170, 0.2);
716
+ }
717
+
718
+ /* Animations */
719
+ .fade-in {
720
+ animation: fadeIn 0.5s ease-out forwards;
721
+ }
722
+
723
+ @keyframes fadeIn {
724
+ from { opacity: 0; transform: translateY(20px); }
725
+ to { opacity: 1; transform: translateY(0); }
726
+ }
727
+
728
+ .stagger-1 { animation-delay: 0.1s; }
729
+ .stagger-2 { animation-delay: 0.2s; }
730
+ .stagger-3 { animation-delay: 0.3s; }
731
+
732
+ /* Reduced motion */
733
+ @media (prefers-reduced-motion: reduce) {
734
+ *, *::before, *::after {
735
+ animation-duration: 0.01ms !important;
736
+ animation-iteration-count: 1 !important;
737
+ transition-duration: 0.01ms !important;
738
+ }
739
+ }
740
+
741
+ /* Responsive */
742
+ @media (max-width: 640px) {
743
+ .main-container {
744
+ padding: 1rem;
745
+ }
746
+
747
+ .header {
748
+ flex-direction: column;
749
+ gap: 1rem;
750
+ text-align: center;
751
+ }
752
+
753
+ .audio-container {
754
+ padding: 1rem;
755
+ }
756
+
757
+ .upload-area {
758
+ padding: 2rem 1rem;
759
+ }
760
+
761
+ .recorder-controls {
762
+ flex-wrap: wrap;
763
+ justify-content: center;
764
+ }
765
+
766
+ .player-controls {
767
+ flex-wrap: wrap;
768
+ }
769
+
770
+ .volume-control {
771
+ width: 100%;
772
+ justify-content: center;
773
+ }
774
+
775
+ .source-selector {
776
+ width: 100%;
777
+ }
778
+
779
+ .source-btn {
780
+ flex: 1;
781
+ justify-content: center;
782
+ }
783
+ }
784
+
785
+ /* Focus states */
786
+ button:focus-visible,
787
+ input:focus-visible {
788
+ outline: 2px solid var(--accent);
789
+ outline-offset: 2px;
790
+ }
791
+ </style>
792
+ </head>
793
+ <body>
794
+ <div class="bg-atmosphere"></div>
795
+ <div class="noise-overlay"></div>
796
+ <div class="grid-pattern"></div>
797
+
798
+ <div class="main-container">
799
+ <header class="header fade-in">
800
+ <div class="logo">
801
+ <div class="logo-icon">
802
+ <i data-lucide="audio-waveform" style="width: 24px; height: 24px; color: white;"></i>
803
+ </div>
804
+ <span class="logo-text">Audio Studio</span>
805
+ </div>
806
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="built-with" target="_blank" rel="noopener">
807
+ Built with anycoder
808
+ </a>
809
+ </header>
810
+
811
+ <div class="audio-wrapper">
812
+ <div class="block-label fade-in stagger-1">
813
+ <i data-lucide="music"></i>
814
+ <span>Audio Input</span>
815
+ </div>
816
+
817
+ <div class="audio-container fade-in stagger-2" id="audioContainer">
818
+ <!-- Drop Zone -->
819
+ <div class="drop-zone" id="dropZone">
820
+ <div class="drop-zone-icon">
821
+ <i data-lucide="upload" style="width: 28px; height: 28px; color: var(--bg);"></i>
822
+ </div>
823
+ <span class="drop-zone-text">Drop audio file here</span>
824
+ </div>
825
+
826
+ <!-- Source Selector -->
827
+ <div class="source-selector">
828
+ <button class="source-btn active" data-source="microphone" id="micSource">
829
+ <i data-lucide="mic"></i>
830
+ Microphone
831
+ </button>
832
+ <button class="source-btn" data-source="upload" id="uploadSource">
833
+ <i data-lucide="upload"></i>
834
+ Upload
835
+ </button>
836
+ </div>
837
+
838
+ <!-- Microphone Source -->
839
+ <div id="microphonePanel">
840
+ <div class="recorder-container">
841
+ <div class="waveform-display">
842
+ <canvas class="waveform-canvas" id="waveformCanvas"></canvas>
843
+ <div class="recording-indicator" id="recordingIndicator">
844
+ <span class="recording-dot"></span>
845
+ <span>REC</span>
846
+ </div>
847
+ </div>
848
+
849
+ <div class="recorder-controls">
850
+ <button class="control-btn" id="pauseBtn" disabled aria-label="Pause recording">
851
+ <i data-lucide="pause"></i>
852
+ </button>
853
+
854
+ <button class="record-btn" id="recordBtn" aria-label="Start recording">
855
+ <i data-lucide="mic" id="recordIcon"></i>
856
+ </button>
857
+
858
+ <button class="control-btn" id="stopBtn" disabled aria-label="Stop recording">
859
+ <i data-lucide="square"></i>
860
+ </button>
861
+ </div>
862
+
863
+ <div class="timer" id="timer">00:00</div>
864
+ </div>
865
+ </div>
866
+
867
+ <!-- Upload Source -->
868
+ <div id="uploadPanel" style="display: none;">
869
+ <div class="upload-area" id="uploadArea">
870
+ <div class="upload-icon">
871
+ <i data-lucide="file-audio" style="width: 32px; height: 32px;"></i>
872
+ </div>
873
+ <div class="upload-title">Drop audio file or click to upload</div>
874
+ <div class="upload-subtitle">Maximum file size: 50MB</div>
875
+ <div class="upload-formats">
876
+ <span class="format-tag">MP3</span>
877
+ <span class="format-tag">WAV</span>
878
+ <span class="format-tag">OGG</span>
879
+ <span class="format-tag">FLAC</span>
880
+ <span class="format-tag">AAC</span>
881
+ <span class="format-tag">WEBM</span>
882
+ </div>
883
+ <input type="file" id="fileInput" accept="audio/*" style="display: none;">
884
+ </div>
885
+ </div>
886
+
887
+ <!-- Audio Player -->
888
+ <div class="player-container" id="playerContainer">
889
+ <div class="player-header">
890
+ <div class="player-info">
891
+ <div class="player-thumbnail">
892
+ <i data-lucide="music" style="width: 24px; height: 24px; color: white;"></i>
893
+ </div>
894
+ <div>
895
+ <div class="player-name" id="playerName">Recording</div>
896
+ <div class="player-meta" id="playerMeta">WAV - 44.1kHz</div>
897
+ </div>
898
+ </div>
899
+ <div class="player-actions">
900
+ <button class="action-btn" id="downloadBtn" aria-label="Download">
901
+ <i data-lucide="download"></i>
902
+ </button>
903
+ <button class="action-btn danger" id="clearBtn" aria-label="Clear">
904
+ <i data-lucide="trash-2"></i>
905
+ </button>
906
+ </div>
907
+ </div>
908
+
909
+ <div class="player-waveform" id="playerWaveform">
910
+ <div class="progress-overlay" id="progressOverlay"></div>
911
+ <div class="waveform-bars" id="waveformBars"></div>
912
+ </div>
913
+
914
+ <div class="player-controls">
915
+ <button class="play-btn" id="playBtn" aria-label="Play">
916
+ <i data-lucide="play" id="playIcon"></i>
917
+ </button>
918
+
919
+ <span class="time-display" id="currentTime">0:00</span>
920
+
921
+ <div class="progress-bar" id="progressBar">
922
+ <div class="progress-fill" id="progressFill"></div>
923
+ </div>
924
+
925
+ <span class="time-display" id="totalTime">0:00</span>
926
+
927
+ <div class="volume-control">
928
+ <i data-lucide="volume-2" style="width: 18px; height: 18px; color: var(--fg-muted);"></i>
929
+ <input type="range" class="volume-slider" id="volumeSlider" min="0" max="100" value="80">
930
+ </div>
931
+ </div>
932
+ </div>
933
+
934
+ <!-- Status Message -->
935
+ <div class="status-message error" id="statusMessage" style="display: none;"></div>
936
+
937
+ <!-- Streaming Bar -->
938
+ <div class="streaming-bar" id="streamingBar" style="display: none;">
939
+ <div class="streaming-progress"></div>
940
+ </div>
941
+ </div>
942
+ </div>
943
+ </div>
944
+
945
+ <audio id="audioElement" style="display: none;"></audio>
946
+
947
+ <script>
948
+ // Initialize Lucide icons
949
+ lucide.createIcons();
950
+
951
+ // State
952
+ let audioContext = null;
953
+ let analyser = null;
954
+ let mediaRecorder = null;
955
+ let audioChunks = [];
956
+ let isRecording = false;
957
+ let isPaused = false;
958
+ let startTime = 0;
959
+ let elapsedTime = 0;
960
+ let timerInterval = null;
961
+ let animationId = null;
962
+ let currentAudioBlob = null;
963
+ let currentAudioUrl = null;
964
+
965
+ // DOM Elements
966
+ const audioContainer = document.getElementById('audioContainer');
967
+ const dropZone = document.getElementById('dropZone');
968
+ const micSource = document.getElementById('micSource');
969
+ const uploadSource = document.getElementById('uploadSource');
970
+ const microphonePanel = document.getElementById('microphonePanel');
971
+ const uploadPanel = document.getElementById('uploadPanel');
972
+ const uploadArea = document.getElementById('uploadArea');
973
+ const fileInput = document.getElementById('fileInput');
974
+ const waveformCanvas = document.getElementById('waveformCanvas');
975
+ const recordBtn = document.getElementById('recordBtn');
976
+ const recordIcon = document.getElementById('recordIcon');
977
+ const pauseBtn = document.getElementById('pauseBtn');
978
+ const stopBtn = document.getElementById('stopBtn');
979
+ const timer = document.getElementById('timer');
980
+ const recordingIndicator = document.getElementById('recordingIndicator');
981
+ const playerContainer = document.getElementById('playerContainer');
982
+ const playerName = document.getElementById('playerName');
983
+ const playerMeta = document.getElementById('playerMeta');
984
+ const playBtn = document.getElementById('playBtn');
985
+ const playIcon = document.getElementById('playIcon');
986
+ const currentTimeEl = document.getElementById('currentTime');
987
+ const totalTimeEl = document.getElementById('totalTime');
988
+ const progressBar = document.getElementById('progressBar');
989
+ const progressFill = document.getElementById('progressFill');
990
+ const progressOverlay = document.getElementById('progressOverlay');
991
+ const volumeSlider = document.getElementById('volumeSlider');
992
+ const downloadBtn = document.getElementById('downloadBtn');
993
+ const clearBtn = document.getElementById('clearBtn');
994
+ const statusMessage = document.getElementById('statusMessage');
995
+ const streamingBar = document.getElementById('streamingBar');
996
+ const audioElement = document.getElementById('audioElement');
997
+ const waveformBars = document.getElementById('waveformBars');
998
+
999
+ // Canvas setup
1000
+ const ctx = waveformCanvas.getContext('2d');
1001
+
1002
+ function resizeCanvas() {
1003
+ const rect = waveformCanvas.getBoundingClientRect();
1004
+ waveformCanvas.width = rect.width * window.devicePixelRatio;
1005
+ waveformCanvas.height = rect.height * window.devicePixelRatio;
1006
+ ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
1007
+ }
1008
+
1009
+ resizeCanvas();
1010
+ window.addEventListener('resize', resizeCanvas);
1011
+
1012
+ // Source switching
1013
+ micSource.addEventListener('click', () => {
1014
+ micSource.classList.add('active');
1015
+ uploadSource.classList.remove('active');
1016
+ microphonePanel.style.display = 'block';
1017
+ uploadPanel.style.display = 'none';
1018
+ });
1019
+
1020
+ uploadSource.addEventListener('click', () => {
1021
+ uploadSource.classList.add('active');
1022
+ micSource.classList.remove('active');
1023
+ uploadPanel.style.display = 'block';
1024
+ microphonePanel.style.display = 'none';
1025
+ });
1026
+
1027
+ // Drag and drop
1028
+ audioContainer.addEventListener('dragover', (e) => {
1029
+ e.preventDefault();
1030
+ audioContainer.classList.add('dragging');
1031
+ });
1032
+
1033
+ audioContainer.addEventListener('dragleave', (e) => {
1034
+ e.preventDefault();
1035
+ audioContainer.classList.remove('dragging');
1036
+ });
1037
+
1038
+ audioContainer.addEventListener('drop', (e) => {
1039
+ e.preventDefault();
1040
+ audioContainer.classList.remove('dragging');
1041
+ const files = e.dataTransfer.files;
1042
+ if (files.length > 0 && files[0].type.startsWith('audio/')) {
1043
+ handleFile(files[0]);
1044
+ }
1045
+ });
1046
+
1047
+ // Upload area click
1048
+ uploadArea.addEventListener('click', () => fileInput.click());
1049
+ fileInput.addEventListener('change', (e) => {
1050
+ if (e.target.files.length > 0) {
1051
+ handleFile(e.target.files[0]);
1052
+ }
1053
+ });
1054
+
1055
+ // Handle file upload
1056
+ function handleFile(file) {
1057
+ if (file.size > 50 * 1024 * 1024) {
1058
+ showStatus('File size exceeds 50MB limit', 'error');
1059
+ return;
1060
+ }
1061
+
1062
+ currentAudioBlob = file;
1063
+ currentAudioUrl = URL.createObjectURL(file);
1064
+
1065
+ playerName.textContent = file.name;
1066
+ playerMeta.textContent = `${file.type.split('/')[1].toUpperCase()} - ${(file.size / 1024 / 1024).toFixed(2)} MB`;
1067
+
1068
+ audioElement.src = currentAudioUrl;
1069
+ audioElement.addEventListener('loadedmetadata', () => {
1070
+ totalTimeEl.textContent = formatTime(audioElement.duration);
1071
+ generateWaveform();
1072
+ });
1073
+
1074
+ showPlayer();
1075
+ showStatus('Audio file loaded successfully', 'success');
1076
+ }
1077
+
1078
+ // Recording functions
1079
+ async function startRecording() {
1080
+ try {
1081
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
1082
+
1083
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
1084
+ analyser = audioContext.createAnalyser();
1085
+ analyser.fftSize = 256;
1086
+
1087
+ const source = audioContext.createMediaStreamSource(stream);
1088
+ source.connect(analyser);
1089
+
1090
+ mediaRecorder = new MediaRecorder(stream);
1091
+ audioChunks = [];
1092
+
1093
+ mediaRecorder.ondataavailable = (e) => {
1094
+ audioChunks.push(e.data);
1095
+ };
1096
+
1097
+ mediaRecorder.onstop = () => {
1098
+ const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
1099
+ currentAudioBlob = audioBlob;
1100
+ currentAudioUrl = URL.createObjectURL(audioBlob);
1101
+
1102
+ playerName.textContent = 'Recording';
1103
+ playerMeta.textContent = 'WAV - Recorded audio';
1104
+
1105
+ audioElement.src = currentAudioUrl;
1106
+ audioElement.addEventListener('loadedmetadata', () => {
1107
+ totalTimeEl.textContent = formatTime(audioElement.duration);
1108
+ generateWaveform();
1109
+ });
1110
+
1111
+ showPlayer();
1112
+ };
1113
+
1114
+ mediaRecorder.start();
1115
+ isRecording = true;
1116
+ startTime = Date.now();
1117
+ startTimer();
1118
+ visualize();
1119
+
1120
+ recordBtn.classList.add('recording');
1121
+ recordIcon.setAttribute('data-lucide', 'square');
1122
+ lucide.createIcons();
1123
+ pauseBtn.disabled = false;
1124
+ stopBtn.disabled = false;
1125
+ recordingIndicator.classList.add('active');
1126
+
1127
+ } catch (err) {
1128
+ console.error('Error accessing microphone:', err);
1129
+ showStatus('Could not access microphone. Please check permissions.', 'error');
1130
+ }
1131
+ }
1132
+
1133
+ function stopRecording() {
1134
+ if (mediaRecorder && mediaRecorder.state !== 'inactive') {
1135
+ mediaRecorder.stop();
1136
+ mediaRecorder.stream.getTracks().forEach(track => track.stop());
1137
+ }
1138
+
1139
+ isRecording = false;
1140
+ isPaused = false;
1141
+ clearInterval(timerInterval);
1142
+ cancelAnimationFrame(animationId);
1143
+
1144
+ recordBtn.classList.remove('recording');
1145
+ recordIcon.setAttribute('data-lucide', 'mic');
1146
+ lucide.createIcons();
1147
+ pauseBtn.disabled = true;
1148
+ stopBtn.disabled = true;
1149
+ recordingIndicator.classList.remove('active');
1150
+
1151
+ if (audioContext) {
1152
+ audioContext.close();
1153
+ }
1154
+
1155
+ clearCanvas();
1156
+ }
1157
+
1158
+ function pauseRecording() {
1159
+ if (mediaRecorder && mediaRecorder.state === 'recording') {
1160
+ mediaRecorder.pause();
1161
+ isPaused = true;
1162
+ clearInterval(timerInterval);
1163
+ pauseBtn.innerHTML = '<i data-lucide="play"></i>';
1164
+ lucide.createIcons();
1165
+ } else if (mediaRecorder && mediaRecorder.state === 'paused') {
1166
+ mediaRecorder.resume();
1167
+ isPaused = false;
1168
+ startTimer();
1169
+ pauseBtn.innerHTML = '<i data-lucide="pause"></i>';
1170
+ lucide.createIcons();
1171
+ }
1172
+ }
1173
+
1174
+ // Timer
1175
+ function startTimer() {
1176
+ timerInterval = setInterval(() => {
1177
+ elapsedTime = Date.now() - startTime;
1178
+ timer.textContent = formatTime(elapsedTime / 1000);
1179
+ }, 100);
1180
+ }
1181
+
1182
+ function formatTime(seconds) {
1183
+ const mins = Math.floor(seconds / 60);
1184
+ const secs = Math.floor(seconds % 60);
1185
+ return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
1186
+ }
1187
+
1188
+ // Visualization
1189
+ function visualize() {
1190
+ if (!analyser) return;
1191
+
1192
+ const bufferLength = analyser.frequencyBinCount;
1193
+ const dataArray = new Uint8Array(bufferLength);
1194
+ const width = waveformCanvas.width / window.devicePixelRatio;
1195
+ const height = waveformCanvas.height / window.devicePixelRatio;
1196
+
1197
+ function draw() {
1198
+ if (!isRecording) return;
1199
+
1200
+ animationId = requestAnimationFrame(draw);
1201
+ analyser.getByteFrequencyData(dataArray);
1202
+
1203
+ ctx.fillStyle = '#12121a';
1204
+ ctx.fillRect(0, 0, width, height);
1205
+
1206
+ const barWidth = (width / bufferLength) * 2.5;
1207
+ let x = 0;
1208
+
1209
+ for (let i = 0; i < bufferLength; i++) {
1210
+ const barHeight = (dataArray[i] / 255) * height * 0.8;
1211
+
1212
+ const gradient = ctx.createLinearGradient(0, height - barHeight, 0, height);
1213
+ gradient.addColorStop(0, '#00d4aa');
1214
+ gradient.addColorStop(1, 'rgba(0, 212, 170, 0.2)');
1215
+
1216
+ ctx.fillStyle = gradient;
1217
+ ctx.fillRect(x, height - barHeight, barWidth - 1, barHeight);
1218
+
1219
+ x += barWidth;
1220
+ }
1221
+ }
1222
+
1223
+ draw();
1224
+ }
1225
+
1226
+ function clearCanvas() {
1227
+ const width = waveformCanvas.width / window.devicePixelRatio;
1228
+ const height = waveformCanvas.height / window.devicePixelRatio;
1229
+ ctx.fillStyle = '#12121a';
1230
+ ctx.fillRect(0, 0, width, height);
1231
+ }
1232
+
1233
+ // Generate static waveform for player
1234
+ function generateWaveform() {
1235
+ waveformBars.innerHTML = '';
1236
+ const barCount = 100;
1237
+
1238
+ for (let i = 0; i < barCount; i++) {
1239
+ const bar = document.createElement('div');
1240
+ bar.className = 'waveform-bar';
1241
+ bar.style.height = `${Math.random() * 50 + 20}px`;
1242
+ waveformBars.appendChild(bar);
1243
+ }
1244
+ }
1245
+
1246
+ // Player controls
1247
+ playBtn.addEventListener('click', () => {
1248
+ if (audioElement.paused) {
1249
+ audioElement.play();
1250
+ playIcon.setAttribute('data-lucide', 'pause');
1251
+ lucide.createIcons();
1252
+ } else {
1253
+ audioElement.pause();
1254
+ playIcon.setAttribute('data-lucide', 'play');
1255
+ lucide.createIcons();
1256
+ }
1257
+ });
1258
+
1259
+ audioElement.addEventListener('timeupdate', () => {
1260
+ const progress = (audioElement.currentTime / audioElement.duration) * 100;
1261
+ progressFill.style.width = `${progress}%`;
1262
+ progressOverlay.style.width = `${progress}%`;
1263
+ currentTimeEl.textContent = formatTime(audioElement.currentTime);
1264
+
1265
+ // Update waveform bars
1266
+ const bars = waveformBars.querySelectorAll('.waveform-bar');
1267
+ const activeIndex = Math.floor((audioElement.currentTime / audioElement.duration) * bars.length);
1268
+ bars.forEach((bar, i) => {
1269
+ bar.classList.toggle('active', i <= activeIndex);
1270
+ });
1271
+ });
1272
+
1273
+ audioElement.addEventListener('ended', () => {
1274
+ playIcon.setAttribute('data-lucide', 'play');
1275
+ lucide.createIcons();
1276
+ });
1277
+
1278
+ progressBar.addEventListener('click', (e) => {
1279
+ const rect = progressBar.getBoundingClientRect();
1280
+ const pos = (e.clientX - rect.left) / rect.width;
1281
+ audioElement.currentTime = pos * audioElement.duration;
1282
+ });
1283
+
1284
+ playerWaveform.addEventListener('click', (e) => {
1285
+ const rect = playerWaveform.getBoundingClientRect();
1286
+ const pos = (e.clientX - rect.left) / rect.width;
1287
+ audioElement.currentTime = pos * audioElement.duration;
1288
+ });
1289
+
1290
+ volumeSlider.addEventListener('input', (e) => {
1291
+ audioElement.volume = e.target.value / 100;
1292
+ });
1293
+
1294
+ // Download
1295
+ downloadBtn.addEventListener('click', () => {
1296
+ if (currentAudioBlob) {
1297
+ const url = URL.createObjectURL(currentAudioBlob);
1298
+ const a = document.createElement('a');
1299
+ a.href = url;
1300
+ a.download = 'recording.wav';
1301
+ a.click();
1302
+ URL.revokeObjectURL(url);
1303
+ }
1304
+ });
1305
+
1306
+ // Clear
1307
+ clearBtn.addEventListener('click', () => {
1308
+ audioElement.pause();
1309
+ audioElement.src = '';
1310
+ currentAudioBlob = null;
1311
+ if (currentAudioUrl) {
1312
+ URL.revokeObjectURL(currentAudioUrl);
1313
+ currentAudioUrl = null;
1314
+ }
1315
+ hidePlayer();
1316
+ timer.textContent = '00:00';
1317
+ elapsedTime = 0;
1318
+ });
1319
+
1320
+ // Event listeners
1321
+ recordBtn.addEventListener('click', () => {
1322
+ if (isRecording) {
1323
+ stopRecording();
1324
+ } else {
1325
+ startRecording();
1326
+ }
1327
+ });
1328
+
1329
+ pauseBtn.addEventListener('click', pauseRecording);
1330
+ stopBtn.addEventListener('click', stopRecording);
1331
+
1332
+ // Helper functions
1333
+ function showPlayer() {
1334
+ playerContainer.classList.add('active');
1335
+ microphonePanel.style.display = 'none';
1336
+ uploadPanel.style.display = 'none';
1337
+ micSource.style.display = 'none';
1338
+ uploadSource.style.display = 'none';
1339
+ }
1340
+
1341
+ function hidePlayer() {
1342
+ playerContainer.classList.remove('active');
1343
+ const activeSource = micSource.classList.contains('active') ? 'mic' : 'upload';
1344
+ if (activeSource === 'mic') {
1345
+ microphonePanel.style.display = 'block';
1346
+ } else {
1347
+ uploadPanel.style.display = 'block';
1348
+ }
1349
+ micSource.style.display = 'flex';
1350
+ uploadSource.style.display = 'flex';
1351
+ }
1352
+
1353
+ function showStatus(message, type) {
1354
+ statusMessage.textContent = message;
1355
+ statusMessage.className = `status-message ${type}`;
1356
+ statusMessage.style.display = 'flex';
1357
+
1358
+ setTimeout(() => {
1359
+ statusMessage.style.display = 'none';
1360
+ }, 3000);
1361
+ }
1362
+
1363
+ // Initial canvas clear
1364
+ clearCanvas();
1365
+
1366
+ // Set initial volume
1367
+ audioElement.volume = 0.8;
1368
+ </script>
1369
+ </body>
1370
+ </html>