Elias207 commited on
Commit
89691ac
·
verified ·
1 Parent(s): ff92e58

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +101 -314
index.html CHANGED
@@ -35,40 +35,23 @@
35
  @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
36
 
37
  body {
38
- font-family: var(--app-font);
39
- background-color: var(--app-bg);
40
- color: var(--text-primary);
41
- margin: 0;
42
- padding: 2.5rem 1rem;
43
- display: flex;
44
- justify-content: center;
45
- align-items: flex-start;
46
- min-height: 100vh;
47
  }
48
  .container { max-width: 820px; width: 100%; }
49
 
50
- header {
51
- text-align: center;
52
- margin-bottom: 2.5rem;
53
- animation: fadeIn 0.8s 0.1s ease-out backwards;
54
- }
55
  h1 {
56
- font-size: 2.8rem;
57
- font-weight: 800;
58
- margin: 0 0 0.8rem 0;
59
  background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
60
- -webkit-background-clip: text;
61
- -webkit-text-fill-color: transparent;
62
- letter-spacing: -1px;
63
  }
64
  .subtitle { font-size: 1.1rem; color: var(--text-secondary); margin-top: 0; }
65
 
66
  main, .gallery-section {
67
- padding: 3rem;
68
- background-color: var(--panel-bg);
69
- border-radius: var(--radius-card);
70
- box-shadow: var(--shadow-xl);
71
- border: 1px solid var(--panel-border);
72
  animation: fadeIn 0.8s 0.3s ease-out backwards;
73
  }
74
  .gallery-section { margin-top: 3rem; animation-delay: 0.5s; }
@@ -76,121 +59,38 @@
76
  .form-group { margin-bottom: 2.5rem; }
77
  .form-group:last-child { margin-bottom: 0; }
78
  label.form-label {
79
- display: flex;
80
- align-items: center;
81
- gap: 0.75rem;
82
- font-weight: 700;
83
- color: var(--text-primary);
84
- font-size: 1.2em;
85
- margin-bottom: 1.2rem;
86
  }
87
  label.form-label svg { width: 24px; height: 24px; color: var(--accent-primary); }
88
 
89
- /* --- New Uploader with Image Preview --- */
90
  #drop-zone {
91
- position: relative;
92
- border: 2px dashed var(--input-border);
93
- border-radius: var(--radius-input);
94
- padding: 2rem;
95
- text-align: center;
96
- cursor: pointer;
97
- transition: var(--transition-smooth);
98
- background-color: var(--input-bg);
99
- min-height: 250px;
100
- display: flex;
101
- align-items: center;
102
- justify-content: center;
103
- overflow: hidden;
104
- }
105
- #drop-zone.drag-over, #drop-zone:hover {
106
- border-color: var(--accent-primary);
107
- background-color: #fff;
108
- box-shadow: 0 0 15px var(--accent-primary-glow);
109
- }
110
- #drop-zone-content {
111
- display: flex;
112
- flex-direction: column;
113
- align-items: center;
114
- gap: 1rem;
115
- transition: opacity 0.3s ease;
116
  }
 
 
117
  #drop-zone-content svg { width: 48px; height: 48px; color: var(--accent-primary); stroke-width: 1.5; opacity: 0.8; }
118
  #drop-zone-content p { margin: 0; color: var(--text-secondary); font-weight: 500; }
119
-
120
- #image-preview {
121
- position: absolute;
122
- top: 0; left: 0; width: 100%; height: 100%;
123
- object-fit: contain;
124
- padding: 1rem;
125
- box-sizing: border-box;
126
- background-color: var(--input-bg);
127
- opacity: 0;
128
- visibility: hidden;
129
- transition: opacity 0.3s ease, visibility 0.3s;
130
- pointer-events: none;
131
- }
132
  #image-preview.visible { opacity: 1; visibility: visible; pointer-events: auto; }
133
- #remove-preview-btn {
134
- position: absolute;
135
- top: 1rem;
136
- left: 1rem; /* Adjusted for RTL */
137
- background-color: rgba(0,0,0,0.5);
138
- color: white;
139
- border: none;
140
- border-radius: 50%;
141
- width: 32px;
142
- height: 32px;
143
- font-size: 1.5rem;
144
- line-height: 1;
145
- cursor: pointer;
146
- display: flex;
147
- align-items: center;
148
- justify-content: center;
149
- opacity: 0;
150
- visibility: hidden;
151
- transition: opacity 0.3s ease, visibility 0.3s;
152
- z-index: 10;
153
- }
154
  #remove-preview-btn.visible { opacity: 1; visibility: visible; }
155
 
