Hamed744 commited on
Commit
81cbc33
·
verified ·
1 Parent(s): d553427

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +154 -107
index.html CHANGED
@@ -38,6 +38,14 @@
38
  from { opacity: 0; transform: translateY(20px); }
39
  to { opacity: 1; transform: translateY(0); }
40
  }
 
 
 
 
 
 
 
 
41
 
42
 
43
  body {
@@ -55,7 +63,7 @@
55
  display: flex;
56
  justify-content: center;
57
  align-items: flex-start;
58
- overflow-x: hidden; /* Prevent horizontal scrollbar from animations */
59
  }
60
 
61
  .container {
@@ -65,27 +73,27 @@
65
  }
66
 
67
  .app-header {
68
- padding: 0.5rem 0 2.5rem 0; /* Reduced bottom padding */
69
  text-align: center;
70
- margin-bottom: 1.5rem; /* Reduced bottom margin */
71
  animation: fadeInDown 0.8s 0.1s ease-out backwards;
72
  }
73
  .app-header h1 {
74
- font-size: 2.1em; /* باز هم کوچکتر شد */
75
  font-weight: 900;
76
- margin:0 0 0.6rem 0; /* Reduced bottom margin */
77
  background: linear-gradient(45deg, var(--accent-primary), var(--accent-secondary));
78
  -webkit-background-clip: text;
79
  -webkit-text-fill-color: transparent;
80
- letter-spacing: -0.5px; /* Adjusted spacing */
81
  }
82
  .app-header p {
83
- font-size: 1em; /* باز هم کوچکتر شد */
84
  color: var(--text-secondary);
85
  margin-top:0;
86
- opacity: 0.85; /* Slightly more subtle */
87
  font-weight: 400;
88
- line-height: 1.6; /* Adjusted line height */
89
  }
90
 
91
  .main-content {
@@ -109,7 +117,7 @@
109
  margin-bottom: 0;
110
  }
111
 
112
- .info-tooltip-icon {
113
  display: inline-flex;
114
  align-items: center;
115
  justify-content: center;
@@ -126,49 +134,14 @@
126
  transition: var(--transition-smooth);
127
  user-select: none;
128
  }
129
- .info-tooltip-icon:hover, .info-tooltip-icon:focus {
130
  background-color: var(--accent-primary);
131
  color: white;
132
  border-color: var(--accent-primary);
133
  transform: scale(1.1);
134
  outline: none;
135
  }
136
- .tooltip-text {
137
- visibility: hidden;
138
- width: 280px;
139
- background-color: var(--text-primary);
140
- color: #fff;
141
- text-align: right;
142
- border-radius: var(--radius-input);
143
- padding: 10px 15px;
144
- position: absolute;
145
- z-index: 10;
146
- bottom: 140%;
147
- left: 50%;
148
- transform: translateX(-50%) translateY(10px);
149
- opacity: 0;
150
- transition: opacity 0.3s, visibility 0.3s, transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
151
- font-size: 0.88em;
152
- font-weight: 400;
153
- line-height: 1.6;
154
- box-shadow: var(--shadow-medium);
155
- }
156
- .tooltip-text::after {
157
- content: "";
158
- position: absolute;
159
- top: 100%;
160
- left: 50%;
161
- margin-left: -5px;
162
- border-width: 5px;
163
- border-style: solid;
164
- border-color: var(--text-primary) transparent transparent transparent;
165
- }
166
- .info-tooltip-icon.active .tooltip-text {
167
- visibility: visible;
168
- opacity: 1;
169
- transform: translateX(-50%) translateY(0);
170
- }
171
-
172
 
173
  label {
174
  display: block;
@@ -273,34 +246,48 @@
273
  }
274
 
275
 
276
- /* --- مودال گالری گویندگان --- */
277
- #speaker-modal {
278
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
279
- background-color: rgba(18, 24, 38, 0.7);
280
  backdrop-filter: blur(8px) saturate(150%);
281
- display: none; align-items: center; justify-content: center;
 
