Hamed744 commited on
Commit
41da9f6
·
verified ·
1 Parent(s): 0d3789d

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +660 -360
index.html CHANGED
@@ -3,207 +3,702 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Alpha TTS - نسخه پریمیوم</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
  --app-font: 'Vazirmatn', sans-serif;
12
- --bg-color: #0b0f19;
13
- --panel-bg: rgba(30, 35, 50, 0.5);
14
- --panel-border: rgba(255, 255, 255, 0.1);
15
- --blur-intensity: 15px;
16
- --primary-glow-start: #38bdf8;
17
- --primary-glow-end: #818cf8;
18
- --secondary-glow-start: #a78bfa;
19
- --secondary-glow-end: #f472b6;
20
- --text-primary: #f1f5f9;
21
- --text-secondary: #94a3b8;
22
  --radius-card: 24px;
23
- --radius-input: 12px;
24
- --shadow-glow: 0 0 30px -5px var(--primary-glow-start);
 
 
 
 
25
  }
26
 
27
- @keyframes aurora-bg {
28
- 0% { background-position: 0% 50%; }
29
- 50% { background-position: 100% 50%; }
30
- 100% { background-position: 0% 50%; }
31
  }
32
 
33
  body {
34
  font-family: var(--app-font);
35
  direction: rtl;
36
- background-color: var(--bg-color);
37
- background-image: radial-gradient(at 27% 37%, hsla(215, 98%, 61%, 0.1) 0px, transparent 50%),
38
- radial-gradient(at 76% 2%, hsla(242, 94%, 70%, 0.1) 0px, transparent 50%),
39
- radial-gradient(at 97% 96%, hsla(339, 87%, 65%, 0.1) 0px, transparent 50%);
40
- color: var(--text-primary);
41
  font-size: 16px;
42
- line-height: 1.7;
43
- margin: 0;
44
- padding: 40px 0;
45
  min-height: 100vh;
46
  -webkit-font-smoothing: antialiased;
47
  -moz-osx-font-smoothing: grayscale;
 
48
  overflow-x: hidden;
49
  }
50
 
51
- .container { max-width: 800px; width: 90%; margin: 0 auto; padding-bottom: 40px; }
52
- .app-header { text-align: center; margin-bottom: 3rem; }
53
- .app-header h1 {
54
- font-size: 3.5em;
55
- font-weight: 800;
56
- margin: 0 0 0.5rem 0;
57
- background: linear-gradient(90deg, var(--primary-glow-start), var(--primary-glow-end));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  -webkit-background-clip: text;
59
  -webkit-text-fill-color: transparent;
60
- text-shadow: 0 0 40px rgba(129, 140, 248, 0.3);
61
  }
62
- .app-header p { font-size: 1.25em; color: var(--text-secondary); margin-top:0; }
63
 
64
- .main-content {
65
- padding: 2.5rem;
66
- background: var(--panel-bg);
67
- backdrop-filter: blur(var(--blur-intensity));
68
- -webkit-backdrop-filter: blur(var(--blur-intensity));
69
- border-radius: var(--radius-card);
70
- border: 1px solid var(--panel-border);
71
- box-shadow: 0 10px 40px -10px rgba(0,0,0,0.3);
72
  }
73
 
74
- .form-group { margin-bottom: 2.5rem; }
75
- label { display: block; font-weight: 600; color: var(--text-primary); font-size: 1.1em; margin-bottom: 1rem; }
76
- textarea, input[type="text"] {
77
- width: 100%;
78
- padding: 1rem;
79
- border-radius: var(--radius-input);
80
- border: 1px solid var(--panel-border);
81
- background-color: rgba(15, 20, 30, 0.5);
82
- box-shadow: none;
83
- font-family: var(--app-font);
84
- font-size: 1rem;
85
- color: var(--text-primary);
86
- box-sizing: border-box;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  transition: all 0.3s ease;
88
  }