156
- textarea {
157
- width: 100%; padding: 1rem 1.2rem; border-radius: var(--radius-input); border: 1px solid var(--input-border);
158
- background-color: var(--input-bg); color: var(--text-primary); box-shadow: var(--shadow-sm) inset;
159
- font-family: var(--app-font); font-size: 1rem; box-sizing: border-box; transition: var(--transition-smooth);
160
- min-height: 90px; resize: vertical;
161
- }
162
- textarea:focus {
163
- outline: none; border-color: var(--accent-primary);
164
- box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset; background-color: var(--panel-bg);
165
- }
166
- #submit-btn {
167
- display: flex; align-items: center; justify-content: center; gap: 10px;
168
- width: 100%; padding: 1.1rem; font-size: 1.2rem; font-weight: 700;
169
- background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%);
170
- color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer;
171
- transition: all 0.3s ease;
172
- box-shadow: 0 6px 12px -3px var(--accent-primary-glow), 0 6px 12px -3px var(--accent-secondary-glow);
173
- margin-top: 1.5rem;
174
- }
175
- #submit-btn:hover:not(:disabled) {
176
- transform: translateY(-5px) scale(1.02);
177
- box-shadow: 0 8px 20px -4px var(--accent-primary-glow), 0 8px 20px -4px var(--accent-secondary-glow);
178
- }
179
  #submit-btn:disabled { background: var(--text-tertiary); cursor: not-allowed; box-shadow: none; opacity: 0.7; }
180
  #submit-btn .spinner { width: 20px; height: 20px; border: 3px solid rgba(255, 255, 255, 0.4); border-top-color: #fff; border-radius: 50%; animation: spin 0.8s linear infinite; display: none; }
181
 
182
- #result-container {
183
- min-height: 220px; position: relative;
184
- box-sizing: border-box; padding: 1rem; background-color: var(--input-bg);
185
- border-radius: var(--radius-card); border: 2px dashed var(--input-border);
186
- box-shadow: var(--shadow-sm) inset; transition: var(--transition-smooth);
187
- display: flex; align-items: center; justify-content: center;
188
- }
189
  #result-container.loading, #result-container.has-content { border-style: solid; border-color: var(--panel-border); }
190
  #loading-placeholder { display: none; flex-direction: column; align-items: center; gap: 1.5rem; }
191
  #result-container.loading #loading-placeholder { display: flex; animation: fadeIn 0.5s; }
192
-
193
- /* Bigger Loader */
194
  .orbital-loader { width: 100px; height: 100px; position: relative; animation: spin 10s linear infinite; }
195
  .orbit { position: absolute; top: 50%; left: 50%; border: 2px dashed rgba(74, 108, 250, 0.35); border-radius: 50%; transform-origin: center center; }
196
  .orbit:nth-child(1) { width: 35px; height: 35px; margin: -17.5px 0 0 -17.5px; animation: spin 2.8s linear infinite reverse; }
@@ -200,23 +100,9 @@
200
  .orbit:nth-child(2) .satellite { top: 50%; left: -5px; background-color: var(--accent-secondary); transform: translateY(-50%); }
201
  .loading-text { font-weight: 500; color: var(--text-secondary); }
202
 
203
- #result-grid {
204
- display: none;
205
- grid-template-columns: repeat(2, 1fr); /* Bigger results */
206
- gap: 1rem;
207
- width: 100%;
208
- }
209
  #result-container.has-content #result-grid { display: grid; }
210
- #result-grid img {
211
- width: 100%;
212
- aspect-ratio: 1 / 1; /* Make them square */
213
- object-fit: cover;
214
- border-radius: var(--radius-input);
215
- cursor: pointer;
216
- transition: var(--transition-smooth);
217
- box-shadow: var(--shadow-md);
218
- border: 1px solid var(--panel-border);
219
- }
220
  #result-grid img:hover { transform: scale(1.05); box-shadow: var(--shadow-lg); z-index: 10; position: relative; }
221
  #error-message { color: var(--danger-color); text-align: center; margin-top: 1rem; display: none; font-weight: 500; }
222
 
@@ -227,11 +113,13 @@
227
  #clear-history-btn svg { width: 18px; height: 18px; }
228
  #history-grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; margin-top: 1.5rem; }
229
  #history-grid:empty::before { content: 'هنوز تصویری خلق نکرده‌اید. اولین ویرایش شما اینجا ذخیره خواهد شد.'; color: var(--text-secondary); grid-column: 1 / -1; text-align: center; padding: 3rem 1rem; background-color: var(--input-bg); border-radius: var(--radius-card); }
230
- .history-item { background-color: var(--input-bg); border-radius: var(--radius-card); border: 1px solid var(--panel-border); padding: 1.5rem; transition: var(--transition-smooth); }
231
  .history-item-prompt { font-weight: 600; margin: 0 0 1rem 0; word-break: break-word; }
232
  .history-item-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.75rem; }
233
  .history-item-grid img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: var(--radius-input); cursor: pointer; transition: transform 0.2s ease, box-shadow 0.2s ease; border: 1px solid var(--panel-border); }
234
  .history-item-grid img:hover { transform: scale(1.1); box-shadow: var(--shadow-md); z-index: 5; position: relative; }
 
 
235
 
236
  /* Lightbox Styles */
237
  #lightbox { position: fixed; inset: 0; background-color: rgba(18, 24, 38, 0.6); backdrop-filter: blur(10px) saturate(150%); display: none; align-items: center; justify-content: center; z-index: 1000; opacity: 0; transition: opacity 0.3s; }
