Hamed744 commited on
Commit
f4b63d0
·
verified ·
1 Parent(s): 06f7f41

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +642 -525
index.html CHANGED
@@ -8,384 +8,701 @@
8
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
9
 
10
  :root {
11
- --app-font: 'Vazirmatn', sans-serif;
12
- --app-bg: #F0F7FA; /* Very light cool gray/blue */
13
- --panel-bg: #FFFFFF;
14
- --panel-border: #E0E8EF;
15
- --text-primary: #1A202C; /* Dark Gray */
16
- --text-secondary: #4A5568; /* Medium Gray */
17
- --accent-primary: #2563EB; /* Vibrant Blue */
18
- --accent-primary-hover: #1D4ED8;
19
- --accent-secondary: #0D9488; /* Teal */
20
- --accent-secondary-hover: #0F766E;
21
- --input-bg: #F7FAFC;
22
- --input-border-focus: var(--accent-primary);
23
-
24
- --radius-card: 20px;
25
- --radius-input: 12px;
26
- --shadow-subtle: 0 4px 12px -2px rgba(26, 32, 44, 0.06);
27
- --shadow-medium: 0 8px 20px -4px rgba(26, 32, 44, 0.1);
28
- --shadow-strong: 0 12px 30px -6px rgba(26, 32, 44, 0.15);
29
  }
30
 
31
  body {
32
- font-family: var(--app-font);
33
  direction: rtl;
34
- background-color: var(--app-bg);
 
 
35
  color: var(--text-primary);
36
- font-size: 16px;
37
- line-height: 1.7;
38
- margin: 0;
39
- padding: 2rem 0;
40
  min-height: 100vh;
41
- -webkit-font-smoothing: antialiased;
42
- -moz-osx-font-smoothing: grayscale;
43
- display: flex;
44
- justify-content: center;
45
- align-items: flex-start;
 
 
 
 
 
 
 
 
 
46
  }
47
 
48
- .container {
49
- max-width: 720px;
50
- width: 90%;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  margin: 0 auto;
 
 
 
52
  }
53
 