89
- textarea:focus, input[type="text"]:focus {
90
- outline: none;
91
- border-color: var(--primary-glow-start);
92
- box-shadow: 0 0 15px -2px var(--primary-glow-start);
93
- background-color: rgba(30, 35, 50, 0.7);
94
- }
95
-
96
- /* --- نمایش گوینده منتخب --- */
97
- #selected-speaker-display { text-align: center; }
98
- #selected-speaker-card { display: inline-flex; align-items: center; background: rgba(15, 20, 30, 0.5); border-radius: 99px; padding: 10px; border: 1px solid var(--panel-border); }
99
- #selected-speaker-card img { width: 80px; height: 80px; border-radius: 50%; object-fit: cover; margin-left: 20px; border: 3px solid var(--primary-glow-end); box-shadow: 0 0 15px rgba(129, 140, 248, 0.5); }
100
- #selected-speaker-info h3 { margin: 0; font-size: 1.5em; font-weight: 700; color: var(--text-primary); }
101
- #selected-speaker-info p { margin: 5px 0 0; color: var(--text-secondary); font-size: 0.9em; }
102
- #change-speaker-btn { display: block; margin: 1.2rem auto 0; padding: 10px 24px; border-radius: var(--radius-input); background-color: rgba(255, 255, 255, 0.1); border: 1px solid var(--panel-border); cursor: pointer; font-family: var(--app-font); font-weight: 500; color: var(--text-secondary); transition: all 0.2s ease; }
103
- #change-speaker-btn:hover { color: var(--text-primary); background-color: rgba(255, 255, 255, 0.15); border-color: var(--primary-glow-start); }
104
-
105
- /* --- مودال گالری گویندگان --- */
106
- #speaker-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(11, 15, 25, 0.7); backdrop-filter: blur(10px); display: none; align-items: center; justify-content: center; z-index: 1000; opacity: 0; transition: opacity 0.3s ease; }
107
- #speaker-modal.visible { display: flex; opacity: 1; }
108
- .modal-content { background: var(--panel-bg); padding: 2rem; border-radius: var(--radius-card); border: 1px solid var(--panel-border); width: 90%; max-width: 700px; max-height: 85vh; overflow-y: auto; transform: scale(0.95); transition: transform 0.3s ease; }
109
- #speaker-modal.visible .modal-content { transform: scale(1); }
110
- .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); }
111
- .modal-header h2 { margin: 0; }
112
- .close-modal-btn { background: none; border: none; font-size: 2.2rem; cursor: pointer; color: #9ca3af; transition: color 0.2s ease, transform 0.2s ease; line-height: 1; }
113
- .close-modal-btn:hover { color: var(--text-primary); transform: rotate(90deg); }
114
- #speaker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 1.5rem; }
115
- .speaker-card { cursor: pointer; transition: all 0.3s ease; text-align: center; }
116
- .speaker-card .speaker-visual { border: 2px solid transparent; border-radius: var(--radius-card); overflow: hidden; position: relative; transition: all 0.3s ease; background: rgba(0,0,0,0.2); }
117
- .speaker-card:hover .speaker-visual { transform: translateY(-5px) scale(1.03); box-shadow: 0 8px 25px rgba(0,0,0,0.3); }
118
- .speaker-card input[type="radio"] { display: none; }
119
- .speaker-card img { width: 100%; height: 120px; object-fit: cover; display: block; filter: grayscale(50%); transition: filter 0.3s ease; }
120
- .speaker-card:hover img, .speaker-card input[type="radio"]:checked ~ .speaker-visual img { filter: grayscale(0%); }
121
- .speaker-card .speaker-name { padding: 0.8rem 0.5rem; font-weight: 500; font-size: 0.95em; color: var(--text-secondary); transition: color 0.2s; }
122
- .speaker-card input[type="radio"]:checked + .speaker-visual { border-color: var(--primary-glow-end); box-shadow: 0 0 20px -2px var(--primary-glow-end); }
123
- .speaker-card input[type="radio"]:checked + .speaker-visual .speaker-name { color: var(--text-primary); font-weight: 700; }
124
-
125
- /* --- Slider & Button & Output --- */
126
- .slider-container { display: flex; align-items: center; gap: 1.5rem; }
127
- input[type="range"] { flex-grow: 1; -webkit-appearance: none; appearance: none; width: 100%; height: 6px; background: rgba(0,0,0,0.3); border-radius: 5px; outline: none; transition: background 0.2s; }
128
- input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 22px; height: 22px; background: var(--text-primary); border-radius: 50%; cursor: pointer; box-shadow: 0 0 10px 2px rgba(255,255,255,0.5); transition: all 0.2s ease; }
129
- input[type="range"]:hover::-webkit-slider-thumb { background: var(--primary-glow-start); box-shadow: 0 0 15px 2px var(--primary-glow-start); }
130
- #temperature-value { font-weight: 700; background-color: rgba(0,0,0,0.3); padding: 0.4rem 1rem; border-radius: 8px; border: 1px solid var(--panel-border); min-width: 45px; text-align: center; color: var(--text-secondary); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
- #generate-btn {
133
- width: 100%;
134
- padding: 1.1rem 1.5rem;
135
- font-size: 1.25em;
136
- font-weight: 700;
137
- font-family: var(--app-font);
138
- background: linear-gradient(90deg, var(--secondary-glow-start), var(--primary-glow-start));
139
- color: white;
140
- border: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  border-radius: var(--radius-input);
 
 
 
 
 
142
  cursor: pointer;
143
- transition: all 0.35s ease;
144
- box-shadow: 0 5px 20px -5px rgba(167, 139, 250, 0.6);
145
- background-size: 200% auto;
 
 
146
  }
147
- #generate-btn:hover:not(:disabled) {
148
- background-position: right center;
149
- transform: translateY(-3px) scale(1.02);
150
- box-shadow: 0 8px 30px -8px rgba(167, 139, 250, 0.8);
 
 
 
 
 
151
  }
