Hamed744 commited on
Commit
13de499
·
verified ·
1 Parent(s): f4b63d0

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +660 -646
index.html CHANGED
@@ -5,708 +5,441 @@
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Alpha TTS Nova - نسل جدید تبدیل متن به صدا</title>
7
  <style>
8
- @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
9
 
10
  :root {
11
- --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
12
- --secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
13
- --accent-gradient: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
14
- --success-gradient: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
15
- --glass-bg: rgba(255, 255, 255, 0.15);
16
- --glass-border: rgba(255, 255, 255, 0.2);
17
- --text-primary: #1a1a2e;
18
- --text-secondary: #6c7293;
19
- --text-light: #ffffff;
20
- --shadow-glow: 0 20px 40px rgba(102, 126, 234, 0.3);
21
- --shadow-card: 0 15px 35px rgba(50, 50, 93, 0.1);
22
- --transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
23
- }
24
-
25
- * {
26
- margin: 0;
27
- padding: 0;
28
- box-sizing: border-box;
 
29
  }
30
 
31
  body {
32
- font-family: 'Vazirmatn', sans-serif;
33
  direction: rtl;
34
- background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
35
- background-size: 400% 400%;
36
- animation: gradientShift 15s ease infinite;
37
  color: var(--text-primary);
 
 
 
 
38
  min-height: 100vh;
39
- overflow-x: hidden;
40
- position: relative;
41
- }
42
-
43
- body::before {
44
- content: '';
45
- position: fixed;
46
- top: 0;
47
- left: 0;
48
- width: 100%;
49
- height: 100%;
50
- background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="75" cy="75" r="1" fill="rgba(255,255,255,0.05)"/><circle cx="50" cy="10" r="0.5" fill="rgba(255,255,255,0.08)"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
51
- pointer-events: none;
52
- z-index: -1;
53
- }
54
-
55
- @keyframes gradientShift {
56
- 0% { background-position: 0% 50%; }
57
- 50% { background-position: 100% 50%; }
58
- 100% { background-position: 0% 50%; }
59
- }
60
-
61
- .floating-shapes {
62
- position: fixed;
63
- top: 0;
64
- left: 0;
65
- width: 100%;
66
- height: 100%;
67
- pointer-events: none;
68
- z-index: -1;
69
- }
70
-
71
- .shape {
72
- position: absolute;
73
- background: rgba(255, 255, 255, 0.1);
74
- border-radius: 50%;
75
- animation: float 20s infinite linear;
76
- }
77
-
78
- .shape:nth-child(1) {
79
- width: 80px;
80
- height: 80px;
81
- top: 20%;
82
- left: 10%;
83
- animation-delay: 0s;
84
- }
85
-
86
- .shape:nth-child(2) {
87
- width: 120px;
88
- height: 120px;
89
- top: 60%;
90
- left: 80%;
91
- animation-delay: -5s;
92
- }
93
-
94
- .shape:nth-child(3) {
95
- width: 60px;
96
- height: 60px;
97
- top: 80%;
98
- left: 20%;
99
- animation-delay: -10s;
100
- }
101
-
102
- @keyframes float {
103
- 0% { transform: translateY(0px) rotate(0deg); opacity: 0.7; }
104
- 33% { transform: translateY(-20px) rotate(120deg); opacity: 0.4; }
105
- 66% { transform: translateY(10px) rotate(240deg); opacity: 0.7; }
106
- 100% { transform: translateY(0px) rotate(360deg); opacity: 0.4; }
107
  }
108
 
109
- .container {
110
- max-width: 900px;
 
111
  margin: 0 auto;
112
- padding: 2rem;
113
- position: relative;
114
- z-index: 1;
115
  }
116
 
117
- .app-header {
118
- text-align: center;
119
- margin-bottom: 3rem;
120
- animation: slideInUp 1s ease-out;
121
  }
122
-
123
- .app-header h1 {
124
- font-size: 3.5rem;
125
- font-weight: 800;
126
- background: linear-gradient(135deg, #fff 0%, #f0f8ff 100%);
127
  -webkit-background-clip: text;
128
  -webkit-text-fill-color: transparent;
129
- background-clip: text;
130
- margin-bottom: 1rem;
131
- text-shadow: 0 0 30px rgba(255, 255, 255, 0.5);
132
- animation: glow 3s ease-in-out infinite alternate;
133
- }
134
-
135
- @keyframes glow {
136
- from { text-shadow: 0 0 20px rgba(255, 255, 255, 0.5); }
137
- to { text-shadow: 0 0 40px rgba(255, 255, 255, 0.8); }
138
- }
139
-
140
- .app-header p {
141
- font-size: 1.3rem;
142
- color: rgba(255, 255, 255, 0.9);
143
- font-weight: 300;
144
- letter-spacing: 0.5px;
145
- }
146
-
147
- @keyframes slideInUp {
148
- from {
149
- opacity: 0;
150
- transform: translateY(30px);
151
- }
152
- to {
153
- opacity: 1;
154
- transform: translateY(0);
155
- }
156
- }
157
-
158
- .main-content {
159
- background: var(--glass-bg);
160
- backdrop-filter: blur(20px);
161
- border: 1px solid var(--glass-border);
162
- border-radius: 25px;
163
- padding: 3rem;
164
- box-shadow: var(--shadow-card);
165
- animation: slideInUp 1s ease-out 0.2s both;
166
- position: relative;
167
- overflow: hidden;
168
- }
169
-
170
- .main-content::before {
171
- content: '';
172
- position: absolute;
173
- top: 0;
174
- left: 0;
175
- right: 0;
176
- height: 1px;
177
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.6), transparent);
178
- }
179
-
180
- .form-group {
181
- margin-bottom: 2.5rem;
182
- animation: slideInUp 0.6s ease-out both;
183
- }
184
-
185
- .form-group:nth-child(1) { animation-delay: 0.1s; }
186
- .form-group:nth-child(2) { animation-delay: 0.2s; }
187
- .form-group:nth-child(3) { animation-delay: 0.3s; }
188
- .form-group:nth-child(4) { animation-delay: 0.4s; }
189
-
190
- label {
191
- display: block;
192
- font-weight: 600;
193
- color: var(--text-light);
194
- font-size: 1.1rem;
195
- margin-bottom: 1rem;
196
- text-shadow: 0 2px 4px rgba(0,0,0,0.3);
197
- }
198
-
199
- textarea, input[type="text"] {
200
- width: 100%;
201
- padding: 1.2rem 1.5rem;
202
- border: 2px solid rgba(255, 255, 255, 0.2);
203
- border-radius: 15px;
204
- background: rgba(255, 255, 255, 0.1);
205
- backdrop-filter: blur(10px);
206
- color: var(--text-light);
207
- font-family: 'Vazirmatn', sans-serif;
208
- font-size: 1rem;
209
- transition: var(--transition);
210
- resize: vertical;
211
- }
212
-
213
- textarea::placeholder, input[type="text"]::placeholder {
214
- color: rgba(255, 255, 255, 0.6);
215
- }
216
-
217
- textarea:focus, input[type="text"]:focus {
218
- outline: none;
219
- border-color: rgba(255, 255, 255, 0.6);
220
- background: rgba(255, 255, 255, 0.15);
221
- box-shadow: 0 0 25px rgba(255, 255, 255, 0.2);
222
- transform: translateY(-2px);
223
- }
224
-
225
- textarea {
226
- min-height: 120px;
227
- }
228
-
229
- /* Speaker Selection Styles */
230
- #selected-speaker-display {
231
- text-align: center;
232
- margin-top: 1rem;
233
- }
234
-
235
- #selected-speaker-card {
236
- display: inline-flex;
237
- align-items: center;
238
- background: rgba(255, 255, 255, 0.1);
239
- backdrop-filter: blur(15px);
240
- border: 2px solid rgba(255, 255, 255, 0.2);
241
- border-radius: 20px;
242
- padding: 1rem 1.5rem;
243
- transition: var(--transition);
244
  cursor: pointer;
245
- position: relative;
246
- overflow: hidden;
247
- }
248
-
249
- #selected-speaker-card::before {
250
- content: '';
251
- position: absolute;
252
- top: 0;
253
- left: -100%;
254
- width: 100%;
255
- height: 100%;
256
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
257
- transition: left 0.6s;
258
- }
259
-
260
- #selected-speaker-card:hover::before {
261
- left: 100%;
262
  }
