Elias207 commited on
Commit
e9d891d
·
verified ·
1 Parent(s): 9094548

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +327 -361
index.html CHANGED
@@ -9,335 +9,207 @@
9
  :root {
10
  --app-font: 'Vazirmatn', sans-serif;
11
  --app-bg: #F8F9FC;
12
- --app-bg-gradient: linear-gradient(170deg, #F8F9FC 0%, #EFF3FA 100%);
13
- --panel-bg: rgba(255, 255, 255, 0.9);
14
  --panel-border: #EAEFF7;
15
  --input-bg: #F6F8FB;
16
  --input-border: #E1E7EF;
17
  --text-primary: #1A202C;
18
- --text-secondary: #5A6782;
19
  --text-tertiary: #8A94A6;
20
- --accent-primary: #4F46E5;
21
- --accent-primary-hover: #4338CA;
22
- --accent-primary-glow: rgba(79, 70, 229, 0.2);
23
- --accent-secondary: #10B981;
24
- --accent-secondary-glow: rgba(16, 185, 129, 0.2);
25
- --success-color: #10B981;
26
- --danger-color: #EF4444;
27
- --danger-color-hover: #DC2626;
28
- --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.04);
29
  --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
30
- --shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.07), 0 4px 6px -4px rgba(26, 32, 44, 0.06);
31
- --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.08), 0 8px 10px -6px rgba(26, 32, 44, 0.06);
32
- --radius-card: 28px;
33
- --radius-btn: 16px;
34
- --radius-input: 14px;
35
  --transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
 
 
 
 
 
 
 
 
 
 
 
36
  }
37
 
38
- /* --- Keyframe Animations --- */
39
- @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
40
- @keyframes aurora-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
41
- @keyframes scale-up-down { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } }
42
- @keyframes shimmer { 0% { transform: translateX(-100%) skewX(-20deg); } 100% { transform: translateX(200%) skewX(-20deg); } }
43
- @keyframes scan-line-anim { 0% { top: -10%; } 100% { top: 110%; } }
44
- @keyframes glow-pulse { 0%, 100% { opacity: 0.2; } 50% { opacity: 0.5; } }
45
- @keyframes fadeInScaleUp { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
46
- @keyframes fill-progress { from { width: 0%; } to { width: 100%; } }
47
-
48
- body {
49
- font-family: var(--app-font);
50
- background: var(--app-bg-gradient);
51
- color: var(--text-primary);
52
- margin: 0;
53
- padding: 3rem 1rem;
54
- display: flex;
55
- justify-content: center;
56
- align-items: flex-start;
57
- min-height: 100vh;
58
  }
 
 
 
 
 
 
 
59
 
 
60
  .container { max-width: 820px; width: 100%; }
 
 
 
61
 
62
- /* --- Enhanced Header --- */
63
- header {
64
- text-align: center;
65
- margin-bottom: 3rem;
66
- animation: fadeIn 0.8s ease-out backwards;
67
- }
68
- .aurora-visualizer {
69
- width: 150px;
70
- height: 150px;
71
  margin: 0 auto 1.5rem;
72
  position: relative;
73
  display: flex;
74
  align-items: center;
75
  justify-content: center;
76
  }
77
- .icon-container {
78
- width: 80px;
79
- height: 80px;
80
- background: #fff;
81
- border-radius: 24px;
82
- box-shadow: var(--shadow-lg);
 
83
  display: flex;
84
  align-items: center;
85
  justify-content: center;
 
86
  position: relative;
87
  z-index: 2;
88
- animation: scale-up-down 4s ease-in-out infinite;
89
- border: 1px solid rgba(225, 231, 239, 0.8);
90
  }
91
- .icon-container svg { width: 40px; height: 40px; color: var(--accent-primary); }
92
- .aurora-band {
93
- position: absolute;
94
- top: 50%; left: 50%;
95
- width: 150%; height: 150%;
96
- border-radius: 50%;
97
- filter: blur(25px);
98
- opacity: 0.6;
99
- mix-blend-mode: screen;
100
  }
101
- .aurora-band.one { background: linear-gradient(45deg, var(--accent-primary), var(--accent-secondary)); animation: aurora-spin 10s linear infinite; }
102
- .aurora-band.two { background: linear-gradient(-45deg, #8B5CF6, #EC4899); animation: aurora-spin 12s linear infinite reverse; width: 120%; height: 120%; }
103
-
104
- h1 {
105
- font-size: 3rem;
106
- font-weight: 800;
107
- margin: 0;
108
- background: linear-gradient(90deg, var(--accent-primary) 0%, #293a8b 100%);
109
- -webkit-background-clip: text;
110
- -webkit-text-fill-color: transparent;
111
- letter-spacing: -1.5px;
112
  }
113
- .subtitle { font-size: 1.15rem; color: var(--text-secondary); margin-top: 0.75rem; }
114
- .instruction { font-size: 0.95rem; color: var(--text-tertiary); margin-top: 1rem; font-weight: 500; }
115
-
116
- main {
117
- padding: 3rem;
118
- background: var(--panel-bg);
119
- border-radius: var(--radius-card);
120
- box-shadow: var(--shadow-xl);
121
- border: 1px solid var(--panel-border);
122
- animation: fadeIn 0.8s 0.2s ease-out backwards;
123
- backdrop-filter: blur(10px);
124
  }
125
-
 
 
 
 
126
  .form-group { margin-bottom: 2.5rem; }
127
  .form-group:last-child { margin-bottom: 0; }
128
- .form-label { display: flex; align-items: center; gap: 0.75rem; font-weight: 700; color: var(--text-primary); font-size: 1.25em; margin-bottom: 1.2rem; }
129
- .form-label svg { width: 26px; height: 26px; color: var(--accent-primary); }
130
-
131
- #image-drop-zone {
132
- position: relative;
133
- border: 2px dashed var(--input-border);
134
- border-radius: var(--radius-input);
135
- padding: 2.5rem;
136
- text-align: center;
137
- cursor: pointer;
138
- transition: var(--transition-smooth);
139
- background-color: var(--input-bg);
140
- overflow: hidden;
141
- }
142
- #image-drop-zone:hover, #image-drop-zone.drag-over {
143
- border-color: var(--accent-primary);
144
- background-color: #fff;
145
- box-shadow: 0 0 20px var(--accent-primary-glow);
146
- transform: scale(1.02);
147
- }
148
  #image-drop-zone.has-image { border-style: solid; border-color: var(--success-color); padding: 0; cursor: default; }
149
- #image-drop-zone.has-image:hover { transform: none; box-shadow: none; }
150
- .upload-content p { margin: 0; color: var(--text-secondary); font-weight: 500; font-size: 1.05rem; }
151
- .upload-icon svg { width: 52px; height: 52px; color: var(--accent-primary); stroke-width: 1.5; opacity: 0.7; margin-bottom: 1rem; }
152
  #imagePreview { display: none; width: 100%; height: 100%; object-fit: contain; position: absolute; top: 0; left: 0; }
153
  #image-drop-zone.has-image .upload-content { display: none; }
154
  #image-drop-zone.has-image #imagePreview { display: block; }
155
-
156
- textarea {
157
- width: 100%;
158
- padding: 1rem 1.2rem;
159
- border-radius: var(--radius-input);
160
- border: 1px solid var(--input-border);
161
- background-color: var(--input-bg);
162
- color: var(--text-primary);
163
- box-shadow: var(--shadow-sm) inset;
164
- font-family: var(--app-font);
165
- font-size: 1.05rem;
166
- box-sizing: border-box;
167
- transition: var(--transition-smooth);
168
- min-height: 120px;
169
- resize: vertical;
170
- }
171
- textarea:focus {
172
- outline: none;
173
- border-color: var(--accent-primary);
174
- box-shadow: 0 0 0 4px var(--accent-primary-glow), var(--shadow-sm) inset;
175
- background-color: #fff;
176
- }
177
-
178
- #generateButton {
179
- position: relative;
180
- display: flex;
181
- align-items: center;
182
- justify-content: center;
183
- gap: 0.75rem;
184
- width: 100%;
185
- padding: 1.2rem;
186
- font-size: 1.25rem;
187
- font-weight: 700;
188
- background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%);
189
- color: #fff;
190
- border: none;
191
- border-radius: var(--radius-btn);
192
- cursor: pointer;
193
- transition: all 0.3s ease;
194
- box-shadow: 0 6px 15px -3px var(--accent-primary-glow), 0 6px 15px -3px var(--accent-secondary-glow);
195
- margin-top: 1.5rem;
196
- overflow: hidden;
197
- }
198
- #generateButton .icon-sparkle { transition: var(--transition-smooth); }
199
- #generateButton:hover:not(:disabled) {
200
- transform: translateY(-5px) scale(1.02);
201
- box-shadow: 0 8px 25px -4px var(--accent-primary-glow), 0 8px 25px -4px var(--accent-secondary-glow);
202
- }
203
- #generateButton:hover:not(:disabled) .icon-sparkle { transform: rotate(15deg) scale(1.1); }
204
- #generateButton::before { /* Shimmer Effect */
205
- content: '';
206
- position: absolute;
207
- top: 0;
208
- left: 0;
209
- width: 50%;
210
- height: 100%;
211
- background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.4) 50%, rgba(255,255,255,0) 100%);
212
- transform: translateX(-150%) skewX(-20deg);
213
- transition: transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
214
- }
215
- #generateButton:hover:not(:disabled)::before { transform: translateX(250%) skewX(-20deg); }
216
  #generateButton:disabled { background: var(--text-tertiary); cursor: not-allowed; box-shadow: none; opacity: 0.7; }