282
  z-index: 1000; opacity: 0;
283
  transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
284
  }
285
- #speaker-modal.visible { display: flex; opacity: 1; }
286
- .modal-content {
 
287
  background: var(--panel-bg);
288
  padding: 2rem;
289
  border-radius: var(--radius-card);
290
- width: 90%; max-width: 700px;
291
- max-height: 85vh; overflow-y: auto;
292
- transform: scale(0.95) translateY(20px);
293
- transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.35s;
294
- border: 1px solid var(--panel-border);
295
  box-shadow: var(--shadow-strong);
296
- opacity: 0;
 
 
 
297
  }
298
- #speaker-modal.visible .modal-content { transform: scale(1) translateY(0); opacity: 1;}
299
- .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.8rem; padding-bottom: 1rem; border-bottom: 1px solid var(--panel-border); }
 
 
 
 
 
 
 
300
  .modal-header h2 { margin: 0; font-size: 1.6em; font-weight: 800; color: var(--accent-primary);}
301
  .close-modal-btn { background: none; border: none; font-size: 2.5rem; cursor: pointer; color: var(--text-secondary); transition: var(--transition-smooth); line-height: 1; }
302
  .close-modal-btn:hover { color: var(--accent-primary); transform: rotate(90deg) scale(1.1); }
303
 
 
 
 
 
 
 
304
  #speaker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 1.2rem; }
