Hamed744 commited on
Commit
5cdc6dd
·
verified ·
1 Parent(s): 1b7416e

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +120 -168
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>آلفا TTS - رابط کاربری مدرن</title>
7
  <style>
8
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap');
9
 
@@ -12,7 +12,7 @@
12
  --app-header-grad-start: #1a2980;
13
  --app-header-grad-end: #26d0ce;
14
  --app-panel-bg: #FFFFFF;
15
- --app-input-bg: #F7F7F7;
16
  --app-button-bg: #5f27cd;
17
  --app-button-hover-bg: #481e9e;
18
  --app-main-bg: linear-gradient(170deg, #F3E8FF 0%, #E0F2FE 100%);
@@ -36,148 +36,53 @@
36
  margin: 0;
37
  padding: 0;
38
  min-height: 100vh;
39
- display: flex;
40
- flex-direction: column;
41
  -webkit-font-smoothing: antialiased;
42
  -moz-osx-font-smoothing: grayscale;
43
  }
44
 
45
- .container {
46
- max-width: 950px;
47
- width: 95%;
48
- margin: 0 auto;
49
- padding-bottom: 40px;
50
- }
51
-
52
- .app-header {
53
- padding: 3rem 1.5rem 5rem 1.5rem;
54
- text-align: center;
55
- background-image: linear-gradient(135deg, var(--app-header-grad-start) 0%, var(--app-header-grad-end) 100%);
56
- color: white;
57
- border-bottom-left-radius: var(--radius-card);
58
- border-bottom-right-radius: var(--radius-card);
59
- box-shadow: 0 6px 20px -5px rgba(0,0,0,0.2);
60
- }
61
- .app-header h1 {
62
- font-size: 2.8em;
63
- font-weight: 800;
64
- margin:0 0 0.5rem 0;
65
- text-shadow: 0 2px 4px rgba(0,0,0,0.15);
66
- }
67
- .app-header p {
68
- font-size: 1.2em;
69
- color: rgba(255,255,255,0.9);
70
- margin-top:0;
71
- opacity: 0.9;
72
- }
73
-
74
- .main-content {
75
- padding: 2.5rem;
76
- margin: -3.5rem auto 2rem auto;
77
- background-color: var(--app-panel-bg);
78
- border-radius: var(--radius-card);
79
- box-shadow: var(--shadow-card);
80
- }
81
 
82
- .form-group {
83
- margin-bottom: 2.5rem;
84
- }
 
 
 
 
 
85
 
86
- label {
87
- display: block;
88
- font-weight: 700;
89
- color: var(--app-text-primary);
90
- font-size: 1.1em;
91
- margin-bottom: 0.8rem;
92
- }
 
 
93
 
94
- textarea, input[type="text"] {
95
- width: 100%;
96
- padding: 1rem;
97
- border-radius: var(--radius-input);
98
- border: 2px solid var(--app-border-color);
99
- background-color: var(--app-input-bg);
100
- box-shadow: none;
101
- font-family: var(--app-font);
102
- font-size: 1rem;
103
- box-sizing: border-box;
104
- transition: all 0.2s ease-in-out;
105
- }
106
-
107
- textarea:focus, input[type="text"]:focus {
108
- outline: none;
109
- border-color: var(--app-button-bg);
110
- box-shadow: 0 0 0 3px rgba(95, 39, 205, 0.2);
111
- background-color: #fff;
112
- }
113
-
114
- /* --- Speaker Selection Grid (طراحی جدید) --- */
115
- .speaker-gallery {
116
- background-color: var(--app-input-bg);
117
- padding: 1.5rem;
118
- border-radius: var(--radius-card);
119
- border: 1px solid var(--app-border-color);
120
- }
121
- #speaker-grid {
122
- display: grid;
123
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
124
- gap: 1.5rem;
125
- }
126
- @media (min-width: 768px) {
127
- #speaker-grid {
128
- grid-template-columns: repeat(4, 1fr);
129
- }
130
- }
131
- .speaker-card {
132
- cursor: pointer;
133
- transition: all 0.3s ease;
134
- }
135
- .speaker-card .speaker-visual {
136
- border: 3px solid transparent;
137
- border-radius: var(--radius-card);
138
- overflow: hidden;
139
- text-align: center;
140
- box-shadow: 0 4px 15px rgba(0,0,0,0.08);
141
- position: relative;
142
- background-color: #fff;
143
- }
144
- .speaker-card:hover .speaker-visual {
145
- transform: translateY(-5px);
146
- box-shadow: 0 8px 25px rgba(0,0,0,0.12);
147
- }
148
- .speaker-card input[type="radio"] {
149
- display: none;
150
- }
151
- .speaker-card img {
152
- width: 100%;
153
- height: 150px;
154
- object-fit: cover;
155
- display: block;
156
- }
157
- .speaker-card .speaker-name {
158
- padding: 0.8rem 0.5rem;
159
- font-weight: 500;
160
- background-color: rgba(255, 255, 255, 0.8);
161
- backdrop-filter: blur(5px);
162
- transition: background-color 0.3s ease;
163
- }
164
- .speaker-card input[type="radio"]:checked + .speaker-visual {
165
- border-color: var(--app-button-bg);
166
- box-shadow: var(--speaker-selected-glow);
167
- transform: translateY(-2px) scale(1.02);
168
- }
169
- .speaker-card input[type="radio"]:checked + .speaker-visual .speaker-name {
170
- background-color: var(--app-button-bg);
171
- color: white;
172
- font-weight: 700;
173
- }
174
 