263
-
264
  #selected-speaker-card:hover {
265
- transform: translateY(-5px) scale(1.02);
266
- box-shadow: 0 15px 35px rgba(255, 255, 255, 0.2);
267
- border-color: rgba(255, 255, 255, 0.4);
268
- }
269
-
270
- #selected-speaker-card img {
271
- width: 80px;
272
- height: 80px;
273
- border-radius: 50%;
274
- object-fit: cover;
275
- margin-left: 1.5rem;
276
- border: 3px solid rgba(255, 255, 255, 0.5);
277
- box-shadow: 0 0 20px rgba(255, 255, 255, 0.3);
278
- transition: var(--transition);
279
  }
280
-
281
  #selected-speaker-card:hover img {
282
- transform: scale(1.1);
283
- box-shadow: 0 0 30px rgba(255, 255, 255, 0.5);
284
- }
285
-
286
- #selected-speaker-info h3 {
287
- margin: 0;
288
- font-size: 1.4rem;
289
- font-weight: 700;
290
- color: var(--text-light);
291
- }
292
-
293
- #selected-speaker-info p {
294
- margin: 0.5rem 0 0;
295
- color: rgba(255, 255, 255, 0.8);
296
- font-size: 0.9rem;
297
- }
298
-
299
- #change-speaker-btn {
300
- display: block;
301
- margin: 1.5rem auto 0;
302
- padding: 0.8rem 2rem;
303
- border: 2px solid rgba(255, 255, 255, 0.5);
304
- border-radius: 25px;
305
- background: transparent;
306
- color: var(--text-light);
307
- cursor: pointer;
308
- font-family: 'Vazirmatn', sans-serif;
309
- font-weight: 600;
310
- transition: var(--transition);
311
- position: relative;
312
- overflow: hidden;
313
- }
314
-
315
- #change-speaker-btn::before {
316
- content: '';
317
- position: absolute;
318
- top: 0;
319
- left: 0;
320
- width: 0;
321
- height: 100%;
322
- background: rgba(255, 255, 255, 0.2);
323
- transition: width 0.3s ease;
324
- }
325
-
326
- #change-speaker-btn:hover::before {
327
- width: 100%;
328
- }
329
-
330
- #change-speaker-btn:hover {
331
- transform: translateY(-3px);
332
- box-shadow: 0 10px 25px rgba(255, 255, 255, 0.2);
333
- border-color: rgba(255, 255, 255, 0.8);
334
- }
335
-
336
- /* Modal Styles */
337
- #speaker-modal {
338
- position: fixed;
339
- top: 0;
340
- left: 0;
341
- width: 100%;
342
- height: 100%;
343
- background: rgba(0, 0, 0, 0.8);
344
- backdrop-filter: blur(10px);
345
- display: none;
346
- align-items: center;
347
- justify-content: center;
348
- z-index: 1000;
349
  opacity: 0;
350
- transition: opacity 0.4s ease;
351
- }
352
-
353
- #speaker-modal.visible {
354
- display: flex;
355
- opacity: 1;
356
- }
357
-
358
- .modal-content {
359
- background: rgba(255, 255, 255, 0.15);
360
- backdrop-filter: blur(25px);
361
- border: 1px solid rgba(255, 255, 255, 0.2);
362
- border-radius: 25px;
363
- padding: 2.5rem;
364
- width: 90%;
365
- max-width: 800px;
366
- max-height: 85vh;
367
- overflow-y: auto;
368
- transform: scale(0.9) translateY(50px);
369
- transition: transform 0.4s ease, opacity 0.4s ease;
370
- opacity: 0;
371
- }
372
-
373
- #speaker-modal.visible .modal-content {
374
- transform: scale(1) translateY(0);
375
- opacity: 1;
376
- }
377
-
378
- .modal-header {
379
- display: flex;
380
- justify-content: space-between;
381
- align-items: center;
382
- margin-bottom: 2rem;
383
- padding-bottom: 1rem;
384
- border-bottom: 1px solid rgba(255, 255, 255, 0.2);
385
- }
386
-
387
- .modal-header h2 {
388
- margin: 0;
389
- font-size: 1.8rem;
390
- color: var(--text-light);
391
- font-weight: 700;
392
- }
393
-
394
- .close-modal-btn {
395
- background: none;
396
- border: none;
397
- font-size: 2rem;
398
- cursor: pointer;
399
- color: rgba(255, 255, 255, 0.8);
400
- transition: var(--transition);
401
- width: 40px;
402
- height: 40px;
403
- border-radius: 50%;
404
- display: flex;
405
- align-items: center;
406
- justify-content: center;
407
  }
408
-
409
- .close-modal-btn:hover {
410
- color: var(--text-light);
411
- background: rgba(255, 255, 255, 0.1);
412
- transform: rotate(90deg);
413
- }
414
-
415
- #speaker-grid {
416
- display: grid;
417
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
418
- gap: 1.5rem;
419
- }
420
-
421
- .speaker-card {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  cursor: pointer;
423
- transition: var(--transition);
424
- text-align: center;
425
- position: relative;
426
- }
427
-
428
- .speaker-card .speaker-visual {
429
- border: 3px solid transparent;
430
- border-radius: 20px;
431
- overflow: hidden;
432
- background: rgba(255, 255, 255, 0.1);
433
- backdrop-filter: blur(10px);
434
- transition: var(--transition);
435
- position: relative;
436
- }
437
-
438
- .speaker-card:hover .speaker-visual {
439
- transform: translateY(-8px) scale(1.05);
440
- box-shadow: 0 15px 35px rgba(255, 255, 255, 0.2);
441
- border-color: rgba(255, 255, 255, 0.4);
442
- }
443
-
444
- .speaker-card input[type="radio"] {
445
- display: none;
446
- }
447
-
448
- .speaker-card img {
449
- width: 100%;
450
- height: 140px;
451
- object-fit: cover;
452
- display: block;
453
- transition: transform 0.3s ease;
454
- }
455
-
456
- .speaker-card:hover img {
457
- transform: scale(1.1);
458
- }
459
-
460
- .speaker-card .speaker-name {
461
- padding: 1rem;
462
- font-weight: 600;
463
- font-size: 0.95rem;
464
- color: var(--text-light);
465
- }
466
-
467
- .speaker-card input[type="radio"]:checked + .speaker-visual {
468
- border-color: #4facfe;
469
- box-shadow: 0 0 25px rgba(79, 172, 254, 0.5);
470
- background: rgba(79, 172, 254, 0.2);
471
- }
472
-
473
- /* Slider Styles */
474
- .slider-container {
475
- display: flex;
476
- align-items: center;
477
- gap: 1.5rem;
478
- }
479
-
480
- input[type="range"] {
481
- flex-grow: 1;
482
- -webkit-appearance: none;
483
- width: 100%;
484
- height: 8px;
485
- background: rgba(255, 255, 255, 0.2);
486
- border-radius: 10px;
487
- outline: none;
488
- cursor: pointer;
489
- transition: background 0.3s;
490
- }
491
-
492
- input[type="range"]::-webkit-slider-track {
493
- background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
494
- height: 8px;
495
- border-radius: 10px;
496
- }
497
-
498
- input[type="range"]::-webkit-slider-thumb {
499
- -webkit-appearance: none;
500
- width: 24px;
501
- height: 24px;
502
- background: #fff;
503
- border-radius: 50%;
504
- cursor: pointer;
505
- box-shadow: 0 0 15px rgba(79, 172, 254, 0.5);
506
- transition: transform 0.2s ease;
507
- }
508
-
509
- input[type="range"]::-webkit-slider-thumb:hover {
510
- transform: scale(1.2);
511
- box-shadow: 0 0 25px rgba(79, 172, 254, 0.8);
512
- }
513
-
514
- #temperature-value {
515
- font-weight: 600;
516
- background: rgba(255, 255, 255, 0.2);
517
- color: var(--text-light);
518
- padding: 0.5rem 1rem;
519
- border-radius: 15px;
520
- border: 1px solid rgba(255, 255, 255, 0.3);
521
- min-width: 60px;
522
- text-align: center;
523
- backdrop-filter: blur(10px);
524
- }
525
-
526
- /* Generate Button */
527
- #generate-btn {
528
- width: 100%;
529
- padding: 1.5rem 2rem;
530
- font-size: 1.3rem;
531
- font-weight: 700;
532
- font-family: 'Vazirmatn', sans-serif;
533
- background: var(--accent-gradient);
534
- color: var(--text-light);
535
  border: none;
