Elias207 commited on
Commit
fef2e2e
·
verified ·
1 Parent(s): fa8704b

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +231 -317
index.html CHANGED
@@ -4,323 +4,264 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>فتوشاپ هوش مصنوعی (نسخه گالری)</title>
7
- <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700&display=swap" rel="stylesheet">
8
  <style>
9
  :root {
10
- --bg-color: #f8f9fa;
11
- --text-color: #212529;
 
12
  --card-bg: #ffffff;
13
- --border-color: #dee2e6;
14
- --primary-color-start: #3b82f6;
15
- --primary-color-end: #8b5cf6;
16
- --shadow-color: rgba(0, 0, 0, 0.08);
17
- --drop-zone-bg: #f1f5f9;
18
- --drop-zone-border-active: #a5b4fc;
 
19
  }
20
 
21
- /* --- Dark Mode --- */
22
  .dark {
23
- --bg-color: #111827;
24
- --text-color: #d1d5db;
25
- --card-bg: #1f2937;
26
- --border-color: #374151;
27
- --shadow-color: rgba(0, 0, 0, 0.25);
28
- --drop-zone-bg: #374151;
29
- --drop-zone-border-active: #6366f1;
 
30
  }
31
 
32
- /* --- Base Styles & Animations --- */
33
  @keyframes fadeInUp {
34
- from {
35
- opacity: 0;
36
- transform: translateY(20px);
37
- }
38
- to {
39
- opacity: 1;
40
- transform: translateY(0);
41
- }
42
  }
43
 
44
  body {
45
  font-family: 'Vazirmatn', sans-serif;
46
- background-color: var(--bg-color);
 
47
  color: var(--text-color);
48
  margin: 0;
49
- padding: 2rem;
50
  display: flex;
51
  justify-content: center;
52
  align-items: flex-start;
53
  min-height: 100vh;
54
- transition: background-color 0.3s, color 0.3s;
55
  }
56
 
57
  .container {
58
- max-width: 700px; /* Reduced width for a more focused single-column layout */
59
  width: 100%;
60
  }
61
 
62
- /* --- Header --- */
63
  header {
64
  text-align: center;
65
- margin-bottom: 2.5rem;
66
- animation: fadeInUp 0.5s ease-out forwards;
67
  }
68
 
69
  h1 {
70
- font-size: 2.5rem;
71
- font-weight: 700;
 
72
  margin: 0;
73
  background: linear-gradient(45deg, var(--primary-color-start), var(--primary-color-end));
74
  -webkit-background-clip: text;
75
  -webkit-text-fill-color: transparent;
76
- background-clip: text;
77
- color: transparent;
78
  }
79
 
80
  p.subtitle {
81
- font-size: 1.1rem;
82
- color: #6b7280;
83
- margin-top: 0.5rem;
84
  }
 
85
 
86
- .dark p.subtitle { color: #9ca3af; }
87
-
88
- /* --- Main Content Card --- */
89
  main {
90
  background-color: var(--card-bg);
91
- border-radius: 1.5rem;
92
- box-shadow: 0 20px 50px -10px var(--shadow-color);
93
- padding: 2.5rem;
94
- transition: background-color 0.3s, box-shadow 0.3s;
95
- animation: fadeInUp 0.5s ease-out 0.2s forwards;
96
- opacity: 0; /* Initially hidden for animation */
 
97
  }
98
 
99
- /* --- Section Styling (Panel, Controls) --- */
100
- .panel, .controls {
101
- margin-bottom: 2.5rem;
102
- }
103
- .controls { margin-bottom: 2rem; }
104
  .panel:last-child { margin-bottom: 0; }
 
105
 
106
  h2 {
107
- font-size: 1.25rem;
108
- font-weight: 600;
109
- margin-top: 0;
110
- margin-bottom: 1.5rem;
111
- border-bottom: 2px solid var(--border-color);
112
- padding-bottom: 0.75rem;
113
  display: flex;
114
  align-items: center;
115
  gap: 0.75rem;
 
116
  }
 
117
 
118
- /* --- Drop Zone & Image Preview --- */
119
  #drop-zone {
120
- border: 3px dashed var(--border-color);
121
- border-radius: 1rem;
122
- padding: 2rem;
123
- text-align: center;
124
- cursor: pointer;
125
- transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
126
- position: relative;
127
- overflow: hidden;
128
- background-color: var(--drop-zone-bg);
129
- display: flex;
130
- flex-direction: column;
131
- justify-content: center;
132
- align-items: center;
133
- min-height: 250px;
134
- }
135
-
136
- #drop-zone.dragover {
137
- border-color: var(--drop-zone-border-active);
138
- background-color: rgba(99, 102, 241, 0.1);
139
- transform: scale(1.02);
140
- }
141
-
142
- #drop-zone-icon {
143
- width: 50px;
144
- height: 50px;
145
- margin-bottom: 1rem;
146
- color: #9ca3af;
147
- transition: transform 0.3s ease;
148
- }
149
- .dark #drop-zone-icon { color: #6b7280; }
150
- #drop-zone:hover #drop-zone-icon { transform: translateY(-5px); }
151
-
152
- #drop-zone-text {
153
- color: #4b5563;
154
- font-weight: 500;
155
- }
156
- .dark #drop-zone-text { color: #d1d5db; }
157
-
158
  #image-preview {