54
- .app-header {
55
- padding: 0.5rem 0 2.5rem 0;
56
- text-align: center;
57
- margin-bottom: 1.5rem;
58
- }
59
- .app-header h1 {
60
- font-size: 2.8em;
61
- font-weight: 800;
62
- margin:0 0 0.5rem 0;
63
- color: var(--accent-primary);
64
- }
65
- .app-header p {
66
- font-size: 1.15em;
67
- color: var(--text-secondary);
68
- margin-top:0;
69
- opacity: 0.9;
70
- }
71
-
72
- .main-content {
73
- padding: 2.5rem;
74
- background-color: var(--panel-bg);
75
- border-radius: var(--radius-card);
76
- box-shadow: var(--shadow-medium);
77
- border: 1px solid var(--panel-border);
78
- }
79
-
80
- .form-group { margin-bottom: 2.2rem; }
81
- label {
82
- display: block;
83
- font-weight: 600;
84
- color: var(--text-primary);
85
- font-size: 1.05em;
86
- margin-bottom: 0.75rem;
87
- }
88
- textarea, input[type="text"] {
89
- width: 100%;
90
- padding: 0.9rem 1.1rem;
91
- border-radius: var(--radius-input);
92
- border: 1px solid var(--panel-border);
93
- background-color: var(--input-bg);
94
- color: var(--text-primary);
95
- box-shadow: var(--shadow-subtle);
96
- font-family: var(--app-font);
97
- font-size: 1rem;
98
- box-sizing: border-box;
99
- transition: all 0.25s ease-in-out;
100
- }
101
- textarea:focus, input[type="text"]:focus {
102
- outline: none;
103
- border-color: var(--input-border-focus);
104
- box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2), var(--shadow-subtle);
105
- background-color: #fff;
106
- }
107
- textarea { min-height: 110px; }
108
-
109
- /* --- نمایش گوینده منتخب --- */
110
- #selected-speaker-display { text-align: center; margin-top: 1rem; }
111
- #selected-speaker-card {
112
- display: inline-flex;
113
- align-items: center;
114
- background: linear-gradient(145deg, var(--input-bg), #fff);
115
- border-radius: 99px;
116
- padding: 10px 12px 10px 20px; /* R L T B */
117
- box-shadow: var(--shadow-medium);
118
- border: 1px solid var(--panel-border);
119
- transition: all 0.3s ease;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  cursor: pointer;
 
 
121
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  #selected-speaker-card:hover {
123
- transform: translateY(-3px) scale(1.02);
124
- box-shadow: var(--shadow-strong);
125
- }
126
- #selected-speaker-card img {
127
- width: 75px; height: 75px;
128
- border-radius: 50%;
129
- object-fit: cover;
130
- margin-left: 18px;
131
- border: 3px solid var(--accent-secondary);
132
- box-shadow: 0 0 12px -2px rgba(13, 148, 136, 0.5);
133
- background-color: #e0e0e0;
134
- }
135
- #selected-speaker-info h3 { margin: 0; font-size: 1.35em; font-weight: 700; color: var(--text-primary); }
136
- #selected-speaker-info p { margin: 4px 0 0; color: var(--text-secondary); font-size: 0.88em; }
137
-
138
- #change-speaker-btn {
139
- display: block; margin: 1.2rem auto 0;
140
- padding: 10px 24px;
141
- border-radius: var(--radius-input);
142
- background-color: transparent;
143
- border: 2px solid var(--accent-primary);
144
- color: var(--accent-primary);
145
- cursor: pointer;
146
- font-family: var(--app-font); font-weight: 600;
147
- transition: all 0.2s ease;
148
- }
149
- #change-speaker-btn:hover {
150
- background-color: var(--accent-primary);
151
- color: #fff;
152
- transform: translateY(-2px);
153
- box-shadow: 0 4px 10px -2px rgba(37, 99, 235, 0.3);
154
- }
155
-
156
- /* --- مودال گالری گویندگان --- */
157
- #speaker-modal {
158
- position: fixed; top: 0; left: 0; width: 100%; height: 100%;
159
- background-color: rgba(26, 32, 44, 0.5);
160
- backdrop-filter: blur(8px) saturate(150%);
161
- display: none; align-items: center; justify-content: center;
162
- z-index: 1000; opacity: 0;
163
- transition: opacity 0.35s cubic-bezier(0.4, 0, 0.2, 1);
164
- }
165
- #speaker-modal.visible { display: flex; opacity: 1; }
166
- .modal-content {
167
- background: var(--panel-bg);
168
- padding: 2rem;
169
- border-radius: var(--radius-card);
170
- width: 90%; max-width: 680px;
171
- max-height: 85vh; overflow-y: auto;
172
- transform: scale(0.92) translateY(20px);
173
- transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.35s;
174
- border: 1px solid var(--panel-border);
175
- box-shadow: var(--shadow-strong);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  opacity: 0;
 
177
  }