536
- border-radius: 20px;
537
- cursor: pointer;
538
- transition: var(--transition);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
539
  position: relative;
540
  overflow: hidden;
541
- box-shadow: 0 10px 30px rgba(79, 172, 254, 0.4);
542
- margin-top: 1rem;
543
  }
544
-
545
- #generate-btn::before {
546
- content: '';
547
- position: absolute;
548
- top: 0;
549
- left: -100%;
550
- width: 100%;
551
- height: 100%;
552
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
553
- transition: left 0.8s;
554
  }
555
-
556
- #generate-btn:hover::before {
557
- left: 100%;
 
558
  }
559
-
560
- #generate-btn:hover:not(:disabled) {
561
- transform: translateY(-5px);
562
- box-shadow: 0 20px 40px rgba(79, 172, 254, 0.6);
563
  }
564
-
565
- #generate-btn:disabled {
566
- background: rgba(255, 255, 255, 0.2);
567
- cursor: not-allowed;
568
  transform: none;
569
- box-shadow: none;
570
  }
571
-
572
- /* Output Section */
573
- #output-section {
574
- margin-top: 3rem;
575
- padding: 3rem;
576
- background: rgba(255, 255, 255, 0.1);
577
- backdrop-filter: blur(15px);
578
- border-radius: 25px;
579
- min-height: 250px;
580
- display: flex;
581
- align-items: center;
582
- justify-content: center;
583
- flex-direction: column;
584
- gap: 2rem;
585
- border: 2px dashed rgba(255, 255, 255, 0.3);
586
- transition: var(--transition);
587
- animation: slideInUp 1s ease-out 0.6s both;
588
  }
589
-
590
- #status-message {
591
- font-weight: 500;
592
- color: rgba(255, 255, 255, 0.9);
593
- text-align: center;
594
- font-size: 1.2rem;
595
  }
 
 
 
 
 
 
 
596
 
597
- #audio-player {
598
- width: 100%;
599
- margin-top: 1rem;
600
- display: none;
601
- border-radius: 15px;
602
- background: rgba(255, 255, 255, 0.1);
603
- }
604
 
605
- /* Enhanced Loading Animation */
606
  #loading-animation-wrapper {
607
- display: none;
608
  flex-direction: column;
609
  align-items: center;
610
  justify-content: center;
611
  gap: 2rem;
612
  width: 100%;
613
- min-height: 200px;
614
  }
615
 
616
- .neural-network-loader {
617
- width: 120px;
618
  height: 120px;
619
  position: relative;
620
- animation: rotate-network 8s linear infinite;
621
  }
622
-
623
- .neural-node {
624
  position: absolute;
625
- width: 12px;
626
- height: 12px;
627
  border-radius: 50%;
628
- background: #4facfe;
629
- box-shadow: 0 0 15px #4facfe, 0 0 30px #00f2fe;
630
- animation: node-pulse 2s ease-in-out infinite;
631
  }
632
 