159
- max-width: 100%;
160
- max-height: 300px;
161
- border-radius: 0.75rem;
162
- object-fit: contain;
163
- display: none;
164
  }
 
165
 
166
- /* --- Controls (Textarea & Button) --- */
167
  textarea {
168
- width: 100%;
169
- padding: 1rem;
170
- border: 2px solid var(--border-color);
171
- border-radius: 0.75rem;
172
- background-color: var(--bg-color);
173
- color: var(--text-color);
174
- font-family: 'Vazirmatn', sans-serif;
175
- font-size: 1rem;
176
- resize: vertical;
177
- min-height: 80px;
178
- box-sizing: border-box;
179
- transition: all 0.3s ease;
180
  }
181
-
182
  textarea:focus {
183
- outline: none;
184
- border-color: var(--primary-color-start);
185
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
186
  }
187
 
188
  button#submit-btn {
189
- width: 100%;
190
- padding: 1rem;
191
- border: none;
192
- border-radius: 0.75rem;
193
- background-image: linear-gradient(45deg, var(--primary-color-start), var(--primary-color-end));
194
- background-size: 150% 150%;
195
- color: white;
196
- font-size: 1.1rem;
197
- font-weight: 600;
198
- cursor: pointer;
199
- margin-top: 1rem;
200
- transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
201
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
202
  }
203
-
204
  button#submit-btn:hover:not(:disabled) {
205
- transform: translateY(-3px) scale(1.01);
206
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
207
  background-position: right center;
208
  }
209
-
210
- button#submit-btn:disabled {
211
- opacity: 0.5;
212
- cursor: not-allowed;
213
  }
214
-
215
- /* --- Result Area & Loading Animation --- */
216
- @keyframes pulse-bg {
217
- 0% { background-color: var(--drop-zone-bg); }
218
- 50% { background-color: color-mix(in srgb, var(--drop-zone-bg) 85%, var(--primary-color-start)); }
219
- 100% { background-color: var(--drop-zone-bg); }
220
  }
 
 
 
 
221
 
222
  #result-container {
223
- border: 3px solid var(--border-color);
224
- border-radius: 1rem;
225
- min-height: 300px;
226
- display: flex;
227
- justify-content: center;
228
- align-items: center;
229
  position: relative;
230
- background-color: var(--drop-zone-bg);
231
- overflow: hidden;
232
- padding: 0.5rem;
233
- transition: border-color 0.3s ease;
234
  }
235
 
236
- #result-container.loading {
237
- animation: pulse-bg 2s infinite ease-in-out;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  }
 
 
 
 
 
 
239
 
240
  #result-grid {
241
- display: grid;
242
- grid-template-columns: repeat(2, 1fr);
243
- gap: 0.5rem;
244
- width: 100%;
245
- height: 100%;
246
  }
247
-
248
- @keyframes popIn {
249
- from {
250
- transform: scale(0.8) translateY(10px);
251
- opacity: 0;
252
- }
253
- to {
254
- transform: scale(1) translateY(0);
255
- opacity: 1;
256
- }
257
  }
258
-
259
  #result-grid img {
260
- width: 100%;
261
- height: 100%;
262
- object-fit: cover;
263
- border-radius: 0.75rem;
264
- cursor: pointer;
265
- transition: transform 0.2s ease, box-shadow 0.2s ease;
266
- opacity: 0; /* Hidden for animation */
267
- animation: popIn 0.5s cubic-bezier(0.25, 0.8, 0.25, 1) forwards;
268
  }
269
-
270
  #result-grid img:hover {
271
- transform: scale(1.05);
272
- box-shadow: 0 5px 15px var(--shadow-color);
273
- }
274
-
275
- .spinner {
276
- width: 50px;
277
- height: 50px;
278
- border: 5px solid rgba(0,0,0,0.1);
279
- border-left-color: var(--primary-color-start);
280
- border-radius: 50%;
281
- animation: spin 1s linear infinite;
282
- position: absolute;
283
- display: none;
284
- z-index: 10;
285
- }
286
-
287
- @keyframes spin {
288
- to { transform: rotate(360deg); }
289
- }
290
-
291
- #error-message {
292
- color: #ef4444;
293
- text-align: center;
294
- margin-top: 1rem;
295
- display: none;
296
- font-weight: 500;
297
  }
298
 
299
- /* --- Lightbox Styles (Unchanged) --- */
 
300
  #lightbox {
301
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
302
- background-color: rgba(0, 0, 0, 0.85); z-index: 1000; display: none;
303
  justify-content: center; align-items: center; padding: 2rem;
304
- box-sizing: border-box; backdrop-filter: blur(5px);
 
 
 
 
305
  }
306
- #lightbox-content { position: relative; max-width: 80vw; max-height: 90vh; }
307
- #lightbox-img { max-width: 100%; max-height: 90vh; object-fit: contain; border-radius: 1rem; }
308
  #lightbox-close, #lightbox-download {
309
- position: absolute; top: -40px; background-color: rgba(255, 255, 255, 0.2);
310
- color: white; border: none; width: 35px; height: 35px; border-radius: 50%;
311
- cursor: pointer; font-size: 1.5rem; display: flex; justify-content: center;
312
- align-items: center; transition: background-color 0.2s;
313
  }
