Opera8 commited on
Commit
70521ce
·
verified ·
1 Parent(s): f13ec06

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +113 -163
index.html CHANGED
@@ -19,6 +19,8 @@
19
  --accent-secondary: #9F7AEA; /* رنگ دوم برای گرادینت */
20
  --accent-glow: rgba(74, 108, 250, 0.2);
21
  --success-color: #38A169;
 
 
22
  --radius-card: 20px;
23
  --radius-btn: 14px;
24
  }
@@ -26,123 +28,60 @@
26
  * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
27
 
28
  body {
29
- font-family: var(--app-font);
30
- background-color: var(--app-bg);
31
- color: var(--text-primary);
32
- margin: 0;
33
- padding: 20px 15px;
34
- min-height: 100vh;
35
- display: flex;
36
- flex-direction: column;
37
- align-items: center;
38
  }
39
 
40
  .container { max-width: 650px; width: 100%; z-index: 2; position: relative; }
41
 
42
  #music-canvas {
43
- position: fixed; top: 0; left: 0; width: 100%; height: 400px;
44
- z-index: 0; opacity: 0.5; pointer-events: none;
45
- mask-image: linear-gradient(to bottom, black, transparent);
46
- -webkit-mask-image: linear-gradient(to bottom, black, transparent);
47
  }
48
 
49
  header { text-align: center; margin-bottom: 2rem; position: relative; }
50
-
51
  .logo-box {
52
- width: 80px; height: 80px; margin: 0 auto 15px;
53
- background: #fff; border-radius: 50%;
54
- display: flex; align-items: center; justify-content: center;
55
- box-shadow: 0 10px 25px var(--accent-glow);
56
- color: var(--accent-primary);
57
  }
58
-
59
  h1 {
60
- font-size: 1.8rem; font-weight: 800; margin: 0;
61
- background: linear-gradient(90deg, #2d3748, #4A6CFA);
62
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
63
  }
64
  .subtitle { font-size: 0.9rem; color: var(--text-secondary); margin-top: 5px; }
65
 
66
- .card {
67
- background: var(--panel-bg);
68
- border-radius: var(--radius-card);
69
- box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.05);
70
- border: 1px solid var(--panel-border);
71
- padding: 25px; margin-bottom: 20px;
72
- }
73
 