633
- .neural-node:nth-child(1) { top: 0; left: 50%; transform: translateX(-50%); animation-delay: 0s; }
634
- .neural-node:nth-child(2) { top: 15%; right: 15%; animation-delay: -0.25s; background: #00f2fe; }
635
- .neural-node:nth-child(3) { top: 50%; right: 0; transform: translateY(-50%); animation-delay: -0.5s; }
636
- .neural-node:nth-child(4) { bottom: 15%; right: 15%; animation-delay: -0.75s; background: #00f2fe; }
637
- .neural-node:nth-child(5) { bottom: 0; left: 50%; transform: translateX(-50%); animation-delay: -1s; }
638
- .neural-node:nth-child(6) { bottom: 15%; left: 15%; animation-delay: -1.25s; background: #00f2fe; }
639
- .neural-node:nth-child(7) { top: 50%; left: 0; transform: translateY(-50%); animation-delay: -1.5s; }
640
- .neural-node:nth-child(8) { top: 15%; left: 15%; animation-delay: -1.75s; background: #00f2fe; }
641
 
642
- .neural-connection {
643
  position: absolute;
644
- height: 1px;
645
- background: linear-gradient(90deg, transparent, #4facfe, transparent);
646
- transform-origin: left center;
647
- animation: connection-flow 3s ease-in-out infinite;
 
648
  }
649
 
650
- @keyframes rotate-network {
 
 
 
 
 
651
  from { transform: rotate(0deg); }
652
  to { transform: rotate(360deg); }
653
  }
654
-
655
- @keyframes node-pulse {
656
- 0%, 100% { transform: scale(0.8); opacity: 0.6; }
657
- 50% { transform: scale(1.4); opacity: 1; }
658
  }
659
-
660
- @keyframes connection-flow {
661
- 0%, 100% { opacity: 0; }
662
- 50% { opacity: 0.8; }
663
  }
664
-
665
- #loading-text {
666
- font-size: 1.3rem;
667
- font-weight: 600;
668
- color: var(--text-light);
669
- text-align: center;
670
- animation: text-glow 2s ease-in-out infinite alternate;
671
  }
672
-
673
- @keyframes text-glow {
674
- from { text-shadow: 0 0 10px rgba(255, 255, 255, 0.5); }
675
- to { text-shadow: 0 0 20px rgba(255, 255, 255, 0.8); }
 
 
 
 
 
676
  }
677
 
678
- /* Responsive Design */
679
- @media (max-width: 768px) {
680
- .container {
681
- padding: 1rem;
682
- }
683
-
684
- .app-header h1 {
685
- font-size: 2.5rem;
686
- }
687
 
688
- .main-content {
689
- padding: 2rem;
690
- }
691
-
692
- #speaker-grid {
693
- grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
694
- gap: 1rem;
695
- }
696
  }
 
697
  </style>
698
  </head>
699
  <body>
700
- <div class="floating-shapes">
701
- <div class="shape"></div>
702
- <div class="shape"></div>
703
- <div class="shape"></div>
704
- </div>
705
-
706
  <div class="container">
707
  <header class="app-header">
708
  <h1>آلفا TTS Nova</h1>
709
- <p>صدایی نو، تجربه‌ای نوین در تبدیل متن به گفتار</p>
710
  </header>
711
 
712
  <main class="main-content">
@@ -715,9 +448,8 @@
715
  <label for="text-input">📝 متن مورد نظر شما</label>
716
  <textarea id="text-input" rows="4" placeholder="متن خود را اینجا وارد کنید...">سلام دنیا! این یک نمونه متن برای آزمایش نسل جدید تبدیل متن به صدا آلفا نوا است. امیدوارم از کیفیت آن لذت ببرید.</textarea>
717
  </div>
718
-
719
  <div class="form-group">
720
- <label for="prompt-input">🗣️ سبک و لحن گفتار (اختیاری)</label>
721
  <input type="text" id="prompt-input" value="با صدایی شفاف، دوستانه و پرانرژی." placeholder="مثال: با لحنی رسمی و موقر، یا شاد و کودکانه">
722
  </div>
723
 
@@ -728,10 +460,10 @@
728
  <img id="selected-speaker-img" src="" alt="عکس گوینده">
729
  <div id="selected-speaker-info">
730
  <h3 id="selected-speaker-name"></h3>
731
- <p>گوینده پیش‌فرض</p>
732
  </div>
733
  </div>
734
- <button type="button" id="change-speaker-btn">مشاهده همه گویندگان</button>
735
  </div>
736
  </div>
737
 
@@ -749,15 +481,16 @@
749
  <div id="output-section">
750
  <div id="status-message">صدای تولید شده در اینجا نمایش داده خواهد شد.</div>
751
  <div id="loading-animation-wrapper">
752
- <div class="neural-network-loader">
753
- <div class="neural-node"></div>
754
- <div class="neural-node"></div>
755
- <div class="neural-node"></div>
756
- <div class="neural-node"></div>
757
- <div class="neural-node"></div>
758
- <div class="neural-node"></div>
759
- <div class="neural-node"></div>
760
- <div class="neural-node"></div>
 
761
  </div>
762
  <p id="loading-text">در حال پردازش هوشمند و تولید صدا...</p>
763
  </div>
@@ -770,7 +503,7 @@
770
  <div class="modal-content">
771
  <div class="modal-header">
772
  <h2>گالری گویندگان آلفا نوا</h2>
773
- <button type="button" class="close-modal-btn">×</button>
774
  </div>
775
  <div id="speaker-grid"></div>
776
  </div>
@@ -780,5 +513,286 @@
780
 
781
  <script>
782
  document.addEventListener('DOMContentLoaded', () => {
783
- const HF_SPACE_URL = "https://hamed744-ttspro.hf.space";
784
- const JOIN_QUEUE_
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Alpha TTS Nova - نسل جدید تبدیل متن به صدا</title>
7
  <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800;900&display=swap');
9
 
10
  :root {
11
+ --app-font: 'Vazirmatn', sans-serif;
12
+ --app-bg: #F4F7FC; /* Slightly lighter and cooler */
13
+ --panel-bg: #FFFFFF;
14
+ --panel-border: #E8EEF3; /* Softer border */
15
+ --text-primary: #121826; /* Darker, richer black */
16
+ --text-secondary: #5C677D; /* Softer secondary text */
17
+ --accent-primary: #3B82F6; /* Brighter Blue */
18
+ --accent-primary-hover: #2563EB; /* Darker Blue for hover */
19
+ --accent-secondary: #10B981; /* Vibrant Teal/Green */
20
+ --accent-secondary-hover: #059669;
21
+ --input-bg: #F8FAFC; /* Slightly off-white for inputs */
22
+ --input-border-focus: var(--accent-primary);
23
+
24
+ --radius-card: 24px; /* More rounded */
25
+ --radius-input: 14px; /* More rounded */
26
+ --shadow-subtle: 0 2px 8px rgba(26, 32, 44, 0.04);
27
+ --shadow-medium: 0 6px 16px -2px rgba(26, 32, 44, 0.08), 0 3px 8px -3px rgba(26,32,44,0.05);
28
+ --shadow-strong: 0 10px 25px -5px rgba(26, 32, 44, 0.12), 0 6px 15px -6px rgba(26,32,44,0.08);
29
+ --transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); /* Smooth transition */
30
  }
31
 
32
  body {
33
+ font-family: var(--app-font);
34
  direction: rtl;
35
+ background-color: var(--app-bg);
 
 
36
  color: var(--text-primary);
37
+ font-size: 16px;
38
+ line-height: 1.8; /* Increased line-height for better readability */
39
+ margin: 0;
40
+ padding: 2.5rem 0; /* More padding */
41
  min-height: 100vh;
42
+ -webkit-font-smoothing: antialiased;
43
+ -moz-osx-font-smoothing: grayscale;
44
+ display: flex;
45
+ justify-content: center;
46
+ align-items: flex-start;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  }
48
 
49
+ .container {
50
+ max-width: 780px; /* Slightly wider */
51
+ width: 92%;
52
  margin: 0 auto;
 
 
 
53
  }
54
 
55
+ .app-header {
56
+ padding: 0.5rem 0 3rem 0;
57
+ text-align: center;
58
+ margin-bottom: 2rem;
59
  }
60
+ .app-header h1 {
61
+ font-size: 3.2em; /* Larger */
62
+ font-weight: 900; /* Bolder */
63
+ margin:0 0 0.75rem 0;
64
+ background: linear-gradient(45deg, var(--accent-primary), var(--accent-secondary));
65
  -webkit-background-clip: text;
66
  -webkit-text-fill-color: transparent;
67
+ letter-spacing: -1px;
68
+ }
69
+ .app-header p {
70
+ font-size: 1.25em; /* Larger */
71
+ color: var(--text-secondary);
72
+ margin-top:0;
73
+ opacity: 1;
74
+ font-weight: 400;
75
+ }
76
+
77
+ .main-content {
78
+ padding: 3rem; /* More padding */
79
+ background-color: var(--panel-bg);
80
+ border-radius: var(--radius-card);
81
+ box-shadow: var(--shadow-strong); /* Stronger shadow for main panel */
82
+ border: 1px solid var(--panel-border);
83
+ }
84
+
85
+ .form-group { margin-bottom: 2.5rem; }
86
+ label {
87
+ display: block;
88
+ font-weight: 700; /* Bolder labels */
89
+ color: var(--text-primary);
90
+ font-size: 1.1em;
91
+ margin-bottom: 1rem;
92
+ }
93
+ textarea, input[type="text"] {
94
+ width: 100%;
95
+ padding: 1rem 1.2rem; /* More padding */
96
+ border-radius: var(--radius-input);
97
+ border: 1px solid var(--panel-border);
98
+ background-color: var(--input-bg);
99
+ color: var(--text-primary);
100
+ box-shadow: var(--shadow-subtle) inset; /* Subtle inset shadow */
101
+ font-family: var(--app-font);
102
+ font-size: 1.05rem;
103
+ box-sizing: border-box;
104
+ transition: var(--transition-smooth);
105
+ }
106
+ textarea:focus, input[type="text"]:focus {
107
+ outline: none;
108
+ border-color: var(--input-border-focus);
109
+ box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.2), var(--shadow-subtle) inset; /* Enhanced focus */
110
+ background-color: #fff;
111
+ }
112
+ textarea { min-height: 120px; resize: vertical; }
113
+
114
+ /* --- نمایش گوینده منتخب --- */
115
+ #selected-speaker-display { text-align: center; margin-top: 1rem; position: relative; }
116
+ #selected-speaker-card {
117
+ display: inline-flex;
118
+ align-items: center;
119
+ background: linear-gradient(135deg, var(--input-bg) 0%, #fff 100%);
120
+ border-radius: var(--radius-card); /* Match card radius */
121
+ padding: 1rem 1.2rem; /* R L T B -- More padding */
122
+ box-shadow: var(--shadow-medium);
123
+ border: 1px solid var(--panel-border);
124
+ transition: var(--transition-smooth);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  cursor: pointer;
126
+ position: relative; /* For pseudo-elements if needed */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  }
 
128
  #selected-speaker-card:hover {
129
+ transform: translateY(-4px) scale(1.03);
130
+ box-shadow: var(--shadow-strong);
131
+ }
132
+ #selected-speaker-card img {
133
+ width: 80px; height: 80px; /* Larger image */
134
+ border-radius: 50%;
135
+ object-fit: cover;
136
+ margin-left: 20px;
137
+ border: 4px solid var(--accent-secondary);
138
+ box-shadow: 0 0 15px -3px rgba(16, 185, 129, 0.6);
139
+ background-color: #e0e0e0;
140
+ transition: var(--transition-smooth);
 
 
141
  }
 
142
  #selected-speaker-card:hover img {
143
+ transform: scale(1.05) rotate(3deg);
144
+ }
145
+ #selected-speaker-info h3 { margin: 0; font-size: 1.45em; font-weight: 800; color: var(--text-primary); }
146
+ #selected-speaker-info p { margin: 5px 0 0; color: var(--text-secondary); font-size: 0.9em; font-weight: 500; }
147
+
148
+ #change-speaker-btn { /* Style updated, but will be primarily triggered by card click */
149
+ display: block; margin: 1.5rem auto 0;
150
+ padding: 12px 28px;
151
+ border-radius: var(--radius-input);
152
+ background: transparent;
153
+ border: 2px solid var(--accent-primary);
154
+ color: var(--accent-primary);
155
+ cursor: pointer;
156
+ font-family: var(--app-font); font-weight: 700;
157
+ transition: var(--transition-smooth);
158
+ }
159
+ #change-speaker-btn:hover {
160
+ background-color: var(--accent-primary);
161
+ color: #fff;
162
+ transform: translateY(-3px) scale(1.02);
163
+ box-shadow: 0 6px 12px -3px rgba(59, 130, 246, 0.4);
164
+ }
165
+
166
+ /* --- مودال گالری گویندگان --- */
167
+ #speaker-modal {
168
+ position: fixed; top: 0; left: 0; width: 100%; height: 100%;
169
+ background-color: rgba(18, 24, 38, 0.6); /* Darker backdrop */
170
+ backdrop-filter: blur(10px) saturate(180%);
171
+ display: none; align-items: center; justify-content: center;
172
+ z-index: 1000; opacity: 0;
173
+ transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1);
174
+ }
175
+ #speaker-modal.visible { display: flex; opacity: 1; }
176
+ .modal-content {
177
+ background: var(--panel-bg);
178
+ padding: 2.5rem;
179
+ border-radius: var(--radius-card);
180
+ width: 90%; max-width: 720px; /* Wider modal */
181
+ max-height: 90vh; overflow-y: auto;
182
+ transform: scale(0.9) translateY(30px);
183
+ transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s;
184
+ border: 1px solid var(--panel-border);
185
+ box-shadow: var(--shadow-strong);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  opacity: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  }
188
+ #speaker-modal.visible .modal-content { transform: scale(1) translateY(0); opacity: 1;}
189
+ .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; padding-bottom: 1.2rem; border-bottom: 1px solid var(--panel-border); }
190
+ .modal-header h2 { margin: 0; font-size: 1.8em; font-weight: 800; color: var(--accent-primary);}
191
+ .close-modal-btn { background: none; border: none; font-size: 2.8rem; cursor: pointer; color: var(--text-secondary); transition: var(--transition-smooth); line-height: 1; }
192
+ .close-modal-btn:hover { color: var(--accent-primary); transform: rotate(180deg) scale(1.1); }
193
+
194
+ #speaker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(125px, 1fr)); gap: 1.5rem; }
195
+ @media (min-width: 640px) { #speaker-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); } } /* Adjusted for better spacing */
196
+ .speaker-card { cursor: pointer; transition: var(--transition-smooth); text-align: center; position: relative;}
197
+ .speaker-card .speaker-visual {
198
+ border: 3px solid transparent;
199
+ border-radius: var(--radius-card);
200
+ overflow: hidden;
201
+ box-shadow: var(--shadow-subtle);
202
+ position: relative;
203
+ background-color: var(--input-bg);
204
+ transition: var(--transition-smooth);
205
+ padding: 8px; /* Added padding around image */
206
+ }
207
+ .speaker-card:hover .speaker-visual {
208
+ transform: translateY(-6px) scale(1.05);
209
+ box-shadow: var(--shadow-medium);
210
+ }
211
+ .speaker-card input[type="radio"] { display: none; }
212
+ .speaker-card img {
213
+ width: 100%; height: 125px; /* Slightly larger */
214
+ object-fit: cover; display: block;
215
+ background-color: #e0e0e0;
216
+ transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
217
+ border-radius: calc(var(--radius-card) - 8px); /* Inner radius */
218
+ }
219
+ .speaker-card:hover img { transform: scale(1.1); }
220
+ .speaker-card .speaker-name { padding: 1rem 0.5rem 0.2rem; font-weight: 600; font-size: 0.95em; color: var(--text-secondary); transition: color 0.2s; }
221
+ .speaker-card input[type="radio"]:checked + .speaker-visual {
222
+ border-color: var(--accent-secondary);
223
+ box-shadow: 0 0 20px -5px rgba(16, 185, 129, 0.7);
224
+ background: linear-gradient(135deg, var(--accent-secondary), var(--accent-primary-hover));
225
+ }
226
+ .speaker-card input[type="radio"]:checked + .speaker-visual img {
227
+ border: 3px solid white; /* White border for selected image */
228
+ }
229
+ .speaker-card input[type="radio"]:checked + .speaker-visual .speaker-name {
230
+ color: #fff; /* White text on gradient */
231
+ font-weight: 700;
232
+ }
233
+
234
+ /* --- Slider & Button & Output --- */
235
+ .slider-container { display: flex; align-items: center; gap: 1.5rem; }
236
+ input[type="range"] {
237
+ flex-grow: 1; -webkit-appearance: none; appearance: none;
238
+ width: 100%; height: 10px; /* Thicker track */
239
+ background: #EAF0F6; /* Lighter track bg */
240
+ border-radius: 5px; outline: none;
241
  cursor: pointer;
242
+ transition: background 0.2s;
243
+ }
244
+ input[type="range"]::-webkit-slider-runnable-track {
245
+ background: linear-gradient(to right, var(--accent-secondary) 0%, var(--accent-primary) 100%);
246
+ height: 10px;
247
+ border-radius: 5px;
248
+ }
249
+ input[type="range"]::-webkit-slider-thumb {
250
+ -webkit-appearance: none; appearance: none;
251
+ width: 24px; height: 24px; /* Larger thumb */
252
+ background: #fff;
253
+ border-radius: 50%; cursor: pointer;
254
+ border: 4px solid var(--accent-primary); /* Thicker border */
255
+ box-shadow: 0 3px 8px rgba(0,0,0,0.2);
256
+ margin-top: -7px;
257
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
258
+ }
259
+ input[type="range"]::-webkit-slider-thumb:hover {
260
+ transform: scale(1.15);
261
+ box-shadow: 0 4px 10px rgba(59, 130, 246, 0.4);
262
+ }
263
+ input[type="range"]::-moz-range-thumb {
264
+ width: 24px; height: 24px;
265
+ background: #fff;
266
+ border-radius: 50%; cursor: pointer;
267
+ border: 4px solid var(--accent-primary);
268
+ box-shadow: 0 3px 8px rgba(0,0,0,0.2);
269
+ }
270
+ input[type="range"]::-moz-range-track {
271
+ background: linear-gradient(to right, var(--accent-secondary) 0%, var(--accent-primary) 100%);
272
+ height: 10px;
273
+ border-radius: 5px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  border: none;
275
+ }
276
+ #temperature-value {
277
+ font-weight: 700; background-color: var(--input-bg);
278
+ padding: 0.6rem 1.2rem;
279
+ border-radius: 10px;
280
+ border: 1px solid var(--panel-border);
281
+ min-width: 50px; text-align: center;
282
+ color: var(--accent-primary); /* Accent color for value */
283
+ font-size: 1.05em;
284
+ box-shadow: var(--shadow-subtle);
285
+ }
286
+
287
+ #generate-btn {
288
+ width: 100%; padding: 1.2rem 1.5rem; /* More padding */
289
+ font-size: 1.35em; font-weight: 800; /* Bolder, larger */
290
+ font-family: var(--app-font);
291
+ background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%);
292
+ color: #fff;
293
+ border: none;
294
+ border-radius: var(--radius-input);
295
+ cursor: pointer;
296
+ transition: var(--transition-smooth), transform 0.15s ease-out; /* Added transform transition */
297
+ box-shadow: 0 6px 18px -5px rgba(59, 130, 246, 0.5), 0 6px 18px -5px rgba(16, 185, 129, 0.4);
298
  position: relative;