@@ -247,136 +135,66 @@
247
  @media (max-width: 768px) {
248
  main, .gallery-section { padding: 1.5rem; }
249
  h1 { font-size: 2.2rem; }
 
250
  .history-item-grid { grid-template-columns: repeat(2, 1fr); }
251
  }
252
  </style>
253
  </head>
254
  <body>
255
  <div class="container">
256
- <header>
257
- <h1>فتوشاپ هوش مصنوعی ✨</h1>
258
- <p class="subtitle">تصاویر خود را با قدرت هوش مصنوعی و به زبان ساده ویرایش کنید</p>
259
- </header>
260
-
261
  <main>
262
  <div class="form-group">
263
- <label for="drop-zone" class="form-label">
264
- <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="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>
265
- ۱. تصویر خود را انتخاب کنید
266
- </label>
267
  <div id="drop-zone">
268
- <div id="drop-zone-content">
269
- <svg><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>
270
- <p>فایل تصویر را اینجا بکشید یا برای انتخاب کلیک کنید</p>
271
- </div>
272
- <img id="image-preview" src="#" alt="پیش‌نمایش تصویر">
273
- <button type="button" id="remove-preview-btn" title="حذف تصویر">&times;</button>
274
  </div>
275
  <input type="file" id="file-input" accept="image/*" hidden>
276
  </div>
277
-
278
  <div class="form-group">
279
- <label for="prompt-input" class="form-label">
280
- <svg xmlns="http://www.w.org/2000/svg" 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>
281
- ۲. دستور ویرایش را بنویسید
282
- </label>
283
  <textarea id="prompt-input" rows="3" placeholder="مثال: پس‌زمینه را حذف کن و یک ساحل آفتابی قرار بده"></textarea>
284
- <button id="submit-btn" disabled>
285
- <span id="btn-text">خلق کن</span>
286
- <div class="spinner"></div>
287
- </button>
288
  <p id="error-message"></p>
289
  </div>
290
-
291
  <div class="form-group">
292
- <label class="form-label">
293
- <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>
294
- ۳. نتیجه را ببینید
295
- </label>
296
  <div id="result-container">
297
- <div id="loading-placeholder">
298
- <div class="orbital-loader"><div class="orbit"><div class="satellite"></div></div><div class="orbit"><div class="satellite"></div></div></div>
299
- <p class="loading-text">هوش مصنوعی در حال خلق اثر شماست...</p>
300
- </div>
301
  <div id="result-grid"></div>
302
  </div>
303
  </div>
304
  </main>
305
-
306
  <section class="gallery-section">
307
  <div class="gallery-header">
308
- <label class="form-label">
309
- <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"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg>
310
- گالری و تاریخچه شما
311
- </label>
312
- <button id="clear-history-btn">
313
- <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="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
314
- <span>پاک کردن</span>
315
- </button>
316
  </div>
317
  <div id="history-grid"></div>
318
  </section>
319
  </div>
320
-
321
  <div id="lightbox">
322
  <div id="lightbox-content">
323
- <img id="lightbox-img" src="">
324
- <button id="lightbox-close" class="lightbox-btn">&times;</button>
325
- <a id="lightbox-download" href="#" download="edited-image.png" title="دانلود تصویر" class="lightbox-btn">
326
- <svg fill="white" viewBox="0 0 24 24"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg>
327
- </a>
328
  </div>
329
  </div>
330
 
331
  <script>
332
  const API_URL = 'https://alfa-editor-worker.onrender.com/api/edit';
333
 
334
- const dropZone = document.getElementById('drop-zone');
335
- const dropZoneContent = document.getElementById('drop-zone-content');
336
- const fileInput = document.getElementById('file-input');
337
- const imagePreview = document.getElementById('image-preview');
338
- const removePreviewBtn = document.getElementById('remove-preview-btn');
339
- const promptInput = document.getElementById('prompt-input');
340
- const submitBtn = document.getElementById('submit-btn');
341
- const btnText = document.getElementById('btn-text');
342
- const btnSpinner = submitBtn.querySelector('.spinner');
343
- const resultContainer = document.getElementById('result-container');
344
- const resultGrid = document.getElementById('result-grid');
345
- const errorMessage = document.getElementById('error-message');
346
- const lightbox = document.getElementById('lightbox');
347
- const lightboxImg = document.getElementById('lightbox-img');
348
- const lightboxClose = document.getElementById('lightbox-close');
349
- const lightboxDownload = document.getElementById('lightbox-download');
350
- const historyGrid = document.getElementById('history-grid');
351
- const clearHistoryBtn = document.getElementById('clear-history-btn');
352
 
353
  let uploadedFile = null;
354
 