305
  @media (min-width: 640px) { #speaker-grid { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); } }
306
  .speaker-card { cursor: pointer; transition: var(--transition-smooth); text-align: center; position: relative;}
@@ -317,7 +304,7 @@
317
  .speaker-card:hover .speaker-visual {
318
  transform: translateY(-5px) scale(1.06);
319
  box-shadow: var(--shadow-medium);
320
- border-color: rgba(var(--accent-secondary-rgb), 0.3);
321
  }
322
  .speaker-card input[type="radio"] { display: none; }
323
  .speaker-card img {
@@ -344,6 +331,30 @@
344
  font-weight: 700;
345
  }
346
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  /* --- Slider & Button & Output --- */
348
  .slider-container { display: flex; align-items: center; gap: 1.2rem; }
349
  input[type="range"] {
@@ -582,8 +593,8 @@
582
  <div class="form-group">
583
  <div class="label-with-info">
584
  <label for="temperature-slider">🌡️ میزان خلاقیت و نوآوری صدا</label>
585
- <div class="info-tooltip-icon" id="temp-info-icon" role="button" tabindex="0" aria-label="اطلاعات بیشتر">!
586
- <span class="tooltip-text" id="temp-tooltip-text">مقادیر بالاتر منجر به صدایی متنوع‌تر و گاهی غیرمنتظره‌تر می‌شود، در حالی که مقادیر پایین‌تر صدایی پایدارتر و قابل پیش‌بینی‌تر تولید می‌کنند. (محدوده: 0.1 تا 1.5)</span>
587
  </div>
588
  </div>
589
  <div class="slider-container">
@@ -610,11 +621,12 @@
610
  </main>
611
  </div>
612
 
613
- <div id="speaker-modal">
614
- <div class="modal-content">
 
615
  <div class="modal-header">
616
  <h2>گالری گویندگان آلفا نوا</h2>
617
- <button type="button" class="close-modal-btn" aria-label="بستن مودال">×</button>
618
  </div>
619
  <div id="speaker-grid">
620
  <!-- Speaker cards will be generated here by JS -->
@@ -622,6 +634,23 @@
622
  </div>
623
  </div>
624
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
625
  <input type="hidden" id="selected_speaker_id_storage" value="Charon">
626
 
627
  <script>
@@ -678,27 +707,30 @@
678
  const loadingAnimationWrapper = document.getElementById('loading-animation-wrapper');
679
 
680
  const selectedSpeakerIdStorage = document.getElementById('selected_speaker_id_storage');
681
- const speakerModal = document.getElementById('speaker-modal');
682
  const changeSpeakerBtn = document.getElementById('change-speaker-btn');
683
  const selectedSpeakerCard = document.getElementById('selected-speaker-card');
684
- const closeModalBtn = document.querySelector('.close-modal-btn');
685
  const speakerGridInModal = document.getElementById('speaker-grid');
686
  const selectedSpeakerImgDisplay = document.getElementById('selected-speaker-img');
687
  const selectedSpeakerNameDisplay = document.getElementById('selected-speaker-name');
688
  const selectedSpeakerDescDisplay = document.getElementById('selected-speaker-desc');
 
 
 
 
689
  const tempInfoIcon = document.getElementById('temp-info-icon');
690
 
 
691
  // Character Counter
692
  const charCountSpan = document.getElementById('char-count');
693
  const charMaxSpan = document.getElementById('char-max');
694
- const MAX_CHARS = 50000; // تغییر به ۵۰,۰۰۰
695
- charMaxSpan.textContent = MAX_CHARS.toLocaleString('fa-IR'); // نمایش عدد با جداکننده فارسی
696
 
697
  textInput.addEventListener('input', () => {
698
  const currentLength = textInput.value.length;
699
  charCountSpan.textContent = currentLength.toLocaleString('fa-IR');
700
  if (currentLength > MAX_CHARS) {
701
- charCountSpan.style.color = 'var(--accent-secondary-hover)'; // یا رنگ هشدار
702
  } else {
703
  charCountSpan.style.color = 'var(--accent-primary)';
704
  }
@@ -714,7 +746,6 @@
714
  const imageIndex = (index * 7 + speaker.id.length * 3 + 5) % 100;
715
  let portraitSizePath = 'thumb/';
716
  if (size === 'large') portraitSizePath = '';
717
-
718
  return `https://randomuser.me/api/portraits/${portraitSizePath}${gender}/${imageIndex}.jpg`;
719
  }
720
 
@@ -735,7 +766,6 @@
735
  cardLabel.className = 'speaker-card';
736
  cardLabel.setAttribute('for', `modal-speaker-${speaker.id}`);
737
  const isChecked = speaker.id === selectedSpeakerIdStorage.value ? 'checked' : '';
738
-
739
  cardLabel.innerHTML = `
740
  <input type="radio" name="modal_speaker_selection" value="${speaker.id}" id="modal-speaker-${speaker.id}" ${isChecked}>
741
  <div class="speaker-visual">
@@ -743,64 +773,81 @@
743
  <div class="speaker-name">${speaker.name}</div>
744
  </div>
745
  `;
746
-
747
  cardLabel.addEventListener('click', (e) => {
748
  if (e.target.name !== "modal_speaker_selection") {
749
  const radio = cardLabel.querySelector('input[type="radio"]');
750
  if(radio) radio.checked = true;
751
  }
752
  updateSelectedSpeakerDisplay(speaker.id);
753
- setTimeout(() => speakerModal.classList.remove('visible'), 200);
754
  });
755
-
756
  speakerGridInModal.appendChild(cardLabel);
757
  });
758
  }
759
 
760
- function openSpeakerModal() {
761
- createSpeakerCardsInModal();
762
- speakerModal.classList.add('visible');
 
763
  setTimeout(() => {
764
- const firstFocusable = speakerModal.querySelector('input[type="radio"]:checked, input[type="radio"], .close-modal-btn');
765
- if (firstFocusable) firstFocusable.focus();
766
- else closeModalBtn.focus();
767
- }, 50);
768
  }
769
 
770
- changeSpeakerBtn.addEventListener('click', openSpeakerModal);
771
- selectedSpeakerCard.addEventListener('click', openSpeakerModal);
 
 
 
 
772
 
773
- closeModalBtn.addEventListener('click', () => speakerModal.classList.remove('visible'));
774
- speakerModal.addEventListener('click', (e) => {
775
- if (e.target === speakerModal) {
776
- speakerModal.classList.remove('visible');
777
- }
778
  });