299
  overflow: hidden;
300
+ letter-spacing: 0.5px;
 
301
  }
302
+ #generate-btn::before {
303
+ content: ''; position: absolute;
304
+ top: 0; left: -150%; width: 70%; height: 100%;
305
+ background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.3) 50%, rgba(255,255,255,0) 100%);
306
+ transform: skewX(-25deg);
307
+ transition: left 0.7s cubic-bezier(0.23, 1, 0.32, 1);
 
 
 
 
308
  }
309
+ #generate-btn:hover::before { left: 150%; }
310
+ #generate-btn:hover:not(:disabled) {
311
+ transform: translateY(-4px) scale(1.01); /* Enhanced hover */
312
+ box-shadow: 0 10px 25px -5px rgba(59, 130, 246, 0.6), 0 10px 25px -5px rgba(16, 185, 129, 0.5);
313
  }
314
+ #generate-btn:active:not(:disabled) {
315
+ transform: translateY(-1px) scale(0.99); /* Click feedback */
 
 
316
  }
317
+ #generate-btn:disabled {
318
+ background: #B8C2CC;
319
+ cursor: not-allowed; box-shadow: none; color: #E8EEF3;
 
320
  transform: none;
 
321
  }
322
+ #generate-btn:disabled::before { display: none; }
323
+
324
+ #output-section {
325
+ margin-top: 3.5rem;
326
+ padding: 2.5rem;
327
+ background-color: var(--input-bg); /* Use input-bg for contrast */
328
+ border-radius: var(--radius-card);
329
+ min-height: 220px;
330
+ display: flex; align-items: center; justify-content: center;
331
+ flex-direction: column; gap: 1.5rem;
332
+ border: 2px dashed var(--panel-border); /* Slightly thicker dashed border */
333
+ transition: var(--transition-smooth);
334
+ position: relative;
335
+ box-shadow: var(--shadow-subtle) inset; /* Subtle inset for empty state */
 
 
 