314
  #lightbox-close { left: 10px; }
315
  #lightbox-download { right: 10px; }
316
- #lightbox-close:hover, #lightbox-download:hover { background-color: rgba(255, 255, 255, 0.4); }
 
 
317
 
318
- /* --- Responsive Design --- */
319
  @media (max-width: 600px) {
320
- body { padding: 1rem; }
321
  main { padding: 1.5rem; }
322
- h1 { font-size: 2rem; }
323
- h2 { font-size: 1.1rem; }
324
  }
325
  </style>
326
  </head>
@@ -333,46 +274,65 @@
333
  </header>
334
 
335
  <main>
336
- <!-- 1. Upload Section -->
337
  <div class="panel">
338
  <h2>
339
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>
340
- ۱. تصویر خود را آپلود کنید
341
  </h2>
342
  <div id="drop-zone">
343
- <svg id="drop-zone-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.2 15c.7-1.2 1-2.5.7-3.9-.6-2.4-2.5-4.2-4.9-4.6-1.1-.2-2.2.3-3.1.9-1.4-2.4-4.1-3.9-7-3.9-4.4 0-8 3.6-8 8 0 1.9.7 3.7 1.8 5.1"></path><path d="M9.5 16.5 12 14l2.5 2.5"></path><path d="M12 14v7"></path></svg>
344
- <span id="drop-zone-text">تصویر را اینجا بکشید یا برای انتخاب کلیک کنید</span>
 
 
345
  <img id="image-preview" src="#" alt="پیش‌نمایش تصویر" />
346
  </div>
347
  <input type="file" id="file-input" accept="image/*" hidden>
348
  </div>
349
 
350
- <!-- 2. Controls Section -->
351
  <div class="controls">
352
  <h2>
353
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 5-3-3-6 6 3 3Z"></path><path d="m6 8-3-3 6-6 3 3Z"></path><path d="M14 4 12 6"></path><path d="m6 16 6 6"></path><path d="M11 11 8 8"></path></svg>
354
- ۲. دستور ویرایش را وارد کنید
355
  </h2>
356
  <textarea id="prompt-input" rows="3" placeholder="مثال: پس‌زمینه را حذف کن و یک ساحل آفتابی قرار بده"></textarea>
357
- <button id="submit-btn" disabled>ویرایش کن</button>
 
 
358
  <p id="error-message"></p>
359
  </div>
360
 
361
- <!-- 3. Result Section -->
362
  <div class="panel">
363
  <h2>
364
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.5 22h-6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v7.5"></path><path d="M2 15h20"></path><path d="M18 22a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z"></path><path d="m22 22-1.5-1.5"></path></svg>
365
- ۳. نتیجه را مشاهده کنید
366
  </h2>
367
  <div id="result-container">
368
- <div class="spinner" id="loader"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  <div id="result-grid"></div>
370
  </div>
371
  </div>
372
  </main>
373
  </div>
374
 
375
- <!-- Lightbox Structure (Unchanged) -->
376
  <div id="lightbox">
377
  <div id="lightbox-content">
378
  <img id="lightbox-img" src="">
@@ -382,22 +342,20 @@
382
  </a>
383
  </div>
384
  </div>
385
-
386
  <script>
387
- // --- API Endpoint (Unchanged) ---
388
  const API_URL = 'https://alfa-editor-worker.onrender.com/api/edit';
389
 
390
  // --- DOM Elements ---
391
  const dropZone = document.getElementById('drop-zone');
 
392
  const fileInput = document.getElementById('file-input');
393
  const imagePreview = document.getElementById('image-preview');
394
- const dropZoneText = document.getElementById('drop-zone-text');
395
- const dropZoneIcon = document.getElementById('drop-zone-icon'); // New element
396
  const promptInput = document.getElementById('prompt-input');
397
  const submitBtn = document.getElementById('submit-btn');
398
- const resultContainer = document.getElementById('result-container'); // New reference
 
399
  const resultGrid = document.getElementById('result-grid');
400
- const loader = document.getElementById('loader');
401
  const errorMessage = document.getElementById('error-message');
402
  const lightbox = document.getElementById('lightbox');
403
  const lightboxImg = document.getElementById('lightbox-img');
@@ -406,88 +364,50 @@
406
 
407
  let uploadedFile = null;
408
 
409
- // --- File Upload Logic (Updated for better UI feedback) ---
410
  dropZone.addEventListener('click', () => fileInput.click());
411
- fileInput.addEventListener('change', (e) => {
412
- const file = e.target.files[0];
413
- if (file) handleFile(file);
414
- });
415
-
416
- ['dragenter', 'dragover'].forEach(eventName => {
417
- dropZone.addEventListener(eventName, (e) => {
418
- e.preventDefault();
419
- dropZone.classList.add('dragover');
420
- }, false);
421
- });
422
-
423
- ['dragleave', 'drop'].forEach(eventName => {
424
- dropZone.addEventListener(eventName, (e) => {
425
- e.preventDefault();
426
- dropZone.classList.remove('dragover');
427
- }, false);
428
- });
429
-
430
- dropZone.addEventListener('drop', (e) => {
431
- const file = e.dataTransfer.files[0];
432
- if (file) handleFile(file);
433
- });
434
 