779
- document.addEventListener('keydown', (e) => {
780
- if (e.key === 'Escape' && speakerModal.classList.contains('visible')) {
781
- speakerModal.classList.remove('visible');
782
- }
783
  });
784
 
785
- tempSlider.addEventListener('input', () => { tempValueSpan.textContent = tempSlider.value; });
786
-
787
- tempInfoIcon.addEventListener('click', (e) => {
788
- e.stopPropagation();
789
- tempInfoIcon.classList.toggle('active');
790
- });
791
- tempInfoIcon.addEventListener('keydown', (e) => {
792
  if (e.key === 'Enter' || e.key === ' ') {
793
  e.preventDefault();
794
- tempInfoIcon.classList.toggle('active');
795
  }
796
  });
797
- document.addEventListener('click', (e) => {
798
- if (!tempInfoIcon.contains(e.target) && tempInfoIcon.classList.contains('active')) {
799
- tempInfoIcon.classList.remove('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
800
  }
801
  });
802
 
803
 
 
 
 
804
  function showLoadingState() {
805
  outputSection.classList.remove('has-content');
806
  statusMessage.style.display = 'none';
@@ -874,7 +921,7 @@
874
 
875
  let finalFilePath = null;
876
  const startTime = Date.now();
877
- const timeoutDuration = 90000; // 90 ثانیه
878
 
879
  while (Date.now() - startTime < timeoutDuration) {
880
  const dataResponse = await fetch(`${GET_DATA_URL_BASE}?session_hash=${sessionHash}`);
 
38
  from { opacity: 0; transform: translateY(20px); }
39
  to { opacity: 1; transform: translateY(0); }
40
  }
41
+ @keyframes modalZoomIn {
42
+ from { opacity: 0; transform: scale(0.8) translateY(20px); }
43
+ to { opacity: 1; transform: scale(1) translateY(0); }
44
+ }
45
+ @keyframes modalZoomOut {
46
+ from { opacity: 1; transform: scale(1) translateY(0); }
47
+ to { opacity: 0; transform: scale(0.8) translateY(20px); }
48
+ }
49
 
50
 
51
  body {
 
63
  display: flex;
64
  justify-content: center;
65
  align-items: flex-start;
66
+ overflow-x: hidden;
67
  }
68
 
69
  .container {
 
73
  }
74
 
75
  .app-header {
76
+ padding: 0.5rem 0 2.5rem 0;
77
  text-align: center;
78
+ margin-bottom: 1.5rem;
79
  animation: fadeInDown 0.8s 0.1s ease-out backwards;
80
  }
81
  .app-header h1 {
82
+ font-size: 2.1em;
83
  font-weight: 900;
84
+ margin:0 0 0.6rem 0;
85
  background: linear-gradient(45deg, var(--accent-primary), var(--accent-secondary));
86
  -webkit-background-clip: text;
87
  -webkit-text-fill-color: transparent;
88
+ letter-spacing: -0.5px;
89
  }
90
  .app-header p {
91
+ font-size: 1em;
92
  color: var(--text-secondary);
93
  margin-top:0;
94
+ opacity: 0.85;
95
  font-weight: 400;
96
+ line-height: 1.6;
97
  }
98
 
99
  .main-content {
 
117
  margin-bottom: 0;
118
  }
119
 
120
+ .info-icon { /* Renamed from info-tooltip-icon for clarity */
121
  display: inline-flex;
122
  align-items: center;
123
  justify-content: center;
 
134
  transition: var(--transition-smooth);
135
  user-select: none;
136
  }
137
+ .info-icon:hover, .info-icon:focus {
138
  background-color: var(--accent-primary);
139
  color: white;
140
  border-color: var(--accent-primary);
141
  transform: scale(1.1);
142
  outline: none;
143
  }
144
+ /* Removed .tooltip-text and .info-tooltip-icon.active as it's now a modal */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
  label {
147
  display: block;
 
246
  }
247
 
248
 
249
+ /* --- مودال عمومی --- */
250
+ .modal-overlay {
251
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
252
+ background-color: rgba(18, 24, 38, 0.6);
253
  backdrop-filter: blur(8px) saturate(150%);
254
+ display: none; /* Hidden by default */
255
+ align-items: center; justify-content: center;
256
  z-index: 1000; opacity: 0;
257
  transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
258
  }
259
+ .modal-overlay.visible { display: flex; opacity: 1; }
260
+
261
+ .modal-dialog {
262
  background: var(--panel-bg);
263
  padding: 2rem;
264
  border-radius: var(--radius-card);
265
+ width: 90%;
 
 
 
 
266
  box-shadow: var(--shadow-strong);
267
+ border: 1px solid var(--panel-border);
268
+ opacity: 0; /* For animation */
269
+ animation-duration: 0.35s;
270
+ animation-fill-mode: forwards;
271
  }
272
+ .modal-overlay.visible .modal-dialog {
273
+ animation-name: modalZoomIn;
274
+ }
275
+ .modal-overlay.hiding .modal-dialog {
276
+ animation-name: modalZoomOut;
277
+ }
278
+
279
+
280
+ .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); }
281
  .modal-header h2 { margin: 0; font-size: 1.6em; font-weight: 800; color: var(--accent-primary);}