178
- #speaker-modal.visible .modal-content { transform: scale(1) translateY(0); opacity: 1;}
179
- .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; padding-bottom: 1rem; border-bottom: 1px solid var(--panel-border); }
180
- .modal-header h2 { margin: 0; font-size: 1.6em; color: var(--accent-primary);}
181
- .close-modal-btn { background: none; border: none; font-size: 2.4rem; cursor: pointer; color: var(--text-secondary); transition: all 0.2s ease; line-height: 1; }
182
- .close-modal-btn:hover { color: var(--accent-primary); transform: rotate(90deg) scale(1.05); }
183
-
184
- #speaker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(115px, 1fr)); gap: 1.3rem; }
185
- @media (min-width: 576px) { #speaker-grid { grid-template-columns: repeat(4, 1fr); } }
186
- .speaker-card { cursor: pointer; transition: all 0.25s ease-out; text-align: center; position: relative;}
187
- .speaker-card .speaker-visual {
188
- border: 3px solid transparent;
189
- border-radius: var(--radius-card);
190
- overflow: hidden;
191
- box-shadow: var(--shadow-subtle);
192
- position: relative;
193
- background-color: #fff;
194
- transition: all 0.25s ease-out;
195
- }
196
- .speaker-card:hover .speaker-visual {
197
- transform: translateY(-5px) scale(1.03);
198
- box-shadow: var(--shadow-medium);
199
- }
200
- .speaker-card input[type="radio"] { display: none; }
201
- .speaker-card img {
202
- width: 100%; height: 115px;
203
- object-fit: cover; display: block;
204
- background-color: #e9e9e9;
205
- transition: transform 0.3s ease;
206
  }
207
- .speaker-card:hover img { transform: scale(1.05); }
208
- .speaker-card .speaker-name { padding: 0.8rem 0.5rem; font-weight: 500; font-size: 0.92em; color: var(--text-secondary); }
209
- .speaker-card input[type="radio"]:checked + .speaker-visual {
210
- border-color: var(--accent-secondary);
211
- box-shadow: 0 0 15px -3px rgba(13, 148, 136, 0.6);
 
 
 
212
  }
213
- .speaker-card input[type="radio"]:checked + .speaker-visual .speaker-name {
214
- color: var(--accent-secondary);
215
- font-weight: 700;
 
 
 
216
  }
217
 
218
- /* --- Slider & Button & Output --- */
219
- .slider-container { display: flex; align-items: center; gap: 1.2rem; }
220
- input[type="range"] {
221
- flex-grow: 1; -webkit-appearance: none; appearance: none;
222
- width: 100%; height: 8px;
223
- background: #E2E8F0;
224
- border-radius: 4px; outline: none;
225
  cursor: pointer;
226
- transition: background 0.2s;
 
 
 
 
 
 
 
227
  }
228
- input[type="range"]::-webkit-slider-runnable-track {
229
- background: linear-gradient(to right, var(--accent-secondary) 0%, var(--accent-primary) 100%);
230
- height: 8px;
231
- border-radius: 4px;
232
- }
233
- input[type="range"]::-webkit-slider-thumb {
234
- -webkit-appearance: none; appearance: none;
235
- width: 22px; height: 22px;
236
- background: #fff;
237
- border-radius: 50%; cursor: pointer;
238
- border: 3px solid var(--accent-primary);
239
- box-shadow: 0 2px 6px rgba(0,0,0,0.15);
240
- margin-top: -7px; /* Center thumb on track */
241
- transition: transform 0.2s ease, box-shadow 0.2s ease;
242
- }
243
- input[type="range"]::-webkit-slider-thumb:hover {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  transform: scale(1.1);
245
- box-shadow: 0 3px 8px rgba(37, 99, 235, 0.3);
246
- }
247
- input[type="range"]::-moz-range-thumb {
248
- width: 22px; height: 22px;
249
- background: #fff;
250
- border-radius: 50%; cursor: pointer;
251
- border: 3px solid var(--accent-primary);
252
- box-shadow: 0 2px 6px rgba(0,0,0,0.15);
253
- }
254
- /* For Firefox, style the track directly */
255
- input[type="range"]::-moz-range-track {
256
- background: linear-gradient(to right, var(--accent-secondary) 0%, var(--accent-primary) 100%);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  height: 8px;
258
- border-radius: 4px;
259
- border: none;
 
 
 
260
  }
261
- #temperature-value {
262
- font-weight: 600; background-color: var(--input-bg);
263
- padding: 0.4rem 1rem;
264
- border-radius: 8px;
265
- border: 1px solid var(--panel-border);
266
- min-width: 45px; text-align: center;
267
- color: var(--text-primary);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  }
269
-
270
- #generate-btn {
271
- width: 100%; padding: 1.05rem 1.5rem;
272
- font-size: 1.25em; font-weight: 700;
273
- font-family: var(--app-font);
274
- background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%);
275
- color: #fff;
276
- border: none;
277
- border-radius: var(--radius-input);
278
- cursor: pointer;
279
- transition: all 0.3s ease;
280
- box-shadow: 0 5px 15px -4px rgba(37, 99, 235, 0.4), 0 5px 15px -4px rgba(13, 148, 136, 0.3);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  position: relative;
282
  overflow: hidden;
 
 
283
  }
284
- #generate-btn::before { /* Shine effect */
285
- content: ''; position: absolute;
286
- top: 0; left: -150%; width: 70%; height: 100%;
287
- background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.4) 50%, rgba(255,255,255,0) 100%);
288
- transform: skewX(-25deg);
289
- transition: left 0.6s cubic-bezier(0.23, 1, 0.32, 1);
290
- }
291
- #generate-btn:hover::before { left: 150%; }
292
- #generate-btn:hover:not(:disabled) {
293
- transform: translateY(-3px);
294
- box-shadow: 0 8px 20px -4px rgba(37, 99, 235, 0.5), 0 8px 20px -4px rgba(13, 148, 136, 0.4);
295
- }
296
- #generate-btn:disabled {
297
- background: #A0AEC0; /* Lighter gray for disabled */
298
- cursor: not-allowed; box-shadow: none; color: #E2E8F0;
 
 
 
 
 
 
 
 
 