435
  function handleFile(file) {
436
- if (!file.type.startsWith('image/')) {
437
- displayError('لطفا یک فایل تصویری انتخاب کنید.');
438
- return;
439
- }
440
-
441
  uploadedFile = file;
442
-
443
  const reader = new FileReader();
444
  reader.onload = (e) => {
445
  imagePreview.src = e.target.result;
446
- imagePreview.style.display = 'block';
447
- dropZoneText.style.display = 'none';
448
- dropZoneIcon.style.display = 'none'; // Hide icon
449
- submitBtn.disabled = promptInput.value.trim() === ''; // Enable if prompt is already filled
450
  clearResult();
451
  };
452
  reader.readAsDataURL(file);
453
  }
454
 
455
- // --- Enable/Disable button based on both inputs ---
456
  function checkFormState() {
457
  submitBtn.disabled = !uploadedFile || promptInput.value.trim() === '';
458
  }
459
- promptInput.addEventListener('input', checkFormState);
460
-
461
 
462
- // --- API Call Logic (Unchanged core logic) ---
463
  submitBtn.addEventListener('click', async () => {
464
  if (submitBtn.disabled) return;
465
-
466
  setLoading(true);
467
-
468
  const formData = new FormData();
469
  formData.append('image', uploadedFile);
470
  formData.append('prompt', promptInput.value.trim());
471
-
472
  try {
473
- const response = await fetch(API_URL, {
474
- method: 'POST',
475
- body: formData,
476
- });
477
-
478
  if (!response.ok) {
479
  const errorData = await response.json().catch(() => ({ error: `خطای سرور: ${response.statusText}` }));
480
  throw new Error(errorData.error || `یک خطای ناشناخته رخ داد.`);
481
  }
482
-
483
  const result = await response.json();
484
-
485
  if (result.image_urls && Array.isArray(result.image_urls) && result.image_urls.length > 0) {
486
  displayResult(result.image_urls);
487
  } else {
488
  throw new Error('پاسخ معتبری از سرور دریافت نشد. ممکن است سرور موقتا با مشکل مواجه شده باشد.');
489
  }
490
-
491
  } catch (error) {
492
  displayError(error.message);
493
  } finally {
@@ -495,32 +415,34 @@
495
  }
496
  });
497
 
498
- // --- UI Helper Functions (Updated for animations) ---
499
  function setLoading(isLoading) {
500
- if (isLoading) {
501
- loader.style.display = 'block';
502
- resultContainer.classList.add('loading'); // Add loading class for pulse animation
 
 
 
 
503
  submitBtn.disabled = true;
504
- submitBtn.textContent = 'در حال پردازش...';
505
  resultGrid.innerHTML = '';
506
  errorMessage.style.display = 'none';
507
  } else {
508
- loader.style.display = 'none';
509
- resultContainer.classList.remove('loading');
510
- submitBtn.disabled = false;
511
- submitBtn.textContent = 'ویرایش کن';
512
- checkFormState(); // Re-check state after loading
513
  }
514
  }
515
 
516
  function displayResult(imageUrls) {
517
- resultGrid.innerHTML = ''; // Clear previous results
518
  imageUrls.forEach((url, index) => {
519
  const img = document.createElement('img');
520
  img.src = url;
521
  img.alt = 'تصویر ویرایش شده';
522
- // Staggered animation delay
523
- img.style.animationDelay = `${index * 100}ms`;
524
  img.addEventListener('click', () => openLightbox(url));
525
  resultGrid.appendChild(img);
526
  });
@@ -536,25 +458,17 @@
536
  errorMessage.style.display = 'block';
537
  }
538
 
539
- // --- Lightbox Functions (Unchanged) ---
540
  function openLightbox(url) {
541
  lightboxImg.src = url;
542
  lightboxDownload.href = url;
543
  lightbox.style.display = 'flex';
544
  }
545
-
546
- function closeLightbox() {
547
- lightbox.style.display = 'none';
548
- }
549
-
550
  lightboxClose.addEventListener('click', closeLightbox);
551
- lightbox.addEventListener('click', (e) => {
552
- if (e.target === lightbox) { closeLightbox(); }
553
- });
554
 
555
- // Initial check in case of page refresh with filled form
556
  checkFormState();
557
-
558
  </script>
559
  </body>
560
  </html>
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>فتوشاپ هوش مصنوعی (نسخه گالری)</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700;800&display=swap" rel="stylesheet">
8
  <style>
9
  :root {
10
+ --bg-start: #f5f7ff;
11
+ --bg-end: #eef2ff;
12
+ --text-color: #1e293b;
13
  --card-bg: #ffffff;
14
+ --border-color: #e2e8f0;
15
+ --primary-color-start: #4f46e5;
16
+ --primary-color-end: #a855f7;
17
+ --shadow-color-light: rgba(79, 70, 229, 0.08);
18
+ --shadow-color-dark: rgba(30, 41, 59, 0.05);
19
+ --drop-zone-bg: #f8fafc;
20
+ --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
21
  }
22
 
 
23
  .dark {
24
+ --bg-start: #0f172a;
25
+ --bg-end: #0b1120;
26
+ --text-color: #e2e8f0;
27
+ --card-bg: #1e293b;
28
+ --border-color: #334155;
29
+ --shadow-color-light: rgba(0, 0, 0, 0.2);
30
+ --shadow-color-dark: rgba(0, 0, 0, 0.25);
31
+ --drop-zone-bg: #111827;
32
  }