152
- #generate-btn:disabled { background: #4b5563; cursor: not-allowed; box-shadow: none; transform: none; color: #9ca3af; }
153
-
154
- #output-section {
155
- margin-top: 2.5rem; padding: 2rem;
156
- background: rgba(15, 20, 30, 0.5);
157
- border-radius: var(--radius-card);
158
- min-height: 180px;
159
- display: flex; align-items: center; justify-content: center;
160
- flex-direction: column; gap: 1rem;
161
- border: 1px dashed var(--panel-border);
162
- transition: all 0.3s ease;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  }
164
- #status-message { font-weight: 500; color: var(--text-secondary); text-align: center; }
165
- #audio-player { width: 100%; margin-top: 1rem; display: none; }
166
- #audio-player::-webkit-media-controls-panel { background-color: rgba(50, 60, 80, 0.8); }
167
- #audio-player::-webkit-media-controls-play-button { color: var(--text-primary); }
168
-
169
- /* --- انیمیشن پردازش پریمیوم --- */
170
- #loading-animation { display: none; flex-direction: column; align-items: center; justify-content: center; gap: 2rem; }
171
- .loader-container { position: relative; width: 120px; height: 120px; }
172
- .ai-core, .particle { position: absolute; top: 50%; left: 50%; border-radius: 50%; }
173
- .ai-core {
174
- width: 40px; height: 40px;
175
- background: radial-gradient(circle, #fff, var(--primary-glow-end));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  transform: translate(-50%, -50%);
177
- box-shadow: 0 0 15px 5px #fff, 0 0 30px 10px var(--primary-glow-end), 0 0 50px 20px var(--secondary-glow-start);
178
- animation: pulse-core 2s infinite ease-in-out;
179
- }
180
- .particle {
181
- width: 8px; height: 8px;
182
- background-color: var(--primary-glow-start);
183
- box-shadow: 0 0 8px var(--primary-glow-start);
184
- transform-origin: 0 0;
185
- }
186
- .p1 { animation: orbit 6s linear infinite; }
187
- .p2 { animation: orbit 7s linear infinite; animation-delay: -1s; }
188
- .p3 { animation: orbit 5s linear infinite; animation-delay: -2s; }
189
- .p4 { animation: orbit 8s linear infinite; animation-delay: -3s; }
190
- #loading-text { font-size: 1.1em; font-weight: 500; color: var(--text-primary); text-shadow: 0 0 10px var(--text-secondary); }
191
-
192
- @keyframes pulse-core {
193
- 0%, 100% { transform: translate(-50%, -50%) scale(0.9); opacity: 0.8; }
194
- 50% { transform: translate(-50%, -50%) scale(1.1); opacity: 1; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  }
196
- @keyframes orbit {
197
- 0% { transform: translate(-50%, -50%) rotate(0deg) translateX(60px) rotate(0deg); }
198
- 100% { transform: translate(-50%, -50%) rotate(360deg) translateX(60px) rotate(-360deg); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  }
200
  </style>
201
  </head>
202
  <body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  <div class="container">
204
  <header class="app-header">
205
- <h1>Alpha TTS</h1>
206
- <p>کیفیت استثنایی صدا، با قدرت هوش مصنوعی</p>
207
  </header>
208
 
209
  <main class="main-content">
@@ -216,6 +711,7 @@
216
  <label for="prompt-input">🗣️ سبک و لحن گفتار (اختیاری)</label>
217
  <input type="text" id="prompt-input" value="با صدایی طبیعی و روان." placeholder="مثال: با لحنی شاد و پرانرژی">
218
  </div>
 
219
  <div class="form-group">
220
  <label>🎤 گوینده منتخب</label>
221
  <div id="selected-speaker-display">
@@ -229,6 +725,7 @@
229
  <button type="button" id="change-speaker-btn">تغییر گوینده</button>
230
  </div>
231
  </div>
 
232
  <div class="form-group">
233
  <label for="temperature-slider">🌡️ میزان خلاقیت صدا (0.1 تا 1.5)</label>
234
  <div class="slider-container">
@@ -236,22 +733,12 @@
236
  <span id="temperature-value">0.9</span>
237
  </div>
238
  </div>
 
239
  <button type="submit" id="generate-btn">🚀 تولید و پخش صدا</button>
240
  </form>
241
 
242
  <div id="output-section">
243
  <div id="status-message">خروجی صدا در اینجا نمایش داده می‌شود</div>
244
- <!-- انیمیشن لودینگ پریمیوم -->
245
- <div id="loading-animation">
246
- <div class="loader-container">
247
- <div class="ai-core"></div>
248
- <div class="particle p1"></div>
249
- <div class="particle p2"></div>
250
- <div class="particle p3"></div>
251
- <div class="particle p4"></div>
252
- </div>
253
- <p id="loading-text">در حال ساخت امواج صوتی با هوش مصنوعی آلفا...</p>
254
- </div>
255
  <audio id="audio-player" controls></audio>
256
  </div>
257
  </main>
@@ -266,195 +753,8 @@
266
  <div id="speaker-grid"></div>
267
  </div>
268
  </div>
269
-
270
  <input type="hidden" id="selected_speaker_id_storage" value="Charon">
271
 
272
  <script>
273
- document.addEventListener('DOMContentLoaded', () => {
274
- const HF_SPACE_URL = "https://hamed744-ttspro.hf.space";
275
- const JOIN_QUEUE_URL = `${HF_SPACE_URL}/gradio_api/queue/join`;
276
- const GET_DATA_URL_BASE = `${HF_SPACE_URL}/gradio_api/queue/data`;
277
- const FILE_URL_BASE = `${HF_SPACE_URL}/gradio_api/file=`;
278
- const FN_INDEX = 1;
279
-
280
- const speakers = [
281
- { 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: "لیدا (زن)" }
282
- ];
283
-
284
- // All DOM elements are selected here as before...
285
- const form = document.getElementById('tts-form');
286
- const textInput = document.getElementById('text-input');
287
- const promptInput = document.getElementById('prompt-input');
288
- const tempSlider = document.getElementById('temperature-slider');
289
- const tempValueSpan = document.getElementById('temperature-value');
290
- const generateBtn = document.getElementById('generate-btn');
291
- const statusMessage = document.getElementById('status-message');
292
- const audioPlayer = document.getElementById('audio-player');
293
- const loadingAnimation = document.getElementById('loading-animation');
294
- const selectedSpeakerIdStorage = document.getElementById('selected_speaker_id_storage');
295
- const speakerModal = document.getElementById('speaker-modal');
296
- const changeSpeakerBtn = document.getElementById('change-speaker-btn');
297
- const closeModalBtn = document.querySelector('.close-modal-btn');
298
- const speakerGridInModal = document.getElementById('speaker-grid');
299
- const selectedSpeakerImgDisplay = document.getElementById('selected-speaker-img');
300
- const selectedSpeakerNameDisplay = document.getElementById('selected-speaker-name');
301
-
302
- function getSpeakerById(id) {
303
- return speakers.find(s => s.id === id);
304
- }
305
-
306
- function getImageUrl(speaker, index, isThumb = false) {
307
- const gender = speaker.name.includes('(مرد)') ? 'men' : (speaker.name.includes('(زن)') ? 'women' : 'lego');
308
- const imageIndex = (index * 11 + 7) % 100; // Use a different seed for variety
309
- const size = isThumb ? 'thumb/' : '';
310
- return `https://randomuser.me/api/portraits/${size}${gender}/${imageIndex}.jpg`;
311
- }
312
-
313
- function updateSelectedSpeakerDisplay(speakerId) {
314
- const speaker = getSpeakerById(speakerId);
315
- if (speaker) {
316
- const speakerIndex = speakers.findIndex(s => s.id === speakerId);
317
- selectedSpeakerImgDisplay.src = getImageUrl(speaker, speakerIndex, false); // Get large image
318
- selectedSpeakerNameDisplay.textContent = speaker.name;
319
- selectedSpeakerIdStorage.value = speaker.id;
320
- }
321
- }
322
-
323
- function createSpeakerCardsInModal() {
324
- speakerGridInModal.innerHTML = '';
325
- speakers.forEach((speaker, index) => {
326
- const card = document.createElement('label');
327
- card.className = 'speaker-card';
328
- card.setAttribute('for', `modal-speaker-${speaker.id}`);
329
- const isChecked = speaker.id === selectedSpeakerIdStorage.value ? 'checked' : '';
330
-
331
- card.innerHTML = `
332
- <input type="radio" name="modal_speaker_selection" value="${speaker.id}" id="modal-speaker-${speaker.id}" ${isChecked}>
333
- <div class="speaker-visual">
334
- <img src="${getImageUrl(speaker, index, true)}" alt="عکس گوینده ${speaker.name}" loading="lazy">
335
- <div class="speaker-name">${speaker.name}</div>
336
- </div>
337
- `;
338
-
339
- card.addEventListener('click', () => {
340
- updateSelectedSpeakerDisplay(speaker.id);
341
- setTimeout(() => speakerModal.classList.remove('visible'), 200);
342
- });
343
-
344
- speakerGridInModal.appendChild(card);
345
- });
346
- }
347
-
348
- // Event Listeners (no change in logic)
349
- changeSpeakerBtn.addEventListener('click', () => { createSpeakerCardsInModal(); speakerModal.classList.add('visible'); });
350
- closeModalBtn.addEventListener('click', () => speakerModal.classList.remove('visible'));
351
- speakerModal.addEventListener('click', (e) => { if (e.target === speakerModal) speakerModal.classList.remove('visible'); });
352
- tempSlider.addEventListener('input', () => { tempValueSpan.textContent = tempSlider.value; });
353
-
354
- function showLoadingState() {
355
- statusMessage.style.display = 'none';
356
- audioPlayer.style.display = 'none';
357
- audioPlayer.src = '';
358
- loadingAnimation.style.display = 'flex';
359
- generateBtn.disabled = true;
360
- generateBtn.textContent = 'در حال پردازش...';
361
- }
362
-
363
- function showResultState(isSuccess, message = '') {
364
- loadingAnimation.style.display = 'none';
365
- if (isSuccess) {
366
- statusMessage.style.display = 'none';
367
- audioPlayer.style.display = 'block';
368
- audioPlayer.play();
369
- } else {
370
- statusMessage.textContent = message || 'یک خطای ناشناخته رخ داد.';
371
- statusMessage.style.display = 'block';
372
- audioPlayer.style.display = 'none';
373
- }
374
- generateBtn.disabled = false;
375
- generateBtn.textContent = '🚀 تولید و پخش صدا';
376
- }
377
-
378
- // The main async function `generateAudio` remains identical in its logic.
379
- // It just calls the new state functions.
380
- async function generateAudio(event) {
381
- event.preventDefault();
382
- showLoadingState();
383
-
384
- const text = textInput.value;
385
- if (!text.trim()) {
386
- showResultState(false, 'خطا: متن ورودی نمی‌تواند خالی باشد.');
387
- return;
388
- }
389
-
390
- const prompt = promptInput.value;
391
- const temperature = parseFloat(tempSlider.value);
392
- const selectedSpeaker = selectedSpeakerIdStorage.value;
393
- const sessionHash = Math.random().toString(36).substring(2);
394
-
395
- const payload = {
396
- fn_index: FN_INDEX,
397
- data: [false, null, text, prompt, selectedSpeaker, temperature],
398
- event_data: null,
399
- session_hash: sessionHash
400
- };
401
-
402
- try {
403
- const joinQueueResponse = await fetch(JOIN_QUEUE_URL, {
404
- method: "POST",
405
- headers: { "Content-Type": "application/json" },
406
- body: JSON.stringify(payload)
407
- });
408
-
409
- if (!joinQueueResponse.ok) {
410
- const errorBody = await joinQueueResponse.text();
411
- throw new Error(`خطا در اتصال به صف (${joinQueueResponse.status})`);
412
- }
413
-
414
- const dataResponse = await fetch(`${GET_DATA_URL_BASE}?session_hash=${sessionHash}`);
415
- const reader = dataResponse.body.getReader();
416
- const decoder = new TextDecoder();
417
- let finalFilePath = null;
418
- let buffer = '';
419
-
420
- while (true) {
421
- const { value, done } = await reader.read();
422
- if (done) break;
423
- buffer += decoder.decode(value, { stream: true });
424
- const lines = buffer.split('\n');
425
- buffer = lines.pop();
426
- for (const line of lines) {
427
- if (!line.startsWith('data:')) continue;
428
- try {
429
- const data = JSON.parse(line.substring(5));
430
- if (data.msg === 'process_completed') {
431
- if (data.success && data.output.data && data.output.data[0] && (data.output.data[0].name || data.output.data[0].path)) {
432
- finalFilePath = data.output.data[0].name || data.output.data[0].path;
433
- } else { console.error("ساختار پیام موفقیت مورد انتظار نبود:", data); }
434
- break;
435
- }
436
- } catch (e) { /* نادیده گرفتن خطاهای پارس */ }
437
- }
438
- if (finalFilePath) break;
439
- }
440
-
441
- if (finalFilePath) {
442
- const audioUrl = `${FILE_URL_BASE}${finalFilePath}`;
443
- audioPlayer.src = audioUrl;
444
- showResultState(true);
445
- } else {
446
- throw new Error('فایل صوتی از سرور دریافت نشد.');
447
- }
448
-
449
- } catch (error) {
450
- console.error('یک خطا در فرآیند رخ داد:', error);
451
- showResultState(false, `یک خطا رخ داد: ${error.message}`);
452
- }
453
- }
454
-
455
- updateSelectedSpeakerDisplay(selectedSpeakerIdStorage.value);
456
- form.addEventListener('submit', generateAudio);
457
- });
458
- </script>
459
- </body>
460
- </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Alpha TTS - رابط کاربری هوشمند با عکس</title>
7
  <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap');
9
 
10
  :root {
11
  --app-font: 'Vazirmatn', sans-serif;
12
+ --app-header-grad-start: #667eea;
13
+ --app-header-grad-end: #764ba2;
14
+ --app-panel-bg: rgba(255, 255, 255, 0.95);
15
+ --app-input-bg: rgba(248, 249, 250, 0.8);
16
+ --app-button-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ --app-button-hover-bg: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
18
+ --app-main-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
19
+ --app-text-primary: #2c3e50;
20
+ --app-text-secondary: #555;
21
+ --app-border-color: rgba(224, 224, 224, 0.5);
22
  --radius-card: 24px;
23
+ --radius-input: 16px;
24
+ --shadow-card: 0 20px 40px -10px rgba(0,0,0,0.1);
25
+ --shadow-button: 0 8px 25px -5px rgba(102, 126, 234, 0.4);
26
+ --speaker-selected-glow: 0 0 25px rgba(102, 126, 234, 0.6);
27
+ --accent-color: #ff6b6b;
28
+ --success-color: #4ecdc4;
29
  }
30
 
31
+ * {
32
+ margin: 0;
33
+ padding: 0;
34
+ box-sizing: border-box;
35
  }
36
 
37
  body {
38
  font-family: var(--app-font);
39
  direction: rtl;
40
+ background: var(--app-main-bg);
41
+ color: var(--app-text-primary);
 
 
 
42
  font-size: 16px;
43
+ line-height: 1.65;
 
 
44
  min-height: 100vh;
45
  -webkit-font-smoothing: antialiased;
46
  -moz-osx-font-smoothing: grayscale;
47
+ position: relative;
48
  overflow-x: hidden;
49
  }
50
 
51
+ /* Background Animation */
52
+ body::before {
53
+ content: '';
54
+ position: fixed;
55
+ top: 0;
56
+ left: 0;
57
+ width: 100%;
58
+ height: 100%;
59
+ background:
60
+ radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
61
+ radial-gradient(circle at 80% 20%, rgba(255, 107, 107, 0.3) 0%, transparent 50%),
62
+ radial-gradient(circle at 40% 40%, rgba(78, 205, 196, 0.2) 0%, transparent 50%);
63
+ animation: backgroundMove 20s ease-in-out infinite;
64
+ z-index: -1;
65
+ }
66
+
67
+ @keyframes backgroundMove {
68
+ 0%, 100% { transform: translateX(0) translateY(0) rotate(0deg); }
69
+ 33% { transform: translateX(-30px) translateY(-50px) rotate(0.5deg); }
70
+ 66% { transform: translateX(20px) translateY(20px) rotate(-0.5deg); }
71
+ }
72
+
73
+ .container {
74
+ max-width: 900px;
75
+ width: 95%;
76
+ margin: 0 auto;
77
+ padding-bottom: 40px;
78
+ }
79
+
80
+ .app-header {
81
+ padding: 4rem 2rem 6rem 2rem;
82
+ text-align: center;
83
+ background: linear-gradient(135deg, var(--app-header-grad-start) 0%, var(--app-header-grad-end) 100%);
84
+ color: white;
85
+ border-bottom-left-radius: var(--radius-card);
86
+ border-bottom-right-radius: var(--radius-card);
87
+ box-shadow: 0 10px 30px -5px rgba(0,0,0,0.3);
88
+ position: relative;
89
+ overflow: hidden;
90
+ }
91
+
92
+ .app-header::before {
93
+ content: '';
94
+ position: absolute;
95
+ top: -50%;
96
+ left: -50%;
97
+ width: 200%;
98
+ height: 200%;
99
+ background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
100
+ animation: headerShine 8s ease-in-out infinite;
101
+ }
102
+
103
+ @keyframes headerShine {
104
+ 0%, 100% { transform: rotate(0deg); opacity: 0.3; }
105
+ 50% { transform: rotate(180deg); opacity: 0.1; }
106
+ }
107
+
108
+ .app-header h1 {
109
+ font-size: 3.2em;
110
+ font-weight: 800;
111
+ margin: 0 0 1rem 0;
112
+ text-shadow: 0 4px 8px rgba(0,0,0,0.2);
113
+ position: relative;
114
+ z-index: 1;
115
+ background: linear-gradient(45deg, #fff, #f0f8ff);
116
  -webkit-background-clip: text;
117
  -webkit-text-fill-color: transparent;
118
+ background-clip: text;
119
  }
 
120
 
121
+ .app-header p {
122
+ font-size: 1.3em;
123
+ color: rgba(255,255,255,0.9);
124
+ margin-top: 0;
125
+ opacity: 0.95;
126
+ position: relative;
127
+ z-index: 1;
 
128
  }
129
 
130
+ .main-content {
131
+ padding: 3rem;
132
+ margin: -4rem auto 2rem auto;
133
+ background: var(--app-panel-bg);
134
+ backdrop-filter: blur(20px);
135
+ border-radius: var(--radius-card);
136
+ box-shadow: var(--shadow-card);
137
+ border: 1px solid rgba(255, 255, 255, 0.2);
138
+ }
139
+
140
+ .form-group {
141
+ margin-bottom: 2.8rem;
142
+ position: relative;
143
+ }
144
+
145
+ label {
146
+ display: block;
147
+ font-weight: 700;
148
+ color: var(--app-text-primary);
149
+ font-size: 1.2em;
150
+ margin-bottom: 1rem;
151
+ position: relative;
152
+ }
153
+
154
+ label::after {
155
+ content: '';
156
+ position: absolute;
157
+ bottom: -5px;
158
+ right: 0;
159
+ width: 30px;
160
+ height: 3px;
161
+ background: linear-gradient(90deg, var(--accent-color), transparent);
162
+ border-radius: 2px;
163
+ }
164
+
165
+ textarea, input[type="text"] {
166
+ width: 100%;
167
+ padding: 1.2rem 1.5rem;
168
+ border-radius: var(--radius-input);
169
+ border: 2px solid var(--app-border-color);
170
+ background: var(--app-input-bg);
171
+ backdrop-filter: blur(10px);
172
+ box-shadow: 0 4px 15px rgba(0,0,0,0.05);
173
+ font-family: var(--app-font);
174
+ font-size: 1rem;
175
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
176
+ position: relative;
177
+ }
178
+
179
+ textarea:focus, input[type="text"]:focus {
180
+ outline: none;
181
+ border-color: var(--accent-color);
182
+ box-shadow: 0 0 0 4px rgba(255, 107, 107, 0.2), 0 8px 25px rgba(0,0,0,0.1);
183
+ background: rgba(255, 255, 255, 0.95);
184
+ transform: translateY(-2px);
185
+ }
186
+
187
+ /* Enhanced Speaker Selection */
188
+ #selected-speaker-display {
189
+ text-align: center;
190
+ }
191
+
192
+ #selected-speaker-card {
193
+ display: inline-flex;
194
+ align-items: center;
195
+ background: linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(248,249,250,0.9) 100%);
196
+ backdrop-filter: blur(15px);
197
+ border-radius: 20px;
198
+ padding: 15px 20px;
199
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
200
+ border: 2px solid rgba(255, 255, 255, 0.3);
201
  transition: all 0.3s ease;
202
  }
203
+
204
+ #selected-speaker-card:hover {
205
+ transform: translateY(-3px);
206
+ box-shadow: 0 15px 40px rgba(0,0,0,0.15);
207
+ }
208
+
209
+ #selected-speaker-card img {
210
+ width: 90px;
211
+ height: 90px;
212
+ border-radius: 50%;
213
+ object-fit: cover;
214
+ margin-left: 20px;
215
+ background-color: #eee;
216
+ border: 3px solid rgba(255, 255, 255, 0.8);
217
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
218
+ }
219
+
220
+ #selected-speaker-info h3 {
221
+ margin: 0;
222
+ font-size: 1.5em;
223
+ color: var(--app-text-primary);
224
+ }
225
+
226
+ #selected-speaker-info p {
227
+ margin: 8px 0 0;
228
+ color: var(--app-text-secondary);
229
+ font-size: 0.95em;
230
+ }
231
+
232
+ #change-speaker-btn {
233
+ display: block;
234
+ margin: 1.5rem auto 0;
235
+ padding: 12px 30px;
236
+ border-radius: 12px;
237
+ background: linear-gradient(135deg, var(--app-input-bg) 0%, rgba(248,249,250,0.9) 100%);
238
+ border: 2px solid var(--app-border-color);
239
+ cursor: pointer;
240
+ font-family: var(--app-font);
241
+ font-weight: 600;
242
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
243
+ backdrop-filter: blur(10px);
244
+ }
245
+
246
+ #change-speaker-btn:hover {
247
+ background: linear-gradient(135deg, #e9ecef 0%, #f8f9fa 100%);
248
+ border-color: var(--accent-color);
249
+ transform: translateY(-2px);
250
+ box-shadow: 0 6px 20px rgba(0,0,0,0.1);
251
+ }
252
+
253
+ /* Enhanced Modal */
254
+ #speaker-modal {
255
+ position: fixed;
256
+ top: 0;
257
+ left: 0;
258
+ width: 100%;
259
+ height: 100%;
260
+ background: rgba(0,0,0,0.6);
261
+ backdrop-filter: blur(10px);
262
+ display: none;
263
+ align-items: center;
264
+ justify-content: center;
265
+ z-index: 1000;
266
+ opacity: 0;
267
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
268
+ }
269
+
270
+ #speaker-modal.visible {
271
+ display: flex;
272
+ opacity: 1;
273
+ }
274
+
275
+ .modal-content {
276
+ background: linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(248,249,250,0.95) 100%);
277
+ backdrop-filter: blur(20px);
278
+ padding: 2.5rem;
279
+ border-radius: var(--radius-card);
280
+ width: 90%;
281
+ max-width: 800px;
282
+ max-height: 85vh;
283
+ overflow-y: auto;
284
+ transform: scale(0.9) translateY(50px);
285
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
286
+ border: 1px solid rgba(255, 255, 255, 0.3);
287
+ box-shadow: 0 25px 50px rgba(0,0,0,0.2);
288
+ }
289
+
290
+ #speaker-modal.visible .modal-content {
291
+ transform: scale(1) translateY(0);
292
+ }
293
+
294
+ .modal-header {
295
+ display: flex;
296
+ justify-content: space-between;
297
+ align-items: center;
298
+ margin-bottom: 2rem;
299
+ }
300
+
301
+ .modal-header h2 {
302
+ margin: 0;
303
+ font-size: 1.8em;
304
+ color: var(--app-text-primary);
305
+ }
306
+
307
+ .close-modal-btn {
308
+ background: none;
309
+ border: none;
310
+ font-size: 2.5rem;
311
+ cursor: pointer;
312
+ color: #999;
313
+ transition: all 0.3s ease;
314
+ width: 40px;
315
+ height: 40px;
316
+ display: flex;
317
+ align-items: center;
318
+ justify-content: center;
319
+ border-radius: 50%;
320
+ }
321
+
322
+ .close-modal-btn:hover {
323
+ color: var(--accent-color);
324
+ background: rgba(255, 107, 107, 0.1);
325
+ transform: rotate(90deg);
326
+ }
327
 