299
  transform: none;
 
300
  }
301
- #generate-btn:disabled::before { display: none; }
302
-
303
- #output-section {
304
- margin-top: 3rem;
305
- padding: 2.5rem;
306
- background-color: #fff;
307
- border-radius: var(--radius-card);
308
- min-height: 200px;
309
- display: flex; align-items: center; justify-content: center;
310
- flex-direction: column; gap: 1.5rem;
311
- border: 1px dashed var(--panel-border);
312
- transition: all 0.3s ease;
313
- position: relative;
314
- box-shadow: var(--shadow-subtle);
 
 
 
315
  }
316
- #status-message { font-weight: 500; color: var(--text-secondary); text-align: center; font-size: 1.1em; }
317
- #audio-player { width: 100%; margin-top: 1rem; display: none; }
318
- #audio-player::-webkit-media-controls-panel { background-color: #f9fafb; border-radius: 8px; }
319
- #audio-player::-webkit-media-controls-play-button { color: var(--accent-primary); }
320
- #audio-player::-webkit-media-controls-current-time-display,
321
- #audio-player::-webkit-media-controls-time-remaining-display { color: var(--text-secondary); }
322
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
- /* --- انیمیشن پردازش جدید: ذرات هوشمند --- */
325
  #loading-animation-wrapper {
326
- display: none; /* Hidden by default */
327
  flex-direction: column;
328
  align-items: center;
329
  justify-content: center;
330
  gap: 2rem;
331
  width: 100%;
332
- min-height: 150px;
333
- perspective: 800px; /* For 3D effect on particles if desired */
334
  }
335
 
336
- .smart-particles-loader {
337
- width: 100px;
338
- height: 100px;
339
  position: relative;
340
- animation: rotate-loader 10s linear infinite;
341
  }
342
-
343
- .particle {
344
  position: absolute;
345
- width: 10px;
346
- height: 10px;
347
  border-radius: 50%;
348
- background-color: var(--accent-primary);
349
- box-shadow: 0 0 8px var(--accent-primary), 0 0 12px var(--accent-secondary);
350
- opacity: 0;
351
- animation: particle-orbit 2.5s ease-in-out infinite,
352
- particle-fade 2.5s ease-in-out infinite;
353
  }
354
 
355
- .particle:nth-child(1) { top: 0; left: 50%; transform: translateX(-50%); animation-delay: 0s; }
356
- .particle:nth-child(2) { top: 15%; left: 85%; transform: translate(-50%, -50%); animation-delay: -0.3s; background-color: var(--accent-secondary);}
357
- .particle:nth-child(3) { top: 50%; left: 100%; transform: translateY(-50%); animation-delay: -0.6s; }
358
- .particle:nth-child(4) { top: 85%; left: 85%; transform: translate(-50%, -50%); animation-delay: -0.9s; background-color: var(--accent-secondary);}
359
- .particle:nth-child(5) { top: 100%; left: 50%; transform: translateX(-50%); animation-delay: -1.2s; }
360
- .particle:nth-child(6) { top: 85%; left: 15%; transform: translate(-50%, -50%); animation-delay: -1.5s; background-color: var(--accent-secondary);}
361
- .particle:nth-child(7) { top: 50%; left: 0; transform: translateY(-50%); animation-delay: -1.8s; }
362
- .particle:nth-child(8) { top: 15%; left: 15%; transform: translate(-50%, -50%); animation-delay: -2.1s; background-color: var(--accent-secondary);}
363
 
 
 
 
 
 
 
 
364
 
365
- @keyframes rotate-loader {
366
  from { transform: rotate(0deg); }
367
  to { transform: rotate(360deg); }
368
  }
369
 
370
- @keyframes particle-orbit {
371
- 0%, 100% { transform: scale(0.5); }
372
- 50% { transform: scale(1.2); }
373
  }
374
- @keyframes particle-fade {
 
375
  0%, 100% { opacity: 0; }
376
- 20%, 80% { opacity: 1; }
377
  }
378
 
379
- #loading-text {
380
- font-size: 1.2em;
381
- font-weight: 600;
382
- color: var(--text-primary);
383
  text-align: center;
 
 
 
 
 
 
384
  }
385
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
  </style>
387
  </head>
388
  <body>
 
 
 
 
 
 
389
  <div class="container">
390
  <header class="app-header">
391
  <h1>آلفا TTS Nova</h1>