33
 
 
34
  @keyframes fadeInUp {
35
+ from { opacity: 0; transform: translateY(30px); }
36
+ to { opacity: 1; transform: translateY(0); }
 
 
 
 
 
 
37
  }
38
 
39
  body {
40
  font-family: 'Vazirmatn', sans-serif;
41
+ background-color: var(--bg-start);
42
+ background-image: radial-gradient(circle at top left, var(--bg-start), var(--bg-end));
43
  color: var(--text-color);
44
  margin: 0;
45
+ padding: 3rem 1rem;
46
  display: flex;
47
  justify-content: center;
48
  align-items: flex-start;
49
  min-height: 100vh;
50
+ transition: background-color 0.4s, color 0.4s;
51
  }
52
 
53
  .container {
54
+ max-width: 750px;
55
  width: 100%;
56
  }
57
 
 
58
  header {
59
  text-align: center;
60
+ margin-bottom: 3rem;
61
+ animation: fadeInUp 0.8s var(--ease-out-expo) forwards;
62
  }
63
 
64
  h1 {
65
+ font-size: 3rem;
66
+ font-weight: 800;
67
+ letter-spacing: -1px;
68
  margin: 0;
69
  background: linear-gradient(45deg, var(--primary-color-start), var(--primary-color-end));
70
  -webkit-background-clip: text;
71
  -webkit-text-fill-color: transparent;
 
 
72
  }
73
 
74
  p.subtitle {
75
+ font-size: 1.2rem;
76
+ color: #64748b;
77
+ margin-top: 0.75rem;
78
  }
79
+ .dark p.subtitle { color: #94a3b8; }
80
 
 
 
 
81
  main {
82
  background-color: var(--card-bg);
83
+ border-radius: 2rem;
84
+ border: 1px solid var(--border-color);
85
+ box-shadow: 0 4px 10px var(--shadow-color-dark), 0 20px 40px var(--shadow-color-light);
86
+ padding: 3rem;
87
+ transition: all 0.4s;
88
+ animation: fadeInUp 0.8s var(--ease-out-expo) 0.2s forwards;
89
+ opacity: 0;
90
  }
91
 
92
+ .panel, .controls { margin-bottom: 3rem; }
 
 
 
 
93
  .panel:last-child { margin-bottom: 0; }
94
+ .controls { margin-bottom: 2.5rem; }
95
 
96
  h2 {
97
+ font-size: 1.4rem;
98
+ font-weight: 700;
99
+ margin: 0 0 1.5rem 0;
100
+ padding-bottom: 1rem;
 
 
101
  display: flex;
102
  align-items: center;
103
  gap: 0.75rem;
104
+ border-bottom: 1px solid var(--border-color);
105
  }
106
+ h2 svg { color: var(--primary-color-start); }
107
 
 
108
  #drop-zone {
109
+ border: 2px dashed var(--border-color); border-radius: 1.5rem;
110
+ padding: 1rem; text-align: center; cursor: pointer;
111
+ transition: all 0.3s var(--ease-out-expo);
112
+ position: relative; overflow: hidden; background-color: var(--drop-zone-bg);
113
+ display: flex; flex-direction: column; justify-content: center;
114
+ align-items: center; min-height: 280px;
115
+ }
116
+ #drop-zone::before {
117
+ content: ''; position: absolute; inset: -2px; border-radius: 1.5rem;
118
+ background: linear-gradient(45deg, var(--primary-color-start), var(--primary-color-end)) border-box;
119
+ -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
120
+ mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
121
+ -webkit-mask-composite: destination-out; mask-composite: subtract;
122
+ opacity: 0; transition: opacity 0.3s ease;
123
+ }
124
+ #drop-zone.dragover, #drop-zone:hover {
125
+ border-color: transparent; transform: translateY(-5px);
126
+ box-shadow: 0 10px 20px -5px var(--shadow-color-light);
127
+ }
128
+ #drop-zone.dragover::before, #drop-zone:hover::before { opacity: 1; }
129
+ #drop-zone-content { transition: opacity 0.3s ease, transform 0.3s ease; }
130
+ #drop-zone:hover #drop-zone-content { transform: translateY(-5px); }
131
+ #drop-zone-icon { width: 50px; height: 50px; margin-bottom: 1rem; color: #94a3b8; }
132
+ #drop-zone-text { color: #475569; font-weight: 500; }
133
+ .dark #drop-zone-text { color: #94a3b8; }
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  #image-preview {
135
+ position: absolute; inset: 0; width: 100%; height: 100%;
136
+ padding: 1rem; box-sizing: border-box; border-radius: 1.5rem;
137
+ object-fit: contain; opacity: 0; transform: scale(0.95);
138
+ transition: opacity 0.4s ease, transform 0.4s ease;
139
+ pointer-events: none;
140
  }
141
+ #image-preview.visible { opacity: 1; transform: scale(1); pointer-events: auto; }
142
 
 
143
  textarea {
144
+ width: 100%; padding: 1rem; border: 2px solid var(--border-color);
145
+ border-radius: 1rem; background-color: var(--drop-zone-bg);
146
+ color: var(--text-color); font-family: 'Vazirmatn', sans-serif;
147
+ font-size: 1rem; resize: vertical; min-height: 80px;
148
+ box-sizing: border-box; transition: all 0.3s ease;
 
 
 
 
 
 
 
149
  }
 