355
- const resetUploader = () => {
356
- uploadedFile = null;
357
- fileInput.value = '';
358
- imagePreview.classList.remove('visible');
359
- removePreviewBtn.classList.remove('visible');
360
- dropZoneContent.style.opacity = '1';
361
- checkFormState();
362
- };
363
-
364
  const handleFile = (file) => {
365
- if (!file || !file.type.startsWith('image/')) {
366
- displayError('لطفا یک فایل تصویری معتبر انتخاب کنید.');
367
- return;
368
- }
369
  uploadedFile = file;
370
  const reader = new FileReader();
371
- reader.onload = (e) => {
372
- imagePreview.src = e.target.result;
373
- imagePreview.classList.add('visible');
374
- removePreviewBtn.classList.add('visible');
375
- dropZoneContent.style.opacity = '0';
376
- };
377
- reader.readAsDataURL(file);
378
- checkFormState();
379
- clearResult();
380
  };
381
 
382
  dropZone.addEventListener('click', () => fileInput.click());
@@ -385,121 +203,90 @@
385
  ['dragenter', 'dragover'].forEach(e => dropZone.addEventListener(e, () => dropZone.classList.add('drag-over')));
386
  ['dragleave', 'drop'].forEach(e => dropZone.addEventListener(e, () => dropZone.classList.remove('drag-over')));
387
  dropZone.addEventListener('drop', e => handleFile(e.dataTransfer.files[0]));
388
- removePreviewBtn.addEventListener('click', (e) => {
389
- e.stopPropagation(); // prevent drop-zone click event
390
- resetUploader();
391
- });
392
 
393
  promptInput.addEventListener('input', checkFormState);
394
- function checkFormState() {
395
- submitBtn.disabled = !uploadedFile || promptInput.value.trim() === '';
396
- }
397
 
398
  submitBtn.addEventListener('click', async () => {
399
- if (submitBtn.disabled) return;
400
- setLoading(true);
401
- const formData = new FormData();
402
- formData.append('image', uploadedFile);
403
- formData.append('prompt', promptInput.value.trim());
404
  try {
405
  const response = await fetch(API_URL, { method: 'POST', body: formData });
406
- if (!response.ok) {
407
- const errorData = await response.json().catch(() => ({ error: `خطای سرور: ${response.statusText}` }));
408
- throw new Error(errorData.error || `یک خطای ناشناخته رخ داد.`);
409
- }
410
  const result = await response.json();
411
- if (result.image_urls && Array.isArray(result.image_urls) && result.image_urls.length > 0) {
412
- displayResult(result.image_urls);
413
- addToHistory({ prompt: promptInput.value.trim(), urls: result.image_urls });
414
- } else {
415
- throw new Error('پاسخ معتبری از سرور دریافت نشد.');
416
- }
417
- } catch (error) {
418
- displayError(error.message);
419
- } finally {
420
- setLoading(false);
421
- }
422
  });
423
 
424
  function setLoading(isLoading) {
425
- if (isLoading) {
426
- resultContainer.classList.remove('has-content');
427
- resultContainer.classList.add('loading');
428
- btnSpinner.style.display = 'inline-block';
429
- btnText.textContent = 'در حال پردازش...';
430
- submitBtn.disabled = true;
431
- resultGrid.innerHTML = '';
432
- errorMessage.style.display = 'none';
433
- } else {
434
- resultContainer.classList.remove('loading');
435
- btnSpinner.style.display = 'none';
436
- btnText.textContent = 'خلق کن';
437
- checkFormState();
438
- }
439
  }
440
  function displayResult(imageUrls) {
441
  resultGrid.innerHTML = '';
442
- imageUrls.forEach((url) => {
443
- const img = document.createElement('img');
444
- img.src = url;
445
- img.alt = 'تصویر ویرایش شده';
446
- img.addEventListener('click', () => openLightbox(url));
447
- resultGrid.appendChild(img);
448
- });
449
  resultContainer.classList.add('has-content');
450
  }
451
- function clearResult() {
452
- resultGrid.innerHTML = '';
453
- errorMessage.style.display = 'none';
454
- resultContainer.classList.remove('has-content');
455
- }
456
- function displayError(message) {
457
- errorMessage.textContent = message;
458
- errorMessage.style.display = 'block';
459
- }
460
 
461
- function openLightbox(url) {
462
- lightboxImg.src = url;
463
- lightboxDownload.href = url;
464
- lightbox.classList.add('visible');
465
- }
466
  function closeLightbox() { lightbox.classList.remove('visible'); }
467
  lightboxClose.addEventListener('click', closeLightbox);
468
  lightbox.addEventListener('click', (e) => { if (e.target === lightbox) closeLightbox(); });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
469
 
470
  const getHistory = () => JSON.parse(localStorage.getItem('aiPhotoshopHistory') || '[]');
471
- const saveHistory = (history) => {
472
- localStorage.setItem('aiPhotoshopHistory', JSON.stringify(history));
473
- renderHistory();
474
- };
475
- const addToHistory = (item) => {
476
- let history = getHistory();
477
- history.unshift(item);
478
- if (history.length > 15) history.pop();
479
- saveHistory(history);
480
- };
481
- const handleClearHistory = () => {
482
- if (confirm('آیا از پاک کردن تمام تاریخچه مطمئن هستید؟')) {
483
- localStorage.removeItem('aiPhotoshopHistory');
484
- renderHistory();
485
  }
486
- };
 