217
-
218
- #result-container {
219
- min-height: 350px;
220
- position: relative;
221
- background-color: var(--input-bg);
222
- border-radius: var(--radius-card);
223
- border: 2px dashed var(--input-border);
224
- box-shadow: var(--shadow-sm) inset;
225
- transition: var(--transition-smooth);
226
- display: flex;
227
- align-items: center;
228
- justify-content: center;
229
- padding: 1rem;
230
- overflow: hidden;
231
- }
232
- #statusSection, #outputSection, #criticalErrorSection { display: none; width: 100%; text-align: center; }
233
  #statusSection.active, #outputSection.active, #criticalErrorSection.active { display: block; animation: fadeIn 0.5s; }
234
- #outputSection.active { animation: fadeInScaleUp 0.6s cubic-bezier(0.4, 0, 0.2, 1); }
235
-
236
- /* --- New Image Animator Loader --- */
237
- #imageAnimatorLoader {
238
- position: relative;
239
- width: 400px;
240
- max-width: 100%;
241
- aspect-ratio: 4 / 3;
242
- border-radius: 20px;
243
- overflow: hidden;
244
- background-color: #111;
245
- box-shadow: 0 0 30px rgba(0,0,0,0.3);
246
- display: flex;
247
- flex-direction: column;
248
- justify-content: flex-end;
249
- align-items: center;
250
- }
251
- #loaderImagePreview {
252
- position: absolute;
253
- top: 0;
254
- left: 0;
255
- width: 100%;
256
- height: 100%;
257
- object-fit: cover;
258
- filter: saturate(0.8) contrast(0.9);
259
- }
260
- .scan-line {
261
- position: absolute;
262
- left: 0;
263
- width: 100%;
264
- height: 4px;
265
- background: linear-gradient(90deg, transparent, var(--accent-secondary), transparent);
266
- box-shadow: 0 0 15px 2px var(--accent-secondary);
267
- animation: scan-line-anim 3s ease-in-out infinite;
268
- border-radius: 50%;
269
- }
270
- .glow-overlay {
271
- position: absolute;
272
- top: 0;
273
- left: 0;
274
- width: 100%;
275
- height: 100%;
276
- background: radial-gradient(circle at center, var(--accent-primary) 0%, transparent 70%);
277
- animation: glow-pulse 4s ease-in-out infinite;
278
- }
279
- .processing-text {
280
- position: relative;
281
- z-index: 2;
282
- color: #fff;
283
- font-size: 1.2rem;
284
- font-weight: 600;
285
- text-shadow: 0 2px 10px #000;
286
- padding: 1rem;
287
- width: 100%;
288
- background: linear-gradient(to top, rgba(0,0,0,0.7), transparent);
289
- }
290
- .progress-bar {
291
- position: absolute; bottom: 0; left: 0; width: 0%; height: 5px;
292
- background: linear-gradient(to right, var(--accent-secondary), var(--accent-primary));
293
- transition: width 1s ease-out;
294
- }
295
  .progress-bar.animate-progress { animation: fill-progress 60s linear forwards; }
296
-
297
  #outputVideo { width: 100%; max-width: 500px; border-radius: var(--radius-input); margin: 1rem auto; display: block; background-color: #000; box-shadow: var(--shadow-md); }
298
- #finalSeed { text-align: center; color: var(--text-tertiary); font-size: 0.9rem; margin-top: 1rem; }
299
-
300
  .video-controls { display: flex; justify-content: center; gap: 1rem; margin-top: 1.5rem; }
301
- .video-button { padding: 0.8rem 1.6rem; border-radius: var(--radius-btn); border: none; cursor: pointer; font-family: var(--app-font); font-weight: 600; font-size: 1rem; transition: var(--transition-smooth); display: flex; align-items: center; gap: 0.5rem; box-shadow: var(--shadow-md); }
302
- .video-button:hover:not(:disabled) { transform: translateY(-3px); box-shadow: var(--shadow-lg); }
303
  .video-button.primary { background-color: var(--accent-primary); color: white; }