328
+ #speaker-grid {
329
+ display: grid;
330
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
331
+ gap: 1.5rem;
332
+ }
333
+
334
+ @media (min-width: 576px) {
335
+ #speaker-grid {
336
+ grid-template-columns: repeat(4, 1fr);
337
+ }
338
+ }
339
+
340
+ .speaker-card {
341
+ cursor: pointer;
342
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
343
+ }
344
+
345
+ .speaker-card .speaker-visual {
346
+ border: 3px solid transparent;
347
+ border-radius: var(--radius-card);
348
+ overflow: hidden;
349
+ text-align: center;
350
+ box-shadow: 0 6px 20px rgba(0,0,0,0.08);
351
+ position: relative;
352
+ background: linear-gradient(135deg, #fff 0%, #f8f9fa 100%);
353
+ backdrop-filter: blur(10px);
354
+ }
355
+
356
+ .speaker-card:hover .speaker-visual {
357
+ transform: translateY(-5px) scale(1.02);
358
+ box-shadow: 0 12px 30px rgba(0,0,0,0.15);
359
+ }
360
+
361
+ .speaker-card input[type="radio"] {
362
+ display: none;
363
+ }
364
+
365
+ .speaker-card img {
366
+ width: 100%;
367
+ height: 140px;
368
+ object-fit: cover;
369
+ display: block;
370
+ background-color: #eee;
371
+ }
372
+
373
+ .speaker-card .speaker-name {
374
+ padding: 1rem 0.8rem;
375
+ font-weight: 600;
376
+ font-size: 0.95em;
377
+ color: var(--app-text-primary);
378
+ }
379
+
380
+ .speaker-card input[type="radio"]:checked + .speaker-visual {
381
+ border-color: var(--accent-color);
382
+ box-shadow: var(--speaker-selected-glow), 0 12px 30px rgba(0,0,0,0.15);
383
+ transform: scale(1.05);
384
+ }
385
+
386
+ /* Enhanced Slider */
387
+ .slider-container {
388
+ display: flex;
389
+ align-items: center;
390
+ gap: 1.5rem;
391
+ background: var(--app-input-bg);
392
+ backdrop-filter: blur(10px);
393
+ padding: 1rem 1.5rem;
394
  border-radius: var(--radius-input);
395
+ border: 2px solid var(--app-border-color);
396
+ }
397
+
398
+ input[type="range"] {
399
+ flex-grow: 1;
400
  cursor: pointer;
401
+ height: 8px;
402
+ background: linear-gradient(90deg, var(--accent-color), var(--success-color));
403
+ border-radius: 5px;
404
+ outline: none;
405
+ -webkit-appearance: none;
406
  }
407
+
408
+ input[type="range"]::-webkit-slider-thumb {
409
+ -webkit-appearance: none;
410
+ width: 20px;
411
+ height: 20px;
412
+ border-radius: 50%;
413
+ background: linear-gradient(135deg, var(--accent-color), #ff8a8a);
414
+ cursor: pointer;
415
+ box-shadow: 0 2px 10px rgba(255, 107, 107, 0.4);
416
  }
417
+
418
+ #temperature-value {
419
+ font-weight: bold;
420
+ background: linear-gradient(135deg, rgba(255,255,255,0.9), rgba(248,249,250,0.9));
421
+ padding: 0.5rem 1rem;
422
+ border-radius: 10px;
423
+ border: 2px solid rgba(255, 107, 107, 0.3);
424
+ min-width: 50px;
425
+ text-align: center;
426
+ color: var(--accent-color);
427
+ font-size: 1.1em;
428
+ }
429
+
430
+ /* Enhanced Generate Button */
431
+ #generate-btn {
432
+ width: 100%;
433
+ padding: 1.5rem 2rem;
434
+ font-size: 1.3em;
435
+ font-weight: 700;
436
+ font-family: var(--app-font);
437
+ background: var(--app-button-bg);
438
+ color: white;
439
+ border: none;
440
+ border-radius: var(--radius-input);
441
+ cursor: pointer;
442
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
443
+ box-shadow: var(--shadow-button);
444
+ position: relative;
445
+ overflow: hidden;
446
+ }
447
+
448
+ #generate-btn::before {
449
+ content: '';
450
+ position: absolute;
451
+ top: 0;
452
+ left: -100%;
453
+ width: 100%;
454
+ height: 100%;
455
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
456
+ transition: left 0.6s;
457
  }