282
  .close-modal-btn { background: none; border: none; font-size: 2.5rem; cursor: pointer; color: var(--text-secondary); transition: var(--transition-smooth); line-height: 1; }
283
  .close-modal-btn:hover { color: var(--accent-primary); transform: rotate(90deg) scale(1.1); }
284
 
285
+
286
+ /* --- مودال گالری گویندگان (اختصاصی) --- */
287
+ #speaker-modal .modal-dialog { /* Target specific modal dialog */
288
+ max-width: 700px;
289
+ max-height: 85vh; overflow-y: auto;
290
+ }
291
  #speaker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 1.2rem; }
292
  @media (min-width: 640px) { #speaker-grid { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); } }
293
  .speaker-card { cursor: pointer; transition: var(--transition-smooth); text-align: center; position: relative;}
 
304
  .speaker-card:hover .speaker-visual {
305
  transform: translateY(-5px) scale(1.06);
306
  box-shadow: var(--shadow-medium);
307
+ /* border-color: rgba(var(--accent-secondary-rgb), 0.3); */ /* Requires CSS var for RGB components */
308
  }
309
  .speaker-card input[type="radio"] { display: none; }
310
  .speaker-card img {
 
331
  font-weight: 700;
332
  }
333
 
334
+ /* --- مودال اطلاعات (اختصاصی) --- */
335
+ #info-modal .modal-dialog {
336
+ max-width: 480px; /* Smaller width for info */
337
+ }
338
+ #info-modal-content p {
339
+ font-size: 1em;
340
+ line-height: 1.7;
341
+ color: var(--text-secondary);
342
+ margin-bottom: 0.5rem;
343
+ }
344
+ #info-modal-content p strong {
345
+ color: var(--text-primary);
346
+ font-weight: 600;
347
+ }
348
+ #info-modal-content .range-info {
349
+ font-size: 0.9em;
350
+ color: var(--accent-primary);
351
+ font-weight: 500;
352
+ margin-top: 1rem;
353
+ display: block;
354
+ text-align: center;
355
+ }
356
+
357
+
358
  /* --- Slider & Button & Output --- */
359
  .slider-container { display: flex; align-items: center; gap: 1.2rem; }