304
- .video-button.primary:hover { background-color: var(--accent-primary-hover); }
305
- .video-button:not(.primary) { background-color: #fff; color: var(--text-secondary); border: 1px solid var(--input-border); }
306
- .video-button:not(.primary):hover { background-color: var(--input-bg); color: var(--text-primary); }
307
-
308
- /* Error messages remain the same for functionality */
309
- .gpu-error-message-container { /* Styling remains unchanged */ text-align: right; background-color: #FFFBEB; padding: 25px; border-radius: 12px; box-shadow: var(--shadow-lg); border: 2px solid #FBBF24; animation: fadeIn 0.5s cubic-bezier(0.4, 0, 0.2, 1) both; }
310
- .gpu-error-message-container h2 { font-size: 1.2em; color: #B45309; margin-bottom: 15px; font-weight: 700; display: flex; align-items: center; border-bottom: 1px solid #FBBF24; padding-bottom: 12px; }
 
 
 
311
  .gpu-error-actions { display: flex; gap: 15px; margin-top: 25px; }
312
- .gpu-error-actions .action-button { padding: 12px 20px; border: none; border-radius: 8px; font-size: 0.95em; font-weight: 600; cursor: pointer; font-family: inherit; width: 100%; box-shadow: var(--shadow-md); transition: var(--transition-smooth); }
313
  .gpu-error-actions .action-button:hover:not(:disabled) { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
314
- .gpu-error-actions .action-button.back-button { background-color: #fff; color: var(--text-tertiary); border: 1px solid var(--panel-border); }
315
- .gpu-error-actions .action-button.retry-button { background: linear-gradient(60deg, #5A67D8, #805AD5); color: white; }
 
 
316
 
317
  @media (max-width: 768px) {
318
- body { padding: 1.5rem 1rem; }
319
- main { padding: 1.5rem; } h1 { font-size: 2.4rem; }
320
- #imageAnimatorLoader { aspect-ratio: 1/1; }
321
  }
322
  </style>
323
  </head>
324
  <body>
325
  <div class="container">
326
  <header>
327
- <div class="aurora-visualizer">
328
- <div class="icon-container">
329
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
330
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
331
- <circle cx="8.5" cy="8.5" r="1.5"></circle>
332
- <polyline points="21 15 16 10 5 21"></polyline>
333
- </svg>
 
 
 
 
 
334
  </div>
335
- <div class="aurora-band one"></div>
336
- <div class="aurora-band two"></div>
 
337
  </div>
338
- <h1>متحرک سازی تصاویر</h1>
339
- <p class="subtitle">به تصاویر ثابت خود جان ببخشید و آنها را با هوش مصنوعی متحرک کنید</p>
340
- <p class="instruction">تصویر خود را آپلود کنید، توضیح دهید چگونه باید حرکت کند، و جادو را تماشا کنید.</p>
341
  </header>
342
 
343
  <main>
@@ -363,28 +235,30 @@
363
  </label>
364
  <textarea id="prompt" rows="4" placeholder="مثال: حرکت ابرها، موج زدن آب، وزش ملایم باد در موها، زوم آهسته به بیرون"></textarea>
365
  <button id="generateButton">
366
- <svg class="icon-sparkle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="26" height="26" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3L12 8L17 10L12 12L10 17L8 12L3 10L8 8L10 3z"/></svg>
367
  <span>شروع متحرک سازی</span>
368
  </button>
369
  </div>
370
 
371
  <div class="form-group">
372
  <div class="form-label">
373
- <svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3L12 8L17 10L12 12L10 17L8 12L3 10L8 8L10 3z"/><path d="M21 14l-1.5 3-3-1.5 3-3 1.5 3z"/><path d="M19.5 2.5l-3 1.5 1.5 3 3-1.5-1.5-3z"/></svg>
374
  ۳. نتیجه را ببینید
375
  </div>
376
  <div id="result-container">
377
  <div id="statusSection">
378
- <!-- این بخش در JS مدیریت می‌شود و نیازی به div جدا برای پیام‌ها ندارد -->
379
  <div id="aiLoader" style="display: none;">
380
- <div id="imageAnimatorLoader">
381
- <img id="loaderImagePreview" src="" alt="Processing image">
382
- <div class="glow-overlay"></div>
383
- <div class="scan-line"></div>
384
- <div class="processing-text">در حال جان بخشیدن به تصویر...</div>
385
- <div class="progress-bar"></div>
386
- </div>
387
- </div>
 
 
388
  </div>
389
 
390
  <div id="outputSection">
@@ -427,10 +301,10 @@
427
  const finalSeedElement = document.getElementById('finalSeed');
428
  const criticalErrorSection = document.getElementById('criticalErrorSection');
429
  const statusSection = document.getElementById('statusSection');
 
430
  const aiLoader = document.getElementById('aiLoader');
431
- const loaderImagePreview = document.getElementById('loaderImagePreview'); // New loader image
432
- const loaderProgressBar = document.querySelector('#imageAnimatorLoader .progress-bar');
433
- const loaderTextOverlay = document.querySelector('#imageAnimatorLoader .processing-text');
434
  const outputSection = document.getElementById('outputSection');
435
  const btnRestart = document.getElementById('btnRestart');
436
  const btnDownloadVideo = document.getElementById('btnDownloadVideo');
@@ -440,7 +314,7 @@
440
  const TRANSLATOR_SPACE_URL_BASE = "https://hamed744-translate-tts-aloha.hf.space/gradio_api";
441
  const FN_INDEX_TRANSLATE_SPEAK = 1;
442
  const DEFAULT_TTS_VOICE_TRANSLATOR = "انگلیسی (آمریکا) - جنی (زن)";
443
- const DEFAULT_NEGATIVE_PROMPT = "bad quality, ugly, blurry, watermark, text, signature, worst quality, low quality";
444
  const DEFAULT_PROMPT_TEXT = "";
445
  const TRANSPARENT_PIXEL_SRC = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
446
  const DEFAULT_VIDEO_DURATION = 5.1;
@@ -453,24 +327,6 @@
453
  return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15).substring(0, length - 7);
454
  }
455
 
456
- function setAiLoaderText(text) {
457
- if (loaderTextOverlay) loaderTextOverlay.textContent = text;
458
- }
459
-
460
- function clearStatus() {
461
- aiLoader.style.display = 'none';
462
- if(loaderProgressBar) {
463
- loaderProgressBar.classList.remove('animate-progress');
464
- loaderProgressBar.style.width = '0%';
465
- }
466
- if (outputVideo.src && outputVideo.src.startsWith('blob:')) {
467
- URL.revokeObjectURL(outputVideo.src);
468
- }
469
- outputVideo.src = '';
470
- finalSeedElement.textContent = '';
471
- btnDownloadVideo.style.display = 'none';
472
- }
473
-
474
  async function retryLastAttempt() {
475
  if (!lastAttemptedVideoPayload || !lastAttemptedVideoPayload.imageSourceFile || !lastAttemptedVideoPayload.persianPrompt) {
476
  showCriticalError("اطلاعات کافی برای تلاش مجدد وجود ندارد. لطفاً از نو شروع کنید.");
@@ -478,19 +334,21 @@
478
  }
479
 
480
  hideCriticalError();
481
- clearStatus();
482
  generateButton.disabled = true;
483
  resultContainer.classList.add('active');
484
  statusSection.classList.add('active');
485
- aiLoader.style.display = 'block'; // Use block for the new loader
486
- loaderImagePreview.src = imagePreview.src;
487
  loaderProgressBar.classList.add('animate-progress');
488
  outputSection.classList.remove('active');
 
489
 
490
  const { persianPrompt, imageSourceFile } = lastAttemptedVideoPayload;
491
 
492
  try {
493
  const englishPromptForVideo = await translatePersianToEnglish(persianPrompt);
 
 
494
  setAiLoaderText("در حال متحرک سازی تصویر...");
495
 
496
  const client = await Client.connect(VIDEO_SPACE_NAME);
@@ -514,41 +372,41 @@
514
  }
515
  }
516
 
517
- function showCriticalError(message) {
518
  const lowerCaseMessage = (message || "").toLowerCase();
519
- let errorHtml = '';
520
- if (lowerCaseMessage.includes("gpu") || lowerCaseMessage.includes("quota") || lowerCaseMessage.includes("capacity") || lowerCaseMessage.includes("queue full")) {
521
- errorHtml = `
522
  <div class="gpu-error-message-container">
523
- <h2>💡 راهنمای رفع محدودیت انیمیشن</h2>
524
- <div class="error-content" style="font-size: 0.9rem; color: #4A5568; line-height: 1.7;">
525
- <p><strong>ارور محدودیت استفاده از GPU:</strong></p>
526
- <p>این خطا به دلیل ترافیک بالای سرور رخ می‌دهد. خبر خوب این است که با یک ترفند ساده قابل رفع است:</p>
527
- <ul style='list-style-type: "▫️ "; padding-right: 20px;'>
528
- <li>اگر از اینترنت سیم‌کارت استفاده می‌کنید، فیلترشکن را خاموش کنید.</li>
529
- <li>گوشی را برای ۱۰ ثانیه در **حالت هواپیما** قرار داده و سپس غیرفعال کنید.</li>
530
- <li>اگر از وای‌فای استفاده میکنید، مودم را یک بار خاموش و روشن کنید.</li>
 
531
  </ul>
532
- <p>با این روش، IP شما تغییر کرده و محدودیت برداشته می‌شود. سپس روی "تلاش مجدد" کلیک کنید.</p>
533
  </div>
534
  <div class="gpu-error-actions">
535
  <button class="action-button back-button" id="gpuErrorGoBackBtn">بازگشت</button>
536
  <button class="action-button retry-button" id="gpuErrorRetryBtn">تلاش مجدد</button>
537
  </div>
538
  </div>`;
539
- criticalErrorSection.innerHTML = errorHtml;
540
  document.getElementById('gpuErrorGoBackBtn').addEventListener('click', hideCriticalError);
541
  document.getElementById('gpuErrorRetryBtn').addEventListener('click', retryLastAttempt);
542
  } else {
543
- errorHtml = `
544
- <div style="color: var(--danger-color); font-weight: 500; margin-bottom: 1.5rem; line-height: 1.6;"><p>${message}</p></div>
545
  <div class="video-controls">
546
  <button id="simpleErrorGoBackBtn" class="video-button">بازگشت</button>
547
  <button id="simpleErrorRetryBtn" class="video-button primary">تلاش مجدد</button>
548
  </div>`;
549
- criticalErrorSection.innerHTML = errorHtml;
550
- document.getElementById('simpleErrorGoBackBtn').addEventListener('click', hideCriticalError);
551
- document.getElementById('simpleErrorRetryBtn').addEventListener('click', retryLastAttempt);
552
  }
553
 
554
  resultContainer.classList.add('active');
@@ -564,12 +422,49 @@
564
  criticalErrorSection.innerHTML = '';
565
  }
566
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
567
  function initializeForm() {
568
  hideCriticalError();
569
  resultContainer.classList.remove('active');
570
  statusSection.classList.remove('active');
571
  outputSection.classList.remove('active');
572
- clearStatus();
573
  lastAttemptedVideoPayload = null;
574
  imageFileInput.value = '';
575
  imagePreview.src = TRANSPARENT_PIXEL_SRC;
@@ -579,23 +474,52 @@
579
  }
580
 
581
  async function translatePersianToEnglish(persianText) {
582
- setAiLoaderText("در حال ترجمه دستور...");
583
  let currentTranslatorSessionHash = generateRandomHash();
584
- const payload = { data: [persianText, DEFAULT_TTS_VOICE_TRANSLATOR, 0, 0, 0], fn_index: FN_INDEX_TRANSLATE_SPEAK, session_hash: currentTranslatorSessionHash };
 
 
 
 
 
 
 
 
 
 
585
  try {
586
- await fetch(`${TRANSLATOR_SPACE_URL_BASE}/queue/join?`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
 
 
 
 
 
 
 
 
587
  return new Promise((resolve, reject) => {
588
  const eventSource = new EventSource(`${TRANSLATOR_SPACE_URL_BASE}/queue/data?session_hash=${currentTranslatorSessionHash}`);
589
- eventSource.onmessage = (event) => {
590
  const data = JSON.parse(event.data);
591
  if (data.msg === "process_completed") {
592
  eventSource.close();
593
- data.success ? resolve(data.output.data[0]) : reject(new Error("ترجمه ناموفق بود."));
 
 
 
 
 
 
594
  }
595
  };
596
- eventSource.onerror = () => { eventSource.close(); reject(new Error("خطا در ارتباط با سرور مترجم.")); };
 
 
 
597
  });
598
- } catch (error) { throw error; }
 
 
 
599
  }
600
 
601
  function handleSuccessfulVideoGeneration(result) {
@@ -604,15 +528,19 @@
604
  showCriticalError('پاسخ دریافتی از سرور انیمیشن معتبر نیست.');
605
  return;
606
  }
 
 
607
  const videoUrl = result.data[0].video.url;
608
  const usedSeed = result.data[1];
609
 
610
- statusSection.classList.remove('active');
611
- outputSection.classList.add('active');
612
- outputVideo.src = videoUrl;
613
- outputVideo.load();
614
- finalSeedElement.textContent = `Seed: ${usedSeed}`;
615
- prepareCustomDownloadLink(videoUrl);
 
 
616
 
617
  generateButton.disabled = false;
618
  }
@@ -632,25 +560,29 @@
632
 
633
  // Event Listeners
634
  document.addEventListener('DOMContentLoaded', initializeForm);
635
- btnRestart.addEventListener('click', initializeForm);
636
-
637
- ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => imageDropZone.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); }));
638
- ['dragenter', 'dragover'].forEach(eventName => imageDropZone.addEventListener(eventName, () => imageDropZone.classList.add('drag-over')));
639
- ['dragleave', 'drop'].forEach(eventName => imageDropZone.addEventListener(eventName, () => imageDropZone.classList.remove('drag-over')));
640
  imageDropZone.addEventListener('drop', e => {
641
- if (e.dataTransfer.files.length > 0) {
642
  imageFileInput.files = e.dataTransfer.files;
643
- imageFileInput.dispatchEvent(new Event('change', { bubbles: true }));
 
644
  }
645
  });
 
 
646
 
647
  imageFileInput.addEventListener('change', function(event) {
648
  const file = event.target.files[0];
649
  hideCriticalError();
650
  if (file) {
651
  if (!file.type.startsWith('image/')) {
652
- alert('لطفا یک فایل تصویر معتبر انتخاب کنید.');
653
  imageFileInput.value = '';
 
 
654
  } else {
655
  const reader = new FileReader();
656
  reader.onload = (e) => {
@@ -669,23 +601,35 @@
669
  const persianPrompt = promptInput.value.trim();
670
  const imageFile = imageFileInput.files[0];
671
 
672
- if (!persianPrompt) { alert('لطفاً دستور حرکت را وارد کنید.'); promptInput.focus(); return; }
673
- if (!imageFile) { alert('لطفاً یک تصویر انتخاب کنید.'); return; }
 
 
 
 
 
 
 
674
 
675
- clearStatus();
676
  hideCriticalError();
677
  generateButton.disabled = true;
678
  resultContainer.classList.add('active');
679
  statusSection.classList.add('active');
680
- aiLoader.style.display = 'block';
681
- loaderImagePreview.src = imagePreview.src; // Set image for the new loader
682
  loaderProgressBar.classList.add('animate-progress');
683
  outputSection.classList.remove('active');
 
684
 
685
- lastAttemptedVideoPayload = { persianPrompt, imageSourceFile: imageFile };
 
 
 
686
 
687
  try {
688
  const englishPrompt = await translatePersianToEnglish(persianPrompt);
 
 
689
  setAiLoaderText("در حال متحرک سازی تصویر...");
690
 
691
  const client = await Client.connect(VIDEO_SPACE_NAME);
@@ -704,10 +648,32 @@
704
  handleSuccessfulVideoGeneration(result);
705
 
706
  } catch (errorCaught) {
707
- showCriticalError(`خطا: ${errorCaught.message}`);
 
708
  }
709
  });
710
  })();
711
  </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
712
  </body>
713
  </html>
 
9
  :root {
10
  --app-font: 'Vazirmatn', sans-serif;
11
  --app-bg: #F8F9FC;
12
+ --panel-bg: #FFFFFF;
 
13
  --panel-border: #EAEFF7;
14
  --input-bg: #F6F8FB;
15
  --input-border: #E1E7EF;
16
  --text-primary: #1A202C;
17
+ --text-secondary: #626F86;
18
  --text-tertiary: #8A94A6;
19
+ --accent-primary: #4A6CFA;
20
+ --accent-primary-hover: #3553D6;
21
+ --accent-primary-glow: rgba(74, 108, 250, 0.25);
22
+ --accent-secondary: #0FD4A8;
23
+ --success-color: #38A169;
24
+ --danger-color: #e53e3e;
25
+ --danger-color-hover: #c53030;
26
+ --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03);
 
27
  --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
28
+ --shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06), 0 4px 6px -4px rgba(26, 32, 44, 0.05);
29
+ --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05);
30
+ --radius-card: 24px;
31
+ --radius-btn: 14px;
32
+ --radius-input: 12px;
33
  --transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