175
- /* --- Slider --- */
176
  .slider-container { display: flex; align-items: center; gap: 1rem; }
177
  input[type="range"] { flex-grow: 1; cursor: pointer; }
178
  #temperature-value { font-weight: bold; background-color: var(--app-input-bg); padding: 0.2rem 0.8rem; border-radius: 8px; border: 1px solid var(--app-border-color); min-width: 40px; text-align: center; }
179
-
180
- /* --- Button & Output --- */
181
  #generate-btn { width: 100%; padding: 1rem 1.5rem; font-size: 1.2em; font-weight: 700; font-family: var(--app-font); background: var(--app-button-bg); color: white; border: none; border-radius: var(--radius-input); cursor: pointer; transition: all 0.3s ease; box-shadow: var(--shadow-button); }
182
  #generate-btn:hover:not(:disabled) { background-color: var(--app-button-hover-bg); transform: translateY(-2px); box-shadow: 0 6px 20px -3px rgba(95, 39, 205, 0.5); }
183
  #generate-btn:disabled { background-color: #999; cursor: not-allowed; box-shadow: none; }
@@ -205,12 +110,19 @@
205
  </div>
206
 
207
  <div class="form-group">
208
- <label>🎤 گوینده را انتخاب کنید</label>
209
- <div class="speaker-gallery">
210
- <div id="speaker-grid"></div>
 
 
 
 
 
 
 
211
  </div>
212
  </div>
213
-
214
  <div class="form-group">
215
  <label for="temperature-slider">🌡️ میزان خلاقیت صدا (0.1 تا 1.5)</label>
216
  <div class="slider-container">
@@ -229,68 +141,106 @@
229
  </main>
230
  </div>