487
  function renderHistory() {
488
  const history = getHistory();
489
  historyGrid.innerHTML = '';
490
  clearHistoryBtn.style.display = history.length > 0 ? 'flex' : 'none';
491
-
492
- history.forEach(item => {
493
  const card = document.createElement('div');
494
  card.className = 'history-item';
495
- const imagesHTML = item.urls.map(url =>
496
- `<img src="${url}" alt="تصویر از تاریخچه" onclick="openLightbox('${url}')">`
497
- ).join('');
498
-
499
  card.innerHTML = `
 
500
  <p class="history-item-prompt">${item.prompt}</p>
501
- <div class="history-item-grid">${imagesHTML}</div>
502
- `;
503
  historyGrid.appendChild(card);
504
  });
505
  }
 
35
  @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
36
 
37
  body {
38
+ font-family: var(--app-font); background-color: var(--app-bg); color: var(--text-primary);
39
+ margin: 0; padding: 2.5rem 1rem; display: flex; justify-content: center;
40
+ align-items: flex-start; min-height: 100vh;
 
 
 
 
 
 
41
  }
42
  .container { max-width: 820px; width: 100%; }
43
 
44
+ header { text-align: center; margin-bottom: 2.5rem; animation: fadeIn 0.8s 0.1s ease-out backwards; }
 
 
 
 
45
  h1 {
46
+ font-size: 2.8rem; font-weight: 800; margin: 0 0 0.8rem 0;
 
 
47
  background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
48
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -1px;
 
 
49
  }
50
  .subtitle { font-size: 1.1rem; color: var(--text-secondary); margin-top: 0; }
51
 
52
  main, .gallery-section {
53
+ padding: 3rem; background-color: var(--panel-bg); border-radius: var(--radius-card);
54
+ box-shadow: var(--shadow-xl); border: 1px solid var(--panel-border);
 
 
 
55
  animation: fadeIn 0.8s 0.3s ease-out backwards;
56
  }
57
  .gallery-section { margin-top: 3rem; animation-delay: 0.5s; }
 
59
  .form-group { margin-bottom: 2.5rem; }
60
  .form-group:last-child { margin-bottom: 0; }
61
  label.form-label {
62
+ display: flex; align-items: center; gap: 0.75rem; font-weight: 700;
63
+ color: var(--text-primary); font-size: 1.2em; margin-bottom: 1.2rem;
 
 
 
 
 
64
  }
65
  label.form-label svg { width: 24px; height: 24px; color: var(--accent-primary); }
66
 
67
+ /* --- Uploader Style to Match Reference Image --- */
68
  #drop-zone {
69
+ position: relative; border: 2px dashed var(--input-border); border-radius: var(--radius-input);
70
+ padding: 2rem; text-align: center; cursor: pointer; transition: var(--transition-smooth);
71
+ background-color: var(--input-bg); min-height: 250px; display: flex; align-items: center;
72
+ justify-content: center; overflow: hidden;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  }
74
+ #drop-zone.drag-over, #drop-zone:hover { border-color: var(--accent-primary); background-color: #fff; box-shadow: 0 0 15px var(--accent-primary-glow); }
75
+ #drop-zone-content { display: flex; flex-direction: column; align-items: center; gap: 1rem; transition: opacity 0.3s ease; }
76
  #drop-zone-content svg { width: 48px; height: 48px; color: var(--accent-primary); stroke-width: 1.5; opacity: 0.8; }
77
  #drop-zone-content p { margin: 0; color: var(--text-secondary); font-weight: 500; }
78
+ #image-preview { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: contain; padding: 1rem; box-sizing: border-box; background-color: var(--input-bg); opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s; pointer-events: none; }
 
 
 
 
 
 
 
 
 
 
 
 
79
  #image-preview.visible { opacity: 1; visibility: visible; pointer-events: auto; }
80
+ #remove-preview-btn { position: absolute; top: 1rem; left: 1rem; background-color: rgba(0,0,0,0.5); color: white; border: none; border-radius: 50%; width: 32px; height: 32px; font-size: 1.5rem; line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s; z-index: 10; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  #remove-preview-btn.visible { opacity: 1; visibility: visible; }
82
 
83
+ 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: 90px; resize: vertical; }
84
+ 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); }
85
+ #submit-btn { display: flex; align-items: center; justify-content: center; gap: 10px; 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 var(--accent-secondary-glow); margin-top: 1.5rem; }
86
+ #submit-btn:hover:not(:disabled) { transform: translateY(-5px) scale(1.02); box-shadow: 0 8px 20px -4px var(--accent-primary-glow), 0 8px 20px -4px var(--accent-secondary-glow); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  #submit-btn:disabled { background: var(--text-tertiary); cursor: not-allowed; box-shadow: none; opacity: 0.7; }
88
  #submit-btn .spinner { width: 20px; height: 20px; border: 3px solid rgba(255, 255, 255, 0.4); border-top-color: #fff; border-radius: 50%; animation: spin 0.8s linear infinite; display: none; }
89
 