150
  textarea:focus {
151
+ outline: none; border-color: var(--primary-color-start);
152
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary-color-start) 15%, transparent);
 
153
  }
154
 
155
  button#submit-btn {
156
+ position: relative; overflow: hidden;
157
+ width: 100%; padding: 1.1rem; border: none; border-radius: 1rem;
158
+ background-image: linear-gradient(45deg, var(--primary-color-start) 0%, var(--primary-color-end) 100%);
159
+ background-size: 200% auto; color: white;
160
+ font-size: 1.1rem; font-weight: 700; cursor: pointer;
161
+ margin-top: 1rem; transition: all 0.4s var(--ease-out-expo);
162
+ box-shadow: 0 4px 15px var(--shadow-color-light);
 
 
 
 
 
 
163
  }
 
164
  button#submit-btn:hover:not(:disabled) {
165
+ transform: translateY(-4px);
166
+ box-shadow: 0 10px 25px -5px var(--shadow-color-light);
167
  background-position: right center;
168
  }
169
+ button#submit-btn:disabled { opacity: 0.5; cursor: not-allowed; }
170
+ .loader-dots {
171
+ display: flex; justify-content: center; align-items: center;
172
+ position: absolute; inset: 0;
173
  }
174
+ .loader-dots div {
175
+ width: 8px; height: 8px; margin: 0 4px; background-color: white; border-radius: 50%;
176
+ animation: bounce 1.4s infinite ease-in-out both;
 
 
 
177
  }
178
+ .loader-dots .dot1 { animation-delay: -0.32s; }
179
+ .loader-dots .dot2 { animation-delay: -0.16s; }
180
+ @keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1.0); } }
181
+ #btn-text.hidden { visibility: hidden; }
182
 
183
  #result-container {
184
+ border: 2px solid var(--border-color); border-radius: 1.5rem;
185
+ min-height: 300px;
186
+ display: flex; /* Kept flex for fallback */
187
+ justify-content: center; /* Kept flex for fallback */
188
+ align-items: center; /* Kept flex for fallback */
 
189
  position: relative;
190
+ background-color: var(--drop-zone-bg); overflow: hidden;
191
+ padding: 0.5rem; transition: border-color 0.3s ease;
 
 
192
  }
193
 
194
+ /* --- CORRECTED: Loading Placeholder Centering --- */
195
+ #loading-placeholder {
196
+ display: none; /* Initially hidden */
197
+ flex-direction: column;
198
+ align-items: center;
199
+ gap: 1.5rem;
200
+ opacity: 0;
201
+ animation: fadeIn 0.5s ease forwards;
202
+
203
+ /* Positioning logic for perfect centering */
204
+ position: absolute;
205
+ top: 50%;
206
+ left: 50%;
207
+ transform: translate(-50%, -50%);
208
+ width: 100%; /* Ensure it takes space to center its content */
209
+ text-align: center;
210
  }
211
+ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
212
+ #result-container.loading #loading-placeholder { display: flex; }
213
+
214
+ .loading-animation { width: 120px; height: 120px; }
215
+ .loading-text { font-weight: 500; color: #64748b; }
216
+ .dark .loading-text { color: #94a3b8; }
217
 
218
  #result-grid {
219
+ display: grid; grid-template-columns: repeat(2, 1fr);
220
+ gap: 0.75rem; width: 100%; height: 100%;
 
 
 
221
  }
222
+ @keyframes popIn-spring {
223
+ 0% { opacity: 0; transform: scale(0.8) translateY(20px); }
224
+ 80% { opacity: 1; transform: scale(1.05) translateY(-5px); }
225
+ 100% { opacity: 1; transform: scale(1) translateY(0); }
 
 
 
 
 
 
226
  }
 
227
  #result-grid img {
228
+ width: 100%; height: 100%; object-fit: cover; border-radius: 1rem;
229
+ cursor: pointer; transition: transform 0.3s ease, box-shadow 0.3s ease;
230
+ opacity: 0; animation: popIn-spring 0.6s var(--ease-out-expo) forwards;
 
 
 
 
 
231
  }
 
232
  #result-grid img:hover {
233
+ transform: scale(1.05) rotate(1deg);
234
+ box-shadow: 0 10px 30px -5px var(--shadow-color-dark);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  }
236
 
237
+ #error-message { color: #ef4444; text-align: center; margin-top: 1rem; display: none; font-weight: 500; }
238
+
239
  #lightbox {
240
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
241
+ background-color: rgba(10, 20, 40, 0.85); z-index: 1000; display: none;
242
  justify-content: center; align-items: center; padding: 2rem;
243
+ box-sizing: border-box; backdrop-filter: blur(8px);
244
+ }
245
+ #lightbox-img {
246
+ max-width: 90vw; max-height: 90vh; object-fit: contain; border-radius: 1rem;
247
+ box-shadow: 0 20px 50px rgba(0,0,0,0.5);
248
  }
 
 
249
  #lightbox-close, #lightbox-download {