336
  }
337
+ #output-section.has-content { /* Style when audio is loaded */
338
+ background-color: #fff;
339
+ border-style: solid;
340
+ border-color: var(--accent-secondary);
341
+ box-shadow: 0 0 25px -5px rgba(16, 185, 129, 0.2);
 
342
  }
343
+ #status-message { font-weight: 500; color: var(--text-secondary); text-align: center; font-size: 1.15em; }
344
+ #audio-player { width: 100%; margin-top: 1rem; display: none; border-radius: 10px; box-shadow: var(--shadow-medium); }
345
+ #audio-player::-webkit-media-controls-panel { background-color: #fff; border-radius: 10px; padding: 5px; box-shadow: var(--shadow-subtle) inset;}
346
+ #audio-player::-webkit-media-controls-play-button { color: var(--accent-primary); background-color: rgba(59, 130, 246, 0.1); border-radius: 50%; margin: 5px;}
347
+ #audio-player::-webkit-media-controls-current-time-display,
348
+ #audio-player::-webkit-media-controls-time-remaining-display { color: var(--text-secondary); font-weight: 500; }
349
+ #audio-player::-webkit-media-controls-timeline { background-color: #EAF0F6; border-radius: 5px; margin: 0 10px;}
350
 
 
 
 
 
 
 
 
351
 
352
+ /* --- انیمیشن پردازش جدید: مدار هوشمند --- */
353
  #loading-animation-wrapper {
354
+ display: none;
355
  flex-direction: column;
356
  align-items: center;
357
  justify-content: center;
358
  gap: 2rem;
359
  width: 100%;
360
+ min-height: 150px;
361
  }
362
 
363
+ .orbital-loader {
364
+ width: 120px; /* Larger */
365
  height: 120px;
366
  position: relative;
367
+ animation: rotate-loader-orbital 12s linear infinite;
368
  }
369
+
370
+ .orbit {
371
  position: absolute;
372
+ top: 50%; left: 50%;
373
+ border: 2px dashed rgba(59, 130, 246, 0.3); /* Dashed orbit lines */
374
  border-radius: 50%;
375
+ transform-origin: center center;
 
 
376
  }
377
 
378
+ .orbit:nth-child(1) { width: 40px; height: 40px; margin: -20px 0 0 -20px; animation: orbit-spin 3s linear infinite reverse; }
379
+ .orbit:nth-child(2) { width: 70px; height: 70px; margin: -35px 0 0 -35px; animation: orbit-spin 4s linear infinite; }
380
+ .orbit:nth-child(3) { width: 100px; height: 100px; margin: -50px 0 0 -50px; animation: orbit-spin 5s linear infinite reverse; }
 
 
 
 
 
381
 
382
+ .orbit .satellite {
383
  position: absolute;
384
+ width: 12px; height: 12px;
385
+ border-radius: 50%;
386
+ background-color: var(--accent-primary);
387
+ box-shadow: 0 0 10px var(--accent-primary), 0 0 15px var(--accent-secondary);
388
+ animation: satellite-pulse 1.5s ease-in-out infinite alternate;
389
  }
390
 
391
+ .orbit:nth-child(1) .satellite { top: -6px; left: 50%; transform: translateX(-50%); }
392
+ .orbit:nth-child(2) .satellite { top: 50%; left: -6px; transform: translateY(-50%); background-color: var(--accent-secondary); }
393
+ .orbit:nth-child(3) .satellite { bottom: -6px; right: 50%; transform: translateX(50%); }
394
+
395
+
396
+ @keyframes rotate-loader-orbital {
397
  from { transform: rotate(0deg); }
398
  to { transform: rotate(360deg); }
399
  }
400
+ @keyframes orbit-spin {
401
+ from { transform: rotate(0deg); }
402
+ to { transform: rotate(360deg); }
 
403
  }
404
+ @keyframes satellite-pulse {
405
+ from { transform: scale(0.8) translate(-50%, -50%) ; opacity: 0.7; } /* Adjust translate based on initial positioning if needed */
406
+ to { transform: scale(1.2) translate(-50%, -50%); opacity: 1; }
 
407
  }
408
+ /* Correcting pulse for satellites not centered initially */
409
+ .orbit:nth-child(1) .satellite { animation-name: satellite-pulse-1; }
410
+ @keyframes satellite-pulse-1 {
411
+ from { transform: scale(0.8) translateX(-50%); opacity: 0.7; }
412
+ to { transform: scale(1.2) translateX(-50%); opacity: 1; }
 
 
413
  }
414
+ .orbit:nth-child(2) .satellite { animation-name: satellite-pulse-2; }
415
+ @keyframes satellite-pulse-2 {
416
+ from { transform: scale(0.8) translateY(-50%); opacity: 0.7; }
417
+ to { transform: scale(1.2) translateY(-50%); opacity: 1; }
418
+ }
419
+ .orbit:nth-child(3) .satellite { animation-name: satellite-pulse-3; }
420
+ @keyframes satellite-pulse-3 {
421
+ from { transform: scale(0.8) translateX(50%); opacity: 0.7; }
422
+ to { transform: scale(1.2) translateX(50%); opacity: 1; }
423
  }
424
 
 
 
 
 
 
 
 
 
 
425
 
426
+ #loading-text {
427
+ font-size: 1.25em;
428
+ font-weight: 700;
429
+ color: var(--text-primary);
430
+ text-align: center;
431
+ background: linear-gradient(45deg, var(--accent-primary), var(--accent-secondary));
432
+ -webkit-background-clip: text;
433
+ -webkit-text-fill-color: transparent;
434
  }
435
+
436
  </style>
437
  </head>
438
  <body>
 
 
 
 
 
 
439
  <div class="container">
440
  <header class="app-header">
441
  <h1>آلفا TTS Nova</h1>
442
+ <p>صدایی نو، تجربه‌ای نوین در تبدیل متن به گفتار با هوش مصنوعی پیشرفته</p>
443
  </header>
444
 
445
  <main class="main-content">
 
448
  <label for="text-input">📝 متن مورد نظر شما</label>
449
  <textarea id="text-input" rows="4" placeholder="متن خود را اینجا وارد کنید...">سلام دنیا! این یک نمونه متن برای آزمایش نسل جدید تبدیل متن به صدا آلفا نوا است. امیدوارم از کیفیت آن لذت ببرید.</textarea>
450
  </div>
 
451
  <div class="form-group">
452
+ <label for="prompt-input">🗣️ توصیف سبک و لحن گفتار (اختیاری)</label>
453
  <input type="text" id="prompt-input" value="با صدایی شفاف، دوستانه و پرانرژی." placeholder="مثال: با لحنی رسمی و موقر، یا شاد و کودکانه">
454
  </div>
455
 
 
460
  <img id="selected-speaker-img" src="" alt="عکس گوینده">
461
  <div id="selected-speaker-info">
462
  <h3 id="selected-speaker-name"></h3>
463
+ <p id="selected-speaker-desc">گوینده پیش‌فرض</p>
464
  </div>
465
  </div>
466
+ <!-- دکمه مشاهده همه گویندگان حذف شد، عملکرد به کارت منتقل شد -->
467
  </div>
468
  </div>
469
 
 
481
  <div id="output-section">
482
  <div id="status-message">صدای تولید شده در اینجا نمایش داده خواهد شد.</div>
483
  <div id="loading-animation-wrapper">
484
+ <div class="orbital-loader">
485
+ <div class="orbit">
486
+ <div class="satellite"></div>
487
+ </div>
488
+ <div class="orbit">
489
+ <div class="satellite"></div>
490
+ </div>
491
+ <div class="orbit">
492
+ <div class="satellite"></div>
493
+ </div>
494
  </div>
495
  <p id="loading-text">در حال پردازش هوشمند و تولید صدا...</p>
496
  </div>
 
503
  <div class="modal-content">
504
  <div class="modal-header">