90
+ #result-container { min-height: 220px; position: relative; box-sizing: border-box; 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; align-items: center; justify-content: center; }
 
 
 
 
 
 
91
  #result-container.loading, #result-container.has-content { border-style: solid; border-color: var(--panel-border); }
92
  #loading-placeholder { display: none; flex-direction: column; align-items: center; gap: 1.5rem; }
93
  #result-container.loading #loading-placeholder { display: flex; animation: fadeIn 0.5s; }
 
 
94
  .orbital-loader { width: 100px; height: 100px; position: relative; animation: spin 10s linear infinite; }
95
  .orbit { position: absolute; top: 50%; left: 50%; border: 2px dashed rgba(74, 108, 250, 0.35); border-radius: 50%; transform-origin: center center; }
96
  .orbit:nth-child(1) { width: 35px; height: 35px; margin: -17.5px 0 0 -17.5px; animation: spin 2.8s linear infinite reverse; }
 
100
  .orbit:nth-child(2) .satellite { top: 50%; left: -5px; background-color: var(--accent-secondary); transform: translateY(-50%); }
101
  .loading-text { font-weight: 500; color: var(--text-secondary); }
102
 
103
+ #result-grid { display: none; grid-template-columns: repeat(2, 1fr); gap: 1rem; width: 100%; }
 
 
 
 
 
104
  #result-container.has-content #result-grid { display: grid; }
105
+ #result-grid img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: var(--radius-input); cursor: pointer; transition: var(--transition-smooth); box-shadow: var(--shadow-md); border: 1px solid var(--panel-border); }
 
 
 
 
 
 
 
 
 
106
  #result-grid img:hover { transform: scale(1.05); box-shadow: var(--shadow-lg); z-index: 10; position: relative; }
107
  #error-message { color: var(--danger-color); text-align: center; margin-top: 1rem; display: none; font-weight: 500; }
108
 
 
113
  #clear-history-btn svg { width: 18px; height: 18px; }
114
  #history-grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; margin-top: 1.5rem; }
115
  #history-grid:empty::before { content: 'هنوز تصویری خلق نکرده‌اید. اولین ویرایش شما اینجا ذخیره خواهد شد.'; color: var(--text-secondary); grid-column: 1 / -1; text-align: center; padding: 3rem 1rem; background-color: var(--input-bg); border-radius: var(--radius-card); }
116
+ .history-item { position: relative; background-color: var(--input-bg); border-radius: var(--radius-card); border: 1px solid var(--panel-border); padding: 1.5rem; transition: var(--transition-smooth); }
117
  .history-item-prompt { font-weight: 600; margin: 0 0 1rem 0; word-break: break-word; }
118
  .history-item-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.75rem; }
119
  .history-item-grid img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: var(--radius-input); cursor: pointer; transition: transform 0.2s ease, box-shadow 0.2s ease; border: 1px solid var(--panel-border); }
120
  .history-item-grid img:hover { transform: scale(1.1); box-shadow: var(--shadow-md); z-index: 5; position: relative; }
121
+ .history-item-delete-btn { position: absolute; top: 0.75rem; left: 0.75rem; background-color: rgba(255, 255, 255, 0.8); backdrop-filter: blur(2px); color: var(--danger-color); border: 1px solid var(--panel-border); width: 28px; height: 28px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 1.5rem; line-height: 1; opacity: 0; transition: all 0.2s; }
122
+ .history-item:hover .history-item-delete-btn { opacity: 1; }
123
 
124
  /* Lightbox Styles */
125
  #lightbox { position: fixed; inset: 0; background-color: rgba(18, 24, 38, 0.6); backdrop-filter: blur(10px) saturate(150%); display: none; align-items: center; justify-content: center; z-index: 1000; opacity: 0; transition: opacity 0.3s; }
 
135
  @media (max-width: 768px) {
136
  main, .gallery-section { padding: 1.5rem; }
137
  h1 { font-size: 2.2rem; }
138
+ #result-grid { grid-template-columns: 1fr; } /* Single column on small screens for bigger images */
139
  .history-item-grid { grid-template-columns: repeat(2, 1fr); }
140
  }
141
  </style>
142
  </head>
143
  <body>
144
  <div class="container">
145
+ <header><h1>فتوشاپ هوش مصنوعی ✨</h1><p class="subtitle">تصاویر خود را با قدرت هوش مصنوعی و به زبان ساده ویرایش کنید</p></header>
 
 
 
 
146
  <main>
147
  <div class="form-group">
148
+ <label for="drop-zone" class="form-label"><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="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>۱. تصویر خود را انتخاب کنید</label>
 
 
 
149
  <div id="drop-zone">
150
+ <div id="drop-zone-content"><svg><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><p>فایل تصویر را اینجا بکشید یا برای انتخاب کلیک کنید</p></div>
151
+ <img id="image-preview" src="#" alt="پیش‌نمایش تصویر"><button type="button" id="remove-preview-btn" title="حذف تصویر">&times;</button>
 
 
 
 
152
  </div>
153
  <input type="file" id="file-input" accept="image/*" hidden>