250
+ position: absolute; top: -50px; background-color: rgba(255, 255, 255, 0.1);
251
+ color: white; border: 1px solid rgba(255,255,255,0.2); width: 40px; height: 40px;
252
+ border-radius: 50%; cursor: pointer; font-size: 1.5rem; display: flex;
253
+ justify-content: center; align-items: center; transition: all 0.2s;
254
  }
255
  #lightbox-close { left: 10px; }
256
  #lightbox-download { right: 10px; }
257
+ #lightbox-close:hover, #lightbox-download:hover {
258
+ background-color: rgba(255, 255, 255, 0.2); transform: scale(1.1);
259
+ }
260
 
 
261
  @media (max-width: 600px) {
262
+ body { padding: 1.5rem 1rem; }
263
  main { padding: 1.5rem; }
264
+ h1 { font-size: 2.5rem; }
 
265
  }
266
  </style>
267
  </head>
 
274
  </header>
275
 
276
  <main>
 
277
  <div class="panel">
278
  <h2>
279
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4Z"/></svg>
280
+ ۱. تصویر خود را انتخاب کنید
281
  </h2>
282
  <div id="drop-zone">
283
+ <div id="drop-zone-content">
284
+ <svg id="drop-zone-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
285
+ <span id="drop-zone-text">تصویر را اینجا بکشید یا برای انتخاب کلیک کنید</span>
286
+ </div>
287
  <img id="image-preview" src="#" alt="پیش‌نمایش تصویر" />
288
  </div>
289
  <input type="file" id="file-input" accept="image/*" hidden>
290
  </div>
291
 
 
292
  <div class="controls">
293
  <h2>
294
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
295
+ ۲. دستور ویرایش را بنویسید
296
  </h2>
297
  <textarea id="prompt-input" rows="3" placeholder="مثال: پس‌زمینه را حذف کن و یک ساحل آفتابی قرار بده"></textarea>
298
+ <button id="submit-btn" disabled>
299
+ <span id="btn-text">خلق کن</span>
300
+ </button>
301
  <p id="error-message"></p>
302
  </div>
303
 
 
304
  <div class="panel">
305
  <h2>
306
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15.5 2H8.6c-.4 0-.8.2-1.1.5-.3.3-.5.7-.5 1.1v12.8c0 .4.2.8.5 1.1.3.3.7.5 1.1.5h9.8c.4 0 .8-.2 1.1-.5.3-.3.5-.7.5-1.1V6.5L15.5 2z"/><path d="M3 7.6v12.8c0 .4.2.8.5 1.1.3.3.7.5 1.1.5h9.8"/><path d="M15 2v5h5"/></svg>
307
+ ۳. نتیجه را ببینید
308
  </h2>
309
  <div id="result-container">