231
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  <script>
233
  document.addEventListener('DOMContentLoaded', () => {
 
234
  const HF_SPACE_URL = "https://hamed744-ttspro.hf.space";
235
  const JOIN_QUEUE_URL = `${HF_SPACE_URL}/gradio_api/queue/join`;
236
  const GET_DATA_URL_BASE = `${HF_SPACE_URL}/gradio_api/queue/data`;
237
  const FILE_URL_BASE = `${HF_SPACE_URL}/gradio_api/file=`;
238
-
239
- const FN_INDEX = 1;
240
 
241
- // --- **لیست جدید گویندگان با اسامی فارسی** ---
242
  const speakers = [
243
- { id: "Charon", name: "شهاب (مرد)" }, { id: "Zephyr", name: "آوا (زن)" },
244
- { id: "Achird", name: "نوید (مرد)" }, { id: "Zubenelgenubi", name: "رویا (زن)" },
245
- { id: "Vindemiatrix", name: "کیان (مرد)" }, { id: "Sadachbia", name: "پریسا (زن)" },
246
- { id: "Sadaltager", name: "آرش (مرد)" }, { id: "Sulafat", name: "شبنم (زن)" },
247
- { id: "Laomedeia", name: "سهیل (مرد)" }, { id: "Achernar", name: "مریم (زن)" },
248
- { id: "Alnilam", name: "بهرام (مرد)" }, { id: "Schedar", name: "نگار (زن)" },
249
- { id: "Gacrux", name: "فرید (مرد)" }, { id: "Pulcherrima", name: "سارا (زن)" },
250
- { id: "Umbriel", name: "مانی (مرد)" }, { id: "Algieba", name: "آناهیتا (زن)" },
251
- { id: "Despina", name: "دلنواز (زن)" }, { id: "Erinome", name: "رسا (مرد)" },
252
- { id: "Algenib", name: "امید (مرد)" }, { id: "Rasalthgeti", name: "الهه (زن)" },
253
- { id: "Orus", name: "بردیا (مرد)" }, { id: "Aoede", name: "ترانه (زن)" },
254
- { id: "Callirrhoe", name: "نیما (مرد)" }, { id: "Autonoe", name: "هستی (زن)" },
255
- { id: "Enceladus", name: "کامیار (مرد)" }, { id: "Iapetus", name: "ستاره (زن)" },
256
- { id: "Puck", name: "پویا (مرد)" }, { id: "Kore", name: "مهتاب (زن)" },
257
- { id: "Fenrir", name: "سام (مرد)" }, { id: "Leda", name: "لیدا (زن)" }
258
  ];
259
-
260
  // --- عناصر DOM ---
261
  const form = document.getElementById('tts-form');
262
  const textInput = document.getElementById('text-input');
263
  const promptInput = document.getElementById('prompt-input');
264
- const speakerGrid = document.getElementById('speaker-grid');
265
  const tempSlider = document.getElementById('temperature-slider');
266
  const tempValueSpan = document.getElementById('temperature-value');
267
  const generateBtn = document.getElementById('generate-btn');
268
  const statusMessage = document.getElementById('status-message');
269
  const audioPlayer = document.getElementById('audio-player');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
 
271
  function createSpeakerCards() {
272
- speakers.forEach((speaker) => {
273
  const card = document.createElement('label');
274
  card.className = 'speaker-card';
275
  card.setAttribute('for', `speaker-${speaker.id}`);
276
 
277
- // از یک سرویس عکس پرتره استفاده می‌کنیم
278
- // اگر جنسیت مشخص است، می‌توانیم عکس‌های مرتبط‌تری انتخاب کنیم
279
- const gender = speaker.name.includes('(مرد)') ? 'men' : 'women';
280
- const imageUrl = `https://randomuser.me/api/portraits/${gender}/${speakers.indexOf(speaker) % 100}.jpg`;
281
-
282
- const isChecked = speaker.id === 'Charon' ? 'checked' : '';
283
-
284
  card.innerHTML = `
285
- <input type="radio" name="speaker" value="${speaker.id}" id="speaker-${speaker.id}" ${isChecked}>
286
  <div class="speaker-visual">
287
- <img src="${imageUrl}" alt="عکس گوینده ${speaker.name}" loading="lazy">
288
  <div class="speaker-name">${speaker.name}</div>
289
  </div>
290
  `;
 
 
 
 
 
 
 
291
  speakerGrid.appendChild(card);
292
  });
293
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
  tempSlider.addEventListener('input', () => { tempValueSpan.textContent = tempSlider.value; });
296
 
@@ -305,7 +255,7 @@
305
  const text = textInput.value;
306
  const prompt = promptInput.value;
307
  const temperature = parseFloat(tempSlider.value);
308
- const selectedSpeaker = document.querySelector('input[name="speaker"]:checked').value;
309
  const sessionHash = Math.random().toString(36).substring(2);
310
 
311
  if (!text.trim()) {
@@ -390,7 +340,9 @@
390
  }
391
  }
392
 
 
393
  createSpeakerCards();
 
394
  form.addEventListener('submit', generateAudio);
395
  });
396
  </script>
 
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
 
 
12
  --app-header-grad-start: #1a2980;
13
  --app-header-grad-end: #26d0ce;
14
  --app-panel-bg: #FFFFFF;
15
+ --app-input-bg: #F8F9FA;
16
  --app-button-bg: #5f27cd;
17
  --app-button-hover-bg: #481e9e;
18
  --app-main-bg: linear-gradient(170deg, #F3E8FF 0%, #E0F2FE 100%);
 
36
  margin: 0;
37
  padding: 0;
38
  min-height: 100vh;
 
 
39
  -webkit-font-smoothing: antialiased;
40
  -moz-osx-font-smoothing: grayscale;
41
  }