505
  <h2>گالری گویندگان آلفا نوا</h2>
506
+ <button type="button" class="close-modal-btn" aria-label="بستن مودال">×</button>
507
  </div>
508
  <div id="speaker-grid"></div>
509
  </div>
 
513
 
514
  <script>
515
  document.addEventListener('DOMContentLoaded', () => {
516
+ const HF_SPACE_URL = "https://hamed744-ttspro.hf.space"; // یا هر آدرسی که دارید
517
+ const JOIN_QUEUE_URL = `${HF_SPACE_URL}/gradio_api/queue/join`;
518
+ const GET_DATA_URL_BASE = `${HF_SPACE_URL}/gradio_api/queue/data`;
519
+ const FILE_URL_BASE = `${HF_SPACE_URL}/gradio_api/file=`;
520
+ const FN_INDEX = 1;
521
+
522
+ const speakers = [
523
+ { id: "Charon", name: "شهاب (مرد)", desc: "صدایی قدرتمند و رسا" },
524
+ { id: "Zephyr", name: "آوا (زن)", desc: "لطیف و دلنشین" },
525
+ { id: "Achird", name: "نوید (مرد)", desc: "جوان و پرانرژی" },
526
+ { id: "Zubenelgenubi", name: "رویا (زن)", desc: "گرم و صمیمی" },
527
+ { id: "Vindemiatrix", name: "کیان (مرد)", desc: "باوقار و رسمی" },
528
+ { id: "Sadachbia", name: "پریسا (زن)", desc: "شاداب و پویا" },
529
+ { id: "Sadaltager", name: "آرش (مرد)", desc: "مطمئن و تاثیرگذار" },
530
+ { id: "Sulafat", name: "شبنم (زن)", desc: "آرام و متین" },
531
+ { id: "Laomedeia", name: "سهیل (مرد)", desc: "دوستانه و گیرا" },
532
+ { id: "Achernar", name: "مریم (زن)", desc: "حرفه‌ای و واضح" },
533
+ { id: "Alnilam", name: "بهرام (مرد)", desc: "حماسی و نافذ" },
534
+ { id: "Schedar", name: "نگار (زن)", desc: "مهربان و شیرین" },
535
+ { id: "Gacrux", name: "فرید (مرد)", desc: "پخته و قابل اعتماد" },
536
+ { id: "Pulcherrima", name: "سارا (زن)", desc: "جذاب و مدرن" },
537
+ { id: "Umbriel", name: "مانی (مرد)", desc: "خلاق و متفاوت" },
538
+ { id: "Algieba", name: "آناهیتا (زن)", desc: "با اصالت و شیک" },
539
+ { id: "Despina", name: "دلنواز (زن)", desc: "هنری و احساسی" },
540
+ { id: "Erinome", name: "رسا (مرد)", desc: "شفاف و گویا" },
541
+ { id: "Algenib", name: "امید (مرد)", desc: "انگیزه بخش و مثبت" },
542
+ { id: "Rasalthgeti", name: "الهه (زن)", desc: "اسرارآمیز و فریبنده" },
543
+ { id: "Orus", name: "بردیا (مرد)", desc: "ورزشی و پرهیجان" },
544
+ { id: "Aoede", name: "ترانه (زن)", desc: "موزیکال و خوش‌آهنگ" },
545
+ { id: "Callirrhoe", name: "نیما (مرد)", desc: "روایتگر و قصه‌گو" },
546
+ { id: "Autonoe", name: "هستی (زن)", desc: "طبیعی و خودمانی" },
547
+ { id: "Enceladus", name: "کامیار (مرد)", desc: "مصمم و جدی" },
548
+ { id: "Iapetus", name: "ستاره (زن)", desc: "درخشان و گیرا" },
549
+ { id: "Puck", name: "پویا (مرد)", desc: "بازیگوش و سرزنده" },
550
+ { id: "Kore", name: "مهتاب (زن)", desc: "نجواگر و آرامش‌بخش" },
551
+ { id: "Fenrir", name: "سام (مرد)", desc: "جسور و بی‌باک" },
552
+ { id: "Leda", name: "لیدا (زن)", desc: "کلاسیک و باوقار" }
553
+ ];
554
+
555
+ const form = document.getElementById('tts-form');
556
+ const textInput = document.getElementById('text-input');
557
+ const promptInput = document.getElementById('prompt-input');
558
+ const tempSlider = document.getElementById('temperature-slider');
559
+ const tempValueSpan = document.getElementById('temperature-value');
560
+ const generateBtn = document.getElementById('generate-btn');
561
+
562
+ const outputSection = document.getElementById('output-section');
563
+ const statusMessage = document.getElementById('status-message');
564
+ const audioPlayer = document.getElementById('audio-player');
565
+ const loadingAnimationWrapper = document.getElementById('loading-animation-wrapper');
566
+
567
+ const selectedSpeakerIdStorage = document.getElementById('selected_speaker_id_storage');
568
+ const speakerModal = document.getElementById('speaker-modal');
569
+ // const changeSpeakerBtn = document.getElementById('change-speaker-btn'); // No longer needed
570
+ const closeModalBtn = document.querySelector('.close-modal-btn');
571
+ const speakerGridInModal = document.getElementById('speaker-grid');
572
+ const selectedSpeakerImgDisplay = document.getElementById('selected-speaker-img');
573
+ const selectedSpeakerNameDisplay = document.getElementById('selected-speaker-name');
574
+ const selectedSpeakerDescDisplay = document.getElementById('selected-speaker-desc');
575
+ const selectedSpeakerCard = document.getElementById('selected-speaker-card');
576
+
577
+ function getSpeakerById(id) {
578
+ return speakers.find(s => s.id === id) || speakers[0]; // Fallback to first speaker
579
+ }
580
+
581
+ function getImageUrl(speaker, index, size = 'medium') {
582
+ const gender = speaker.name.includes('(مرد)') ? 'men' : (speaker.name.includes('(زن)') ? 'women' : 'lego');
583
+ const imageIndex = (index * 13 + 7 + speaker.id.length) % 100; // More variation
584
+ let portraitSizePath = 'thumb/';
585
+ if (size === 'large') portraitSizePath = '';
586
+
587
+ return `https://randomuser.me/api/portraits/${portraitSizePath}${gender}/${imageIndex}.jpg`;
588
+ }
589
+
590
+ function updateSelectedSpeakerDisplay(speakerId) {
591
+ const speaker = getSpeakerById(speakerId);
592
+ const speakerIndex = speakers.findIndex(s => s.id === speaker.id);
593
+ selectedSpeakerImgDisplay.src = getImageUrl(speaker, speakerIndex, 'large');
594
+ selectedSpeakerImgDisplay.alt = `عکس گوینده ${speaker.name}`;
595
+ selectedSpeakerNameDisplay.textContent = speaker.name;
596
+ selectedSpeakerDescDisplay.textContent = speaker.desc || "گوینده منتخب شما";
597
+ selectedSpeakerIdStorage.value = speaker.id;
598
+ }
599
+
600
+ function createSpeakerCardsInModal() {
601
+ speakerGridInModal.innerHTML = '';
602
+ speakers.forEach((speaker, index) => {
603
+ const cardLabel = document.createElement('label');
604
+ cardLabel.className = 'speaker-card';
605
+ cardLabel.setAttribute('for', `modal-speaker-${speaker.id}`);
606
+ const isChecked = speaker.id === selectedSpeakerIdStorage.value ? 'checked' : '';
607
+
608
+ cardLabel.innerHTML = `
609
+ <input type="radio" name="modal_speaker_selection" value="${speaker.id}" id="modal-speaker-${speaker.id}" ${isChecked}>
610
+ <div class="speaker-visual">
611
+ <img src="${getImageUrl(speaker, index, 'thumb')}" alt="${speaker.name}" loading="lazy">
612
+ <div class="speaker-name">${speaker.name}</div>
613
+ </div>
614
+ `;
615
+
616
+ // Event listener on the label itself
617
+ cardLabel.addEventListener('click', (e) => {
618
+ // Check if the click was on the input or something inside,
619
+ // as the input itself will trigger its own change/click
620
+ if (e.target.name !== "modal_speaker_selection") {
621
+ const radio = cardLabel.querySelector('input[type="radio"]');
622
+ if(radio) radio.checked = true; // Manually check if not already
623
+ }
624
+ updateSelectedSpeakerDisplay(speaker.id);
625
+ setTimeout(() => speakerModal.classList.remove('visible'), 250); // Slight delay for visual feedback
626
+ });
627
+
628
+ speakerGridInModal.appendChild(cardLabel);
629
+ });
630
+ }
631
+
632
+ function openSpeakerModal() {
633
+ createSpeakerCardsInModal(); // Refresh cards each time in case selection changed elsewhere
634
+ speakerModal.classList.add('visible');
635
+ // Focus on the first selectable item or the modal itself for accessibility
636
+ setTimeout(() => { // Timeout for transition
637
+ const firstFocusable = speakerModal.querySelector('input[type="radio"], .close-modal-btn');
638
+ if (firstFocusable) firstFocusable.focus();
639
+ else closeModalBtn.focus();
640
+ }, 50);
641
+ }
642
+
643
+ // changeSpeakerBtn.addEventListener('click', openSpeakerModal); // Removed
644
+ selectedSpeakerCard.addEventListener('click', openSpeakerModal);
645
+
646
+ closeModalBtn.addEventListener('click', () => speakerModal.classList.remove('visible'));
647
+ speakerModal.addEventListener('click', (e) => {
648
+ if (e.target === speakerModal) {
649
+ speakerModal.classList.remove('visible');
650
+ }
651
+ });
652
+ // Keyboard accessibility for modal
653
+ document.addEventListener('keydown', (e) => {
654
+ if (e.key === 'Escape' && speakerModal.classList.contains('visible')) {
655
+ speakerModal.classList.remove('visible');
656
+ }
657
+ });
658
+
659
+ tempSlider.addEventListener('input', () => { tempValueSpan.textContent = tempSlider.value; });
660
+
661
+ function showLoadingState() {
662
+ statusMessage.style.display = 'none';
663
+ audioPlayer.style.display = 'none';
664
+ audioPlayer.src = '';
665
+ outputSection.classList.remove('has-content');
666
+ loadingAnimationWrapper.style.display = 'flex';
667
+ generateBtn.disabled = true;
668
+ generateBtn.innerHTML = `
669
+ <svg aria-hidden="true" style="display:inline-block; margin-left: 0.5em; width:1.2em; height:1.2em;" role="status" fill="currentColor" viewBox="0 0 100 101">
670
+ <path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="#E5E7EB"/>
671
+ <path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0492C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentColor"/>
672
+ </svg>
673
+ در حال پردازش...
674
+ `;
675
+ }
676
+
677
+ function showResultState(isSuccess, message = '') {
678
+ loadingAnimationWrapper.style.display = 'none';
679
+ if (isSuccess) {
680
+ statusMessage.style.display = 'none';
681
+ audioPlayer.style.display = 'block';
682
+ outputSection.classList.add('has-content');
683
+ // audioPlayer.play(); // Consider UX for autoplay
684
+ } else {
685
+ statusMessage.textContent = message || 'خطایی رخ داد. لطفاً دوباره تلاش کنید.';
686
+ statusMessage.style.display = 'block';
687
+ audioPlayer.style.display = 'none';
688
+ outputSection.classList.remove('has-content');
689
+ }
690
+ generateBtn.disabled = false;
691
+ generateBtn.textContent = '🚀 تولید صدا با آلفا نوا';
692
+ }
693
+
694
+ async function generateAudio(event) {
695
+ event.preventDefault();
696
+ showLoadingState();
697
+
698
+ const text = textInput.value;
699
+ if (!text.trim()) {
700
+ showResultState(false, 'خطا: متن ورودی نمی‌تواند خالی باشد.');
701
+ return;
702
+ }
703
+
704
+ const promptVal = promptInput.value;
705
+ const temperatureVal = parseFloat(tempSlider.value);
706
+ const selectedSpeakerVal = selectedSpeakerIdStorage.value;
707
+ const sessionHash = Math.random().toString(36).substring(2);
708
+
709
+ const payload = {
710
+ fn_index: FN_INDEX,
711
+ data: [false, null, text, promptVal, selectedSpeakerVal, temperatureVal],
712
+ event_data: null,
713
+ session_hash: sessionHash
714
+ };
715
+
716
+ try {
717
+ const joinQueueResponse = await fetch(JOIN_QUEUE_URL, {
718
+ method: "POST",
719
+ headers: { "Content-Type": "application/json" },
720
+ body: JSON.stringify(payload)
721
+ });
722
+
723
+ if (!joinQueueResponse.ok) {
724
+ const errorBody = await joinQueueResponse.text();
725
+ console.error("Join Queue Error Body:", errorBody);
726
+ throw new Error(`خطا در برقراری ارتباط با سرویس آلفا (${joinQueueResponse.status}). لطفا لحظاتی دیگر تلاش کنید.`);
727
+ }
728
+
729
+ // Start polling for data
730
+ let finalFilePath = null;
731
+ const startTime = Date.now();
732
+ const timeoutDuration = 90000; // 90 seconds timeout for the whole process
733
+
734
+ while (Date.now() - startTime < timeoutDuration) {
735
+ const dataResponse = await fetch(`${GET_DATA_URL_BASE}?session_hash=${sessionHash}`);
736
+ if (!dataResponse.ok) {
737
+ const errorBody = await dataResponse.text();
738
+ console.error("Get Data Error Body:", errorBody);
739
+ throw new Error(`خطا در دریافت داده از سرویس (${dataResponse.status})`);
740
+ }
741
+
742
+ const responseText = await dataResponse.text();
743
+ const lines = responseText.trim().split('\n');
744
+
745
+ for (const line of lines) {
746
+ if (!line.startsWith('data:')) continue;
747
+ try {
748
+ const data = JSON.parse(line.substring(5));
749
+ if (data.msg === 'process_generating' || data.msg === 'process_starts') {
750
+ // Optionally update loading text here if server provides progress
751
+ // console.log("Processing:", data.output?.progress_data?.[0]?.desc || data.msg);
752
+ } else if (data.msg === 'process_completed') {
753
+ if (data.success && data.output.data && data.output.data[0] && (data.output.data[0].name || data.output.data[0].path)) {
754
+ finalFilePath = data.output.data[0].name || data.output.data[0].path;
755
+ } else {
756
+ console.error("ساختار داده پاسخ سرور معتبر نیست یا پردازش ناموفق بود:", data);
757
+ throw new Error(data.output?.error || 'خطای ناشناخته در پردازش سرور.');
758
+ }
759
+ break; // Exit inner loop
760
+ } else if (data.msg === 'queue_full') {
761
+ throw new Error('صف پردازش پر است. لطفا کمی بعد تلاش کنید.');
762
+ }
763
+ } catch (e) {
764
+ console.warn("خطا در تجزیه JSON از stream:", e, "Line:", line);
765
+ }
766
+ }
767
+ if (finalFilePath) break; // Exit outer loop
768
+ await new Promise(resolve => setTimeout(resolve, 1000)); // Poll every 1 second
769
+ }
770
+
771
+ if (finalFilePath) {
772
+ const audioUrl = `${FILE_URL_BASE}${finalFilePath}`;
773
+ audioPlayer.src = audioUrl;
774
+ showResultState(true);
775
+ } else if (Date.now() - startTime >= timeoutDuration) {
776
+ throw new Error('پردازش بیش از حد طول کشید و متوقف شد.');
777
+ } else {
778
+ throw new Error('فایل صوتی از سرور دریافت نشد یا پردازش ناموفق بود.');
779
+ }
780
+
781
+ } catch (error) {
782
+ console.error('خطا در فرآیند تولید صدا:', error);
783
+ showResultState(false, `${error.message}`);
784
+ }
785
+ }
786
+
787
+ // Initial setup
788
+ updateSelectedSpeakerDisplay(selectedSpeakerIdStorage.value || speakers[0].id); // Ensure a default is set
789
+ form.addEventListener('submit', generateAudio);
790
+
791
+ statusMessage.style.display = 'block';
792
+ loadingAnimationWrapper.style.display = 'none';
793
+ audioPlayer.style.display = 'none';
794
+ outputSection.classList.remove('has-content');
795
+ });
796
+ </script>
797
+ </body>
798
+ </html>