34
+
35
+ --igadlm-alpha-color-primary: #5A67D8;
36
+ --igadlm-alpha-color-secondary: #805AD5;
37
+ --igadlm-alpha-color-primary-darker: #4C51BF;
38
+ --igadlm-alpha-color-secondary-darker: #6B46C1;
39
+ --igadlm-alpha-color-text-light: #4A5568;
40
+ --igadlm-alpha-color-warning-bg: #FFFBEB;
41
+ --igadlm-alpha-color-warning-border: #FBBF24;
42
+ --igadlm-alpha-color-warning-text: #B45309;
43
+ --igadlm-alpha-border-radius-md: 8px;
44
+ --igadlm-alpha-border-radius-lg: 12px;
45
  }
46
 
47
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
48
+ @keyframes igadlmAlphaV2fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
49
+
50
+ /* --- انیمیشن جدید برای هدر --- */
51
+ @keyframes pulse-glow {
52
+ 0%, 100% { box-shadow: 0 0 20px var(--accent-primary-glow), 0 0 30px rgba(15, 212, 168, 0.2); }
53
+ 50% { box-shadow: 0 0 35px rgba(74, 108, 250, 0.4), 0 0 50px rgba(15, 212, 168, 0.3); }
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  }
55
+ @keyframes wave {
56
+ 0% { transform: scale(0.5); opacity: 1; }
57
+ 100% { transform: scale(2.5); opacity: 0; }
58
+ }
59
+
60
+ /* --- انیمیشن برای نوار پیشرفت --- */
61
+ @keyframes fill-progress { from { width: 0%; } to { width: 100%; } }
62
 
63
+ body { font-family: var(--app-font); background-color: var(--app-bg); color: var(--text-primary); margin: 0; padding: 2.5rem 1rem; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; }
64
  .container { max-width: 820px; width: 100%; }
65
+ header { position: relative; text-align: center; margin-bottom: 2.5rem; padding: 2rem 0; animation: fadeIn 0.8s 0.1s ease-out backwards; overflow: hidden; }
66
+ #neural-network-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; opacity: 0.7; }
67
+ .header-content { position: relative; z-index: 2; }
68
 
69
+ /* --- استایل انیمیشن جدید هدر --- */
70
+ .animation-visualizer {
71
+ width: 140px;
72
+ height: 140px;
 
 
 
 
 
73
  margin: 0 auto 1.5rem;
74
  position: relative;
75
  display: flex;
76
  align-items: center;
77
  justify-content: center;
78
  }
