Opera8 commited on
Commit
856b00e
·
verified ·
1 Parent(s): 60dbdf0

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +76 -40
index.html CHANGED
@@ -4,7 +4,7 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>تولید صدای هوشمند و پادکست | AI Sada</title>
7
- <meta name="description" content="با AI Sada، متن فارسی خود را به صدایی طبیعی و با کیفیت استودیویی تبدیل کنید. سریع، آسان و رایگان.">
8
  <!-- Font Awesome for Icons -->
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
 
@@ -23,9 +23,7 @@
23
  --radius-card: 24px; --radius-btn: 14px; --radius-input: 12px;
24
  --transition-fast: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
25
  --transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
26
- --transition-bounce: all 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55);
27
  --card-bg: #FFFFFF;
28
- --danger: #E53E3E;
29
  }
30
  @keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
31
  @keyframes fadeInOut { 0% { opacity: 0; transform: translateY(10px); } 10% { opacity: 1; transform: translateY(0); } 90% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(-10px); } }
@@ -39,7 +37,7 @@
39
  @keyframes pulse-soft { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }
40
  @keyframes bounce { 0%, 20%, 50%, 80%, 100% {transform: translateY(0);} 40% {transform: translateY(-15px);} 60% {transform: translateY(-7px);} }
41
 
42
- /* PODCAST STUDIO VIEW */
43
  .podcast-studio-container { width: 100%; animation: fadeIn 0.5s ease-out; }
44
  .podcast-studio-container .form-group-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.2rem; }
45
  .podcast-studio-container #project-speakers-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 1.5rem; text-align: center; }
@@ -96,16 +94,21 @@
96
  .podcast-studio-container .loading-state-wrapper .loading-text { font-size: 0.85em; font-weight: 600; color: var(--accent-primary); white-space: nowrap; transition: all 0.3s; }
97
  .podcast-studio-container .turn-retry-btn { font-family: var(--app-font); font-size: 0.85em; font-weight: 600; background: none; border: 1px solid var(--input-border); color: var(--text-secondary); padding: 0.4rem 0.8rem; border-radius: var(--radius-btn); cursor: pointer; transition: var(--transition-smooth); margin-right: auto; display: flex; align-items: center; gap: 6px; }
98
  .podcast-studio-container .turn-retry-btn:hover { border-color: var(--accent-secondary); color: var(--accent-secondary); background: var(--accent-secondary-glow); }
 
99
  .podcast-studio-container .replace-success-message, .podcast-studio-container .main-update-message { font-family: var(--app-font); font-size: 0.85em; font-weight: 600; padding: 0.4rem 0.8rem; border-radius: var(--radius-btn); white-space: nowrap; display: none; align-items: center; justify-content: center; margin-right: auto; }
100
  .podcast-studio-container .replace-success-message { background-color: var(--accent-secondary-glow); color: var(--accent-secondary); }