74
  .form-label { display: flex; align-items: center; gap: 8px; font-weight: 700; margin-bottom: 12px; color: #2d3748; }
75
  .form-label svg { color: var(--accent-primary); width: 20px; }
76
 
77
  textarea, input, select {
78
- width: 100%; background: var(--input-bg);
79
- border: 2px solid var(--input-border);
80
- border-radius: 12px; padding: 15px;
81
- font-family: inherit; font-size: 1rem; color: #2d3748;
82
- outline: none; transition: border-color 0.3s;
83
  }
84
  textarea { min-height: 120px; resize: vertical; }
85
  textarea:focus, input:focus, select:focus { border-color: var(--accent-primary); background: #fff; }
86
 
87
- /* --- START: دکمه اصلی کاملاً جدید و خفن --- */
88
- @keyframes move-gradient {
89
- 0% { background-position: 0% 50%; }
90
- 50% { background-position: 100% 50%; }
91
- 100% { background-position: 0% 50%; }
92
- }
93
-
94
  .btn-main {
95
- width: 100%; padding: 16px;
96
- background: linear-gradient(110deg, var(--accent-secondary), var(--accent-primary), var(--accent-secondary));
97
- background-size: 200% 100%;
98
- color: #fff; border: none; border-radius: var(--radius-btn);
99
- font-size: 1.1rem; font-weight: 700; cursor: pointer;
100
- display: flex; justify-content: center; align-items: center; gap: 10px;
101
- transition: all 0.3s ease-out;
102
- position: relative;
103
- z-index: 1;
104
  }
105
- .btn-main-content {
106
- display: flex;
107
- align-items: center;
108
- justify-content: center;
109
- gap: 10px;
110
- transition: transform 0.3s ease;
111
- }
112
- .btn-main:hover {
113
- transform: translateY(-4px);
114
- box-shadow: 0 12px 28px -5px rgba(124, 93, 250, 0.4);
115
- }
116
- .btn-main:hover .btn-main-content {
117
- transform: scale(1.05);
118
- }
119
- .btn-main::before { /* لایه زیرین برای افکت حاشیه */
120
- content: '';
121
- position: absolute;
122
- top: -2px; left: -2px; right: -2px; bottom: -2px;
123
  background: linear-gradient(110deg, var(--accent-secondary), var(--accent-primary), var(--accent-secondary));
124
- background-size: 400%;
125
- border-radius: 16px;
126
- z-index: -1;
127
- opacity: 0;
128
- transition: opacity 0.4s ease-out;
129
- }
130
- .btn-main:hover::before {
131
- opacity: 1;
132
- animation: move-gradient 4s linear infinite;
133
  }
 
134
  .btn-main:active { transform: scale(0.98); }
135
  .btn-main:disabled { opacity: 0.7; cursor: not-allowed; filter: grayscale(1); }
136
- /* --- END: دکمه اصلی کاملاً جدید --- */
137
-
138
- .btn-outline {
139
- background: transparent; border: 2px solid var(--input-border);
140
- color: var(--text-secondary); margin-top: 10px;
141
- transition: all 0.2s;
142
- }
143
  .btn-outline:hover { border-color: var(--accent-primary); color: var(--accent-primary); }
144
 
145
- /* تنظیمات پیشرفته */
146
  .accordion {
147
  background-color: var(--input-bg); color: var(--text-primary); cursor: pointer; padding: 15px; width: 100%; border: none;
148
  text-align: right; outline: none; font-size: 0.95rem; font-weight: 600; border-radius: 12px;
@@ -153,55 +92,59 @@
153
  .accordion.active { border-radius: 12px 12px 0 0; border-bottom: none; }
154
  .accordion.active:after { content: '🔼'; }
155
 
156
- .panel {
157
- padding: 0 15px; background-color: var(--input-bg); max-height: 0; overflow: hidden;
158
- transition: max-height 0.3s ease-out; border-radius: 0 0 12px 12px;
159
- margin-bottom: 0; border: 1px solid var(--input-border); border-top: none;
160
- }
161
  .settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; padding: 15px 0; }
162
  .settings-group label { font-size: 0.8rem; color: var(--text-secondary); display: block; margin-bottom: 6px; font-weight: 500; }
163
  .checkbox-wrapper { display: flex; align-items: center; gap: 10px; margin-top: 5px; padding-bottom: 15px; border-top: 1px solid #e2e8f0; padding-top: 15px; }
164
  .checkbox-wrapper input { width: 20px; height: 20px; cursor: pointer; }
165
 
166
- /* پلیر و متن */
167
  .player-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid var(--panel-border); }
168
- #mainDownloadLink {
169
- background-color: rgba(74, 108, 250, 0.1); color: var(--accent-primary); text-decoration: none; font-size: 0.9rem;
170
- font-weight: 700; padding: 8px 16px; border-radius: 10px; transition: 0.2s; display: none;
171
- }
172
  #mainDownloadLink:hover { background-color: rgba(74, 108, 250, 0.2); }
173
- .audio-item { margin-bottom: 10px; }
174
  audio { width: 100%; height: 45px; border-radius: 20px; margin-top: 5px; }
175
- .lyrics-container {
176
- background: var(--input-bg); border-radius: 16px; padding: 20px; max-height: 350px; overflow-y: auto;
177
- white-space: pre-wrap; line-height: 2; font-size: 1rem; color: #4a5568; text-align: center; border: 1px solid var(--input-border); margin-top: 15px;
178
- }
179
  .lyrics-tag { color: var(--accent-primary); font-weight: 800; display: block; margin-top: 20px; margin-bottom: 5px; font-size: 0.9em; letter-spacing: 1px; text-transform: uppercase; }
180
 
181
- /* لودر */
182
  #loader { display: none; text-align: center; padding: 20px; }
183
  .wave-bars { display: flex; justify-content: center; gap: 4px; height: 30px; align-items: flex-end; }
184
  .bar { width: 5px; background: var(--accent-primary); animation: jump 1s infinite; border-radius: 2px; }
185
- .bar:nth-child(2) { animation-delay: 0.1s; height: 60%; }
186
- .bar:nth-child(3) { animation-delay: 0.2s; height: 80%; }
187
- .bar:nth-child(4) { animation-delay: 0.3s; height: 50%; }
188
  @keyframes jump { 0%, 100% { height: 20%; } 50% { height: 100%; } }
189
 
190
- /* تاریخچه */
191
  #historySection { margin-top: 30px; width: 100%; }
192
  .history-title { font-size: 1.2rem; font-weight: 800; color: var(--text-primary); margin-bottom: 15px; display: flex; align-items: center; gap: 10px; }
193
  .history-list { display: grid; gap: 12px; }
194
- .history-card {
195
- background: white; border-radius: 16px; padding: 15px; display: flex; align-items: center; justify-content: space-between;
196
- border: 1px solid var(--panel-border); transition: all 0.3s; cursor: pointer;
197
- }
198
  .history-card:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(74, 108, 250, 0.15); border-color: var(--accent-primary); }