79
+ .image-placeholder {
80
+ width: 90px;
81
+ height: 90px;
82
+ background: linear-gradient(45deg, #e0e7ff, #f0f9ff);
83
+ border-radius: 20px;
84
+ border: 2px solid rgba(74, 108, 250, 0.2);
85
+ box-shadow: 0 8px 25px rgba(26, 32, 44, 0.08);
86
  display: flex;
87
  align-items: center;
88
  justify-content: center;
89
+ animation: pulse-glow 5s ease-in-out infinite;
90
  position: relative;
91
  z-index: 2;
 
 
92
  }
93
+ .image-placeholder svg {
94
+ width: 40px;
95
+ height: 40px;
96
+ color: var(--accent-primary);
97
+ opacity: 0.8;
 
 
 
 
98
  }
99
+ .wave-ring {
100
+ position: absolute;
101
+ width: 90px;
102
+ height: 90px;
103
+ border-radius: 20px;
104
+ border: 2px solid var(--accent-primary);
105
+ animation: wave 3s infinite cubic-bezier(0.65, 0, 0.35, 1);
106
+ z-index: 1;
 
 
 
107
  }
108
+ .wave-ring.two {
109
+ animation-delay: -1.5s;
110
+ border-color: var(--accent-secondary);
 
 
 
 
 
 
 
 
111
  }
112
+
113
+ h1 { font-size: 2.8rem; font-weight: 800; margin: 0; background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -1px; }
114
+ .subtitle { font-size: 1.1rem; color: var(--text-secondary); margin-top: 0.5rem; }
115
+ .instruction { font-size: 0.95rem; color: var(--text-tertiary); margin-top: 0.75rem; font-weight: 500; }
116
+ main { padding: 3rem; background-color: var(--panel-bg); border-radius: var(--radius-card); box-shadow: var(--shadow-xl); border: 1px solid var(--panel-border); animation: fadeIn 0.8s 0.3s ease-out backwards; }
117
  .form-group { margin-bottom: 2.5rem; }
118
  .form-group:last-child { margin-bottom: 0; }
119
+ .form-label { display: flex; align-items: center; gap: 0.75rem; font-weight: 700; color: var(--text-primary); font-size: 1.2em; margin-bottom: 1.2rem; }
120
+ .form-label svg { width: 24px; height: 24px; color: var(--accent-primary); }
121
+ #image-drop-zone { position: relative; border: 2px dashed var(--input-border); border-radius: var(--radius-input); padding: 2.5rem; text-align: center; cursor: pointer; transition: var(--transition-smooth); background-color: var(--input-bg); min-height: 200px; display: flex; flex-direction: column; justify-content: center; align-items: center; overflow: hidden; }
122
+ #image-drop-zone.drag-over, #image-drop-zone:hover:not(.has-image) { border-color: var(--accent-primary); background-color: #fff; box-shadow: 0 0 15px var(--accent-primary-glow); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  #image-drop-zone.has-image { border-style: solid; border-color: var(--success-color); padding: 0; cursor: default; }
124
+ .upload-content { display: flex; flex-direction: column; align-items: center; gap: 1rem; }
125
+ .upload-icon svg { width: 48px; height: 48px; color: var(--accent-primary); stroke-width: 1.5; opacity: 0.8; }
126
+ #image-drop-zone p { margin: 0; color: var(--text-secondary); font-weight: 500; }
127
  #imagePreview { display: none; width: 100%; height: 100%; object-fit: contain; position: absolute; top: 0; left: 0; }
128
  #image-drop-zone.has-image .upload-content { display: none; }
129
  #image-drop-zone.has-image #imagePreview { display: block; }
130
+ textarea { width: 100%; padding: 1rem 1.2rem; border-radius: var(--radius-input); border: 1px solid var(--input-border); background-color: var(--input-bg); color: var(--text-primary); box-shadow: var(--shadow-sm) inset; font-family: var(--app-font); font-size: 1rem; box-sizing: border-box; transition: var(--transition-smooth); min-height: 120px; resize: vertical; }
131
+ textarea:focus { outline: none; border-color: var(--accent-primary); box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset; background-color: var(--panel-bg); }
132
+ #generateButton { display: flex; align-items: center; justify-content: center; gap: 0.75rem; width: 100%; padding: 1.1rem; font-size: 1.2rem; font-weight: 700; background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%); color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; transition: all 0.3s ease; box-shadow: 0 6px 12px -3px var(--accent-primary-glow), 0 6px 12px -3px rgba(15, 212, 168, 0.25); margin-top: 1.5rem; }
133
+ #generateButton svg { width: 24px; height: 24px; margin-left: 4px; filter: drop-shadow(0 0 5px rgba(255,255,255,0.5)); }
134
+ #generateButton:hover:not(:disabled) { transform: translateY(-5px) scale(1.02); box-shadow: 0 8px 20px -4px var(--accent-primary-glow), 0 8px 20px -4px rgba(15, 212, 168, 0.3); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  #generateButton:disabled { background: var(--text-tertiary); cursor: not-allowed; box-shadow: none; opacity: 0.7; }
136
+ #result-container { min-height: 350px; position: relative; padding: 1rem; background-color: var(--input-bg); border-radius: var(--radius-card); border: 2px dashed var(--input-border); box-shadow: var(--shadow-sm) inset; transition: var(--transition-smooth); display: flex; flex-direction: column; align-items: center; justify-content: center; }
137
+ #result-container.active { border-style: solid; border-color: var(--panel-border); }
138
+ #statusSection, #outputSection, #criticalErrorSection { display: none; width: 100%; }
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  #statusSection.active, #outputSection.active, #criticalErrorSection.active { display: block; animation: fadeIn 0.5s; }
140
+ #statusMessages { max-height: 150px; overflow-y: auto; width: 100%; padding: 0.5rem; margin-bottom: 1rem; }
141
+ .status-message { display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem 1rem; margin-bottom: 0.5rem; border-radius: var(--radius-input); font-size: 0.9rem; background: var(--panel-bg); border: 1px solid var(--panel-border); color: var(--text-secondary); }
142
+ .status-message.success { border-left: 4px solid var(--success-color); color: var(--success-color); }
143
+ .status-message.error { border-left: 4px solid var(--danger-color); color: var(--danger-color); }
144
+ .status-icon { width: 18px; height: 18px; }
145
+ #aiLoader { display: none; align-items: center; justify-content: center; }
146
+ .generator-container { position: relative; width: 400px; max-width: 100%; height: 300px; border: 2px solid #38bdf8; border-radius: 20px; overflow: hidden; box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); animation: pulse-loader 5s infinite cubic-bezier(0.4, 0, 0.6, 1); background-color: #161b22; color: #f0f6fc; }
147
+ @keyframes pulse-loader { 0% { box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); } 50% { box-shadow: 0 0 60px rgba(56, 189, 248, 0.7); } 100% { box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); } }
148
+ .noise-layer, .sketch-layer, .building-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
149
+ .noise-layer { background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect width="100" height="100" fill="none"/><filter id="noise"><feTurbulence type="fractalNoise" baseFrequency="0.5" numOctaves="4" stitchTiles="stitch"/></filter><rect width="100%" height="100%" filter="url(%23noise)" opacity="0.6"/></svg>') repeat; opacity: 1; animation: fade-noise 7s infinite ease-in-out; }
150
+ @keyframes fade-noise { 0% { opacity: 1; filter: blur(5px); } 30% { opacity: 0.8; filter: blur(2px); } 100% { opacity: 0; filter: blur(0px); } }
151
+ .sketch-layer { filter: grayscale(1) contrast(1.5) blur(3px); opacity: 0; animation: reveal-sketch 7s infinite ease-in-out; }
152
+ @keyframes reveal-sketch { 0% { opacity: 0; } 20% { opacity: 1; } 60% { opacity: 0.5; } 100% { opacity: 0; } }
153
+ .building-layer { filter: blur(15px); opacity: 0; animation: denoise-color 7s infinite ease-in-out; }
154
+ @keyframes denoise-color { 0% { opacity: 0; } 40% { opacity: 0.6; filter: blur(5px); } 100% { opacity: 1; filter: blur(0px); } }
155
+ .pixel-grid { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: repeating-linear-gradient(0deg, transparent 0 1px, rgba(255,255,255,0.1) 1px 2px), repeating-linear-gradient(90deg, transparent 0 1px, rgba(255,255,255,0.1) 1px 2px); opacity: 1; animation: dissolve-grid 7s infinite ease-in-out; }
156
+ @keyframes dissolve-grid { 0% { opacity: 1; } 70% { opacity: 0.5; } 100% { opacity: 0; } }
157
+ .particles { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle, rgba(56, 189, 248, 0.2) 0%, transparent 50%); animation: flow-particles 7s infinite cubic-bezier(0.4, 0, 0.6, 1); }
158
+ @keyframes flow-particles { 0% { transform: translate(0, 0) scale(1); opacity: 0.5; } 50% { transform: translate(10px, -15px) scale(1.05); opacity: 0.8; } 100% { transform: translate(0, 0) scale(1); opacity: 0.5; } }
159
+ .text-overlay { position: absolute; top: 45%; left: 50%; transform: translate(-50%, -50%); font-size: 24px; font-weight: 700; text-shadow: 0 0 20px rgba(56, 189, 248, 0.8); animation: glow-text 7s infinite ease-in-out; font-family: var(--app-font); width: 90%; text-align: center;}
160
+ @keyframes glow-text { 0% { opacity: 0.7; } 50% { opacity: 1; } 100% { opacity: 0.7; } }
161
+ .progress-bar { position: absolute; bottom: 0; left: 0; width: 0%; height: 6px; background: linear-gradient(to right, #38bdf8, #bb86fc, #facc15); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  .progress-bar.animate-progress { animation: fill-progress 60s linear forwards; }
 
163
  #outputVideo { width: 100%; max-width: 500px; border-radius: var(--radius-input); margin: 1rem auto; display: block; background-color: #000; box-shadow: var(--shadow-md); }
164
+ #finalSeed { text-align: center; color: var(--text-tertiary); font-size: 0.85rem; margin-top: 1rem; }
 
165
  .video-controls { display: flex; justify-content: center; gap: 1rem; margin-top: 1.5rem; }
166
+ .video-button { padding: 0.7rem 1.5rem; border-radius: var(--radius-btn); border: none; cursor: pointer; font-family: var(--app-font); font-weight: 600; font-size: 1rem; transition: var(--transition-smooth); display: flex; align-items: center; gap: 0.5rem; }
 
167
  .video-button.primary { background-color: var(--accent-primary); color: white; }
168
+ .video-button.primary:hover { background-color: var(--accent-primary-hover); transform: translateY(-2px); }
169
+ .video-button:not(.primary) { background-color: var(--input-bg); color: var(--text-secondary); border: 1px solid var(--input-border); }
170
+ .video-button:not(.primary):hover { background-color: var(--panel-border); color: var(--text-primary); }
171
+ .gpu-error-message-container { text-align: right; background-color: var(--igadlm-alpha-color-warning-bg); padding: 25px; border-radius: var(--igadlm-alpha-border-radius-lg); box-shadow: var(--shadow-lg); border: 2px solid var(--igadlm-alpha-color-warning-border); animation: igadlmAlphaV2fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) both; }
172
+ .gpu-error-message-container h2 { font-size: 1.2em; color: var(--igadlm-alpha-color-warning-text); margin-bottom: 15px; font-weight: 700; display: flex; align-items: center; border-bottom: 1px solid var(--igadlm-alpha-color-warning-border); padding-bottom: 12px; }
173
+ .gpu-error-message-container h2 .icon { margin-left: 10px; font-size: 1.3em; }
174
+ .gpu-error-message-container .error-content { font-size: 0.9rem; color: var(--igadlm-alpha-color-text-light); line-height: 1.7; word-break: break-word; white-space: pre-line; }
175
+ .gpu-error-message-container .error-content strong.error-message-title { font-weight: 600; color: var(--igadlm-alpha-color-warning-text); display: block; margin-bottom: 8px;}
176
+ .gpu-error-message-container .error-content ul { list-style-type: "▫️ "; padding-right: 20px; margin-top: 10px; }
177
+ .gpu-error-message-container .error-content li { margin-bottom: 6px; }
178
  .gpu-error-actions { display: flex; gap: 15px; margin-top: 25px; }
179
+ .gpu-error-actions .action-button { padding: 12px 20px; border: none; border-radius: var(--igadlm-alpha-border-radius-md); font-size: 0.95em; font-weight: 600; cursor: pointer; font-family: inherit; width: 100%; box-shadow: var(--shadow-md); transition: var(--transition-smooth); text-transform: none; letter-spacing: normal; }
180
  .gpu-error-actions .action-button:hover:not(:disabled) { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
181
+ .gpu-error-actions .action-button.back-button { background-color: var(--panel-bg); color: var(--text-tertiary); border: 1px solid var(--panel-border); flex-grow: 0.45; }
182
+ .gpu-error-actions .action-button.back-button:hover:not(:disabled) { background-color: var(--input-bg); }
183
+ .gpu-error-actions .action-button.retry-button { background: linear-gradient(60deg, var(--igadlm-alpha-color-primary) 0%, var(--igadlm-alpha-color-secondary) 100%); color: white; flex-grow: 1; }
184
+ .gpu-error-actions .action-button.retry-button:hover:not(:disabled) { background: linear-gradient(60deg, var(--igadlm-alpha-color-primary-darker) 0%, var(--igadlm-alpha-color-secondary-darker) 100%); }
185
 
186
  @media (max-width: 768px) {
187
+ main { padding: 1.5rem; } h1 { font-size: 2.2rem; }
188
+ .generator-container { height: 250px; }
189
+ .text-overlay { font-size: 18px; }
190
  }
191
  </style>
192
  </head>
193
  <body>
194
  <div class="container">
195
  <header>
196
+ <canvas id="neural-network-canvas"></canvas>
197
+ <div class="header-content">
198
+ <div class="animation-visualizer">
199
+ <div class="image-placeholder">
200
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
201
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
202
+ <circle cx="8.5" cy="8.5" r="1.5"></circle>
203
+ <polyline points="21 15 16 10 5 21"></polyline>
204
+ </svg>
205
+ </div>
206
+ <div class="wave-ring"></div>
207
+ <div class="wave-ring two"></div>
208
  </div>
209
+ <h1>متحرک سازی تصاویر</h1>
210
+ <p class="subtitle">به تصاویر ثابت خود جان ببخشید و آنها را با هوش مصنوعی متحرک کنید</p>
211
+ <p class="instruction">تصویر خود را آپلود کنید، توضیح دهید چگونه باید حرکت کند، و جادو را تماشا کنید.</p>
212
  </div>
 
 
 
213
  </header>
214
 
215
  <main>
 
235
  </label>
236
  <textarea id="prompt" rows="4" placeholder="مثال: حرکت ابرها، موج زدن آب، وزش ملایم باد در موها، زوم آهسته به بیرون"></textarea>
237
  <button id="generateButton">
238
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3L12 8L17 10L12 12L10 17L8 12L3 10L8 8L10 3z"/></svg>
239
  <span>شروع متحرک سازی</span>
240
  </button>
241
  </div>
242
 
243
  <div class="form-group">
244
  <div class="form-label">
245
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3L12 8L17 10L12 12L10 17L8 12L3 10L8 8L10 3z"/><path d="M21 14l-1.5 3-3-1.5 3-3 1.5 3z"/><path d="M19.5 2.5l-3 1.5 1.5 3 3-1.5-1.5-3z"/></svg>
246
  ۳. نتیجه را ببینید
247
  </div>
248
  <div id="result-container">
249
  <div id="statusSection">
250
+ <div id="statusMessages"></div>
251
  <div id="aiLoader" style="display: none;">
252
+ <div class="generator-container">
253
+ <div class="noise-layer"></div>
254
+ <div class="sketch-layer"></div>
255
+ <div class="building-layer"></div>
256
+ <div class="pixel-grid"></div>
257
+ <div class="particles"></div>
258
+ <div class="text-overlay">در حال متحرک سازی تصویر...</div>
259
+ <div class="progress-bar"></div>
260
+ </div>
261
+ </div>
262
  </div>
263
 
264
  <div id="outputSection">
 
301
  const finalSeedElement = document.getElementById('finalSeed');
302
  const criticalErrorSection = document.getElementById('criticalErrorSection');
303
  const statusSection = document.getElementById('statusSection');
304
+ const statusMessagesDiv = document.getElementById('statusMessages');
305
  const aiLoader = document.getElementById('aiLoader');
306
+ const loaderProgressBar = document.querySelector('#aiLoader .progress-bar');
307
+ const loaderTextOverlay = document.querySelector('#aiLoader .text-overlay');
 
308
  const outputSection = document.getElementById('outputSection');
309
  const btnRestart = document.getElementById('btnRestart');
310
  const btnDownloadVideo = document.getElementById('btnDownloadVideo');
 
314
  const TRANSLATOR_SPACE_URL_BASE = "https://hamed744-translate-tts-aloha.hf.space/gradio_api";
315
  const FN_INDEX_TRANSLATE_SPEAK = 1;
316
  const DEFAULT_TTS_VOICE_TRANSLATOR = "انگلیسی (آمریکا) - جنی (زن)";
317
+ const DEFAULT_NEGATIVE_PROMPT = "bad quality, ugly, blurry, watermark, text, signature";
318
  const DEFAULT_PROMPT_TEXT = "";
319
  const TRANSPARENT_PIXEL_SRC = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
320
  const DEFAULT_VIDEO_DURATION = 5.1;
 
327
  return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15).substring(0, length - 7);
328
  }