154
  </div>
 
155
  <div class="form-group">
156
+ <label for="prompt-input" class="form-label"><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="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>۲. دستور ویرایش را بنویسید</label>
 
 
 
157
  <textarea id="prompt-input" rows="3" placeholder="مثال: پس‌زمینه را حذف کن و یک ساحل آفتابی قرار بده"></textarea>
158
+ <button id="submit-btn" disabled><span id="btn-text">خلق کن</span><div class="spinner"></div></button>
 
 
 
159
  <p id="error-message"></p>
160
  </div>
 
161
  <div class="form-group">
162
+ <label class="form-label"><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>۳. نتیجه را ببینید</label>
 
 
 
163
  <div id="result-container">
164
+ <div id="loading-placeholder"><div class="orbital-loader"><div class="orbit"><div class="satellite"></div></div><div class="orbit"><div class="satellite"></div></div></div><p class="loading-text">هوش مصنوعی در حال خلق اثر شماست...</p></div>
 
 
 
165
  <div id="result-grid"></div>
166
  </div>
167
  </div>
168
  </main>
 
169
  <section class="gallery-section">
170
  <div class="gallery-header">
171
+ <label class="form-label"><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"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg>گالری و تاریخچه شما</label>
172
+ <button id="clear-history-btn"><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="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg><span>پاک کردن</span></button>
 
 
 
 
 
 
173
  </div>
174
  <div id="history-grid"></div>
175
  </section>
176
  </div>
 
177
  <div id="lightbox">
178
  <div id="lightbox-content">
179
+ <img id="lightbox-img" src=""><button id="lightbox-close" class="lightbox-btn">&times;</button>
180
+ <a id="lightbox-download" href="#" title="دانلود تصویر" class="lightbox-btn"><svg fill="white" viewBox="0 0 24 24"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg></a>
 
 
 
181
  </div>
182
  </div>
183
 
184
  <script>
185
  const API_URL = 'https://alfa-editor-worker.onrender.com/api/edit';
186
 
187
+ const dropZone = document.getElementById('drop-zone'), dropZoneContent = document.getElementById('drop-zone-content'), fileInput = document.getElementById('file-input'), imagePreview = document.getElementById('image-preview'), removePreviewBtn = document.getElementById('remove-preview-btn'), promptInput = document.getElementById('prompt-input'), submitBtn = document.getElementById('submit-btn'), btnText = document.getElementById('btn-text'), btnSpinner = submitBtn.querySelector('.spinner'), resultContainer = document.getElementById('result-container'), resultGrid = document.getElementById('result-grid'), errorMessage = document.getElementById('error-message'), lightbox = document.getElementById('lightbox'), lightboxImg = document.getElementById('lightbox-img'), lightboxClose = document.getElementById('lightbox-close'), lightboxDownload = document.getElementById('lightbox-download'), historyGrid = document.getElementById('history-grid'), clearHistoryBtn = document.getElementById('clear-history-btn');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
 
189
  let uploadedFile = null;
190
 
191
+ const resetUploader = () => { uploadedFile = null; fileInput.value = ''; imagePreview.classList.remove('visible'); removePreviewBtn.classList.remove('visible'); dropZoneContent.style.opacity = '1'; checkFormState(); };
 
 
 
 
 
 
 
 
192
  const handleFile = (file) => {
193
+ if (!file || !file.type.startsWith('image/')) { displayError('لطفا یک فایل تصویری معتبر انتخاب کنید.'); return; }
 
 
 
194
  uploadedFile = file;
195
  const reader = new FileReader();
196
+ reader.onload = (e) => { imagePreview.src = e.target.result; imagePreview.classList.add('visible'); removePreviewBtn.classList.add('visible'); dropZoneContent.style.opacity = '0'; };
197
+ reader.readAsDataURL(file); checkFormState(); clearResult();
 
 
 
 
 
 
 
198
  };
199
 
200
  dropZone.addEventListener('click', () => fileInput.click());
 
203
  ['dragenter', 'dragover'].forEach(e => dropZone.addEventListener(e, () => dropZone.classList.add('drag-over')));
204
  ['dragleave', 'drop'].forEach(e => dropZone.addEventListener(e, () => dropZone.classList.remove('drag-over')));
205
  dropZone.addEventListener('drop', e => handleFile(e.dataTransfer.files[0]));
206
+ removePreviewBtn.addEventListener('click', (e) => { e.stopPropagation(); resetUploader(); });
 
 
 
207
 
208
  promptInput.addEventListener('input', checkFormState);
209
+ function checkFormState() { submitBtn.disabled = !uploadedFile || promptInput.value.trim() === ''; }
 
 
210
 