199
  .h-info { display: flex; align-items: center; gap: 15px; }
200
  .h-icon { width: 45px; height: 45px; background: var(--input-bg); border-radius: 12px; display: flex; align-items: center; justify-content: center; color: var(--accent-primary); font-size: 1.2rem; }
201
  .h-details h4 { margin: 0; font-size: 1rem; color: var(--text-primary); font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px; }
202
  .h-details span { font-size: 0.8rem; color: var(--text-secondary); }
203
- .h-play { color: var(--accent-primary); opacity: 0; transition: 0.3s; transform: scale(0.8); }
204
- .history-card:hover .h-play { opacity: 1; transform: scale(1); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
 
206
  @keyframes slideUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
207
  </style>
@@ -211,20 +154,14 @@
211
 
212
  <div class="container">
213
  <header>
214
- <div class="logo-box">
215
- <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>
216
- </div>
217
  <h1>استودیو موزیک آلفا</h1>
218
  <p class="subtitle">ساخت آهنگ حرفه‌ای با هوش مصنوعی</p>
219
  </header>
220
 
221
  <div id="step1" class="card">
222
- <div class="form-label">
223
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path></svg>
224
- موضوع آهنگ چیه؟
225
- </div>
226
  <textarea id="ideaInput" placeholder="مثال: آهنگ تولد برای سمیرا، سبک پاپ شاد..."></textarea>
227
-
228
  <button class="accordion">تنظیمات پیشرفته</button>
229
  <div class="panel">
230
  <div class="settings-grid">
@@ -256,17 +193,26 @@
256
  </div>
257
 
258
  <div id="historySection">
259
- <div class="history-title">
260
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
261
- آخرین آهنگ‌های ساخته شده
262
- </div>
263
  <div class="history-list" id="historyList"></div>
264
  </div>
265
  </div>
266
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  <script>
268
  const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
269
- let db;
270
 
271
  const ideaInput = document.getElementById('ideaInput');
272
  const processBtn = document.getElementById('processBtn');
@@ -279,6 +225,9 @@
279
  const mainDownloadLink = document.getElementById('mainDownloadLink');
280
  const historyList = document.getElementById('historyList');
281
  const historySection = document.getElementById('historySection');
 
 
 
282
 
283
  const getVal = (id) => document.getElementById(id).value;
284
  const getNum = (id) => Number(document.getElementById(id).value);
@@ -286,41 +235,27 @@
286
 
287
  const acc = document.getElementsByClassName("accordion");
288
  for (let i = 0; i < acc.length; i++) {
289
- acc[i].addEventListener("click", function() {
290
- this.classList.toggle("active");
291
- const panel = this.nextElementSibling;
292
- panel.style.maxHeight = panel.style.maxHeight ? null : panel.scrollHeight + "px";
293
- });
294
  }
295
 
296
  function initDB() {
297
  const request = indexedDB.open("alphaMusicDB", 1);
298
- request.onerror = (event) => console.error("IndexedDB error:", event);
299
- request.onsuccess = (event) => { db = event.target.result; loadHistory(); };
300
- request.onupgradeneeded = (event) => {
301
- event.target.result.createObjectStore("songs", { keyPath: "id" });
302
- };
303
  }
304
 
305
  async function saveToHistory(idea, lyrics, audioUrl) {
306
  try {
307
  const response = await fetch(audioUrl);
308
  const audioBlob = await response.blob();
309
- const newItem = {
310
- id: Date.now(), idea, lyrics, audioBlob,
311
- date: new Date().toLocaleDateString('fa-IR', { hour: '2-digit', minute: '2-digit' })
312
- };
313
  const transaction = db.transaction(["songs"], "readwrite");
314
  const store = transaction.objectStore("songs");
315
  store.add(newItem);
316
-
317
- const countReq = store.count();
318
- countReq.onsuccess = () => {
319
- if (countReq.result > 10) {
320
- store.openCursor().onsuccess = (e) => {
321
- const cursor = e.target.result;
322
- if (cursor) cursor.delete();
323
- };
324
  }
325
  };
326
  transaction.oncomplete = () => loadHistory();
@@ -340,27 +275,53 @@
340
  history.forEach((item) => {
341
  const div = document.createElement('div');
342
  div.className = 'history-card';
343
- div.innerHTML = `<div class="h-info"><div class="h-icon">🎵</div><div class="h-details"><h4>${item.idea.substring(0, 30)}${item.idea.length > 30 ? '...' : ''}</h4><span>${item.date}</span></div></div><div class="h-play">▶ نمایش</div>`;
344
- div.onclick = () => openHistoryItem(item);
 
 
 
 
 
 
 
 
345
  historyList.appendChild(div);
346
  });
347
  };
348
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
 
350
  function openHistoryItem(item) {
351
  step1.style.display = 'none';
352
  historySection.style.display = 'none';
353
-
354
  const audioURL = URL.createObjectURL(item.audioBlob);
355
  const headerText = document.getElementById('resultHeaderText');
356
  headerText.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg> آهنگ آرشیو شده`;
357
  headerText.style.color = 'var(--accent-primary)';
358
-
359
  mainDownloadLink.href = audioURL;
360
  mainDownloadLink.style.display = 'inline-block';
361
  playerWrapper.innerHTML = `<audio controls autoplay src="${audioURL}"></audio>`;
362
  finalLyricsBox.innerHTML = formatLyrics(item.lyrics);
363
-
364
  finalResult.style.display = 'block';
365
  window.scrollTo({ top: 0, behavior: 'smooth' });
366
  }
@@ -369,7 +330,6 @@
369
 
370
  processBtn.addEventListener('click', async () => {
371
  if (!ideaInput.value.trim()) return alert("لطفا موضوع آهنگ را بنویسید");
372
-
373
  processBtn.disabled = true;
374
  step1.style.display = 'none';
375
  historySection.style.display = 'none';
@@ -384,14 +344,7 @@
384
  const { lyrics, musicPrompt } = data;
385
  loaderText.innerText = "در حال ضبط آهنگ در استودیو آلفا...";
386
 
387
- const payload = [
388
- getVal('model_select'), "custom", null, "unknown", musicPrompt, lyrics, 0, "", "", "unknown",
389
- getNum('steps_input'), getNum('cfg_input'), true, getNum('seed_input'), null, -1,
390
- getNum('batch_size'), null, null, 0, -1, "Fill the audio semantic mask based on the given conditions:",
391
- 1, "text2music", false, 0, 1, 3, "ode", "", "mp3", 0.85,
392
- getChk('think_checkbox'), 2, 0, 0.9, "NO USER INPUT", true, true, true, null, false, true, false, false, 0.5, 8, null, [], false, null, null, null, null
393
- ];
394
-
395
  const session_hash = Math.random().toString(36).substring(2);
396
  const joinResp = await fetch(`${ACE_SPACE_URL}gradio_api/queue/join`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: payload, fn_index: 77, session_hash }) });
397
  if (!joinResp.ok) throw new Error('خطا در اتصال به سرور موزیک');
@@ -407,7 +360,6 @@
407
  }
408
  };
409
  eventSource.onerror = () => { throw new Error('ارتباط با سرور قطع شد.'); };
410
-
411
  } catch (e) {
412
  alert("خطا: " + e.message);
413
  loader.style.display = 'none';
@@ -426,13 +378,11 @@
426
  if (processedUrls.has(fullUrl)) return;
427
  processedUrls.add(fullUrl);
428
  hasResult = true;
429
-
430
  if (processedUrls.size === 1) {
431
  mainDownloadLink.href = fullUrl;
432
  mainDownloadLink.style.display = 'inline-block';
433
  saveToHistory(idea, lyrics, fullUrl);
434
  }
435
-
436
  playerWrapper.innerHTML += `<div class="audio-item"><audio controls autoplay src="${fullUrl}"></audio></div>`;
437
  }
438
 
 
19
  --accent-secondary: #9F7AEA; /* رنگ دوم برای گرادینت */
20
  --accent-glow: rgba(74, 108, 250, 0.2);
21
  --success-color: #38A169;
22
+ --danger-color: #e53e3e;
23
+ --danger-glow: rgba(229, 62, 62, 0.25);
24
  --radius-card: 20px;
25
  --radius-btn: 14px;
26
  }
 
28
  * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
29
 
30
  body {
31
+ font-family: var(--app-font); background-color: var(--app-bg); color: var(--text-primary); margin: 0;
32
+ padding: 20px 15px; min-height: 100vh; display: flex; flex-direction: column; align-items: center;
 
 
 
 
 
 
 
33
  }
34
 
35
  .container { max-width: 650px; width: 100%; z-index: 2; position: relative; }
36
 
37
  #music-canvas {
38
+ position: fixed; top: 0; left: 0; width: 100%; height: 400px; z-index: 0; opacity: 0.5; pointer-events: none;
39
+ mask-image: linear-gradient(to bottom, black, transparent); -webkit-mask-image: linear-gradient(to bottom, black, transparent);
 
 
40
  }
41
 
42
  header { text-align: center; margin-bottom: 2rem; position: relative; }
 
43
  .logo-box {
44
+ width: 80px; height: 80px; margin: 0 auto 15px; background: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center;
45
+ box-shadow: 0 10px 25px var(--accent-glow); color: var(--accent-primary);
 
 
 
46
  }
 
47
  h1 {
48
+ font-size: 1.8rem; font-weight: 800; margin: 0; background: linear-gradient(90deg, #2d3748, #4A6CFA);
 
49
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
50
  }
51
  .subtitle { font-size: 0.9rem; color: var(--text-secondary); margin-top: 5px; }
52
 
53
+ .card { background: var(--panel-bg); border-radius: var(--radius-card); box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.05); border: 1px solid var(--panel-border); padding: 25px; margin-bottom: 20px; }
 
 
 
 
 
 
54
 
55
  .form-label { display: flex; align-items: center; gap: 8px; font-weight: 700; margin-bottom: 12px; color: #2d3748; }
56
  .form-label svg { color: var(--accent-primary); width: 20px; }
57
 
58
  textarea, input, select {
59
+ width: 100%; background: var(--input-bg); border: 2px solid var(--input-border); border-radius: 12px; padding: 15px;
60
+ font-family: inherit; font-size: 1rem; color: #2d3748; outline: none; transition: border-color 0.3s;
 
 
 
61
  }
62
  textarea { min-height: 120px; resize: vertical; }
63
  textarea:focus, input:focus, select:focus { border-color: var(--accent-primary); background: #fff; }
64
 
65
+ @keyframes move-gradient { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }
 
 
 
 
 
 
66
  .btn-main {
67
+ width: 100%; padding: 16px; background: linear-gradient(110deg, var(--accent-secondary), var(--accent-primary), var(--accent-secondary));
68
+ background-size: 200% 100%; color: #fff; border: none; border-radius: var(--radius-btn); font-size: 1.1rem; font-weight: 700;
69
+ cursor: pointer; display: flex; justify-content: center; align-items: center; gap: 10px; transition: all 0.3s ease-out; position: relative; z-index: 1;
 
 
 
 
 
 
70
  }
71
+ .btn-main-content { display: flex; align-items: center; justify-content: center; gap: 10px; transition: transform 0.3s ease; }
72
+ .btn-main:hover { transform: translateY(-4px); box-shadow: 0 12px 28px -5px rgba(124, 93, 250, 0.4); }
73
+ .btn-main:hover .btn-main-content { transform: scale(1.05); }
74
+ .btn-main::before {
75
+ content: ''; position: absolute; top: -2px; left: -2px; right: -2px; bottom: -2px;
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  background: linear-gradient(110deg, var(--accent-secondary), var(--accent-primary), var(--accent-secondary));
77
+ background-size: 400%; border-radius: 16px; z-index: -1; opacity: 0; transition: opacity 0.4s ease-out;
 
 
 
 
 
 
 
 
78
  }
79
+ .btn-main:hover::before { opacity: 1; animation: move-gradient 4s linear infinite; }
80
  .btn-main:active { transform: scale(0.98); }
81
  .btn-main:disabled { opacity: 0.7; cursor: not-allowed; filter: grayscale(1); }
82
+ .btn-outline { background: transparent; border: 2px solid var(--input-border); color: var(--text-secondary); margin-top: 10px; transition: all 0.2s; }
 
 
 
 
 
 
83
  .btn-outline:hover { border-color: var(--accent-primary); color: var(--accent-primary); }
84
 
 
85
  .accordion {
86
  background-color: var(--input-bg); color: var(--text-primary); cursor: pointer; padding: 15px; width: 100%; border: none;
87
  text-align: right; outline: none; font-size: 0.95rem; font-weight: 600; border-radius: 12px;
 
92
  .accordion.active { border-radius: 12px 12px 0 0; border-bottom: none; }
93
  .accordion.active:after { content: '🔼'; }
94
 
95
+ .panel { padding: 0 15px; background-color: var(--input-bg); max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; border-radius: 0 0 12px 12px; margin-bottom: 0; border: 1px solid var(--input-border); border-top: none; }
 
 
 
 
96
  .settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; padding: 15px 0; }
97
  .settings-group label { font-size: 0.8rem; color: var(--text-secondary); display: block; margin-bottom: 6px; font-weight: 500; }
98
  .checkbox-wrapper { display: flex; align-items: center; gap: 10px; margin-top: 5px; padding-bottom: 15px; border-top: 1px solid #e2e8f0; padding-top: 15px; }
99
  .checkbox-wrapper input { width: 20px; height: 20px; cursor: pointer; }
100
 
 
101
  .player-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid var(--panel-border); }
102
+ #mainDownloadLink { background-color: rgba(74, 108, 250, 0.1); color: var(--accent-primary); text-decoration: none; font-size: 0.9rem; font-weight: 700; padding: 8px 16px; border-radius: 10px; transition: 0.2s; display: none; }
 
 
 
103
  #mainDownloadLink:hover { background-color: rgba(74, 108, 250, 0.2); }
 
104
  audio { width: 100%; height: 45px; border-radius: 20px; margin-top: 5px; }
105
+ .lyrics-container { background: var(--input-bg); border-radius: 16px; padding: 20px; max-height: 350px; overflow-y: auto; white-space: pre-wrap; line-height: 2; font-size: 1rem; color: #4a5568; text-align: center; border: 1px solid var(--input-border); margin-top: 15px; }
 
 
 
106
  .lyrics-tag { color: var(--accent-primary); font-weight: 800; display: block; margin-top: 20px; margin-bottom: 5px; font-size: 0.9em; letter-spacing: 1px; text-transform: uppercase; }
107
 
 
108
  #loader { display: none; text-align: center; padding: 20px; }
109
  .wave-bars { display: flex; justify-content: center; gap: 4px; height: 30px; align-items: flex-end; }
110
  .bar { width: 5px; background: var(--accent-primary); animation: jump 1s infinite; border-radius: 2px; }
 
 
 
111
  @keyframes jump { 0%, 100% { height: 20%; } 50% { height: 100%; } }
112
 
 
113
  #historySection { margin-top: 30px; width: 100%; }
114
  .history-title { font-size: 1.2rem; font-weight: 800; color: var(--text-primary); margin-bottom: 15px; display: flex; align-items: center; gap: 10px; }
115
  .history-list { display: grid; gap: 12px; }
116
+ .history-card { background: white; border-radius: 16px; padding: 15px; display: flex; align-items: center; justify-content: space-between; border: 1px solid var(--panel-border); transition: all 0.3s; cursor: pointer; position: relative; }
 
 
 
117
  .history-card:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(74, 108, 250, 0.15); border-color: var(--accent-primary); }
118
  .h-info { display: flex; align-items: center; gap: 15px; }
119
  .h-icon { width: 45px; height: 45px; background: var(--input-bg); border-radius: 12px; display: flex; align-items: center; justify-content: center; color: var(--accent-primary); font-size: 1.2rem; }
120
  .h-details h4 { margin: 0; font-size: 1rem; color: var(--text-primary); font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px; }
121
  .h-details span { font-size: 0.8rem; color: var(--text-secondary); }
122
+
123
+ .h-action { position: absolute; right: 15px; top: 15px; bottom: 15px; display: flex; align-items: center; justify-content: center; opacity: 0; transition: 0.3s; transform: scale(0.8); pointer-events: none; }
124
+ .history-card:hover .h-action.play, .history-card.show-delete .h-action.delete { opacity: 1; transform: scale(1); pointer-events: auto; }
125
+ .action-btn { font-weight: 600; display: flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 10px; }
126
+ .play-btn { color: var(--accent-primary); background-color: rgba(74, 108, 250, 0.1); }
127
+ .delete-btn { color: var(--danger-color); background-color: rgba(229, 62, 62, 0.1); }
128
+
129
+ /* مودال حذف */
130
+ .modal-overlay {
131
+ position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); backdrop-filter: blur(5px);
132
+ z-index: 1000; display: none; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s;
133
+ }
134
+ .modal-overlay.open { display: flex; opacity: 1; }
135
+ .modal-content {
136
+ background: white; border-radius: 20px; padding: 25px; transform: scale(0.9); transition: transform 0.3s;
137
+ width: 90%; max-width: 400px; text-align: center;
138
+ }
139
+ .modal-overlay.open .modal-content { transform: scale(1); }
140
+ .modal-content h3 { font-size: 1.3rem; margin-top: 10px; margin-bottom: 10px; color: var(--text-primary); }
141
+ .modal-content p { color: var(--text-secondary); margin-bottom: 25px; line-height: 1.6; }
142
+ .modal-actions { display: flex; gap: 15px; }
143
+ .modal-btn { flex: 1; padding: 12px; border: none; border-radius: var(--radius-btn); font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.2s; }
144
+ .modal-btn-confirm { background-color: var(--danger-color); color: white; box-shadow: 0 4px 15px var(--danger-glow); }
145
+ .modal-btn-confirm:hover { background-color: #c53030; transform: translateY(-2px); box-shadow: 0 6px 20px var(--danger-glow); }
146
+ .modal-btn-cancel { background-color: var(--input-bg); color: var(--text-secondary); border: 1px solid var(--input-border); }
147
+ .modal-btn-cancel:hover { background-color: var(--panel-border); color: var(--text-primary); }
148
 
149
  @keyframes slideUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
150
  </style>
 
154
 
155
  <div class="container">
156
  <header>
157
+ <div class="logo-box"><svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg></div>
 
 
158
  <h1>استودیو موزیک آلفا</h1>
159
  <p class="subtitle">ساخت آهنگ حرفه‌ای با هوش مصنوعی</p>
160
  </header>
161
 
162
  <div id="step1" class="card">
163
+ <div class="form-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path></svg>موضوع آهنگ چیه؟</div>
 
 
 
164
  <textarea id="ideaInput" placeholder="مثال: آهنگ تولد برای سمیرا، سبک پاپ شاد..."></textarea>
 
165
  <button class="accordion">تنظیمات پیشرفته</button>
166
  <div class="panel">
167
  <div class="settings-grid">
 
193
  </div>
194
 
195
  <div id="historySection">
196
+ <div class="history-title"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>آخرین آهنگ‌های ساخته شده</div>
 
 
 
197
  <div class="history-list" id="historyList"></div>
198
  </div>
199
  </div>
200
 
201
+ <!-- مودال تایید حذف -->
202
+ <div id="deleteModal" class="modal-overlay">
203
+ <div class="modal-content">
204
+ <h3>تایید حذف</h3>
205
+ <p>آیا از حذف این آهنگ از سابقه خود مطمئن هستید؟ این عمل غیرقابل بازگشت است.</p>
206
+ <div class="modal-actions">
207
+ <button id="cancelDeleteBtn" class="modal-btn modal-btn-cancel">انصراف</button>
208
+ <button id="confirmDeleteBtn" class="modal-btn modal-btn-confirm">بله، حذف کن</button>
209
+ </div>
210
+ </div>
211
+ </div>
212
+
213
  <script>
214
  const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
215
+ let db, songIdToDelete = null;
216
 
217
  const ideaInput = document.getElementById('ideaInput');
218
  const processBtn = document.getElementById('processBtn');
 
225
  const mainDownloadLink = document.getElementById('mainDownloadLink');
226
  const historyList = document.getElementById('historyList');
227
  const historySection = document.getElementById('historySection');
228
+ const deleteModal = document.getElementById('deleteModal');
229
+ const confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
230
+ const cancelDeleteBtn = document.getElementById('cancelDeleteBtn');
231
 
232
  const getVal = (id) => document.getElementById(id).value;
233
  const getNum = (id) => Number(document.getElementById(id).value);
 
235
 
236
  const acc = document.getElementsByClassName("accordion");
237
  for (let i = 0; i < acc.length; i++) {
238
+ acc[i].addEventListener("click", function() { this.classList.toggle("active"); const panel = this.nextElementSibling; panel.style.maxHeight = panel.style.maxHeight ? null : panel.scrollHeight + "px"; });
 
 
 
 
239
  }
240
 
241
  function initDB() {
242
  const request = indexedDB.open("alphaMusicDB", 1);
243
+ request.onerror = (e) => console.error("IndexedDB error:", e);
244
+ request.onsuccess = (e) => { db = e.target.result; loadHistory(); };
245
+ request.onupgradeneeded = (e) => { e.target.result.createObjectStore("songs", { keyPath: "id" }); };
 
 
246
  }
247
 
248
  async function saveToHistory(idea, lyrics, audioUrl) {
249
  try {
250
  const response = await fetch(audioUrl);
251
  const audioBlob = await response.blob();
252
+ const newItem = { id: Date.now(), idea, lyrics, audioBlob, date: new Date().toLocaleDateString('fa-IR', { hour: '2-digit', minute: '2-digit' }) };
 
 
 
253
  const transaction = db.transaction(["songs"], "readwrite");
254
  const store = transaction.objectStore("songs");
255
  store.add(newItem);
256
+ store.count().onsuccess = (e) => {
257
+ if (e.target.result > 10) {
258
+ store.openCursor().onsuccess = (ev) => { const cursor = ev.target.result; if (cursor) cursor.delete(); };
 
 
 
 
 
259
  }
260
  };
261
  transaction.oncomplete = () => loadHistory();
 
275
  history.forEach((item) => {
276
  const div = document.createElement('div');
277
  div.className = 'history-card';
278
+ div.innerHTML = `<div class="h-info"><div class="h-icon">🎵</div><div class="h-details"><h4>${item.idea.substring(0, 30)}${item.idea.length > 30 ? '...' : ''}</h4><span>${item.date}</span></div></div>
279
+ <div class="h-action play"><button class="action-btn play-btn"> نمایش</button></div>
280
+ <div class="h-action delete"><button class="action-btn delete-btn" onclick="event.stopPropagation(); openDeleteModal(${item.id})">🗑️ حذف</button></div>`;
281
+
282
+ div.onclick = () => { if (!div.classList.contains('show-delete')) openHistoryItem(item); };
283
+ div.oncontextmenu = (ev) => {
284
+ ev.preventDefault();
285
+ document.querySelectorAll('.history-card.show-delete').forEach(c => c.classList.remove('show-delete'));
286
+ div.classList.add('show-delete');
287
+ };
288
  historyList.appendChild(div);
289
  });
290
  };
291
  }
292
+
293
+ document.addEventListener('click', (e) => {
294
+ if (!e.target.closest('.history-card.show-delete')) {
295
+ document.querySelectorAll('.history-card.show-delete').forEach(c => c.classList.remove('show-delete'));
296
+ }
297
+ }, true);
298
+
299
+ function openDeleteModal(id) { songIdToDelete = id; deleteModal.classList.add('open'); }
300
+ function closeDeleteModal() { deleteModal.classList.remove('open'); songIdToDelete = null; }
301
+
302
+ function deleteSongFromDB() {
303
+ if (!songIdToDelete) return;
304
+ const transaction = db.transaction(["songs"], "readwrite");
305
+ const store = transaction.objectStore("songs");
306
+ store.delete(songIdToDelete);
307
+ transaction.oncomplete = () => { closeDeleteModal(); loadHistory(); };
308
+ }
309
+
310
+ cancelDeleteBtn.addEventListener('click', closeDeleteModal);
311
+ deleteModal.addEventListener('click', (e) => { if (e.target === deleteModal) closeDeleteModal(); });
312
+ confirmDeleteBtn.addEventListener('click', deleteSongFromDB);
313
 
314
  function openHistoryItem(item) {
315
  step1.style.display = 'none';
316
  historySection.style.display = 'none';
 
317
  const audioURL = URL.createObjectURL(item.audioBlob);
318
  const headerText = document.getElementById('resultHeaderText');
319
  headerText.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg> آهنگ آرشیو شده`;
320
  headerText.style.color = 'var(--accent-primary)';
 
321
  mainDownloadLink.href = audioURL;
322
  mainDownloadLink.style.display = 'inline-block';
323
  playerWrapper.innerHTML = `<audio controls autoplay src="${audioURL}"></audio>`;
324
  finalLyricsBox.innerHTML = formatLyrics(item.lyrics);
 
325
  finalResult.style.display = 'block';
326
  window.scrollTo({ top: 0, behavior: 'smooth' });
327
  }
 
330
 
331
  processBtn.addEventListener('click', async () => {
332
  if (!ideaInput.value.trim()) return alert("لطفا موضوع آهنگ را بنویسید");
 
333
  processBtn.disabled = true;
334
  step1.style.display = 'none';
335
  historySection.style.display = 'none';
 
344
  const { lyrics, musicPrompt } = data;
345
  loaderText.innerText = "در حال ضبط آهنگ در استودیو آلفا...";
346
 
347
+ const payload = [ getVal('model_select'), "custom", null, "unknown", musicPrompt, lyrics, 0, "", "", "unknown", getNum('steps_input'), getNum('cfg_input'), true, getNum('seed_input'), null, -1, getNum('batch_size'), null, null, 0, -1, "Fill the audio semantic mask based on the given conditions:", 1, "text2music", false, 0, 1, 3, "ode", "", "mp3", 0.85, getChk('think_checkbox'), 2, 0, 0.9, "NO USER INPUT", true, true, true, null, false, true, false, false, 0.5, 8, null, [], false, null, null, null, null ];
 
 
 
 
 
 
 
348
  const session_hash = Math.random().toString(36).substring(2);
349
  const joinResp = await fetch(`${ACE_SPACE_URL}gradio_api/queue/join`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: payload, fn_index: 77, session_hash }) });
350
  if (!joinResp.ok) throw new Error('خطا در اتصال به سرور موزیک');
 
360
  }
361
  };
362
  eventSource.onerror = () => { throw new Error('ارتباط با سرور قطع شد.'); };
 
363
  } catch (e) {
364
  alert("خطا: " + e.message);
365
  loader.style.display = 'none';
 
378
  if (processedUrls.has(fullUrl)) return;
379
  processedUrls.add(fullUrl);
380
  hasResult = true;
 
381
  if (processedUrls.size === 1) {
382
  mainDownloadLink.href = fullUrl;
383
  mainDownloadLink.style.display = 'inline-block';
384
  saveToHistory(idea, lyrics, fullUrl);
385
  }
 
386
  playerWrapper.innerHTML += `<div class="audio-item"><audio controls autoplay src="${fullUrl}"></audio></div>`;
387
  }
388