458
+
459
+ #generate-btn:hover::before {
460
+ left: 100%;
461
+ }
462
+
463
+ #generate-btn:hover:not(:disabled) {
464
+ background: var(--app-button-hover-bg);
465
+ transform: translateY(-3px);
466
+ box-shadow: 0 12px 35px -5px rgba(102, 126, 234, 0.6);
467
+ }
468
+
469
+ #generate-btn:disabled {
470
+ background: linear-gradient(135deg, #999, #777);
471
+ cursor: not-allowed;
472
+ box-shadow: none;
473
+ transform: none;
474
+ }
475
+
476
+ #output-section {
477
+ margin-top: 3rem;
478
+ padding: 2rem;
479
+ background: linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(248,249,250,0.9) 100%);
480
+ backdrop-filter: blur(15px);
481
+ border-radius: var(--radius-card);
482
+ min-height: 120px;
483
+ display: flex;
484
+ align-items: center;
485
+ justify-content: center;
486
+ flex-direction: column;
487
+ gap: 1.5rem;
488
+ border: 2px solid rgba(255, 255, 255, 0.3);
489
+ box-shadow: 0 10px 30px rgba(0,0,0,0.08);
490
+ }
491
+
492
+ #status-message {
493
+ font-weight: 600;
494
+ color: var(--app-text-secondary);
495
+ font-size: 1.1em;
496
+ }
497
+
498
+ #audio-player {
499
+ width: 100%;
500
+ margin-top: 1rem;
501
+ display: none;
502
+ border-radius: 12px;
503
+ }
504
+
505
+ /* Loading Animation */
506
+ .loading-overlay {
507
+ position: fixed;
508
+ top: 0;
509
+ left: 0;
510
+ width: 100%;
511
+ height: 100%;
512
+ background: linear-gradient(135deg, rgba(102, 126, 234, 0.95), rgba(118, 75, 162, 0.95));
513
+ display: none;
514
+ align-items: center;
515
+ justify-content: center;
516
+ z-index: 2000;
517
+ backdrop-filter: blur(20px);
518
+ }
519
+
520
+ .loading-overlay.active {
521
+ display: flex;
522
+ }
523
+
524
+ .loading-content {
525
+ text-align: center;
526
+ color: white;
527
+ max-width: 600px;
528
+ padding: 3rem;
529
+ }
530
+
531
+ .loading-animation {
532
+ position: relative;
533
+ width: 200px;
534
+ height: 200px;
535
+ margin: 0 auto 3rem;
536
+ }
537
+
538
+ .sound-wave {
539
+ position: absolute;
540
+ left: 50%;
541
+ top: 50%;
542
  transform: translate(-50%, -50%);
543
+ width: 8px;
544
+ background: linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4);
545
+ border-radius: 4px;
546
+ animation: soundWave 1.5s ease-in-out infinite;
547
+ }
548
+
549
+ .sound-wave:nth-child(1) { left: 45%; animation-delay: 0s; height: 20px; }
550
+ .sound-wave:nth-child(2) { left: 48%; animation-delay: 0.1s; height: 40px; }
551
+ .sound-wave:nth-child(3) { left: 51%; animation-delay: 0.2s; height: 60px; }
552
+ .sound-wave:nth-child(4) { left: 54%; animation-delay: 0.3s; height: 40px; }
553
+ .sound-wave:nth-child(5) { left: 57%; animation-delay: 0.4s; height: 20px; }
554
+
555
+ @keyframes soundWave {
556
+ 0%, 100% {
557
+ transform: translate(-50%, -50%) scaleY(0.5);
558
+ opacity: 0.7;
559
+ }
560
+ 50% {
561
+ transform: translate(-50%, -50%) scaleY(1.5);
562
+ opacity: 1;
563
+ }
564
+ }
565
+
566
+ .ai-circle {
567
+ position: absolute;
568
+ top: 50%;
569
+ left: 50%;
570
+ transform: translate(-50%, -50%);
571
+ width: 120px;
572
+ height: 120px;
573
+ border: 3px solid rgba(255, 255, 255, 0.2);
574
+ border-radius: 50%;
575
+ animation: aiRotate 3s linear infinite;
576
+ }
577
+
578
+ .ai-circle::before {
579
+ content: '🤖';
580
+ position: absolute;
581
+ top: 50%;
582
+ left: 50%;
583
+ transform: translate(-50%, -50%);
584
+ font-size: 3rem;
585
+ animation: aiPulse 2s ease-in-out infinite;
586
+ }
587
+
588
+ @keyframes aiRotate {
589
+ 0% { transform: translate(-50%, -50%) rotate(0deg); }
590
+ 100% { transform: translate(-50%, -50%) rotate(360deg); }
591
+ }
592
+
593
+ @keyframes aiPulse {
594
+ 0%, 100% { transform: translate(-50%, -50%) scale(1); }
595
+ 50% { transform: translate(-50%, -50%) scale(1.1); }
596
+ }
597
+
598
+ .loading-text h2 {
599
+ font-size: 2.5em;
600
+ margin-bottom: 1rem;
601
+ background: linear-gradient(45deg, #fff, #f0f8ff, #e6f3ff);
602
+ -webkit-background-clip: text;
603
+ -webkit-text-fill-color: transparent;
604
+ background-clip: text;
605
+ animation: textGlow 2s ease-in-out infinite alternate;
606
+ }
607
+
608
+ .loading-text p {
609
+ font-size: 1.3em;
610
+ opacity: 0.9;
611
+ animation: textFade 3s ease-in-out infinite;
612
+ }
613
+
614
+ @keyframes textGlow {
615
+ 0% { opacity: 0.8; }
616
+ 100% { opacity: 1; }
617
+ }
618
+
619
+ @keyframes textFade {
620
+ 0%, 100% { opacity: 0.7; }
621
+ 50% { opacity: 1; }
622
  }
623
+
624
+ .progress-bar {
625
+ width: 100%;
626
+ height: 6px;
627
+ background: rgba(255, 255, 255, 0.2);
628
+ border-radius: 3px;
629
+ margin-top: 2rem;
630
+ overflow: hidden;
631
+ }
632
+
633
+ .progress-fill {
634
+ height: 100%;
635
+ background: linear-gradient(90deg, #ff6b6b, #4ecdc4, #45b7d1);
636
+ border-radius: 3px;
637
+ animation: progressMove 2s ease-in-out infinite;
638
+ }
639
+
640
+ @keyframes progressMove {
641
+ 0% { width: 10%; }
642
+ 50% { width: 80%; }
643
+ 100% { width: 100%; }
644
+ }
645
+
646
+ /* Responsive Design */
647
+ @media (max-width: 768px) {
648
+ .app-header {
649
+ padding: 3rem 1.5rem 4rem;
650
+ }
651
+
652
+ .app-header h1 {
653
+ font-size: 2.5em;
654
+ }
655
+
656
+ .main-content {
657
+ padding: 2rem;
658
+ margin: -3rem auto 1rem;
659
+ }
660
+
661
+ .loading-animation {
662
+ width: 150px;
663
+ height: 150px;
664
+ }
665
+
666
+ .loading-text h2 {
667
+ font-size: 2em;
668
+ }
669
+
670
+ .loading-text p {
671
+ font-size: 1.1em;
672
+ }
673
  }
674
  </style>
675
  </head>
676
  <body>
677
+ <!-- Loading Overlay -->
678
+ <div id="loading-overlay" class="loading-overlay">
679
+ <div class="loading-content">
680
+ <div class="loading-animation">
681
+ <div class="ai-circle"></div>
682
+ <div class="sound-wave"></div>
683
+ <div class="sound-wave"></div>
684
+ <div class="sound-wave"></div>
685
+ <div class="sound-wave"></div>
686
+ <div class="sound-wave"></div>
687
+ </div>
688
+ <div class="loading-text">
689
+ <h2>در حال تبدیل متن به صدا</h2>
690
+ <p>با هوش مصنوعی آلفا</p>
691
+ <div class="progress-bar">
692
+ <div class="progress-fill"></div>
693
+ </div>
694
+ </div>
695
+ </div>
696
+ </div>
697
+
698
  <div class="container">
699
  <header class="app-header">
700
+ <h1>آلفا TTS</h1>
701
+ <p>جادوی تبدیل متن به صدا، به سبک شما</p>
702
  </header>
703
 
704
  <main class="main-content">
 
711
  <label for="prompt-input">🗣️ سبک و لحن گفتار (اختیاری)</label>
712
  <input type="text" id="prompt-input" value="با صدایی طبیعی و روان." placeholder="مثال: با لحنی شاد و پرانرژی">
713
  </div>
714
+
715
  <div class="form-group">
716
  <label>🎤 گوینده منتخب</label>
717
  <div id="selected-speaker-display">
 
725
  <button type="button" id="change-speaker-btn">تغییر گوینده</button>
726
  </div>
727
  </div>
728
+
729
  <div class="form-group">
730
  <label for="temperature-slider">🌡️ میزان خلاقیت صدا (0.1 تا 1.5)</label>
731
  <div class="slider-container">
 
733
  <span id="temperature-value">0.9</span>
734
  </div>
735
  </div>
736
+
737
  <button type="submit" id="generate-btn">🚀 تولید و پخش صدا</button>
738
  </form>
739
 
740
  <div id="output-section">
741
  <div id="status-message">خروجی صدا در اینجا نمایش داده می‌شود</div>
 
 
 
 
 
 
 
 
 
 
 
742
  <audio id="audio-player" controls></audio>
743
  </div>
744
  </main>
 
753
  <div id="speaker-grid"></div>
754
  </div>
755
  </div>
756
+
757
  <input type="hidden" id="selected_speaker_id_storage" value="Charon">
758
 
759
  <script>
760
+ document.addEventListener('DOMContentLoaded', () => {