211
  submitBtn.addEventListener('click', async () => {
212
+ if (submitBtn.disabled) return; setLoading(true);
213
+ const formData = new FormData(); formData.append('image', uploadedFile); formData.append('prompt', promptInput.value.trim());
 
 
 
214
  try {
215
  const response = await fetch(API_URL, { method: 'POST', body: formData });
216
+ if (!response.ok) { const errorData = await response.json().catch(() => ({ error: `خطای سرور: ${response.statusText}` })); throw new Error(errorData.error || `یک خطای ناشناخته رخ داد.`); }
 
 
 
217
  const result = await response.json();
218
+ if (result.image_urls?.length > 0) { displayResult(result.image_urls); addToHistory({ prompt: promptInput.value.trim(), urls: result.image_urls }); }
219
+ else { throw new Error('پاسخ معتبری از سرور دریافت نشد.'); }
220
+ } catch (error) { displayError(error.message); } finally { setLoading(false); }
 
 
 
 
 
 
 
 
221
  });
222
 
223
  function setLoading(isLoading) {
224
+ resultContainer.classList.remove('has-content'); resultContainer.classList.toggle('loading', isLoading);
225
+ btnSpinner.style.display = isLoading ? 'inline-block' : 'none';
226
+ btnText.textContent = isLoading ? 'در حال پردازش...' : 'خلق کن';
227
+ submitBtn.disabled = isLoading;
228
+ if(isLoading) { resultGrid.innerHTML = ''; errorMessage.style.display = 'none'; }
 
 
 
 
 
 
 
 
 
229
  }
230
  function displayResult(imageUrls) {
231
  resultGrid.innerHTML = '';
232
+ imageUrls.forEach((url) => { const img = document.createElement('img'); img.src = url; img.alt = 'تصویر ویرایش شده'; img.addEventListener('click', () => openLightbox(url)); resultGrid.appendChild(img); });
 
 
 
 
 
 
233
  resultContainer.classList.add('has-content');
234
  }
235
+ function clearResult() { resultGrid.innerHTML = ''; errorMessage.style.display = 'none'; resultContainer.classList.remove('has-content'); }
236
+ function displayError(message) { errorMessage.textContent = message; errorMessage.style.display = 'block'; }
 
 
 
 
 
 
 
237
 
238
+ function openLightbox(url) { lightboxImg.src = url; lightbox.classList.add('visible'); }
 
 
 
 
239
  function closeLightbox() { lightbox.classList.remove('visible'); }
240
  lightboxClose.addEventListener('click', closeLightbox);
241
  lightbox.addEventListener('click', (e) => { if (e.target === lightbox) closeLightbox(); });
242
+
243
+ // --- FIXED DOWNLOAD FUNCTION ---
244
+ lightboxDownload.addEventListener('click', async (e) => {
245
+ e.preventDefault();
246
+ const url = lightboxImg.src;
247
+ try {
248
+ const response = await fetch(url);
249
+ const blob = await response.blob();
250
+ const blobUrl = URL.createObjectURL(blob);
251
+ const link = document.createElement('a');
252
+ link.href = blobUrl;
253
+ link.download = `ai-edited-${Date.now()}.png`;
254
+ document.body.appendChild(link);
255
+ link.click();
256
+ document.body.removeChild(link);
257
+ URL.revokeObjectURL(blobUrl);
258
+ } catch (error) {
259
+ console.error('Download failed:', error);
260
+ alert('دانلود با مشکل مواجه شد. لطفا دوباره تلاش کنید.');
261
+ }
262
+ });
263
 
264
  const getHistory = () => JSON.parse(localStorage.getItem('aiPhotoshopHistory') || '[]');
265
+ const saveHistory = (history) => { localStorage.setItem('aiPhotoshopHistory', JSON.stringify(history)); renderHistory(); };
266
+ const addToHistory = (item) => { let history = getHistory(); history.unshift(item); if (history.length > 15) history.pop(); saveHistory(history); };
267
+ const handleClearHistory = () => { if (confirm('آیا از پاک کردن تمام تاریخچه مطمئن هستید؟')) { localStorage.removeItem('aiPhotoshopHistory'); renderHistory(); }};
268
+
269
+ // --- NEW: DELETE INDIVIDUAL HISTORY ITEM ---
270
+ function handleDeleteHistoryItem(index) {
271
+ if (confirm('آیا از حذف این مورد از تاریخچه مطمئن هستید؟')) {
272
+ let history = getHistory();
273
+ history.splice(index, 1);
274
+ saveHistory(history);
 
 
 
 
275
  }
276
+ }
277
+
278
  function renderHistory() {
279
  const history = getHistory();
280
  historyGrid.innerHTML = '';
281
  clearHistoryBtn.style.display = history.length > 0 ? 'flex' : 'none';
282
+ history.forEach((item, index) => {
 
283
  const card = document.createElement('div');
284
  card.className = 'history-item';
285
+ const imagesHTML = item.urls.map(url => `<img src="${url}" alt="تصویر از تاریخچه" onclick="openLightbox('${url}')">`).join('');
 
 
 
286
  card.innerHTML = `
287
+ <button class="history-item-delete-btn" title="حذف این مورد" onclick="handleDeleteHistoryItem(${index})">&times;</button>
288
  <p class="history-item-prompt">${item.prompt}</p>
289
+ <div class="history-item-grid">${imagesHTML}</div>`;
 
290
  historyGrid.appendChild(card);
291
  });
292
  }