@@ -398,6 +715,7 @@
398
  <label for="text-input">📝 متن مورد نظر شما</label>
399
  <textarea id="text-input" rows="4" placeholder="متن خود را اینجا وارد کنید...">سلام دنیا! این یک نمونه متن برای آزمایش نسل جدید تبدیل متن به صدا آلفا نوا است. امیدوارم از کیفیت آن لذت ببرید.</textarea>
400
  </div>
 
401
  <div class="form-group">
402
  <label for="prompt-input">🗣️ سبک و لحن گفتار (اختیاری)</label>
403
  <input type="text" id="prompt-input" value="با صدایی شفاف، دوستانه و پرانرژی." placeholder="مثال: با لحنی رسمی و موقر، یا شاد و کودکانه">
@@ -431,11 +749,15 @@
431
  <div id="output-section">
432
  <div id="status-message">صدای تولید شده در اینجا نمایش داده خواهد شد.</div>
433
  <div id="loading-animation-wrapper">
434
- <div class="smart-particles-loader">
435
- <div class="particle"></div> <div class="particle"></div>
436
- <div class="particle"></div> <div class="particle"></div>
437
- <div class="particle"></div> <div class="particle"></div>
438
- <div class="particle"></div> <div class="particle"></div>
 
 
 
 
439
  </div>
440
  <p id="loading-text">در حال پردازش هوشمند و تولید صدا...</p>
441
  </div>
@@ -459,209 +781,4 @@
459
  <script>