360
  input[type="range"] {
 
593
  <div class="form-group">
594
  <div class="label-with-info">
595
  <label for="temperature-slider">🌡️ میزان خلاقیت و نوآوری صدا</label>
596
+ <div class="info-icon" id="temp-info-icon" role="button" tabindex="0" aria-label="اطلاعات بیشتر">!
597
+ <!-- Tooltip text removed, will be shown in modal -->
598
  </div>
599
  </div>
600
  <div class="slider-container">
 
621
  </main>
622
  </div>
623
 
624
+ <!-- مودال گالری گویندگان -->
625
+ <div id="speaker-modal" class="modal-overlay">
626
+ <div class="modal-dialog">
627
  <div class="modal-header">
628
  <h2>گالری گویندگان آلفا نوا</h2>
629
+ <button type="button" class="close-modal-btn" data-modal-id="speaker-modal" aria-label="بستن مودال">×</button>
630
  </div>
631
  <div id="speaker-grid">
632
  <!-- Speaker cards will be generated here by JS -->
 
634
  </div>
635
  </div>
636
 
637
+ <!-- مودال اطلاعات خلاقیت صدا -->
638
+ <div id="info-modal" class="modal-overlay">
639
+ <div class="modal-dialog">
640
+ <div class="modal-header">
641
+ <h2>🌡️ خلاقیت و نوآوری صدا</h2>
642
+ <button type="button" class="close-modal-btn" data-modal-id="info-modal" aria-label="بستن توضیحات">×</button>
643
+ </div>
644
+ <div id="info-modal-content">
645
+ <p>این تنظیم مشخص می‌کند که هوش مصنوعی تا چه حد در تولید صدا <strong>خلاقیت</strong> به خرج دهد.</p>
646
+ <p><strong>مقادیر بالاتر:</strong> منجر به صدایی متنوع‌تر، پویاتر و گاهی اوقات غیرمنتظره‌تر می‌شود. مناسب برای زمانی که به دنبال لحنی خاص و منحصربه‌فرد هستید.</p>
647
+ <p><strong>مقادیر پایین‌تر:</strong> صدایی پایدارتر، قابل پیش‌بینی‌تر و نزدیک‌تر به صدای استاندارد گوینده تولید می‌کند. مناسب برای خوانش متون رسمی یا زمانی که ثبات لحن اهمیت دارد.</p>
648
+ <span class="range-info">محدوده پیشنهادی: ۰.۱ (پایدار) تا ۱.۵ (بسیار خلاق)</span>
649
+ </div>
650
+ </div>
651
+ </div>
652
+
653
+
654
  <input type="hidden" id="selected_speaker_id_storage" value="Charon">
655
 
656
  <script>
 
707
  const loadingAnimationWrapper = document.getElementById('loading-animation-wrapper');
708
 
709
  const selectedSpeakerIdStorage = document.getElementById('selected_speaker_id_storage');
 
710
  const changeSpeakerBtn = document.getElementById('change-speaker-btn');
711
  const selectedSpeakerCard = document.getElementById('selected-speaker-card');
 
712
  const speakerGridInModal = document.getElementById('speaker-grid');
713
  const selectedSpeakerImgDisplay = document.getElementById('selected-speaker-img');
714
  const selectedSpeakerNameDisplay = document.getElementById('selected-speaker-name');
715
  const selectedSpeakerDescDisplay = document.getElementById('selected-speaker-desc');
716
+
717
+ // Modal Elements
718
+ const speakerModal = document.getElementById('speaker-modal');
719
+ const infoModal = document.getElementById('info-modal');
720
  const tempInfoIcon = document.getElementById('temp-info-icon');
721
 
722
+
723
  // Character Counter
724
  const charCountSpan = document.getElementById('char-count');
725
  const charMaxSpan = document.getElementById('char-max');
726
+ const MAX_CHARS = 50000;
727
+ charMaxSpan.textContent = MAX_CHARS.toLocaleString('fa-IR');
728
 
729
  textInput.addEventListener('input', () => {
730
  const currentLength = textInput.value.length;
731
  charCountSpan.textContent = currentLength.toLocaleString('fa-IR');
732
  if (currentLength > MAX_CHARS) {
733
+ charCountSpan.style.color = 'var(--accent-secondary-hover)';
734
  } else {
735
  charCountSpan.style.color = 'var(--accent-primary)';
736
  }
 
746
  const imageIndex = (index * 7 + speaker.id.length * 3 + 5) % 100;
747
  let portraitSizePath = 'thumb/';
748
  if (size === 'large') portraitSizePath = '';
 
749
  return `https://randomuser.me/api/portraits/${portraitSizePath}${gender}/${imageIndex}.jpg`;
750
  }
751
 
 
766
  cardLabel.className = 'speaker-card';
767
  cardLabel.setAttribute('for', `modal-speaker-${speaker.id}`);
768
  const isChecked = speaker.id === selectedSpeakerIdStorage.value ? 'checked' : '';
 
769
  cardLabel.innerHTML = `
770
  <input type="radio" name="modal_speaker_selection" value="${speaker.id}" id="modal-speaker-${speaker.id}" ${isChecked}>
771
  <div class="speaker-visual">
 
773
  <div class="speaker-name">${speaker.name}</div>
774
  </div>
775
  `;
 
776
  cardLabel.addEventListener('click', (e) => {
777
  if (e.target.name !== "modal_speaker_selection") {
778
  const radio = cardLabel.querySelector('input[type="radio"]');
779
  if(radio) radio.checked = true;
780
  }
781
  updateSelectedSpeakerDisplay(speaker.id);
782
+ hideModal(speakerModal);
783
  });
 
784
  speakerGridInModal.appendChild(cardLabel);
785
  });