42
 
43
+ .container { max-width: 800px; width: 95%; margin: 0 auto; padding-bottom: 40px; }
44
+ .app-header { padding: 3rem 1.5rem 5rem 1.5rem; text-align: center; background-image: linear-gradient(135deg, var(--app-header-grad-start) 0%, var(--app-header-grad-end) 100%); color: white; border-bottom-left-radius: var(--radius-card); border-bottom-right-radius: var(--radius-card); box-shadow: 0 6px 20px -5px rgba(0,0,0,0.2); }
45
+ .app-header h1 { font-size: 2.8em; font-weight: 800; margin:0 0 0.5rem 0; text-shadow: 0 2px 4px rgba(0,0,0,0.15); }
46
+ .app-header p { font-size: 1.2em; color: rgba(255,255,255,0.9); margin-top:0; opacity: 0.9; }
47
+ .main-content { padding: 2.5rem; margin: -3.5rem auto 2rem auto; background-color: var(--app-panel-bg); border-radius: var(--radius-card); box-shadow: var(--shadow-card); }
48
+ .form-group { margin-bottom: 2.5rem; }
49
+ label { display: block; font-weight: 700; color: var(--app-text-primary); font-size: 1.1em; margin-bottom: 0.8rem; }
50
+ textarea, input[type="text"] { width: 100%; padding: 1rem; border-radius: var(--radius-input); border: 2px solid var(--app-border-color); background-color: var(--app-input-bg); box-shadow: none; font-family: var(--app-font); font-size: 1rem; box-sizing: border-box; transition: all 0.2s ease-in-out; }
51
+ textarea:focus, input[type="text"]:focus { outline: none; border-color: var(--app-button-bg); box-shadow: 0 0 0 3px rgba(95, 39, 205, 0.2); background-color: #fff; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
+ /* --- **بخش جدید: نمایش گوینده منتخب** --- */
54
+ #selected-speaker-display { text-align: center; }
55
+ #selected-speaker-card { display: inline-flex; align-items: center; background: #fff; border-radius: 99px; padding: 10px; box-shadow: 0 5px 20px rgba(0,0,0,0.1); border: 2px solid var(--app-border-color); }
56
+ #selected-speaker-card img { width: 80px; height: 80px; border-radius: 50%; object-fit: cover; margin-left: 15px; }
57
+ #selected-speaker-info h3 { margin: 0; font-size: 1.4em; }
58
+ #selected-speaker-info p { margin: 5px 0 0; color: var(--app-text-secondary); font-size: 0.9em; }
59
+ #change-speaker-btn { display: block; margin: 1rem auto 0; padding: 8px 20px; border-radius: 8px; background-color: var(--app-input-bg); border: 1px solid var(--app-border-color); cursor: pointer; font-family: var(--app-font); font-weight: 500; transition: all 0.2s ease; }
60
+ #change-speaker-btn:hover { background-color: #e9ecef; border-color: #ced4da; }
61
 
62
+ /* --- **بخش جدید: مودال گال��ی گویندگان** --- */
63
+ #speaker-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); backdrop-filter: blur(5px); display: none; align-items: center; justify-content: center; z-index: 1000; opacity: 0; transition: opacity 0.3s ease; }
64
+ #speaker-modal.visible { display: flex; opacity: 1; }
65
+ .modal-content { background: #fff; padding: 2rem; border-radius: var(--radius-card); width: 90%; max-width: 700px; max-height: 80vh; overflow-y: auto; transform: scale(0.95); transition: transform 0.3s ease; }
66
+ #speaker-modal.visible .modal-content { transform: scale(1); }
67
+ .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; }
68
+ .modal-header h2 { margin: 0; }
69
+ .close-modal-btn { background: none; border: none; font-size: 2rem; cursor: pointer; color: #aaa; transition: color 0.2s ease; }
70
+ .close-modal-btn:hover { color: #333; }
71
 
72
+ #speaker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 1rem; }
73
+ @media (min-width: 576px) { #speaker-grid { grid-template-columns: repeat(4, 1fr); } }
74
+ .speaker-card { cursor: pointer; transition: all 0.3s ease; }
75
+ .speaker-card .speaker-visual { border: 3px solid transparent; border-radius: var(--radius-card); overflow: hidden; text-align: center; box-shadow: 0 4px 10px rgba(0,0,0,0.05); position: relative; background-color: #fff; }
76
+ .speaker-card:hover .speaker-visual { transform: translateY(-3px); box-shadow: 0 6px 15px rgba(0,0,0,0.1); }
77
+ .speaker-card input[type="radio"] { display: none; }
78
+ .speaker-card img { width: 100%; height: 120px; object-fit: cover; display: block; }
79
+ .speaker-card .speaker-name { padding: 0.7rem 0.5rem; font-weight: 500; font-size: 0.9em; }
80
+ .speaker-card input[type="radio"]:checked + .speaker-visual { border-color: var(--app-button-bg); box-shadow: var(--speaker-selected-glow); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
+ /* --- Slider & Button & Output --- */
83
  .slider-container { display: flex; align-items: center; gap: 1rem; }
84
  input[type="range"] { flex-grow: 1; cursor: pointer; }
85
  #temperature-value { font-weight: bold; background-color: var(--app-input-bg); padding: 0.2rem 0.8rem; border-radius: 8px; border: 1px solid var(--app-border-color); min-width: 40px; text-align: center; }
 
 
86
  #generate-btn { width: 100%; padding: 1rem 1.5rem; font-size: 1.2em; font-weight: 700; font-family: var(--app-font); background: var(--app-button-bg); color: white; border: none; border-radius: var(--radius-input); cursor: pointer; transition: all 0.3s ease; box-shadow: var(--shadow-button); }
87
  #generate-btn:hover:not(:disabled) { background-color: var(--app-button-hover-bg); transform: translateY(-2px); box-shadow: 0 6px 20px -3px rgba(95, 39, 205, 0.5); }
88
  #generate-btn:disabled { background-color: #999; cursor: not-allowed; box-shadow: none; }
 
110
  </div>
111
 
112
  <div class="form-group">
113
+ <label>🎤 گوینده منتخب</label>
114
+ <div id="selected-speaker-display">
115
+ <div id="selected-speaker-card">
116
+ <img id="selected-speaker-img" src="" alt="عکس گوینده">
117
+ <div id="selected-speaker-info">
118
+ <h3 id="selected-speaker-name"></h3>
119
+ <p>برای تغییر، روی دکمه زیر کلیک کنید</p>
120
+ </div>
121
+ </div>
122
+ <button type="button" id="change-speaker-btn">تغییر گوینده</button>
123
  </div>
124
  </div>
125
+
126
  <div class="form-group">
127
  <label for="temperature-slider">🌡️ میزان خلاقیت صدا (0.1 تا 1.5)</label>
128
  <div class="slider-container">
 
141
  </main>
142
  </div>
143
 
144
+ <!-- Modal برای انتخاب گوینده -->
145
+ <div id="speaker-modal">
146
+ <div class="modal-content">
147
+ <div class="modal-header">
148
+ <h2>انتخاب گوینده</h2>
149
+ <button type="button" class="close-modal-btn">×</button>
150
+ </div>
151
+ <div id="speaker-grid"></div>
152
+ </div>
153
+ </div>
154
+
155
+ <!-- این فیلد مخفی برای نگهداری مقدار انتخاب شده است -->
156
+ <input type="hidden" id="selected_speaker_id" value="Charon">
157
+
158
  <script>
159
  document.addEventListener('DOMContentLoaded', () => {
160
+ // --- پیکربندی و متغیرها ---
161
  const HF_SPACE_URL = "https://hamed744-ttspro.hf.space";
162
  const JOIN_QUEUE_URL = `${HF_SPACE_URL}/gradio_api/queue/join`;
163
  const GET_DATA_URL_BASE = `${HF_SPACE_URL}/gradio_api/queue/data`;
164
  const FILE_URL_BASE = `${HF_SPACE_URL}/gradio_api/file=`;
165
+ const FN_INDEX = 1;
 
166
 
 
167
  const speakers = [
168
+ { 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: "لیدا (زن)" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  ];
170
+
171
  // --- عناصر DOM ---
172
  const form = document.getElementById('tts-form');
173
  const textInput = document.getElementById('text-input');
174
  const promptInput = document.getElementById('prompt-input');
 
175
  const tempSlider = document.getElementById('temperature-slider');
176
  const tempValueSpan = document.getElementById('temperature-value');
177
  const generateBtn = document.getElementById('generate-btn');
178
  const statusMessage = document.getElementById('status-message');
179
  const audioPlayer = document.getElementById('audio-player');
180
+ const selectedSpeakerIdInput = document.getElementById('selected_speaker_id');
181
+ const speakerModal = document.getElementById('speaker-modal');
182
+ const changeSpeakerBtn = document.getElementById('change-speaker-btn');
183
+ const closeModalBtn = document.querySelector('.close-modal-btn');
184
+ const speakerGrid = document.getElementById('speaker-grid');
185
+ const selectedSpeakerImg = document.getElementById('selected-speaker-img');
186
+ const selectedSpeakerName = document.getElementById('selected-speaker-name');
187
+
188
+ // --- توابع ---
189
+ function getSpeakerById(id) {
190
+ return speakers.find(s => s.id === id);
191
+ }
192
+
193
+ function getImageUrl(speaker, index) {
194
+ const gender = speaker.name.includes('(مرد)') ? 'men' : 'women';
195
+ return `https://randomuser.me/api/portraits/thumb/${gender}/${index % 100}.jpg`;
196
+ }
197
+
198
+ function updateSelectedSpeakerDisplay(speakerId) {
199
+ const speaker = getSpeakerById(speakerId);
200
+ if (speaker) {
201
+ selectedSpeakerImg.src = getImageUrl(speaker, speakers.findIndex(s => s.id === speakerId));
202
+ selectedSpeakerName.textContent = speaker.name;
203
+ selectedSpeakerIdInput.value = speaker.id;
204
+ }
205
+ }
206
 
207
  function createSpeakerCards() {
208
+ speakers.forEach((speaker, index) => {
209
  const card = document.createElement('label');
210
  card.className = 'speaker-card';
211
  card.setAttribute('for', `speaker-${speaker.id}`);
212
 
 
 
 
 
 
 
 
213
  card.innerHTML = `
214
+ <input type="radio" name="speaker" value="${speaker.id}" id="speaker-${speaker.id}">
215
  <div class="speaker-visual">
216
+ <img src="${getImageUrl(speaker, index)}" alt="عکس گوینده ${speaker.name}" loading="lazy">
217
  <div class="speaker-name">${speaker.name}</div>
218
  </div>
219
  `;
220
+
221
+ card.addEventListener('click', () => {
222
+ updateSelectedSpeakerDisplay(speaker.id);
223
+ // برای بستن خودکار مودال بعد از انتخاب
224
+ setTimeout(() => speakerModal.classList.remove('visible'), 100);
225
+ });
226
+
227
  speakerGrid.appendChild(card);
228
  });
229
  }
230
+
231
+ // --- رویدادهای مودال ---
232
+ changeSpeakerBtn.addEventListener('click', () => {
233
+ // به‌روزرسانی انتخاب فعلی در مودال
234
+ const currentSelected = document.querySelector(`input[name="speaker"][value="${selectedSpeakerIdInput.value}"]`);
235
+ if(currentSelected) currentSelected.checked = true;
236
+ speakerModal.classList.add('visible');
237
+ });
238
+ closeModalBtn.addEventListener('click', () => speakerModal.classList.remove('visible'));
239
+ speakerModal.addEventListener('click', (e) => {
240
+ if (e.target === speakerModal) {
241
+ speakerModal.classList.remove('visible');
242
+ }
243
+ });
244
 
245
  tempSlider.addEventListener('input', () => { tempValueSpan.textContent = tempSlider.value; });
246
 
 
255
  const text = textInput.value;
256
  const prompt = promptInput.value;
257
  const temperature = parseFloat(tempSlider.value);
258
+ const selectedSpeaker = selectedSpeakerIdInput.value; // از فیلد مخفی می‌خوانیم
259
  const sessionHash = Math.random().toString(36).substring(2);
260
 
261
  if (!text.trim()) {
 
340
  }
341
  }
342
 
343
+ // --- راه‌اندازی اولیه ---
344
  createSpeakerCards();
345
+ updateSelectedSpeakerDisplay(selectedSpeakerIdInput.value); // نمایش گوینده پیش‌فرض
346
  form.addEventListener('submit', generateAudio);
347
  });
348
  </script>