329
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  async function retryLastAttempt() {
331
  if (!lastAttemptedVideoPayload || !lastAttemptedVideoPayload.imageSourceFile || !lastAttemptedVideoPayload.persianPrompt) {
332
  showCriticalError("اطلاعات کافی برای تلاش مجدد وجود ندارد. لطفاً از نو شروع کنید.");
 
334
  }
335
 
336
  hideCriticalError();
337
+ clearStatusMessages();
338
  generateButton.disabled = true;
339
  resultContainer.classList.add('active');
340
  statusSection.classList.add('active');
341
+ aiLoader.style.display = 'flex';
 
342
  loaderProgressBar.classList.add('animate-progress');
343
  outputSection.classList.remove('active');
344
+ addStatusMessage('شروع تلاش مجدد...', 'info');
345
 
346
  const { persianPrompt, imageSourceFile } = lastAttemptedVideoPayload;
347
 
348
  try {
349
  const englishPromptForVideo = await translatePersianToEnglish(persianPrompt);
350
+ addStatusMessage(`ترجمه مجدد: ${englishPromptForVideo}`, 'info');
351
+
352
  setAiLoaderText("در حال متحرک سازی تصویر...");
353
 
354
  const client = await Client.connect(VIDEO_SPACE_NAME);
 
372
  }
373
  }