310
+ <div id="loading-placeholder">
311
+ <svg class="loading-animation" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
312
+ <style>
313
+ .circle { animation: a 4s cubic-bezier(.5,0,.5,1) infinite; transform-origin: 50px 50px; }
314
+ .p1 { animation-duration: 2s; stroke: #a855f7; }
315
+ .p2 { animation-duration: 3s; stroke: #8b5cf6; }
316
+ .p3 { animation-duration: 4s; stroke: #6366f1; }
317
+ .p4 { animation-duration: 5s; stroke: #4f46e5; }
318
+ @keyframes a { to { transform: rotate(300deg) scale(0.8); } }
319
+ </style>
320
+ <g>
321
+ <path class="circle p1" d="M50,50 m0,-45 a45,45 0 1 1 0,90 a45,45 0 1 1 0,-90" fill="none" stroke-width="3" stroke-dasharray="282.74" stroke-dashoffset="212.05" stroke-linecap="round"></path>
322
+ <path class="circle p2" d="M50,50 m0,-35 a35,35 0 1 1 0,70 a35,35 0 1 1 0,-70" fill="none" stroke-width="3" stroke-dasharray="219.91" stroke-dashoffset="164.93" stroke-linecap="round"></path>
323
+ <path class="circle p3" d="M50,50 m0,-25 a25,25 0 1 1 0,50 a25,25 0 1 1 0,-50" fill="none" stroke-width="3" stroke-dasharray="157.08" stroke-dashoffset="117.81" stroke-linecap="round"></path>
324
+ <path class="circle p4" d="M50,50 m0,-15 a15,15 0 1 1 0,30 a15,15 0 1 1 0,-30" fill="none" stroke-width="3" stroke-dasharray="94.24" stroke-dashoffset="70.68" stroke-linecap="round"></path>
325
+ </g>
326
+ </svg>
327
+ <p class="loading-text">هوش مصنوعی در حال خلق اثر شماست...</p>
328
+ </div>
329
  <div id="result-grid"></div>
330
  </div>
331
  </div>
332
  </main>
333
  </div>
334
 
335
+ <!-- Lightbox Structure -->
336
  <div id="lightbox">
337
  <div id="lightbox-content">
338
  <img id="lightbox-img" src="">
 
342
  </a>
343
  </div>
344
  </div>
345
+
346
  <script>
 
347
  const API_URL = 'https://alfa-editor-worker.onrender.com/api/edit';
348
 
349
  // --- DOM Elements ---
350
  const dropZone = document.getElementById('drop-zone');
351
+ const dropZoneContent = document.getElementById('drop-zone-content');
352
  const fileInput = document.getElementById('file-input');
353
  const imagePreview = document.getElementById('image-preview');
 
 
354
  const promptInput = document.getElementById('prompt-input');
355
  const submitBtn = document.getElementById('submit-btn');
356
+ const btnText = document.getElementById('btn-text');
357
+ const resultContainer = document.getElementById('result-container');
358
  const resultGrid = document.getElementById('result-grid');
 
359
  const errorMessage = document.getElementById('error-message');
360
  const lightbox = document.getElementById('lightbox');
361
  const lightboxImg = document.getElementById('lightbox-img');
 
364
 
365
  let uploadedFile = null;
366
 
367
+ // --- Event Listeners and Handlers (mostly unchanged) ---
368
  dropZone.addEventListener('click', () => fileInput.click());
369
+ fileInput.addEventListener('change', (e) => handleFile(e.target.files[0]));
370
+ ['dragenter', 'dragover'].forEach(eventName => dropZone.addEventListener(eventName, (e) => { e.preventDefault(); dropZone.classList.add('dragover'); }));
371
+ ['dragleave', 'drop'].forEach(eventName => dropZone.addEventListener(eventName, (e) => { e.preventDefault(); dropZone.classList.remove('dragover'); }));
372
+ dropZone.addEventListener('drop', (e) => handleFile(e.dataTransfer.files[0]));
373
+ promptInput.addEventListener('input', checkFormState);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
 
375
  function handleFile(file) {
376
+ if (!file || !file.type.startsWith('image/')) { displayError('لطفا ��ک فایل تصویری انتخاب کنید.'); return; }
 
 
 
 
377
  uploadedFile = file;
 
378
  const reader = new FileReader();
379
  reader.onload = (e) => {
380
  imagePreview.src = e.target.result;
381
+ imagePreview.classList.add('visible');
382
+ dropZoneContent.style.opacity = '0';
383
+ checkFormState();
 
384
  clearResult();
385
  };
386
  reader.readAsDataURL(file);
387
  }
388
 
 
389
  function checkFormState() {
390
  submitBtn.disabled = !uploadedFile || promptInput.value.trim() === '';
391
  }
 
 
392
 
 
393
  submitBtn.addEventListener('click', async () => {
394
  if (submitBtn.disabled) return;
 
395
  setLoading(true);
 
396
  const formData = new FormData();
397
  formData.append('image', uploadedFile);
398
  formData.append('prompt', promptInput.value.trim());
 
399
  try {
400
+ const response = await fetch(API_URL, { method: 'POST', body: formData });
 
 
 
 
401
  if (!response.ok) {
402
  const errorData = await response.json().catch(() => ({ error: `خطای سرور: ${response.statusText}` }));
403
  throw new Error(errorData.error || `یک خطای ناشناخته رخ داد.`);
404
  }
 
405
  const result = await response.json();
 
406
  if (result.image_urls && Array.isArray(result.image_urls) && result.image_urls.length > 0) {
407
  displayResult(result.image_urls);
408
  } else {
409
  throw new Error('پاسخ معتبری از سرور دریافت نشد. ممکن است سرور موقتا با مشکل مواجه شده باشد.');
410
  }
 
411
  } catch (error) {
412
  displayError(error.message);
413
  } finally {
 
415
  }
416
  });
417
 
418
+ // --- UI Helper Functions (UPDATED) ---
419
  function setLoading(isLoading) {
420
+ if (isLoading) {
421
+ resultContainer.classList.add('loading'); // This now shows the SVG placeholder
422
+ btnText.classList.add('hidden');
423
+ const loader = document.createElement('div');
424
+ loader.className = 'loader-dots';
425
+ loader.innerHTML = '<div class="dot1"></div><div class="dot2"></div><div class="dot3"></div>';
426
+ submitBtn.appendChild(loader);
427
  submitBtn.disabled = true;
 
428
  resultGrid.innerHTML = '';
429
  errorMessage.style.display = 'none';
430
  } else {
431
+ resultContainer.classList.remove('loading'); // This hides the SVG placeholder
432
+ btnText.classList.remove('hidden');
433
+ const loader = submitBtn.querySelector('.loader-dots');
434
+ if (loader) submitBtn.removeChild(loader);
435
+ checkFormState();
436
  }
437
  }
438
 
439
  function displayResult(imageUrls) {
440
+ resultGrid.innerHTML = '';
441
  imageUrls.forEach((url, index) => {
442
  const img = document.createElement('img');
443
  img.src = url;
444
  img.alt = 'تصویر ویرایش شده';
445
+ img.style.animationDelay = `${index * 120}ms`;
 
446
  img.addEventListener('click', () => openLightbox(url));
447
  resultGrid.appendChild(img);
448
  });
 
458
  errorMessage.style.display = 'block';
459
  }
460
 
461
+ // --- Lightbox Functions (unchanged logic) ---
462
  function openLightbox(url) {
463
  lightboxImg.src = url;
464
  lightboxDownload.href = url;
465
  lightbox.style.display = 'flex';
466
  }
467
+ function closeLightbox() { lightbox.style.display = 'none'; }
 
 
 
 
468
  lightboxClose.addEventListener('click', closeLightbox);
469
+ lightbox.addEventListener('click', (e) => { if (e.target === lightbox) closeLightbox(); });
 
 
470
 
 
471
  checkFormState();
 
472
  </script>
473
  </body>
474
  </html>