786
  }
787
 
788
+ // --- Modal Management ---
789
+ function showModal(modalElement) {
790
+ modalElement.classList.add('visible');
791
+ // Focus first focusable element in modal
792
  setTimeout(() => {
793
+ const firstFocusable = modalElement.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
794
+ if (firstFocusable) firstFocusable.focus();
795
+ }, 50); // Allow transition to start
 
796
  }
797
 
798
+ function hideModal(modalElement) {
799
+ modalElement.classList.add('hiding'); // Add class for exit animation
800
+ modalElement.addEventListener('animationend', () => {
801
+ modalElement.classList.remove('visible', 'hiding');
802
+ }, { once: true });
803
+ }
804
 
805
+ // Speaker Modal Listeners
806
+ changeSpeakerBtn.addEventListener('click', () => {
807
+ createSpeakerCardsInModal();
808
+ showModal(speakerModal);
 
809
  });
810
+ selectedSpeakerCard.addEventListener('click', () => {
811
+ createSpeakerCardsInModal();
812
+ showModal(speakerModal);
 
813
  });
814
 
815
+ // Info Modal Listener
816
+ tempInfoIcon.addEventListener('click', () => showModal(infoModal));
817
+ tempInfoIcon.addEventListener('keydown', (e) => {
 
 
 
 
818
  if (e.key === 'Enter' || e.key === ' ') {
819
  e.preventDefault();
820
+ showModal(infoModal);
821
  }
822
  });
823
+
824
+
825
+ // General Modal Close Listeners (for all modals)
826
+ document.querySelectorAll('.modal-overlay').forEach(overlay => {
827
+ overlay.addEventListener('click', (e) => {
828
+ if (e.target === overlay) { // Click on backdrop
829
+ hideModal(overlay);
830
+ }
831
+ });
832
+ });
833
+ document.querySelectorAll('.close-modal-btn').forEach(button => {
834
+ button.addEventListener('click', () => {
835
+ const modalId = button.dataset.modalId;
836
+ if (modalId) {
837
+ hideModal(document.getElementById(modalId));
838
+ }
839
+ });
840
+ });
841
+ document.addEventListener('keydown', (e) => {
842
+ if (e.key === 'Escape') {
843
+ document.querySelectorAll('.modal-overlay.visible').forEach(hideModal);
844
  }
845
  });
846
 
847
 
848
+ tempSlider.addEventListener('input', () => { tempValueSpan.textContent = tempSlider.value; });
849
+
850
+
851
  function showLoadingState() {
852
  outputSection.classList.remove('has-content');
853
  statusMessage.style.display = 'none';
 
921
 
922
  let finalFilePath = null;
923
  const startTime = Date.now();
924
+ const timeoutDuration = 90000;
925
 
926
  while (Date.now() - startTime < timeoutDuration) {
927
  const dataResponse = await fetch(`${GET_DATA_URL_BASE}?session_hash=${sessionHash}`);