374
 
375
+ function showCriticalError(message, isTranslationError = false) {
376
  const lowerCaseMessage = (message || "").toLowerCase();
377
+
378
+ if (lowerCaseMessage.includes("gpu") || lowerCaseMessage.includes("quota") || lowerCaseMessage.includes("exceeded") || lowerCaseMessage.includes("limit") || lowerCaseMessage.includes("capacity") || lowerCaseMessage.includes("queue full")) {
379
+ const detailedGpuErrorHtml = `
380
  <div class="gpu-error-message-container">
381
+ <h2><span class="icon">💡</span> راهنمای رفع محدودیت انیمیشن</h2>
382
+ <div class="error-content">
383
+ <strong class="error-message-title">ارور محدودیت استفاده از GPU</strong>
384
+ <p>مدل‌های هوش مصنوعی برای اجرا به کارت‌های گرافیک بسیار قدرتمند نیاز دارند که این امر گاهی محدودیت‌هایی جی پی یو را در استفاده از آن‌ها ایجاد می‌کند. با این حال، خبر خوب این است که این محدودیت‌ها با استفاده از یک ترفند ساده قابل رفع هستند.</p>
385
+ <p><strong>برای رفع محدودیت هر بار اینگونه عمل کنید:</strong></p>
386
+ <ul>
387
+ <li>اگر از اینترنت سیم‌کارت استفاده می‌کنید، فیلترشکن خود را حتما خاموش کنید.</li>
388
+ <li>به مدت ۱۰ تا ۱۵ ثانیه، گوشی خود را در حالت هواپیما قرار دهید و سپس آن را غیرفعال کنید. این محدودیت با همین روش برداشته میشود</li>
389
+ <li>اگر از وای فای استفاده می‌کنید، مودم خود را یک بار خاموش و روشن کنید (بهتر است برای استفاده نامحدود از برنامه از اینترنت سیم کارت استفاده کنید).</li>
390
  </ul>
391
+ <p>خلاصه: هربار این صفحه اومد اینگونه عمل کنید از اینترنت سیم کارت استفاده کنید، فیلتر شکن خاموش باشه، یک یا دوبار حالت هواپیما رو روشن و خاموش کنید. تلاش مجدداً بزنید این ارور بر طرف میشه، با این روش میشه بصورت نامحدود با این برنامه انیمیشن ساخت☘️</p>
392
  </div>
393
  <div class="gpu-error-actions">
394
  <button class="action-button back-button" id="gpuErrorGoBackBtn">بازگشت</button>
395
  <button class="action-button retry-button" id="gpuErrorRetryBtn">تلاش مجدد</button>
396
  </div>
397
  </div>`;
398
+ criticalErrorSection.innerHTML = detailedGpuErrorHtml;
399
  document.getElementById('gpuErrorGoBackBtn').addEventListener('click', hideCriticalError);
400
  document.getElementById('gpuErrorRetryBtn').addEventListener('click', retryLastAttempt);
401
  } else {
402
+ criticalErrorSection.innerHTML = `
403
+ <div id="criticalErrorMessage" style="color: var(--danger-color); font-weight: 500; margin-bottom: 1.5rem; line-height: 1.6;"><p>${message}</p></div>
404
  <div class="video-controls">
405
  <button id="simpleErrorGoBackBtn" class="video-button">بازگشت</button>
406
  <button id="simpleErrorRetryBtn" class="video-button primary">تلاش مجدد</button>
407
  </div>`;
408
+ document.getElementById('simpleErrorGoBackBtn').addEventListener('click', hideCriticalError);
409
+ document.getElementById('simpleErrorRetryBtn').addEventListener('click', retryLastAttempt);
 
410
  }
411
 
412
  resultContainer.classList.add('active');
 
422
  criticalErrorSection.innerHTML = '';
423
  }
424
 
425
+ function setAiLoaderText(text) {
426
+ if (loaderTextOverlay) loaderTextOverlay.textContent = text;
427
+ }
428
+
429
+ function addStatusMessage(message, type = 'info') {
430
+ if (criticalErrorSection.classList.contains('active') && type === 'error') {
431
+ return;
432
+ }
433
+ resultContainer.classList.add('active');
434
+ statusSection.classList.add('active');
435
+ const messageDiv = document.createElement('div');
436
+ messageDiv.className = `status-message ${type}`;
437
+ const iconSvg = type === 'success'
438
+ ? '<svg class="status-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>'
439
+ : type === 'error'
440
+ ? '<svg class="status-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>'
441
+ : '<svg class="status-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>';
442
+ messageDiv.innerHTML = `${iconSvg}<span class="status-text">${message}</span>`;
443
+ statusMessagesDiv.insertBefore(messageDiv, statusMessagesDiv.firstChild);
444
+ statusMessagesDiv.scrollTop = 0;
445
+ }
446
+
447
+ function clearStatusMessages() {
448
+ statusMessagesDiv.innerHTML = '';
449
+ if(loaderProgressBar) {
450
+ loaderProgressBar.classList.remove('animate-progress');
451
+ loaderProgressBar.style.width = '0%';
452
+ }
453
+ aiLoader.style.display = 'none';
454
+ if (outputVideo.src && outputVideo.src.startsWith('blob:')) {
455
+ URL.revokeObjectURL(outputVideo.src);
456
+ }
457
+ outputVideo.src = '';
458
+ finalSeedElement.textContent = '';
459
+ btnDownloadVideo.style.display = 'none';
460
+ }
461
+
462
  function initializeForm() {
463
  hideCriticalError();
464
  resultContainer.classList.remove('active');
465
  statusSection.classList.remove('active');
466
  outputSection.classList.remove('active');
467
+ clearStatusMessages();
468
  lastAttemptedVideoPayload = null;
469
  imageFileInput.value = '';
470
  imagePreview.src = TRANSPARENT_PIXEL_SRC;
 
474
  }
475
 