460
  document.addEventListener('DOMContentLoaded', () => {
461
  const HF_SPACE_URL = "https://hamed744-ttspro.hf.space";
462
- const JOIN_QUEUE_URL = `${HF_SPACE_URL}/gradio_api/queue/join`;
463
- const GET_DATA_URL_BASE = `${HF_SPACE_URL}/gradio_api/queue/data`;
464
- const FILE_URL_BASE = `${HF_SPACE_URL}/gradio_api/file=`;
465
- const FN_INDEX = 1;
466
-
467
- const speakers = [
468
- { id: "Charon", name: "شهاب (مرد)" }, { id: "Zephyr", name: "آوا (زن)" }, { id: "Achird", name: "نوید (مرد)" }, { id: "Zubenelgenubi", name: "رویا (زن)" }, { id: "Vindemiatrix", name: "کیان (مرد)" }, { id: "Sadachbia", name: "پریسا (زن)" }, { id: "Sadaltager", name: "آرش (مرد)" }, { id: "Sulafat", name: "شبنم (زن)" }, { id: "Laomedeia", name: "سهیل (مرد)" }, { id: "Achernar", name: "مریم (زن)" }, { id: "Alnilam", name: "بهرام (مرد)" }, { id: "Schedar", name: "نگار (زن)" }, { id: "Gacrux", name: "فرید (مرد)" }, { id: "Pulcherrima", name: "سارا (زن)" }, { id: "Umbriel", name: "مانی (مرد)" }, { id: "Algieba", name: "آناهیتا (زن)" }, { id: "Despina", name: "دلنواز (زن)" }, { id: "Erinome", name: "رسا (مرد)" }, { id: "Algenib", name: "امید (مرد)" }, { id: "Rasalthgeti", name: "الهه (زن)" }, { id: "Orus", name: "بردیا (مرد)" }, { id: "Aoede", name: "ترانه (زن)" }, { id: "Callirrhoe", name: "نیما (مرد)" }, { id: "Autonoe", name: "هستی (زن)" }, { id: "Enceladus", name: "کامیار (مرد)" }, { id: "Iapetus", name: "ستاره (زن)" }, { id: "Puck", name: "پویا (مرد)" }, { id: "Kore", name: "مهتاب (زن)" }, { id: "Fenrir", name: "سام (مرد)" }, { id: "Leda", name: "لیدا (زن)" }
469
- ];
470
-
471
- const form = document.getElementById('tts-form');
472
- const textInput = document.getElementById('text-input');
473
- const promptInput = document.getElementById('prompt-input');
474
- const tempSlider = document.getElementById('temperature-slider');
475
- const tempValueSpan = document.getElementById('temperature-value');
476
- const generateBtn = document.getElementById('generate-btn');
477
-
478
- const statusMessage = document.getElementById('status-message');
479
- const audioPlayer = document.getElementById('audio-player');
480
- const loadingAnimationWrapper = document.getElementById('loading-animation-wrapper');
481
-
482
- const selectedSpeakerIdStorage = document.getElementById('selected_speaker_id_storage');
483
- const speakerModal = document.getElementById('speaker-modal');
484
- const changeSpeakerBtn = document.getElementById('change-speaker-btn');
485
- const closeModalBtn = document.querySelector('.close-modal-btn');
486
- const speakerGridInModal = document.getElementById('speaker-grid');
487
- const selectedSpeakerImgDisplay = document.getElementById('selected-speaker-img');
488
- const selectedSpeakerNameDisplay = document.getElementById('selected-speaker-name');
489
- const selectedSpeakerCard = document.getElementById('selected-speaker-card');
490
- const selectedSpeakerInfoP = selectedSpeakerCard.querySelector('#selected-speaker-info p');
491
-
492
- function getSpeakerById(id) {
493
- return speakers.find(s => s.id === id);
494
- }
495
-
496
- function getImageUrl(speaker, index, size = 'medium') {
497
- const gender = speaker.name.includes('(مرد)') ? 'men' : (speaker.name.includes('(زن)') ? 'women' : 'lego');
498
- const imageIndex = (index * 13 + 7) % 100;
499
- let portraitSizePath = 'thumb/';
500
- if (size === 'large') portraitSizePath = '';
501
-
502
- return `https://randomuser.me/api/portraits/${portraitSizePath}${gender}/${imageIndex}.jpg`;
503
- }
504
-
505
- function updateSelectedSpeakerDisplay(speakerId) {
506
- const speaker = getSpeakerById(speakerId);
507
- if (speaker) {
508
- const speakerIndex = speakers.findIndex(s => s.id === speakerId);
509
- selectedSpeakerImgDisplay.src = getImageUrl(speaker, speakerIndex, 'large');
510
- selectedSpeakerImgDisplay.alt = `عکس گوینده ${speaker.name}`;
511
- selectedSpeakerNameDisplay.textContent = speaker.name;
512
- selectedSpeakerInfoP.textContent = "گوینده منتخب شما";
513
- selectedSpeakerIdStorage.value = speaker.id;
514
- }
515
- }
516
-
517
- function createSpeakerCardsInModal() {
518
- speakerGridInModal.innerHTML = '';
519
- speakers.forEach((speaker, index) => {
520
- const card = document.createElement('label');
521
- card.className = 'speaker-card';
522
- card.setAttribute('for', `modal-speaker-${speaker.id}`);
523
- const isChecked = speaker.id === selectedSpeakerIdStorage.value ? 'checked' : '';
524
-
525
- card.innerHTML = `
526
- <input type="radio" name="modal_speaker_selection" value="${speaker.id}" id="modal-speaker-${speaker.id}" ${isChecked}>
527
- <div class="speaker-visual">
528
- <img src="${getImageUrl(speaker, index, 'thumb')}" alt="${speaker.name}" loading="lazy">
529
- <div class="speaker-name">${speaker.name}</div>
530
- </div>
531
- `;
532
-
533
- card.addEventListener('click', () => {
534
- updateSelectedSpeakerDisplay(speaker.id);
535
- setTimeout(() => speakerModal.classList.remove('visible'), 250);
536
- });
537
-
538
- speakerGridInModal.appendChild(card);
539
- });
540
- }
541
-
542
- function openSpeakerModal() {
543
- createSpeakerCardsInModal();
544
- speakerModal.classList.add('visible');
545
- }
546
-
547
- changeSpeakerBtn.addEventListener('click', openSpeakerModal);
548
- selectedSpeakerCard.addEventListener('click', openSpeakerModal); // Allow clicking on card too
549
-
550
- closeModalBtn.addEventListener('click', () => speakerModal.classList.remove('visible'));
551
- speakerModal.addEventListener('click', (e) => {
552
- if (e.target === speakerModal) {
553
- speakerModal.classList.remove('visible');
554
- }
555
- });
556
-
557
- tempSlider.addEventListener('input', () => { tempValueSpan.textContent = tempSlider.value; });
558
-
559
- function showLoadingState() {
560
- statusMessage.style.display = 'none';
561
- audioPlayer.style.display = 'none';
562
- audioPlayer.src = '';
563
- loadingAnimationWrapper.style.display = 'flex';
564
- generateBtn.disabled = true;
565
- generateBtn.textContent = 'در حال پردازش...';
566
- }
567
-
568
- function showResultState(isSuccess, message = '') {
569
- loadingAnimationWrapper.style.display = 'none';
570
- if (isSuccess) {
571
- statusMessage.style.display = 'none';
572
- audioPlayer.style.display = 'block';
573
- // Consider if autoplay is desired: audioPlayer.play();
574
- } else {
575
- statusMessage.textContent = message || 'خطایی رخ داد. لطفاً دوباره تلاش کنید.';
576
- statusMessage.style.display = 'block';
577
- audioPlayer.style.display = 'none';
578
- }
579
- generateBtn.disabled = false;
580
- generateBtn.textContent = '🚀 تولید صدا با آلفا نوا';
581
- }
582
-
583
- async function generateAudio(event) {
584
- event.preventDefault();
585
- showLoadingState();
586
-
587
- const text = textInput.value;
588
- if (!text.trim()) {
589
- showResultState(false, 'خطا: متن ورودی نمی‌تواند خالی باشد.');
590
- return;
591
- }
592
-
593
- const promptVal = promptInput.value;
594
- const temperatureVal = parseFloat(tempSlider.value);
595
- const selectedSpeakerVal = selectedSpeakerIdStorage.value;
596
- const sessionHash = Math.random().toString(36).substring(2);
597
-
598
- const payload = {
599
- fn_index: FN_INDEX,
600
- data: [false, null, text, promptVal, selectedSpeakerVal, temperatureVal],
601
- event_data: null,
602
- session_hash: sessionHash
603
- };
604
-
605
- try {
606
- const joinQueueResponse = await fetch(JOIN_QUEUE_URL, {
607
- method: "POST",
608
- headers: { "Content-Type": "application/json" },
609
- body: JSON.stringify(payload)
610
- });
611
-
612
- if (!joinQueueResponse.ok) {
613
- const errorBody = await joinQueueResponse.text();
614
- throw new Error(`خطا در ارتباط با سرویس آلفا (${joinQueueResponse.status}).`);
615
- }
616
-
617
- const dataResponse = await fetch(`${GET_DATA_URL_BASE}?session_hash=${sessionHash}`);
618
- const reader = dataResponse.body.getReader();
619
- const decoder = new TextDecoder();
620
- let finalFilePath = null;
621
- let buffer = '';
622
-
623
- while (true) {
624
- const { value, done } = await reader.read();
625
- if (done) break;
626
- buffer += decoder.decode(value, { stream: true });
627
- const lines = buffer.split('\n');
628
- buffer = lines.pop();
629
- for (const line of lines) {
630
- if (!line.startsWith('data:')) continue;
631
- try {
632
- const data = JSON.parse(line.substring(5));
633
- if (data.msg === 'process_completed') {
634
- if (data.success && data.output.data && data.output.data[0] && (data.output.data[0].name || data.output.data[0].path)) {
635
- finalFilePath = data.output.data[0].name || data.output.data[0].path;
636
- } else { console.error("ساختار داده پاسخ سرور معتبر نیست:", data); }
637
- break;
638
- }
639
- } catch (e) { /* Ignore JSON parse errors */ }
640
- }
641
- if (finalFilePath) break;
642
- }
643
-
644
- if (finalFilePath) {
645
- const audioUrl = `${FILE_URL_BASE}${finalFilePath}`;
646
- audioPlayer.src = audioUrl;
647
- showResultState(true);
648
- } else {
649
- throw new Error('فایل صوتی از سرور دریافت نشد. پردازش ممکن است ناموفق بوده باشد.');
650
- }
651
-
652
- } catch (error) {
653
- console.error('خطا در فرآیند تولید صدا:', error);
654
- showResultState(false, `خطا: ${error.message}`);
655
- }
656
- }
657
-
658
- updateSelectedSpeakerDisplay(selectedSpeakerIdStorage.value);
659
- form.addEventListener('submit', generateAudio);
660
-
661
- statusMessage.style.display = 'block';
662
- loadingAnimationWrapper.style.display = 'none';
663
- audioPlayer.style.display = 'none';
664
- });
665
- </script>
666
- </body>
667
- </html>
 
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>
 
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="مثال: با لحنی رسمی و موقر، یا شاد و کودکانه">
 
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>
 
781
  <script>
782
  document.addEventListener('DOMContentLoaded', () => {
783
  const HF_SPACE_URL = "https://hamed744-ttspro.hf.space";
784
+ const JOIN_QUEUE_