101
  .podcast-studio-container .main-update-message { background: linear-gradient(90deg, #e6fffa, #b2f5ea); color: #2c7a7b; border: 1px solid #81e6d9; box-shadow: 0 2px 5px rgba(0,0,0,0.05); }
102
 
103
  .beautiful-download-btn { display: flex; align-items: center; justify-content: center; gap: 12px; width: 100%; max-width: 400px; margin: 1.5rem auto 0.5rem auto; padding: 16px 24px; background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); color: #fff; border: none; border-radius: 14px; font-family: var(--app-font); font-weight: 800; font-size: 1.1em; cursor: pointer; position: relative; overflow: hidden; box-shadow: 0 4px 6px -1px rgba(37, 99, 235, 0.1), 0 2px 4px -1px rgba(37, 99, 235, 0.06); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); border-bottom: 4px solid #1d4ed8; }
104
  .beautiful-download-btn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 10px 15px -3px rgba(37, 99, 235, 0.2), 0 4px 6px -2px rgba(37, 99, 235, 0.1); filter: brightness(1.05); }
 
105
 
106
  #clear-history-btn-podcast { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; max-width: 300px; margin: 1rem auto 0 auto; padding: 12px; background-color: #fff; border: 2px dashed #cbd5e0; color: #718096; font-family: var(--app-font); font-size: 0.95em; font-weight: 700; border-radius: 12px; cursor: pointer; transition: all 0.3s ease; }
107
  #clear-history-btn-podcast:hover { border-color: #fc8181; color: #e53e3e; background-color: #fff5f5; transform: translateY(-2px); }
108
-
 
 
 
109
  /* Modals and Options */
110
  #long-text-modal .modal-dialog { max-width: 600px; background: linear-gradient(160deg, #ffffff 0%, #f7f9fc 100%); padding: 3rem 2.5rem; max-height: 90vh; overflow-y: auto; }
111
  #long-text-modal .modal-header { border: none; justify-content: center; flex-direction: column; text-align: center; margin-bottom: 1.5rem; }
@@ -119,6 +122,7 @@
119
  .option-content { flex-grow: 1; text-align: right; }
120
  .option-title { font-weight: 800; font-size: 1rem; color: var(--text-primary); margin-bottom: 2px; }
121
  .option-subtitle { font-size: 0.85rem; color: var(--text-secondary); line-height: 1.4; }
 
122
  .special-ai-card { background: linear-gradient(135deg, #f3f4f6 0%, #ffffff 100%); border-color: #d1d5db; box-shadow: 0 4px 6px rgba(0,0,0,0.05); }
123
  .special-ai-card:hover { border-color: #8b5cf6; background: linear-gradient(135deg, #f5f3ff 0%, #ffffff 100%); box-shadow: 0 10px 20px rgba(139, 92, 246, 0.15); }
124
  .special-ai-card .option-icon { background: #ede9fe; color: #8b5cf6; }
@@ -132,8 +136,10 @@
132
  .confirm-modal-dialog p { color: var(--text-secondary); margin: 0 0 2rem 0; line-height: 1.7; }
133
  .confirm-modal-actions { display: flex; gap: 1rem; justify-content: center; }
134
  .confirm-btn, .cancel-btn { font-family: var(--app-font); font-size: 1em; font-weight: 700; padding: 0.8rem 1.5rem; border-radius: var(--radius-btn); border: none; cursor: pointer; transition: all 0.2s ease; flex-grow: 1; }
135
- .confirm-btn { background-color: #e53e3e; color: white; }
136
- .cancel-btn { background-color: var(--accent-primary); color: white; }
 
 
137
 
138
  .ai-podcast-card { grid-column: 1 / -1; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 18px !important; display: flex !important; flex-direction: row !important; align-items: center !important; justify-content: space-between !important; padding: 0 1.5rem !important; margin-top: 10px; cursor: pointer; min-height: 90px; }
139
  .ai-podcast-card:hover { transform: translateY(-5px) scale(1.02) !important; box-shadow: 0 15px 30px rgba(118, 75, 162, 0.6) !important; }
@@ -141,10 +147,12 @@
141
  .ai-podcast-title { font-size: 1.3rem; font-weight: 900; color: white; margin-bottom: 4px; display: flex; align-items: center; gap: 8px; }
142
  .ai-podcast-subtitle { font-size: 0.9rem; color: rgba(255,255,255,0.9); font-weight: 500; }
143
  .ai-podcast-icon { font-size: 2.5rem; margin-left: 1rem; animation: pulse-soft 2s infinite; }
 
 
144
 
145
  #ai-podcast-modal .modal-header h2 { background: linear-gradient(90deg, #FF0080, #7928CA); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-size: 1.8rem; }
146
  #ai-podcast-input { width: 100%; min-height: 180px; padding: 1.2rem; border-radius: 16px; border: 2px solid var(--input-border); background-color: var(--input-bg); font-family: var(--app-font); font-size: 1rem; line-height: 1.8; resize: vertical; transition: all 0.3s; margin: 1.5rem 0; }
147
- #ai-podcast-input:focus { outline: none; border-color: #7928CA; background-color: white; }
148
 
149
  .sample-player-container { position: absolute; top: 50%; left: 4px; transform: translateY(-50%); display: flex; flex-direction: column; gap: 4px; z-index: 6; }
150
  .sample-play-btn { width: 24px; height: 24px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0; background: linear-gradient(145deg, rgba(255, 255, 255, 0.25), rgba(0, 0, 0, 0.25)); border: 1px solid rgba(255, 255, 255, 0.15); }
@@ -154,11 +162,11 @@
154
  .sample-play-btn.playing { background: var(--accent-secondary); }
155
  .sample-play-btn.playing .play-icon-svg { display: none; }
156
  .sample-play-btn.playing .playing-indicator { display: flex; }
157
- @keyframes sound-wave { 0% { height: 2px; } 25% { height: 8px; } 50% { height: 4px; } 100% { height: 2px; } }
158
 
159
  #sample-text-display { position: fixed; bottom: 25px; left: 50%; width: 90%; max-width: 600px; background: rgba(255, 255, 255, 0.85); backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.6); border-radius: 20px; padding: 20px 24px; box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.15); z-index: 2000; text-align: right; direction: rtl; display: flex; flex-direction: column; align-items: flex-start; opacity: 0; visibility: hidden; transform: translateX(-50%) translateY(30px) scale(0.95); transition: all 0.5s; }
160
  #sample-text-display.active { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0) scale(1); }
161
- #sample-text-display h4 { margin: 0 0 10px; color: var(--accent-primary); font-size: 0.8em; font-weight: 800; }
 
162
  #sample-text-display p { margin: 0; color: var(--text-primary); font-weight: 500; font-size: 0.9em; line-height: 1.7; }
163
 
164
  #speaker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 1.5rem; padding-bottom: 0; transition: padding-bottom 0.4s; }
@@ -249,10 +257,6 @@
249
  .feature-card h3 { font-size: 1.3em; margin-bottom: 0.5rem; color: var(--text-primary); }
250
  .site-footer { text-align: center; padding: 2rem 0; margin-top: 3rem; border-top: 1px solid var(--panel-border); color: var(--text-tertiary); }
251
  .site-footer a { color: var(--accent-primary); text-decoration: none; font-weight: 600; }
252
- .contact-section { background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%); padding: 3rem 2rem; margin-top: 4rem; border-radius: var(--radius-card); text-align: center; color: white; box-shadow: var(--shadow-xl); }
253
- .contact-section h2 { margin: 0 0 1rem 0; font-size: 2em; font-weight: 800; text-shadow: 0 2px 4px rgba(0,0,0,0.2); }
254
- .contact-section p { margin: 0 0 2rem 0; opacity: 0.9; font-size: 1.1em; }
255
- .contact-button { display: inline-flex; align-items: center; gap: 12px; padding: 1rem 2rem; background-color: white; color: #1DA1F2; border-radius: var(--radius-btn); text-decoration: none; font-weight: 700; font-size: 1.1em; box-shadow: var(--shadow-lg); transition: all 0.3s ease; }
256
  </style>
257
  </head>
258
  <body>
@@ -262,7 +266,7 @@
262
  <div id="standard-view">
263
  <header class="app-header">
264
  <h1>مولد صدای هوشمند Ai Sada</h1>
265
- <p>کیفیت استودیو، قدرت هوش مصنوعی. متن فارسی را به صدایی طبیعی و با کیفیت استودیویی تبدیل کنید یا پادکست‌های چند نفره بسازید. این سرویس کاملاً <strong>رایگان</strong> و بدون محدودیت است.</p>
266
  </header>
267
  <main class="main-content">
268
  <form id="standard-tts-form" onsubmit="return false;">
@@ -341,18 +345,22 @@
341
  <div class="option-card" data-mode="single">
342
  <div class="option-icon">👤</div>
343
  <div class="option-content"><div class="option-title">با یک گوینده</div><div class="option-subtitle">متن به صورت یکپارچه با گوینده انتخابی شما خوانده می‌شود.</div></div>
 
344
  </div>
345
  <div class="option-card" data-mode="double">
346
  <div class="option-icon">👥</div>
347
  <div class="option-content"><div class="option-title">با دو گوینده</div><div class="option-subtitle">متن به صورت هوشمند بین دو گوینده تصادفی تقسیم می‌شود.</div></div>
 
348
  </div>
349
  <div class="option-card" data-mode="multi">
350
  <div class="option-icon">🎭</div>
351
  <div class="option-content"><div class="option-title">با چندین گوینده</div><div class="option-subtitle">هوش مصنوعی متن را تحلیل کرده و با چندین گوینده تصادفی اجرا می‌کند.</div></div>
 
352
  </div>
353
  <div class="option-card special-ai-card" data-mode="ai-podcast">
354
  <div class="option-icon">🎙️</div>
355
  <div class="option-content"><div class="option-title">تبدیل متن به پادکست</div><div class="option-subtitle">هوش مصنوعی متن شما را به سناریوی پادکست حرفه‌ای تبدیل می‌کند.</div></div>
 
356
  </div>
357
  </div>
358
  <div style="text-align: center; margin-top: 1.5rem;">
@@ -401,7 +409,7 @@
401
 
402
  <section class="landing-section features">
403
  <h2>ویژگی‌های کلیدی Ai Sada</h2>
404
- <p class="subtitle">ابزارهایی قدرتمند برای خلق تجربه‌های صوتی بی‌نظیر کاملاً رایگان.</p>
405
  <div class="features-grid">
406
  <div class="feature-card"><div class="icon">🔊</div><h3>کیفیت صدای استودیویی</h3><p>صداهای تولید شده با هوش مصنوعی پیشرفته ما، کاملا طبیعی، شفاف و بدون نویز هستند.</p></div>
407
  <div class="feature-card"><div class="icon">🎭</div><h3>۳۰ گوینده متنوع</h3><p>از میان گالری وسیعی از صداهای مرد و زن با احساسات و لحن‌های مختلف، گوینده دلخواه خود را انتخاب کنید.</p></div>
@@ -409,15 +417,6 @@
409
  </div>
410
  </section>
411
 
412
- <section class="contact-section">
413
- <h2>پشتیبانی و ارتباط با ما</h2>
414
- <p>برای سوالات، پیشنهادات یا دریافت پشتیبانی، از طریق تلگرام با ما در ارتباط باشید.</p>
415
- <a href="https://t.me/ezmarynoori" target="_blank" rel="noopener noreferrer" class="contact-button">
416
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M9.78 18.65l.28-4.23 7.68-6.92c.34-.31-.07-.46-.52-.19L7.74 13.3 3.64 12c-.88-.25-.89-.86.2-1.3l15.97-6.16c.73-.33 1.43.18 1.15 1.3l-2.72 12.58c-.28 1.13-1.04 1.4-1.74.88L14.25 16l-2.47 2.35c-.22.24-.42.44-.69.44l.32-4.14z"></path></svg>
417
- <span>ارتباط در تلگرام</span>
418
- </a>
419
- </section>
420
-
421
  <footer class="site-footer"><p>&copy; 1404 - تمام حقوق برای <a href="#">Ai Sada</a> محفوظ است.</p></footer>
422
  </div>
423
 
@@ -473,7 +472,7 @@ document.addEventListener('DOMContentLoaded', () => {
473
  { id: "Enceladus", name: "کامیار (مرد)", gender: "male", desc: "مصمم و جدی", imgUrl: "https://uploadkon.ir/uploads/127805_25IMG-۲۰۲۵۰۷۰۵-۱۲۲۴۱۴.jpg", samples:["https://uploadkon.ir/uploads/566606_26کامیار-یک-2-.mp3"], sampleTexts: [""] },
474
  { id: "Iapetus", name: "کیانوش (مرد)", gender: "male", desc: "درخشان و گیرا", imgUrl: "https://uploadkon.ir/uploads/c98b05_25IMG-۲۰۲۵۰۷۰۵-۱۲۲۶۰۵.jpg", samples:["https://uploadkon.ir/uploads/63fd06_26کیانوش-یک-2-.mp3"], sampleTexts: [""] },
475
  { id: "Puck", name: "پویا (مرد)", gender: "male", desc: "بازیگوش و سرزنده", imgUrl: "https://uploadkon.ir/uploads/ca3605_25IMG-۲۰۲۵۰۷۰۵-۱۲۲۸۳۹.jpg", samples:["https://uploadkon.ir/uploads/7d1306_26پویا-یک-2-.mp3"], sampleTexts: [""] },
476
- { id: "Kore", name: "مهتاب (زن)", gender: "female", desc: "نجواگر و آرامش‌بخش", imgUrl: "https://uploadkon.ir/uploads/b66605_25IMG-۲۰۲۵۰۷۰۵-۱۲۳۰۳۵.jpg", samples:["https://uploadkon.ir/uploads/9bfc06_26مهتاب-یک-2-.mp3"], sampleTexts: [""] },
477
  { id: "Fenrir", name: "سام (مرد)", gender: "male", desc: "جسور و بی‌باک", imgUrl: "https://uploadkon.ir/uploads/03c005_25IMG-۲۰۲۵۰۷۰۵-۱۲۳۴۱۳.jpg", samples:["https://uploadkon.ir/uploads/467f06_26سام-یک-2-.mp3"], sampleTexts: [""] },
478
  { id: "Leda", name: "لیدا (زن)", gender: "female", desc: "کلاسیک و باوقار", imgUrl: "https://uploadkon.ir/uploads/710305_25IMG-۲۰۲۵۰۷۰۵-۱۲۳۷۳۱.jpg", samples:["https://uploadkon.ir/uploads/547606_26لیدا-یک-2-.mp3"], sampleTexts: [""] }
479
  ];
@@ -687,6 +686,7 @@ document.addEventListener('DOMContentLoaded', () => {
687
  <div class="ai-podcast-title">ساخت پادکست هوشمند</div>
688
  <div class="ai-podcast-subtitle">تبدیل موضوع یا مقاله به گفتگوی حرفه‌ای</div>
689
  </div>
 
690
  `;
691
  podcastCard.addEventListener('click', () => {
692
  hideModal(speakerModal);
@@ -752,10 +752,19 @@ document.addEventListener('DOMContentLoaded', () => {
752
  studioContainer.className = 'podcast-studio-container';
753
  studioContainer.innerHTML = `
754
  <div class="form-group"><label>🎤 تیم گویندگان</label><div id="project-speakers-container"><div id="add-speaker-card" class="speaker-display-card"><div class="plus-icon">+</div><h3>افزودن گوینده</h3></div></div></div>
755
- <div class="form-group"><label>📜 سناریوی گفتگو</label><div id="podcast-script-container"></div><button type="button" id="add-turn-btn">+ افزودن نوبت گفتگو</button></div>
 
 
 
 
756
  <button type="submit" id="generate-btn-podcast" class="generate-btn"><span class="btn-text">🎙️ ساخت فایل صوتی نهایی</span><div class="spinner"></div></button>
757
- <div id="podcast-output-section" class="output-section" style="margin-top: 2rem; border-style: solid; background-color: transparent;"><div class="status-message">فایل نهایی در اینجا ظاهر خواهد شد.</div></div>
758
- <button type="button" id="clear-history-btn-podcast"><svg viewBox="0 0 24 24"><path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"></path></svg> حذف کامل پروژه و بازگشت</button>
 
 
 
 
 
759
  `;
760
  container.appendChild(studioContainer);
761
 
@@ -813,15 +822,30 @@ document.addEventListener('DOMContentLoaded', () => {
813
  turnDiv.className = 'script-turn';
814
  const turnIndex = container.children.length;
815
  turnDiv.innerHTML = `
816
- <div class="turn-speaker-selector"><div class="custom-select-container" data-selected-id="${speakerId}"><div class="custom-select-trigger"></div><div class="custom-select-options"></div></div></div>
 
 
 
 
 
817
  <div class="turn-content">
818
  <textarea>${text || ''}</textarea>
819
  <div class="turn-player-container">
820
- <button type="button" class="turn-play-btn"><svg viewBox="0 0 24 24" class="play-icon"><path d="M8 5v14l11-7z"></path></svg><svg viewBox="0 0 24 24" class="pause-icon"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"></path></svg></button>
 
 
 
821
  <audio class="turn-audio" style="display:none;"></audio>
822
- <div class="loading-state-wrapper" style="display:none;"><div class="spinner"></div><span class="loading-text">در حال جایگزینی...</span></div>
823
- <button type="button" class="turn-retry-btn">جایگزین قطعه</button>
824
- <span class="replace-success-message">جایگزین شد</span>
 
 
 
 
 
 
 
825
  </div>
826
  </div>
827
  <button type="button" class="remove-turn-btn" title="حذف نوبت">×</button>`;
@@ -931,7 +955,7 @@ document.addEventListener('DOMContentLoaded', () => {
931
  hideModal(longTextModal);
932
  await clearProjectState();
933
  activePodcastSpeakers =[];
934
- podcastMasterAudioBlobs = [];
935
 
936
  const chunks = splitText(text);
937
  let scriptData =[];
@@ -954,7 +978,7 @@ document.addEventListener('DOMContentLoaded', () => {
954
  }
955
 
956
  async function asyncPool(poolLimit, array, iteratorFn) {
957
- const ret = []; const executing =[];
958
  for (const item of array) {
959
  const p = Promise.resolve().then(() => iteratorFn(item));
960
  ret.push(p);
@@ -1107,8 +1131,13 @@ document.addEventListener('DOMContentLoaded', () => {
1107
  const retryBtn = playerContainer.querySelector('.turn-retry-btn');
1108
  const loadingWrapper = playerContainer.querySelector('.loading-state-wrapper');
1109
  const successMsg = playerContainer.querySelector('.replace-success-message');
 
1110
 
1111
- retryBtn.style.display = 'none'; loadingWrapper.style.display = 'flex'; successMsg.style.display = 'none';
 
 
 
 
1112
  try {
1113
  const response = await fetch(`${API_BASE}/generate`, {
1114
  method: 'POST',
@@ -1156,11 +1185,18 @@ document.addEventListener('DOMContentLoaded', () => {
1156
  await saveAudioSegment(turnIndex, finalBlob);
1157
  setupTurnPlayer(turnDiv, turnIndex);
1158
 
1159
- loadingWrapper.style.display = 'none'; successMsg.style.display = 'flex';
 
 
 
1160
  setTimeout(async () => {
1161
  successMsg.style.display = 'none';
1162
- if (podcastMasterAudioBlobs.every(b => b)) await rebuildFinalAudio(false);
1163
- retryBtn.style.display = 'flex';
 
 
 
 
1164
  }, 2500);
1165
  } catch (error) {
1166
  alert("خطا در جایگزینی قطعه.");
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>تولید صدای هوشمند و پادکست | AI Sada</title>
7
+ <meta name="description" content="با AI Sada، متن فارسی خود را به صدایی طبیعی و با کیفیت استودیویی تبدیل کنید.">
8
  <!-- Font Awesome for Icons -->
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
 
 
23
  --radius-card: 24px; --radius-btn: 14px; --radius-input: 12px;
24
  --transition-fast: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
25
  --transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
 
26
  --card-bg: #FFFFFF;
 
27
  }
28
  @keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
29
  @keyframes fadeInOut { 0% { opacity: 0; transform: translateY(10px); } 10% { opacity: 1; transform: translateY(0); } 90% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(-10px); } }
 
37
  @keyframes pulse-soft { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }
38
  @keyframes bounce { 0%, 20%, 50%, 80%, 100% {transform: translateY(0);} 40% {transform: translateY(-15px);} 60% {transform: translateY(-7px);} }
39
 
40
+ /* PODCAST STUDIO VIEW EXACTLY AS ORIGINAL */
41
  .podcast-studio-container { width: 100%; animation: fadeIn 0.5s ease-out; }
42
  .podcast-studio-container .form-group-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.2rem; }
43
  .podcast-studio-container #project-speakers-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 1.5rem; text-align: center; }
 
94
  .podcast-studio-container .loading-state-wrapper .loading-text { font-size: 0.85em; font-weight: 600; color: var(--accent-primary); white-space: nowrap; transition: all 0.3s; }
95
  .podcast-studio-container .turn-retry-btn { font-family: var(--app-font); font-size: 0.85em; font-weight: 600; background: none; border: 1px solid var(--input-border); color: var(--text-secondary); padding: 0.4rem 0.8rem; border-radius: var(--radius-btn); cursor: pointer; transition: var(--transition-smooth); margin-right: auto; display: flex; align-items: center; gap: 6px; }
96
  .podcast-studio-container .turn-retry-btn:hover { border-color: var(--accent-secondary); color: var(--accent-secondary); background: var(--accent-secondary-glow); }
97
+ .podcast-studio-container .turn-retry-btn svg { width: 16px; height: 16px; fill: currentColor; }
98
  .podcast-studio-container .replace-success-message, .podcast-studio-container .main-update-message { font-family: var(--app-font); font-size: 0.85em; font-weight: 600; padding: 0.4rem 0.8rem; border-radius: var(--radius-btn); white-space: nowrap; display: none; align-items: center; justify-content: center; margin-right: auto; }
99
  .podcast-studio-container .replace-success-message { background-color: var(--accent-secondary-glow); color: var(--accent-secondary); }
100
  .podcast-studio-container .main-update-message { background: linear-gradient(90deg, #e6fffa, #b2f5ea); color: #2c7a7b; border: 1px solid #81e6d9; box-shadow: 0 2px 5px rgba(0,0,0,0.05); }
101
 
102
  .beautiful-download-btn { display: flex; align-items: center; justify-content: center; gap: 12px; width: 100%; max-width: 400px; margin: 1.5rem auto 0.5rem auto; padding: 16px 24px; background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); color: #fff; border: none; border-radius: 14px; font-family: var(--app-font); font-weight: 800; font-size: 1.1em; cursor: pointer; position: relative; overflow: hidden; box-shadow: 0 4px 6px -1px rgba(37, 99, 235, 0.1), 0 2px 4px -1px rgba(37, 99, 235, 0.06); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); border-bottom: 4px solid #1d4ed8; }
103
  .beautiful-download-btn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 10px 15px -3px rgba(37, 99, 235, 0.2), 0 4px 6px -2px rgba(37, 99, 235, 0.1); filter: brightness(1.05); }
104
+ .beautiful-download-btn:active:not(:disabled) { transform: translateY(2px); border-bottom-width: 0px; margin-top: calc(1.5rem + 4px); }
105
 
106
  #clear-history-btn-podcast { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; max-width: 300px; margin: 1rem auto 0 auto; padding: 12px; background-color: #fff; border: 2px dashed #cbd5e0; color: #718096; font-family: var(--app-font); font-size: 0.95em; font-weight: 700; border-radius: 12px; cursor: pointer; transition: all 0.3s ease; }
107
  #clear-history-btn-podcast:hover { border-color: #fc8181; color: #e53e3e; background-color: #fff5f5; transform: translateY(-2px); }
108
+ #clear-history-btn-podcast svg { width: 18px; height: 18px; fill: currentColor; }
109
+
110
+ @media (max-width: 768px) { .podcast-studio-container .script-turn { flex-direction: column; } .podcast-studio-container .remove-turn-btn { align-self: flex-end; margin-top: -1.5rem; margin-right: -0.5rem; } .podcast-studio-container .turn-speaker-selector { width: 100%; } }
111
+
112
  /* Modals and Options */
113
  #long-text-modal .modal-dialog { max-width: 600px; background: linear-gradient(160deg, #ffffff 0%, #f7f9fc 100%); padding: 3rem 2.5rem; max-height: 90vh; overflow-y: auto; }
114
  #long-text-modal .modal-header { border: none; justify-content: center; flex-direction: column; text-align: center; margin-bottom: 1.5rem; }
 
122
  .option-content { flex-grow: 1; text-align: right; }
123
  .option-title { font-weight: 800; font-size: 1rem; color: var(--text-primary); margin-bottom: 2px; }
124
  .option-subtitle { font-size: 0.85rem; color: var(--text-secondary); line-height: 1.4; }
125
+ .option-arrow { color: var(--text-tertiary); transition: transform 0.3s ease; font-size: 1.2rem; } .option-card:hover .option-arrow { color: var(--accent-primary); transform: translateX(-5px); }
126
  .special-ai-card { background: linear-gradient(135deg, #f3f4f6 0%, #ffffff 100%); border-color: #d1d5db; box-shadow: 0 4px 6px rgba(0,0,0,0.05); }
127
  .special-ai-card:hover { border-color: #8b5cf6; background: linear-gradient(135deg, #f5f3ff 0%, #ffffff 100%); box-shadow: 0 10px 20px rgba(139, 92, 246, 0.15); }
128
  .special-ai-card .option-icon { background: #ede9fe; color: #8b5cf6; }
 
136
  .confirm-modal-dialog p { color: var(--text-secondary); margin: 0 0 2rem 0; line-height: 1.7; }
137
  .confirm-modal-actions { display: flex; gap: 1rem; justify-content: center; }
138
  .confirm-btn, .cancel-btn { font-family: var(--app-font); font-size: 1em; font-weight: 700; padding: 0.8rem 1.5rem; border-radius: var(--radius-btn); border: none; cursor: pointer; transition: all 0.2s ease; flex-grow: 1; }
139
+ .confirm-btn { background-color: #e53e3e; color: white; box-shadow: 0 4px 10px -2px rgba(229, 62, 62, 0.4); }
140
+ .confirm-btn:hover { background-color: #c53030; transform: translateY(-2px); box-shadow: 0 6px 14px -3px rgba(229, 62, 62, 0.5); }
141
+ .cancel-btn { background-color: var(--accent-primary); color: white; box-shadow: 0 4px 10px -2px rgba(74, 108, 250, 0.4); }
142
+ .cancel-btn:hover { background-color: var(--accent-primary-hover); transform: translateY(-2px); box-shadow: 0 6px 14px -3px rgba(74, 108, 250, 0.5); }
143
 
144
  .ai-podcast-card { grid-column: 1 / -1; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 18px !important; display: flex !important; flex-direction: row !important; align-items: center !important; justify-content: space-between !important; padding: 0 1.5rem !important; margin-top: 10px; cursor: pointer; min-height: 90px; }
145
  .ai-podcast-card:hover { transform: translateY(-5px) scale(1.02) !important; box-shadow: 0 15px 30px rgba(118, 75, 162, 0.6) !important; }
 
147
  .ai-podcast-title { font-size: 1.3rem; font-weight: 900; color: white; margin-bottom: 4px; display: flex; align-items: center; gap: 8px; }
148
  .ai-podcast-subtitle { font-size: 0.9rem; color: rgba(255,255,255,0.9); font-weight: 500; }
149
  .ai-podcast-icon { font-size: 2.5rem; margin-left: 1rem; animation: pulse-soft 2s infinite; }
150
+ .ai-arrow-icon { width: 30px; height: 30px; background: rgba(255,255,255,0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; transition: 0.3s; }
151
+ .ai-podcast-card:hover .ai-arrow-icon { background: white; color: #764ba2; transform: translateX(-5px); }
152
 
153
  #ai-podcast-modal .modal-header h2 { background: linear-gradient(90deg, #FF0080, #7928CA); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-size: 1.8rem; }
154
  #ai-podcast-input { width: 100%; min-height: 180px; padding: 1.2rem; border-radius: 16px; border: 2px solid var(--input-border); background-color: var(--input-bg); font-family: var(--app-font); font-size: 1rem; line-height: 1.8; resize: vertical; transition: all 0.3s; margin: 1.5rem 0; }
155
+ #ai-podcast-input:focus { outline: none; border-color: #7928CA; background-color: white; box-shadow: 0 0 0 4px rgba(121, 40, 202, 0.1); }
156
 
157
  .sample-player-container { position: absolute; top: 50%; left: 4px; transform: translateY(-50%); display: flex; flex-direction: column; gap: 4px; z-index: 6; }
158
  .sample-play-btn { width: 24px; height: 24px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0; background: linear-gradient(145deg, rgba(255, 255, 255, 0.25), rgba(0, 0, 0, 0.25)); border: 1px solid rgba(255, 255, 255, 0.15); }
 
162
  .sample-play-btn.playing { background: var(--accent-secondary); }
163
  .sample-play-btn.playing .play-icon-svg { display: none; }
164
  .sample-play-btn.playing .playing-indicator { display: flex; }
 
165
 
166
  #sample-text-display { position: fixed; bottom: 25px; left: 50%; width: 90%; max-width: 600px; background: rgba(255, 255, 255, 0.85); backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.6); border-radius: 20px; padding: 20px 24px; box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.15); z-index: 2000; text-align: right; direction: rtl; display: flex; flex-direction: column; align-items: flex-start; opacity: 0; visibility: hidden; transform: translateX(-50%) translateY(30px) scale(0.95); transition: all 0.5s; }
167
  #sample-text-display.active { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0) scale(1); }
168
+ #sample-text-display h4 { margin: 0 0 10px; color: var(--accent-primary); font-size: 0.8em; font-weight: 800; display: flex; align-items: center; gap: 8px; }
169
+ #sample-text-display h4::before { content: '\f10d'; font-family: 'Font Awesome 6 Free'; font-weight: 900; font-size: 1.2em; opacity: 0.7; }
170
  #sample-text-display p { margin: 0; color: var(--text-primary); font-weight: 500; font-size: 0.9em; line-height: 1.7; }
171
 
172
  #speaker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 1.5rem; padding-bottom: 0; transition: padding-bottom 0.4s; }
 
257
  .feature-card h3 { font-size: 1.3em; margin-bottom: 0.5rem; color: var(--text-primary); }
258
  .site-footer { text-align: center; padding: 2rem 0; margin-top: 3rem; border-top: 1px solid var(--panel-border); color: var(--text-tertiary); }
259
  .site-footer a { color: var(--accent-primary); text-decoration: none; font-weight: 600; }
 
 
 
 
260
  </style>
261
  </head>
262
  <body>
 
266
  <div id="standard-view">
267
  <header class="app-header">
268
  <h1>مولد صدای هوشمند Ai Sada</h1>
269
+ <p>کیفیت استودیو، قدرت هوش مصنوعی. متن فارسی را به صدایی طبیعی و با کیفیت استودیویی تبدیل کنید یا پادکست‌های چند نفره بسازید.</p>
270
  </header>
271
  <main class="main-content">
272
  <form id="standard-tts-form" onsubmit="return false;">
 
345
  <div class="option-card" data-mode="single">
346
  <div class="option-icon">👤</div>
347
  <div class="option-content"><div class="option-title">با یک گوینده</div><div class="option-subtitle">متن به صورت یکپارچه با گوینده انتخابی شما خوانده می‌شود.</div></div>
348
+ <div class="option-arrow">❮</div>
349
  </div>
350
  <div class="option-card" data-mode="double">
351
  <div class="option-icon">👥</div>
352
  <div class="option-content"><div class="option-title">با دو گوینده</div><div class="option-subtitle">متن به صورت هوشمند بین دو گوینده تصادفی تقسیم می‌شود.</div></div>
353
+ <div class="option-arrow">❮</div>
354
  </div>
355
  <div class="option-card" data-mode="multi">
356
  <div class="option-icon">🎭</div>
357
  <div class="option-content"><div class="option-title">با چندین گوینده</div><div class="option-subtitle">هوش مصنوعی متن را تحلیل کرده و با چندین گوینده تصادفی اجرا می‌کند.</div></div>
358
+ <div class="option-arrow">❮</div>
359
  </div>
360
  <div class="option-card special-ai-card" data-mode="ai-podcast">
361
  <div class="option-icon">🎙️</div>
362
  <div class="option-content"><div class="option-title">تبدیل متن به پادکست</div><div class="option-subtitle">هوش مصنوعی متن شما را به سناریوی پادکست حرفه‌ای تبدیل می‌کند.</div></div>
363
+ <div class="option-arrow">✨</div>
364
  </div>
365
  </div>
366
  <div style="text-align: center; margin-top: 1.5rem;">
 
409
 
410
  <section class="landing-section features">
411
  <h2>ویژگی‌های کلیدی Ai Sada</h2>
412
+ <p class="subtitle">ابزارهایی قدرتمند برای خلق تجربه‌های صوتی بی‌نظیر.</p>
413
  <div class="features-grid">
414
  <div class="feature-card"><div class="icon">🔊</div><h3>کیفیت صدای استودیویی</h3><p>صداهای تولید شده با هوش مصنوعی پیشرفته ما، کاملا طبیعی، شفاف و بدون نویز هستند.</p></div>
415
  <div class="feature-card"><div class="icon">🎭</div><h3>۳۰ گوینده متنوع</h3><p>از میان گالری وسیعی از صداهای مرد و زن با احساسات و لحن‌های مختلف، گوینده دلخواه خود را انتخاب کنید.</p></div>
 
417
  </div>
418
  </section>
419
 
 
 
 
 
 
 
 
 
 
420
  <footer class="site-footer"><p>&copy; 1404 - تمام حقوق برای <a href="#">Ai Sada</a> محفوظ است.</p></footer>
421
  </div>
422
 
 
472
  { id: "Enceladus", name: "کامیار (مرد)", gender: "male", desc: "مصمم و جدی", imgUrl: "https://uploadkon.ir/uploads/127805_25IMG-۲۰۲۵۰۷۰۵-۱۲۲۴۱۴.jpg", samples:["https://uploadkon.ir/uploads/566606_26کامیار-یک-2-.mp3"], sampleTexts: [""] },
473
  { id: "Iapetus", name: "کیانوش (مرد)", gender: "male", desc: "درخشان و گیرا", imgUrl: "https://uploadkon.ir/uploads/c98b05_25IMG-۲۰۲۵۰۷۰۵-۱۲۲۶۰۵.jpg", samples:["https://uploadkon.ir/uploads/63fd06_26کیانوش-یک-2-.mp3"], sampleTexts: [""] },
474
  { id: "Puck", name: "پویا (مرد)", gender: "male", desc: "بازیگوش و سرزنده", imgUrl: "https://uploadkon.ir/uploads/ca3605_25IMG-۲۰۲۵۰۷۰۵-۱۲۲۸۳۹.jpg", samples:["https://uploadkon.ir/uploads/7d1306_26پویا-یک-2-.mp3"], sampleTexts: [""] },
475
+ { id: "Kore", name: "مهتاب (زن)", gender: "female", desc: "نجواگر و آرامش‌بخش", imgUrl: "https://uploadkon.ir/uploads/b66605_25IMG-۲۰۲۵۰۷۰۵-۱۲۳0۳۵.jpg", samples:["https://uploadkon.ir/uploads/9bfc06_26مهتاب-یک-2-.mp3"], sampleTexts: [""] },
476
  { id: "Fenrir", name: "سام (مرد)", gender: "male", desc: "جسور و بی‌باک", imgUrl: "https://uploadkon.ir/uploads/03c005_25IMG-۲۰۲۵۰۷۰۵-۱۲۳۴۱۳.jpg", samples:["https://uploadkon.ir/uploads/467f06_26سام-یک-2-.mp3"], sampleTexts: [""] },
477
  { id: "Leda", name: "لیدا (زن)", gender: "female", desc: "کلاسیک و باوقار", imgUrl: "https://uploadkon.ir/uploads/710305_25IMG-۲۰۲۵۰۷۰۵-۱۲۳۷۳۱.jpg", samples:["https://uploadkon.ir/uploads/547606_26لیدا-یک-2-.mp3"], sampleTexts: [""] }
478
  ];
 
686
  <div class="ai-podcast-title">ساخت پادکست هوشمند</div>
687
  <div class="ai-podcast-subtitle">تبدیل موضوع یا مقاله به گفتگوی حرفه‌ای</div>
688
  </div>
689
+ <div class="ai-arrow-icon"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg></div>
690
  `;
691
  podcastCard.addEventListener('click', () => {
692
  hideModal(speakerModal);
 
752
  studioContainer.className = 'podcast-studio-container';
753
  studioContainer.innerHTML = `
754
  <div class="form-group"><label>🎤 تیم گویندگان</label><div id="project-speakers-container"><div id="add-speaker-card" class="speaker-display-card"><div class="plus-icon">+</div><h3>افزودن گوینده</h3></div></div></div>
755
+ <div class="form-group">
756
+ <label>📜 سناریوی گفتگو</label>
757
+ <div id="podcast-script-container"></div>
758
+ <button type="button" id="add-turn-btn">+ افزودن نوبت گفتگو</button>
759
+ </div>
760
  <button type="submit" id="generate-btn-podcast" class="generate-btn"><span class="btn-text">🎙️ ساخت فایل صوتی نهایی</span><div class="spinner"></div></button>
761
+ <div id="podcast-output-section" class="output-section" style="margin-top: 2rem; border-style: solid; background-color: transparent;">
762
+ <div class="status-message">فایل نهایی در اینجا ظاهر خواهد شد.</div>
763
+ </div>
764
+ <button type="button" id="clear-history-btn-podcast">
765
+ <svg viewBox="0 0 24 24"><path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"></path></svg>
766
+ حذف کامل پروژه و شروع مجدد
767
+ </button>
768
  `;
769
  container.appendChild(studioContainer);
770
 
 
822
  turnDiv.className = 'script-turn';
823
  const turnIndex = container.children.length;
824
  turnDiv.innerHTML = `
825
+ <div class="turn-speaker-selector">
826
+ <div class="custom-select-container" data-selected-id="${speakerId}">
827
+ <div class="custom-select-trigger"></div>
828
+ <div class="custom-select-options"></div>
829
+ </div>
830
+ </div>
831
  <div class="turn-content">
832
  <textarea>${text || ''}</textarea>
833
  <div class="turn-player-container">
834
+ <button type="button" class="turn-play-btn">
835
+ <svg viewBox="0 0 24 24" class="play-icon"><path d="M8 5v14l11-7z"></path></svg>
836
+ <svg viewBox="0 0 24 24" class="pause-icon"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"></path></svg>
837
+ </button>
838
  <audio class="turn-audio" style="display:none;"></audio>
839
+ <div class="loading-state-wrapper" style="display:none;">
840
+ <div class="spinner"></div>
841
+ <span class="loading-text">در حال جایگزینی...</span>
842
+ </div>
843
+ <button type="button" class="turn-retry-btn">
844
+ <svg viewBox="0 0 24 24"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"></path></svg>
845
+ جایگزین قطعه
846
+ </button>
847
+ <span class="replace-success-message">فایل جدید جایگزین شد</span>
848
+ <span class="main-update-message">فایل اصلی بروز شد</span>
849
  </div>
850
  </div>
851
  <button type="button" class="remove-turn-btn" title="حذف نوبت">×</button>`;
 
955
  hideModal(longTextModal);
956
  await clearProjectState();
957
  activePodcastSpeakers =[];
958
+ podcastMasterAudioBlobs =[];
959
 
960
  const chunks = splitText(text);
961
  let scriptData =[];
 
978
  }
979
 
980
  async function asyncPool(poolLimit, array, iteratorFn) {
981
+ const ret =[]; const executing =[];
982
  for (const item of array) {
983
  const p = Promise.resolve().then(() => iteratorFn(item));
984
  ret.push(p);
 
1131
  const retryBtn = playerContainer.querySelector('.turn-retry-btn');
1132
  const loadingWrapper = playerContainer.querySelector('.loading-state-wrapper');
1133
  const successMsg = playerContainer.querySelector('.replace-success-message');
1134
+ const mainMsg = playerContainer.querySelector('.main-update-message');
1135
 
1136
+ retryBtn.style.display = 'none';
1137
+ loadingWrapper.style.display = 'flex';
1138
+ successMsg.style.display = 'none';
1139
+ mainMsg.style.display = 'none';
1140
+
1141
  try {
1142
  const response = await fetch(`${API_BASE}/generate`, {
1143
  method: 'POST',
 
1185
  await saveAudioSegment(turnIndex, finalBlob);
1186
  setupTurnPlayer(turnDiv, turnIndex);
1187
 
1188
+ loadingWrapper.style.display = 'none';
1189
+ successMsg.style.display = 'flex';
1190
+ successMsg.style.animation = 'fadeInOut 2.5s forwards';
1191
+
1192
  setTimeout(async () => {
1193
  successMsg.style.display = 'none';
1194
+ if (podcastMasterAudioBlobs.every(b => b)) {
1195
+ mainMsg.style.display = 'flex';
1196
+ mainMsg.style.animation = 'fadeInOut 2.5s forwards';
1197
+ await rebuildFinalAudio(false);
1198
+ }
1199
+ setTimeout(() => { mainMsg.style.display = 'none'; retryBtn.style.display = 'flex'; }, 2500);
1200
  }, 2500);
1201
  } catch (error) {
1202
  alert("خطا در جایگزینی قطعه.");