476
  async function translatePersianToEnglish(persianText) {
 
477
  let currentTranslatorSessionHash = generateRandomHash();
478
+ const payload = {
479
+ data: [persianText, DEFAULT_TTS_VOICE_TRANSLATOR, 0, 0, 0],
480
+ event_data: null,
481
+ fn_index: FN_INDEX_TRANSLATE_SPEAK,
482
+ session_hash: currentTranslatorSessionHash
483
+ };
484
+
485
+ if (aiLoader.style.display !== 'none') {
486
+ setAiLoaderText("در حال ترجمه دستور شما...");
487
+ }
488
+
489
  try {
490
+ const joinResponse = await fetch(`${TRANSLATOR_SPACE_URL_BASE}/queue/join?`, {
491
+ method: 'POST',
492
+ headers: { 'Content-Type': 'application/json' },
493
+ body: JSON.stringify(payload)
494
+ });
495
+ if (!joinResponse.ok) throw new Error(`خطای اتصال به سرور مترجم: ${joinResponse.status}`);
496
+ const joinData = await joinResponse.json();
497
+ if (!joinData.event_id) throw new Error("پاسخ نامعتبر از سرور مترجم.");
498
+
499
  return new Promise((resolve, reject) => {
500
  const eventSource = new EventSource(`${TRANSLATOR_SPACE_URL_BASE}/queue/data?session_hash=${currentTranslatorSessionHash}`);
501
+ eventSource.onmessage = function(event) {
502
  const data = JSON.parse(event.data);
503
  if (data.msg === "process_completed") {
504
  eventSource.close();
505
+ if (data.success && data.output?.data?.[0]) {
506
+ resolve(data.output.data[0]);
507
+ } else {
508
+ reject(new Error(data.output?.error || "ترجمه ناموفق بود."));
509
+ }
510
+ } else if (data.msg === "process_starts") {
511
+ if (aiLoader.style.display !== 'none') setAiLoaderText("ترجمه در سرور آغاز شد...");
512
  }
513
  };
514
+ eventSource.onerror = (err) => {
515
+ eventSource.close();
516
+ reject(new Error("خطا در ارتباط با سرور مترجم."));
517
+ };
518
  });
519
+ } catch (error) {
520
+ console.error("خطای کلی در ترجمه:", error);
521
+ throw error;
522
+ }
523
  }
524
 
525
  function handleSuccessfulVideoGeneration(result) {
 
528
  showCriticalError('پاسخ دریافتی از سرور انیمیشن معتبر نیست.');
529
  return;
530
  }
531
+
532
+ addStatusMessage('انیمیشن شما آماده شد! 🎉', 'success');
533
  const videoUrl = result.data[0].video.url;
534
  const usedSeed = result.data[1];
535
 
536
+ setTimeout(() => {
537
+ statusSection.classList.remove('active');
538
+ outputSection.classList.add('active');
539
+ outputVideo.src = videoUrl;
540
+ outputVideo.load();
541
+ finalSeedElement.textContent = `Seed: ${usedSeed}`;
542
+ prepareCustomDownloadLink(videoUrl);
543
+ }, 300);
544
 
545
  generateButton.disabled = false;
546
  }
 
560
 
561
  // Event Listeners
562
  document.addEventListener('DOMContentLoaded', initializeForm);
563
+
564
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { imageDropZone.addEventListener(eventName, ev => { ev.preventDefault(); ev.stopPropagation(); }); });
565
+ ['dragenter', 'dragover'].forEach(eventName => { imageDropZone.addEventListener(eventName, () => { if (!imageDropZone.classList.contains('has-image')) { imageDropZone.classList.add('drag-over'); } }); });
566
+ ['dragleave', 'drop'].forEach(eventName => { imageDropZone.addEventListener(eventName, () => { imageDropZone.classList.remove('drag-over'); }); });
 
567
  imageDropZone.addEventListener('drop', e => {
568
+ if (!imageDropZone.classList.contains('has-image') && e.dataTransfer.files.length > 0) {
569
  imageFileInput.files = e.dataTransfer.files;
570
+ const changeEvent = new Event('change', { bubbles: true });
571
+ imageFileInput.dispatchEvent(changeEvent);
572
  }
573
  });
574
+
575
+ btnRestart.addEventListener('click', initializeForm);
576
 
577
  imageFileInput.addEventListener('change', function(event) {
578
  const file = event.target.files[0];
579
  hideCriticalError();
580
  if (file) {
581
  if (!file.type.startsWith('image/')) {
582
+ addStatusMessage('فرمت نامعتبر', 'error');
583
  imageFileInput.value = '';
584
+ imagePreview.src = TRANSPARENT_PIXEL_SRC;
585
+ imageDropZone.classList.remove('has-image');
586
  } else {
587
  const reader = new FileReader();
588
  reader.onload = (e) => {
 
601
  const persianPrompt = promptInput.value.trim();
602
  const imageFile = imageFileInput.files[0];
603
 
604
+ if (!persianPrompt) {
605
+ addStatusMessage('لطفاً دستور حرکت را وارد کنید.', 'error');
606
+ promptInput.focus();
607
+ return;
608
+ }
609
+ if (!imageFile) {
610
+ addStatusMessage('لطفاً تصویر انتخاب کنید.', 'error');
611
+ return;
612
+ }
613
 
614
+ clearStatusMessages();
615
  hideCriticalError();
616
  generateButton.disabled = true;
617
  resultContainer.classList.add('active');
618
  statusSection.classList.add('active');
619
+ aiLoader.style.display = 'flex';
 
620
  loaderProgressBar.classList.add('animate-progress');
621
  outputSection.classList.remove('active');
622
+ addStatusMessage('در حال آماده سازی...', 'info');
623
 
624
+ lastAttemptedVideoPayload = {
625
+ persianPrompt: persianPrompt,
626
+ imageSourceFile: imageFile
627
+ };
628
 
629
  try {
630
  const englishPrompt = await translatePersianToEnglish(persianPrompt);
631
+ addStatusMessage(`ترجمه: ${englishPrompt}`, 'info');
632
+
633
  setAiLoaderText("در حال متحرک سازی تصویر...");
634
 
635
  const client = await Client.connect(VIDEO_SPACE_NAME);
 
648
  handleSuccessfulVideoGeneration(result);
649
 
650
  } catch (errorCaught) {
651
+ const isTranslationErr = errorCaught.message.toLowerCase().includes("مترجم");
652
+ showCriticalError(`خطا: ${errorCaught.message}`, isTranslationErr);
653
  }
654
  });
655
  })();
656
  </script>
657
+
658
+ <script>
659
+ // --- Header Animation Script ---
660
+ document.addEventListener('DOMContentLoaded', () => {
661
+ const canvas = document.getElementById('neural-network-canvas');
662
+ if (!canvas) return;
663
+ const header = canvas.parentElement;
664
+ const ctx = canvas.getContext('2d');
665
+ let particles = [];
666
+ const particleCount = 20; const maxDistance = 100;
667
+ const computedStyles = getComputedStyle(document.documentElement);
668
+ const particleColor = computedStyles.getPropertyValue('--accent-primary').trim();
669
+ const lineColor = computedStyles.getPropertyValue('--text-tertiary').trim();
670
+ function resizeCanvas() { canvas.width = header.clientWidth; canvas.height = header.clientHeight; init(); }
671
+ class Particle { constructor() { this.x = Math.random() * canvas.width; this.y = Math.random() * canvas.height; this.vx = (Math.random() - 0.5) * 0.3; this.vy = (Math.random() - 0.5) * 0.3; this.radius = 1.2; } update() { this.x += this.vx; this.y += this.vy; if (this.x < 0 || this.x > canvas.width) this.vx *= -1; if (this.y < 0 || this.y > canvas.height) this.vy *= -1; } draw() { ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fillStyle = particleColor; ctx.fill(); } }
672
+ function init() { particles = []; for (let i = 0; i < particleCount; i++) { particles.push(new Particle()); } }
673
+ function connectParticles() { for (let i = 0; i < particles.length; i++) { for (let j = i + 1; j < particles.length; j++) { const dx = particles[i].x - particles[j].x; const dy = particles[i].y - particles[j].y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < maxDistance) { ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.strokeStyle = lineColor; ctx.lineWidth = 0.2; ctx.globalAlpha = 1 - distance / maxDistance; ctx.stroke(); } } } ctx.globalAlpha = 1; }
674
+ function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach(particle => { particle.update(); particle.draw(); }); connectParticles(); requestAnimationFrame(animate); }
675
+ window.addEventListener('resize', resizeCanvas); resizeCanvas(); animate();
676
+ });
677
+ </script>
678
  </body>
679
  </html>