Elias207 commited on
Commit
4de0fae
·
verified ·
1 Parent(s): 5c3a601

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +315 -304
index.html CHANGED
@@ -3,188 +3,276 @@
3
  <head>
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
- --danger-color: #ef4444;
21
- --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
 
 
 
 
 
 
 
 
 
 
22
  }
23
 
24
- .dark { /* Dark mode variables can be added here if needed */ }
 
25
 
26
- @keyframes fadeInUp {
27
- from { opacity: 0; transform: translateY(30px); }
28
- to { opacity: 1; transform: translateY(0); }
29
- }
30
-
31
  body {
32
- font-family: 'Vazirmatn', sans-serif;
33
- background-color: var(--bg-start);
34
- background-image: radial-gradient(circle at top left, var(--bg-start), var(--bg-end));
35
- color: var(--text-color);
36
  margin: 0;
37
- padding: 3rem 1rem;
38
  display: flex;
39
  justify-content: center;
40
  align-items: flex-start;
41
  min-height: 100vh;
42
- transition: background-color 0.4s, color 0.4s;
43
  }
 
44
 
45
- .container { max-width: 750px; width: 100%; }
46
- header { text-align: center; margin-bottom: 3rem; animation: fadeInUp 0.8s var(--ease-out-expo) forwards; }
47
- h1 { font-size: 3rem; font-weight: 800; letter-spacing: -1px; margin: 0; background: linear-gradient(45deg, var(--primary-color-start), var(--primary-color-end)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
48
- p.subtitle { font-size: 1.2rem; color: #64748b; margin-top: 0.75rem; }
 
 
 
 
 
 
 
 
 
 
 
49
 
50
  main, .gallery-section {
51
- background-color: var(--card-bg); border-radius: 2rem; border: 1px solid var(--border-color);
52
- box-shadow: 0 4px 10px var(--shadow-color-dark), 0 20px 40px var(--shadow-color-light);
53
- padding: 3rem; transition: all 0.4s; animation: fadeInUp 0.8s var(--ease-out-expo) forwards; opacity: 0;
 
 
 
54
  }
55
- main { animation-delay: 0.2s; }
56
- .gallery-section { margin-top: 3rem; animation-delay: 0.4s; }
57
 
58
- .panel, .controls { margin-bottom: 3rem; }
59
- .panel:last-child { margin-bottom: 0; }
60
- .controls { margin-bottom: 2.5rem; }
61
-
62
- h2 { font-size: 1.4rem; font-weight: 700; margin: 0 0 1.5rem 0; padding-bottom: 1rem; display: flex; align-items: center; gap: 0.75rem; border-bottom: 1px solid var(--border-color); }
63
- h2 svg { color: var(--primary-color-start); }
 
 
 
 
 
 
64
 
65
- #drop-zone {
66
- border: 2px dashed var(--border-color); border-radius: 1.5rem; padding: 2rem; text-align: center;
67
- cursor: pointer; transition: all 0.3s var(--ease-out-expo); position: relative; overflow: hidden;
68
- background-color: var(--drop-zone-bg); display: flex; flex-direction: column; justify-content: center;
69
- align-items: center; min-height: 250px;
 
 
 
 
70
  }
71
- #drop-zone.dragover { border-color: var(--primary-color-start); }
72
- #drop-zone-content { transition: opacity 0.3s ease; display: flex; flex-direction: column; align-items: center; gap: 1rem; }
73
- .upload-btn-styled {
74
- background-image: linear-gradient(45deg, var(--primary-color-start) 0%, var(--primary-color-end) 100%); color: white;
75
- border: none; padding: 0.75rem 1.5rem; border-radius: 0.75rem; font-family: 'Vazirmatn', sans-serif;
76
- font-weight: 600; font-size: 1rem; cursor: pointer; margin-top: 1rem; transition: all 0.3s ease;
77
  }
78
- .upload-btn-styled:hover { transform: translateY(-3px) scale(1.05); box-shadow: 0 8px 15px var(--shadow-color-light); }
79
- #image-preview { position: absolute; inset: 0; width: 100%; height: 100%; padding: 1rem; box-sizing: border-box; border-radius: 1.5rem; object-fit: contain; opacity: 0; transform: scale(0.95); transition: opacity 0.4s ease, transform 0.4s ease; pointer-events: none; backdrop-filter: blur(4px); }
80
- #image-preview.visible { opacity: 1; transform: scale(1); pointer-events: auto; }
81
-
82
- textarea { width: 100%; padding: 1rem; border: 2px solid var(--border-color); border-radius: 1rem; background-color: var(--drop-zone-bg); color: var(--text-color); font-family: 'Vazirmatn', sans-serif; font-size: 1rem; resize: vertical; min-height: 80px; box-sizing: border-box; transition: all 0.3s ease; }
83
- textarea:focus { outline: none; border-color: var(--primary-color-start); box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary-color-start) 15%, transparent); }
84
-
85
- button#submit-btn { position: relative; overflow: hidden; width: 100%; padding: 1.1rem; border: none; border-radius: 1rem; background-image: linear-gradient(45deg, var(--primary-color-start) 0%, var(--primary-color-end) 100%); background-size: 200% auto; color: white; font-size: 1.1rem; font-weight: 700; cursor: pointer; margin-top: 1rem; transition: all 0.4s var(--ease-out-expo); box-shadow: 0 4px 15px var(--shadow-color-light); }
86
- button#submit-btn:hover:not(:disabled) { transform: translateY(-4px); box-shadow: 0 10px 25px -5px var(--shadow-color-light); background-position: right center; }
87
- button#submit-btn:disabled { opacity: 0.5; cursor: not-allowed; }
88
- .loader-dots { display: flex; justify-content: center; align-items: center; position: absolute; inset: 0; }
89
- .loader-dots div { width: 8px; height: 8px; margin: 0 4px; background-color: white; border-radius: 50%; animation: bounce 1.4s infinite ease-in-out both; }
90
- .loader-dots .dot1 { animation-delay: -0.32s; } .loader-dots .dot2 { animation-delay: -0.16s; }
91
- @keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1.0); } }
92
- #btn-text.hidden { visibility: hidden; }
93
-
94
- #result-container { border: 2px solid var(--border-color); border-radius: 1.5rem; min-height: 300px; position: relative; background-color: var(--drop-zone-bg); overflow: hidden; padding: 0.75rem; transition: border-color 0.3s ease; }
95
- #loading-placeholder { display: none; flex-direction: column; align-items: center; justify-content: center; gap: 1.5rem; opacity: 0; animation: fadeIn 0.5s ease forwards; position: absolute; inset: 0; }
96
- @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
97
- #result-container.loading #loading-placeholder { display: flex; }
98
- .creative-loader { width: 100px; height: 100px; position: relative; } /* Styles for creative loader remain the same */
99
- .creative-loader svg { width: 100%; height: 100%; } .creative-loader .arc { fill: none; stroke-width: 4; stroke-linecap: round; transform-origin: 50% 50%; animation: spin-loader 2s linear infinite; } .creative-loader .arc1 { stroke: var(--primary-color-start); animation-duration: 2.2s; } .creative-loader .arc2 { stroke: var(--primary-color-end); animation-duration: 1.8s; animation-direction: reverse; } .creative-loader .arc3 { stroke: #8b5cf6; animation-duration: 2s; } .creative-loader .star { fill: var(--primary-color-end); transform-origin: center; animation: pulse-star 1.5s ease-in-out infinite; } @keyframes spin-loader { to { transform: rotate(360deg); } } @keyframes pulse-star { 0%, 100% { transform: scale(0.9); opacity: 0.8; } 50% { transform: scale(1.1); opacity: 1; } } .loading-text { font-weight: 500; color: #64748b; }
100
-
101
- #result-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.75rem; }
102
- @keyframes popIn-spring { 0% { opacity: 0; transform: scale(0.8) translateY(20px); } 80% { opacity: 1; transform: scale(1.05) translateY(-5px); } 100% { opacity: 1; transform: scale(1) translateY(0); } }
103
- .result-img { width: 100%; height: 100%; object-fit: cover; border-radius: 1rem; cursor: pointer; transition: transform 0.3s ease, box-shadow 0.3s ease; opacity: 0; animation: popIn-spring 0.6s var(--ease-out-expo) forwards; }
104
- .result-img:hover { transform: scale(1.05) rotate(1deg); box-shadow: 0 10px 30px -5px var(--shadow-color-dark); }
105
-
106
- #error-message { color: var(--danger-color); text-align: center; margin-top: 1rem; display: none; font-weight: 500; }
107
 
108
- /* -- LIGHTBOX IMPROVEMENTS -- */
109
- #lightbox { position: fixed; inset: 0; background-color: rgba(10, 20, 40, 0.9); z-index: 1000; display: none; justify-content: center; align-items: center; padding: 1rem; box-sizing: border-box; backdrop-filter: blur(8px); }
110
- #lightbox-content { position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; }
111
- #lightbox-img { max-width: 85vw; max-height: 85vh; object-fit: contain; border-radius: 1rem; box-shadow: 0 20px 50px rgba(0,0,0,0.5); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
- .lightbox-btn { background-color: rgba(255, 255, 255, 0.1); color: white; border: 1px solid rgba(255,255,255,0.2); width: 50px; height: 50px; border-radius: 50%; cursor: pointer; display: flex; justify-content: center; align-items: center; transition: all 0.2s; text-decoration: none; }
114
- .lightbox-btn:hover { background-color: rgba(255, 255, 255, 0.2); transform: scale(1.1); }
115
- .lightbox-btn.disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
- #lightbox-close { position: absolute; top: 20px; right: 20px; z-index: 1002; }
118
- #lightbox-download { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 1002; }
 
 
 
 
 
 
 
 
 
 
119
 
120
- #lightbox-prev, #lightbox-next { position: absolute; top: 50%; transform: translateY(-50%); z-index: 1001; }
121
- #lightbox-prev { left: 20px; } #lightbox-next { right: 20px; }
122
-
123
  /* --- History Gallery Styles --- */
124
  .gallery-header { display: flex; justify-content: space-between; align-items: center; }
125
- #clear-history-btn { background: none; border: 1px solid var(--border-color); color: #64748b; padding: 0.5rem 1rem; border-radius: 0.75rem; cursor: pointer; display: flex; align-items: center; gap: 0.5rem; font-family: 'Vazirmatn', sans-serif; font-weight: 500; transition: all 0.2s; }
126
  #clear-history-btn:hover { border-color: var(--danger-color); color: var(--danger-color); }
127
-
128
  #history-grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; margin-top: 1.5rem; }
129
- #history-grid:empty::before { content: 'هنوز تصویری خلق نکرده‌اید. اولین ویرایش شما اینجا ذخیره خواهد شد.'; color: #64748b; grid-column: 1 / -1; text-align: center; padding: 3rem 1rem; background-color: var(--drop-zone-bg); border-radius: 1rem; }
130
- .history-item { background-color: var(--drop-zone-bg); border-radius: 1.25rem; border: 1px solid var(--border-color); overflow: hidden; box-shadow: 0 2px 8px var(--shadow-color-dark); transition: all 0.4s ease; opacity: 0; animation: popIn-spring 0.6s var(--ease-out-expo) forwards; display: flex; flex-direction: column; }
131
- .history-item.removing { transform: scale(0.9); opacity: 0; }
132
- .history-item:hover { transform: translateY(-8px); box-shadow: 0 10px 20px var(--shadow-color-dark); }
133
-
134
- .history-item-thumbnails { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.5rem; padding: 0.5rem; }
135
- .history-item-thumbnails img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 0.75rem; cursor: pointer; transition: transform 0.2s ease; }
136
- .history-item-thumbnails img:hover { transform: scale(1.08); }
137
 
138
- .history-item-content { padding: 0 1.25rem 1.25rem 1.25rem; border-top: 1px solid var(--border-color); }
139
- .history-item-prompt { font-size: 0.95rem; font-weight: 500; margin: 1rem 0; line-height: 1.6em; }
140
- .history-item-footer { display: flex; justify-content: flex-end; align-items: center; }
141
- .delete-btn { background-color: transparent; color: var(--danger-color); border: 1px solid transparent; padding: 0.5rem; border-radius: 50%; cursor: pointer; display: flex; align-items: center; gap: 0.5rem; transition: all 0.2s; }
142
- .delete-btn:hover { background-color: color-mix(in srgb, var(--danger-color) 10%, transparent); border-color: color-mix(in srgb, var(--danger-color) 20%, transparent); }
143
- .delete-btn svg { width: 20px; height: 20px; }
 
 
 
 
144
 
145
- @media (max-width: 600px) {
146
- body { padding: 1.5rem 1rem; }
147
  main, .gallery-section { padding: 1.5rem; }
148
- h1 { font-size: 2.5rem; }
149
- .history-item-thumbnails { grid-template-columns: repeat(2, 1fr); }
150
- #lightbox-prev, #lightbox-next { bottom: 20px; top: auto; transform: translateY(0); }
151
- #lightbox-prev { left: 20px; } #lightbox-next { right: 20px; }
152
- #lightbox-download { left: 50%; transform: translateX(-50%); }
153
  }
154
  </style>
155
  </head>
156
  <body>
157
-
158
  <div class="container">
159
- <!-- HEADER AND UPLOAD SECTIONS (UNCHANGED HTML) -->
160
  <header>
161
  <h1>فتوشاپ هوش مصنوعی ✨</h1>
162
  <p class="subtitle">تصاویر خود را با قدرت هوش مصنوعی و به زبان ساده ویرایش کنید</p>
163
  </header>
164
 
165
  <main>
166
- <div class="panel">
167
- <h2>1. تصویر خود را انتخاب کنید</h2>
168
- <div id="drop-zone">
169
- <div id="drop-zone-content">
170
- <span id="drop-zone-text">تصویر را اینجا بکشید یا از دکمه زیر استفاده کنید</span>
171
- <button class="upload-btn-styled">انتخاب فایل</button>
 
 
 
 
 
 
 
 
 
 
172
  </div>
173
- <img id="image-preview" src="#" alt="پیش‌نمایش تصویر" />
174
  </div>
175
  <input type="file" id="file-input" accept="image/*" hidden>
176
  </div>
177
- <div class="controls">
178
- <h2>2. دستور ویرایش را بنویسید</h2>
 
 
 
 
179
  <textarea id="prompt-input" rows="3" placeholder="مثال: پس‌زمینه را حذف کن و یک ساحل آفتابی قرار بده"></textarea>
180
- <button id="submit-btn" disabled><span id="btn-text">خلق کن</span></button>
 
 
 
181
  <p id="error-message"></p>
182
  </div>
183
- <div class="panel">
184
- <h2>3. نتیجه را ببینید</h2>
 
 
 
 
185
  <div id="result-container">
186
  <div id="loading-placeholder">
187
- <div class="creative-loader"><svg viewBox="0 0 50 50"><g><circle class="arc arc1" cx="25" cy="25" r="22"></circle><circle class="arc arc2" cx="25" cy="25" r="18"></circle><circle class="arc arc3" cx="25" cy="25" r="14"></circle><path class="star" d="M25,21 L26.9,24.1 L30,25 L26.9,25.9 L25,29 L23.1,25.9 L20,25 L23.1,24.1 Z"></path></g></svg></div>
188
  <p class="loading-text">هوش مصنوعی در حال خلق اثر شماست...</p>
189
  </div>
190
  <div id="result-grid"></div>
@@ -192,103 +280,91 @@
192
  </div>
193
  </main>
194
 
195
- <!-- HISTORY SECTION (UNCHANGED HTML) -->
196
  <section class="gallery-section">
197
  <div class="gallery-header">
198
- <h2>گالری و تاریخچه شما</h2>
199
- <button id="clear-history-btn" style="display: none;">
200
- <span>پاک کردن همه</span>
 
 
 
 
201
  </button>
202
  </div>
203
  <div id="history-grid"></div>
204
  </section>
205
  </div>
206
 
207
- <!-- IMPROVED LIGHTBOX WITH NAVIGATION -->
208
  <div id="lightbox">
209
  <div id="lightbox-content">
210
- <button id="lightbox-prev" class="lightbox-btn" title="تصویر قبلی">
211
- <svg fill="white" width="24" height="24" viewBox="0 0 24 24"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>
212
- </button>
213
- <img id="lightbox-img" src="">
214
- <button id="lightbox-next" class="lightbox-btn" title="تصویر بعدی">
215
- <svg fill="white" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
216
- </button>
217
  </div>
218
- <a id="lightbox-download" href="#" download="edited-image.png" title="دانلود تصویر" class="lightbox-btn">
219
- <svg fill="white" width="24" height="24" viewBox="0 0 24 24"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg>
220
- </a>
221
- <button id="lightbox-close" class="lightbox-btn">&times;</button>
222
  </div>
223
 
224
  <script>
225
- // Constants
226
  const API_URL = 'https://alfa-editor-worker.onrender.com/api/edit';
227
- const HISTORY_KEY = 'aiPhotoshopHistory';
228
-
229
- // Element Selectors
230
  const fileInput = document.getElementById('file-input');
231
- const dropZone = document.getElementById('drop-zone');
232
- const dropZoneContent = document.getElementById('drop-zone-content');
233
- const uploadBtnStyled = document.querySelector('.upload-btn-styled');
234
- const imagePreview = document.getElementById('image-preview');
235
  const promptInput = document.getElementById('prompt-input');
236
  const submitBtn = document.getElementById('submit-btn');
237
  const btnText = document.getElementById('btn-text');
 
238
  const resultContainer = document.getElementById('result-container');
239
  const resultGrid = document.getElementById('result-grid');
240
  const errorMessage = document.getElementById('error-message');
241
- const historyGrid = document.getElementById('history-grid');
242
- const clearHistoryBtn = document.getElementById('clear-history-btn');
243
- // Lightbox Elements
244
  const lightbox = document.getElementById('lightbox');
245
  const lightboxImg = document.getElementById('lightbox-img');
246
  const lightboxClose = document.getElementById('lightbox-close');
247
  const lightboxDownload = document.getElementById('lightbox-download');
248
- const lightboxPrev = document.getElementById('lightbox-prev');
249
- const lightboxNext = document.getElementById('lightbox-next');
250
 
251
- // State variables
252
  let uploadedFile = null;
253
- let currentLightboxGroup = [];
254
- let currentLightboxIndex = 0;
255
- let currentBlobUrl = null;
256
 
257
- // --- Event Listeners Setup ---
258
- dropZone.addEventListener('click', () => fileInput.click());
259
- uploadBtnStyled.addEventListener('click', (e) => { e.stopPropagation(); fileInput.click(); });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  fileInput.addEventListener('change', (e) => handleFile(e.target.files[0]));
261
- ['dragenter', 'dragover'].forEach(eventName => dropZone.addEventListener(eventName, (e) => { e.preventDefault(); dropZone.classList.add('dragover'); }));
262
- ['dragleave', 'drop'].forEach(eventName => dropZone.addEventListener(eventName, (e) => { e.preventDefault(); dropZone.classList.remove('dragover'); }));
263
- dropZone.addEventListener('drop', (e) => handleFile(e.dataTransfer.files[0]));
264
- promptInput.addEventListener('input', checkFormState);
265
- submitBtn.addEventListener('click', handleSubmit);
266
- clearHistoryBtn.addEventListener('click', handleClearHistory);
267
- lightboxClose.addEventListener('click', closeLightbox);
268
- lightbox.addEventListener('click', (e) => { if (e.target === lightbox) closeLightbox(); });
269
- lightboxPrev.addEventListener('click', showPrevImage);
270
- lightboxNext.addEventListener('click', showNextImage);
271
- document.addEventListener('DOMContentLoaded', renderHistory);
272
 
273
- function handleFile(file) {
274
- if (!file || !file.type.startsWith('image/')) { displayError('لطفا یک فایل تصویری انتخاب کنید.'); return; }
275
- uploadedFile = file;
276
- const reader = new FileReader();
277
- reader.onload = (e) => {
278
- imagePreview.src = e.target.result;
279
- imagePreview.classList.add('visible');
280
- dropZoneContent.style.opacity = '0';
281
- checkFormState();
282
- clearResult();
283
- };
284
- reader.readAsDataURL(file);
285
- }
286
-
287
  function checkFormState() {
288
  submitBtn.disabled = !uploadedFile || promptInput.value.trim() === '';
289
  }
290
 
291
- async function handleSubmit() {
292
  if (submitBtn.disabled) return;
293
  setLoading(true);
294
  const formData = new FormData();
@@ -296,11 +372,14 @@
296
  formData.append('prompt', promptInput.value.trim());
297
  try {
298
  const response = await fetch(API_URL, { method: 'POST', body: formData });
299
- if (!response.ok) throw new Error(`خطای سرور: ${response.statusText}`);
 
 
 
300
  const result = await response.json();
301
- if (result.image_urls && result.image_urls.length > 0) {
302
  displayResult(result.image_urls);
303
- addToHistory({ prompt: promptInput.value.trim(), urls: result.image_urls, id: Date.now() });
304
  } else {
305
  throw new Error('پاسخ معتبری از سرور دریافت نشد.');
306
  }
@@ -309,164 +388,96 @@
309
  } finally {
310
  setLoading(false);
311
  }
312
- }
313
 
 
314
  function setLoading(isLoading) {
315
  if (isLoading) {
 
316
  resultContainer.classList.add('loading');
317
- btnText.classList.add('hidden');
318
- if (!submitBtn.querySelector('.loader-dots')) {
319
- const loader = document.createElement('div');
320
- loader.className = 'loader-dots';
321
- loader.innerHTML = '<div></div><div></div><div></div>';
322
- submitBtn.appendChild(loader);
323
- }
324
  submitBtn.disabled = true;
325
  resultGrid.innerHTML = '';
326
  errorMessage.style.display = 'none';
327
  } else {
328
  resultContainer.classList.remove('loading');
329
- btnText.classList.remove('hidden');
330
- const loader = submitBtn.querySelector('.loader-dots');
331
- if (loader) loader.remove();
332
  checkFormState();
333
  }
334
  }
335
-
336
  function displayResult(imageUrls) {
337
  resultGrid.innerHTML = '';
338
- imageUrls.forEach((url, index) => {
339
  const img = document.createElement('img');
340
  img.src = url;
341
  img.alt = 'تصویر ویرایش شده';
342
- img.className = 'result-img';
343
- img.style.animationDelay = `${index * 120}ms`;
344
- img.addEventListener('click', () => openLightbox(imageUrls, index));
345
  resultGrid.appendChild(img);
346
  });
 
347
  }
348
-
349
  function clearResult() {
350
  resultGrid.innerHTML = '';
351
  errorMessage.style.display = 'none';
 
352
  }
353
  function displayError(message) {
354
  errorMessage.textContent = message;
355
  errorMessage.style.display = 'block';
356
  }
357
-
358
- // --- Lightbox Functions ---
359
- function openLightbox(urls, index) {
360
- currentLightboxGroup = urls;
361
- currentLightboxIndex = index;
362
- lightbox.style.display = 'flex';
363
- updateLightboxImage();
364
- }
365
-
366
- function closeLightbox() {
367
- lightbox.style.display = 'none';
368
- if (currentBlobUrl) {
369
- URL.revokeObjectURL(currentBlobUrl);
370
- currentBlobUrl = null;
371
- }
372
- }
373
 
374
- function showPrevImage() {
375
- currentLightboxIndex = (currentLightboxIndex - 1 + currentLightboxGroup.length) % currentLightboxGroup.length;
376
- updateLightboxImage();
377
- }
378
- function showNextImage() {
379
- currentLightboxIndex = (currentLightboxIndex + 1) % currentLightboxGroup.length;
380
- updateLightboxImage();
381
- }
382
-
383
- async function updateLightboxImage() {
384
- const url = currentLightboxGroup[currentLightboxIndex];
385
  lightboxImg.src = url;
386
-
387
- // --- ROBUST DOWNLOAD LOGIC ---
388
- lightboxDownload.classList.add('disabled'); // Disable while fetching
389
- if (currentBlobUrl) URL.revokeObjectURL(currentBlobUrl);
390
- try {
391
- const response = await fetch(url);
392
- const blob = await response.blob();
393
- currentBlobUrl = URL.createObjectURL(blob);
394
- lightboxDownload.href = currentBlobUrl;
395
- lightboxDownload.download = `ai-photoshop-${Date.now()}.png`;
396
- lightboxDownload.classList.remove('disabled');
397
- } catch (error) {
398
- console.error('Download failed:', error);
399
- // Optionally hide or keep it disabled
400
- }
401
- }
402
-
403
- // --- History Management Functions ---
404
- function getHistory() {
405
- try { return JSON.parse(localStorage.getItem(HISTORY_KEY)) || []; } catch (e) { return []; }
406
  }
407
- function saveHistory(history) {
408
- localStorage.setItem(HISTORY_KEY, JSON.stringify(history));
 
 
 
 
 
 
409
  renderHistory();
410
- }
411
- function addToHistory(item) {
412
  let history = getHistory();
413
  history.unshift(item);
414
- if (history.length > 20) history.pop(); // Limit history size
415
  saveHistory(history);
416
- }
417
- function deleteHistoryItem(id) {
418
- let history = getHistory().filter(item => item.id !== id);
419
- saveHistory(history);
420
- }
421
- function handleClearHistory() {
422
  if (confirm('آیا از پاک کردن تمام تاریخچه مطمئن هستید؟')) {
423
- localStorage.removeItem(HISTORY_KEY);
424
  renderHistory();
425
  }
426
- }
427
-
428
  function renderHistory() {
429
  const history = getHistory();
430
  historyGrid.innerHTML = '';
431
  clearHistoryBtn.style.display = history.length > 0 ? 'flex' : 'none';
432
 
433
- history.forEach((item, historyIndex) => {
434
  const card = document.createElement('div');
435
  card.className = 'history-item';
436
- card.style.animationDelay = `${historyIndex * 80}ms`;
437
-
438
- const thumbnailsContainer = document.createElement('div');
439
- thumbnailsContainer.className = 'history-item-thumbnails';
440
- item.urls.forEach((url, urlIndex) => {
441
- const img = document.createElement('img');
442
- img.src = url;
443
- img.addEventListener('click', () => openLightbox(item.urls, urlIndex));
444
- thumbnailsContainer.appendChild(img);
445
- });
446
-
447
- const contentContainer = document.createElement('div');
448
- contentContainer.className = 'history-item-content';
449
- contentContainer.innerHTML = `
450
- <p class="history-item-prompt">${item.prompt}</p>
451
- <div class="history-item-footer">
452
- <button class="delete-btn" data-id="${item.id}" title="حذف این آیتم">
453
- <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="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>
454
- </button>
455
- </div>`;
456
 
457
- contentContainer.querySelector('.delete-btn').addEventListener('click', (e) => {
458
- e.stopPropagation();
459
- const idToDelete = Number(e.currentTarget.getAttribute('data-id'));
460
- const cardElement = e.currentTarget.closest('.history-item');
461
- cardElement.classList.add('removing');
462
- setTimeout(() => deleteHistoryItem(idToDelete), 400);
463
- });
464
-
465
- card.appendChild(thumbnailsContainer);
466
- card.appendChild(contentContainer);
467
  historyGrid.appendChild(card);
468
  });
469
  }
 
 
 
470
  checkFormState();
471
  </script>
472
  </body>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI Photoshop Pro</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
+ --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
+ --danger-color: #e53e3e;
24
+ --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03);
25
+ --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
26
+ --shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06), 0 4px 6px -4px rgba(26, 32, 44, 0.05);
27
+ --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05);
28
+ --radius-card: 24px;
29
+ --radius-btn: 14px;
30
+ --radius-input: 12px;
31
+ --transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
32
  }
33
 
34
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
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; }
 
75
 
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 Style --- */
90
+ #upload-area {
91
+ border: 2px dashed var(--input-border);
92
+ border-radius: var(--radius-input);
93
+ padding: 2rem;
94
+ text-align: center;
95
+ cursor: pointer;
96
+ transition: var(--transition-smooth);
97
+ background-color: var(--input-bg);
98
  }
99
+ #upload-area.drag-over, #upload-area:hover {
100
+ border-color: var(--accent-primary);
101
+ background-color: #fff;
102
+ box-shadow: 0 0 15px var(--accent-primary-glow);
 
 
103
  }
104
+ #upload-icon svg { width: 48px; height: 48px; color: var(--accent-primary); margin-bottom: 1rem; stroke-width: 1.5; opacity: 0.8; }
105
+ #upload-area p { margin: 0; color: var(--text-secondary); font-weight: 500; }
106
+ #file-preview {
107
+ display: none;
108
+ align-items: center;
109
+ justify-content: space-between;
110
+ padding: 1rem 1.2rem;
111
+ background-color: var(--input-bg);
112
+ border-radius: var(--radius-input);
113
+ border: 1px solid var(--panel-border);
114
+ animation: fadeIn 0.3s;
115
+ }
116
+ #file-info { display: flex; align-items: center; gap: 1rem; overflow: hidden; }
117
+ #file-info svg { color: var(--accent-secondary); width: 28px; height: 28px; flex-shrink: 0; }
118
+ #file-name { font-weight: 600; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
119
+ #remove-file-btn { background: none; border: none; color: var(--text-tertiary); cursor: pointer; font-size: 1.5rem; transition: all 0.2s; flex-shrink: 0; padding: 0 .5rem; }
120
+ #remove-file-btn:hover { color: var(--danger-color); transform: scale(1.1); }
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
+ textarea {
123
+ width: 100%;
124
+ padding: 1rem 1.2rem;
125
+ border-radius: var(--radius-input);
126
+ border: 1px solid var(--input-border);
127
+ background-color: var(--input-bg);
128
+ color: var(--text-primary);
129
+ box-shadow: var(--shadow-sm) inset;
130
+ font-family: var(--app-font);
131
+ font-size: 1rem;
132
+ box-sizing: border-box;
133
+ transition: var(--transition-smooth);
134
+ min-height: 90px;
135
+ resize: vertical;
136
+ }
137
+ textarea:focus {
138
+ outline: none;
139
+ border-color: var(--accent-primary);
140
+ box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset;
141
+ background-color: var(--panel-bg);
142
+ }
143
+ #submit-btn {
144
+ display: flex; align-items: center; justify-content: center; gap: 10px;
145
+ width: 100%; padding: 1.1rem; font-size: 1.2rem; font-weight: 700;
146
+ background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%);
147
+ color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer;
148
+ transition: all 0.3s ease;
149
+ box-shadow: 0 6px 12px -3px var(--accent-primary-glow), 0 6px 12px -3px var(--accent-secondary-glow);
150
+ margin-top: 1.5rem;
151
+ }
152
+ #submit-btn:hover:not(:disabled) {
153
+ transform: translateY(-5px) scale(1.02);
154
+ box-shadow: 0 8px 20px -4px var(--accent-primary-glow), 0 8px 20px -4px var(--accent-secondary-glow);
155
+ }
156
+ #submit-btn:disabled { background: var(--text-tertiary); cursor: not-allowed; box-shadow: none; opacity: 0.7; }
157
+ #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; }
158
 
159
+ #result-container {
160
+ min-height: 180px; position: relative;
161
+ box-sizing: border-box; padding: 1rem; background-color: var(--input-bg);
162
+ border-radius: var(--radius-card); border: 2px dashed var(--input-border);
163
+ box-shadow: var(--shadow-sm) inset; transition: var(--transition-smooth);
164
+ display: flex; align-items: center; justify-content: center;
165
+ }
166
+ #result-container.loading, #result-container.has-content { border-style: solid; border-color: var(--panel-border); }
167
+ #loading-placeholder { display: none; flex-direction: column; align-items: center; gap: 1rem; }
168
+ #result-container.loading #loading-placeholder { display: flex; animation: fadeIn 0.5s; }
169
+ /* Using the TTS loader */
170
+ .orbital-loader { width: 80px; height: 80px; position: relative; animation: spin 10s linear infinite; }
171
+ .orbit { position: absolute; top: 50%; left: 50%; border: 2px dashed rgba(74, 108, 250, 0.35); border-radius: 50%; transform-origin: center center; }
172
+ .orbit:nth-child(1) { width: 25px; height: 25px; margin: -12.5px 0 0 -12.5px; animation: spin 2.8s linear infinite reverse; }
173
+ .orbit:nth-child(2) { width: 50px; height: 50px; margin: -25px 0 0 -25px; animation: spin 3.8s linear infinite; }
174
+ .orbit .satellite { position: absolute; width: 8px; height: 8px; border-radius: 50%; background-color: var(--accent-primary); box-shadow: 0 0 8px var(--accent-primary), 0 0 12px var(--accent-secondary); }
175
+ .orbit:nth-child(1) .satellite { top: -4px; left: 50%; transform: translateX(-50%); }
176
+ .orbit:nth-child(2) .satellite { top: 50%; left: -4px; background-color: var(--accent-secondary); transform: translateY(-50%); }
177
+ .loading-text { font-weight: 500; color: var(--text-secondary); }
178
 
179
+ #result-grid {
180
+ display: none; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
181
+ gap: 1rem; width: 100%;
182
+ }
183
+ #result-container.has-content #result-grid { display: grid; }
184
+ #result-grid img {
185
+ width: 100%; height: 150px; object-fit: cover; border-radius: var(--radius-input);
186
+ cursor: pointer; transition: var(--transition-smooth); box-shadow: var(--shadow-md);
187
+ border: 1px solid var(--panel-border);
188
+ }
189
+ #result-grid img:hover { transform: scale(1.05); box-shadow: var(--shadow-lg); z-index: 10; position: relative; }
190
+ #error-message { color: var(--danger-color); text-align: center; margin-top: 1rem; display: none; font-weight: 500; }
191
 
 
 
 
192
  /* --- History Gallery Styles --- */
193
  .gallery-header { display: flex; justify-content: space-between; align-items: center; }
194
+ #clear-history-btn { background: none; border: 1px solid var(--panel-border); color: var(--text-secondary); padding: 0.5rem 1rem; border-radius: var(--radius-btn); cursor: pointer; display: none; align-items: center; gap: 0.5rem; font-family: var(--app-font); font-weight: 500; transition: all 0.2s; }
195
  #clear-history-btn:hover { border-color: var(--danger-color); color: var(--danger-color); }
196
+ #clear-history-btn svg { width: 18px; height: 18px; }
197
  #history-grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; margin-top: 1.5rem; }
198
+ #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); }
199
+ .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); }
200
+ .history-item:hover { transform: translateY(-5px); box-shadow: var(--shadow-lg); }
201
+ .history-item-prompt { font-weight: 600; margin: 0 0 1rem 0; word-break: break-word; }
202
+ .history-item-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.75rem; }
203
+ .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); }
204
+ .history-item-grid img:hover { transform: scale(1.1); box-shadow: var(--shadow-md); z-index: 5; position: relative; }
 
205
 
206
+ /* --- Lightbox Styles --- */
207
+ #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; }
208
+ #lightbox.visible { display: flex; opacity: 1; }
209
+ #lightbox-content { position: relative; animation: fadeIn 0.3s ease; }
210
+ #lightbox-img { max-width: 85vw; max-height: 80vh; object-fit: contain; border-radius: var(--radius-card); box-shadow: var(--shadow-xl); }
211
+ .lightbox-btn { position: absolute; background-color: rgba(255, 255, 255, 0.1); color: white; border: 1px solid rgba(255,255,255,0.2); width: 44px; height: 44px; border-radius: 50%; cursor: pointer; display: flex; justify-content: center; align-items: center; transition: all 0.2s; text-decoration: none; }
212
+ .lightbox-btn:hover { background-color: rgba(255, 255, 255, 0.2); transform: scale(1.1); }
213
+ #lightbox-close { top: -50px; right: 0; font-size: 1.8rem; }
214
+ #lightbox-download { top: -50px; left: 0; }
215
+ #lightbox-download svg { width: 22px; height: 22px; }
216
 
217
+ @media (max-width: 768px) {
 
218
  main, .gallery-section { padding: 1.5rem; }
219
+ h1 { font-size: 2.2rem; }
220
+ #result-grid { grid-template-columns: repeat(2, 1fr); }
221
+ #result-grid img { height: auto; aspect-ratio: 1/1; }
222
+ .history-item-grid { grid-template-columns: repeat(2, 1fr); }
 
223
  }
224
  </style>
225
  </head>
226
  <body>
 
227
  <div class="container">
 
228
  <header>
229
  <h1>فتوشاپ هوش مصنوعی ✨</h1>
230
  <p class="subtitle">تصاویر خود را با قدرت هوش مصنوعی و به زبان ساده ویرایش کنید</p>
231
  </header>
232
 
233
  <main>
234
+ <div class="form-group">
235
+ <label for="upload-area" class="form-label">
236
+ <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>
237
+ ۱. تصویر خود را انتخاب کنید
238
+ </label>
239
+ <div id="upload-container">
240
+ <label id="upload-area">
241
+ <div id="upload-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" 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></div>
242
+ <p>فایل تصویر را اینجا بکشید یا برای انتخاب کلیک کنید</p>
243
+ </label>
244
+ <div id="file-preview">
245
+ <div id="file-info">
246
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M13.5,16V19H10.5V16H8L12,12L16,16H13.5M13,9V3.5L18.5,9H13Z" /></svg>
247
+ <span id="file-name"></span>
248
+ </div>
249
+ <button type="button" id="remove-file-btn" title="حذف فایل">&times;</button>
250
  </div>
 
251
  </div>
252
  <input type="file" id="file-input" accept="image/*" hidden>
253
  </div>
254
+
255
+ <div class="form-group">
256
+ <label for="prompt-input" class="form-label">
257
+ <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>
258
+ ۲. دستور ویرایش را بنویسید
259
+ </label>
260
  <textarea id="prompt-input" rows="3" placeholder="مثال: پس‌زمینه را حذف کن و یک ساحل آفتابی قرار بده"></textarea>
261
+ <button id="submit-btn" disabled>
262
+ <span id="btn-text">خلق کن</span>
263
+ <div class="spinner"></div>
264
+ </button>
265
  <p id="error-message"></p>
266
  </div>
267
+
268
+ <div class="form-group">
269
+ <label class="form-label">
270
+ <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>
271
+ ۳. نتیجه را ببینید
272
+ </label>
273
  <div id="result-container">
274
  <div id="loading-placeholder">
275
+ <div class="orbital-loader"><div class="orbit"><div class="satellite"></div></div><div class="orbit"><div class="satellite"></div></div></div>
276
  <p class="loading-text">هوش مصنوعی در حال خلق اثر شماست...</p>
277
  </div>
278
  <div id="result-grid"></div>
 
280
  </div>
281
  </main>
282
 
 
283
  <section class="gallery-section">
284
  <div class="gallery-header">
285
+ <label class="form-label">
286
+ <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>
287
+ گالری و تاریخچه شما
288
+ </label>
289
+ <button id="clear-history-btn">
290
+ <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>
291
+ <span>پاک کردن</span>
292
  </button>
293
  </div>
294
  <div id="history-grid"></div>
295
  </section>
296
  </div>
297
 
 
298
  <div id="lightbox">
299
  <div id="lightbox-content">
300
+ <img id="lightbox-img" src="">
301
+ <button id="lightbox-close" class="lightbox-btn">&times;</button>
302
+ <a id="lightbox-download" href="#" download="edited-image.png" title="دانلود تصویر" class="lightbox-btn">
303
+ <svg fill="white" viewBox="0 0 24 24"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg>
304
+ </a>
 
 
305
  </div>
 
 
 
 
306
  </div>
307
 
308
  <script>
 
309
  const API_URL = 'https://alfa-editor-worker.onrender.com/api/edit';
310
+
311
+ // --- DOM Elements ---
312
+ const uploadArea = document.getElementById('upload-area');
313
  const fileInput = document.getElementById('file-input');
314
+ const filePreview = document.getElementById('file-preview');
315
+ const fileNameSpan = document.getElementById('file-name');
316
+ const removeFileBtn = document.getElementById('remove-file-btn');
 
317
  const promptInput = document.getElementById('prompt-input');
318
  const submitBtn = document.getElementById('submit-btn');
319
  const btnText = document.getElementById('btn-text');
320
+ const btnSpinner = submitBtn.querySelector('.spinner');
321
  const resultContainer = document.getElementById('result-container');
322
  const resultGrid = document.getElementById('result-grid');
323
  const errorMessage = document.getElementById('error-message');
 
 
 
324
  const lightbox = document.getElementById('lightbox');
325
  const lightboxImg = document.getElementById('lightbox-img');
326
  const lightboxClose = document.getElementById('lightbox-close');
327
  const lightboxDownload = document.getElementById('lightbox-download');
328
+ const historyGrid = document.getElementById('history-grid');
329
+ const clearHistoryBtn = document.getElementById('clear-history-btn');
330
 
 
331
  let uploadedFile = null;
 
 
 
332
 
333
+ // --- Uploader Logic ---
334
+ const resetUploader = () => {
335
+ uploadedFile = null;
336
+ fileInput.value = '';
337
+ uploadArea.style.display = 'block';
338
+ filePreview.style.display = 'none';
339
+ checkFormState();
340
+ };
341
+ const handleFile = (file) => {
342
+ if (!file || !file.type.startsWith('image/')) {
343
+ displayError('لطفا یک فایل تصویری معتبر انتخاب کنید.');
344
+ return;
345
+ }
346
+ uploadedFile = file;
347
+ fileNameSpan.textContent = file.name;
348
+ uploadArea.style.display = 'none';
349
+ filePreview.style.display = 'flex';
350
+ checkFormState();
351
+ clearResult();
352
+ };
353
+ uploadArea.addEventListener('click', () => fileInput.click());
354
  fileInput.addEventListener('change', (e) => handleFile(e.target.files[0]));
355
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(e => uploadArea.addEventListener(e, p => { p.preventDefault(); p.stopPropagation(); }));
356
+ ['dragenter', 'dragover'].forEach(e => uploadArea.addEventListener(e, () => uploadArea.classList.add('drag-over')));
357
+ ['dragleave', 'drop'].forEach(e => uploadArea.addEventListener(e, () => uploadArea.classList.remove('drag-over')));
358
+ uploadArea.addEventListener('drop', e => handleFile(e.dataTransfer.files[0]));
359
+ removeFileBtn.addEventListener('click', resetUploader);
 
 
 
 
 
 
360
 
361
+ // --- Form & Submission ---
362
+ promptInput.addEventListener('input', checkFormState);
 
 
 
 
 
 
 
 
 
 
 
 
363
  function checkFormState() {
364
  submitBtn.disabled = !uploadedFile || promptInput.value.trim() === '';
365
  }
366
 
367
+ submitBtn.addEventListener('click', async () => {
368
  if (submitBtn.disabled) return;
369
  setLoading(true);
370
  const formData = new FormData();
 
372
  formData.append('prompt', promptInput.value.trim());
373
  try {
374
  const response = await fetch(API_URL, { method: 'POST', body: formData });
375
+ if (!response.ok) {
376
+ const errorData = await response.json().catch(() => ({ error: `خطای سرور: ${response.statusText}` }));
377
+ throw new Error(errorData.error || `یک خطای ناشناخته رخ داد.`);
378
+ }
379
  const result = await response.json();
380
+ if (result.image_urls && Array.isArray(result.image_urls) && result.image_urls.length > 0) {
381
  displayResult(result.image_urls);
382
+ addToHistory({ prompt: promptInput.value.trim(), urls: result.image_urls });
383
  } else {
384
  throw new Error('پاسخ معتبری از سرور دریافت نشد.');
385
  }
 
388
  } finally {
389
  setLoading(false);
390
  }
391
+ });
392
 
393
+ // --- UI State Management ---
394
  function setLoading(isLoading) {
395
  if (isLoading) {
396
+ resultContainer.classList.remove('has-content');
397
  resultContainer.classList.add('loading');
398
+ btnSpinner.style.display = 'inline-block';
399
+ btnText.textContent = 'در حال پردازش...';
 
 
 
 
 
400
  submitBtn.disabled = true;
401
  resultGrid.innerHTML = '';
402
  errorMessage.style.display = 'none';
403
  } else {
404
  resultContainer.classList.remove('loading');
405
+ btnSpinner.style.display = 'none';
406
+ btnText.textContent = 'خلق کن';
 
407
  checkFormState();
408
  }
409
  }
 
410
  function displayResult(imageUrls) {
411
  resultGrid.innerHTML = '';
412
+ imageUrls.forEach((url) => {
413
  const img = document.createElement('img');
414
  img.src = url;
415
  img.alt = 'تصویر ویرایش شده';
416
+ img.addEventListener('click', () => openLightbox(url));
 
 
417
  resultGrid.appendChild(img);
418
  });
419
+ resultContainer.classList.add('has-content');
420
  }
 
421
  function clearResult() {
422
  resultGrid.innerHTML = '';
423
  errorMessage.style.display = 'none';
424
+ resultContainer.classList.remove('has-content');
425
  }
426
  function displayError(message) {
427
  errorMessage.textContent = message;
428
  errorMessage.style.display = 'block';
429
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
 
431
+ // --- Lightbox Functions ---
432
+ function openLightbox(url) {
 
 
 
 
 
 
 
 
 
433
  lightboxImg.src = url;
434
+ lightboxDownload.href = url;
435
+ lightbox.classList.add('visible');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
  }
437
+ function closeLightbox() { lightbox.classList.remove('visible'); }
438
+ lightboxClose.addEventListener('click', closeLightbox);
439
+ lightbox.addEventListener('click', (e) => { if (e.target === lightbox) closeLightbox(); });
440
+
441
+ // --- History Management ---
442
+ const getHistory = () => JSON.parse(localStorage.getItem('aiPhotoshopHistory') || '[]');
443
+ const saveHistory = (history) => {
444
+ localStorage.setItem('aiPhotoshopHistory', JSON.stringify(history));
445
  renderHistory();
446
+ };
447
+ const addToHistory = (item) => {
448
  let history = getHistory();
449
  history.unshift(item);
450
+ if (history.length > 15) history.pop(); // Limit history size
451
  saveHistory(history);
452
+ };
453
+ const handleClearHistory = () => {
 
 
 
 
454
  if (confirm('آیا از پاک کردن تمام تاریخچه مطمئن هستید؟')) {
455
+ localStorage.removeItem('aiPhotoshopHistory');
456
  renderHistory();
457
  }
458
+ };
 
459
  function renderHistory() {
460
  const history = getHistory();
461
  historyGrid.innerHTML = '';
462
  clearHistoryBtn.style.display = history.length > 0 ? 'flex' : 'none';
463
 
464
+ history.forEach(item => {
465
  const card = document.createElement('div');
466
  card.className = 'history-item';
467
+ const imagesHTML = item.urls.map(url =>
468
+ `<img src="${url}" alt="تصویر از تاریخچه" onclick="openLightbox('${url}')">`
469
+ ).join('');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
 
471
+ card.innerHTML = `
472
+ <p class="history-item-prompt">${item.prompt}</p>
473
+ <div class="history-item-grid">${imagesHTML}</div>
474
+ `;
 
 
 
 
 
 
475
  historyGrid.appendChild(card);
476
  });
477
  }
478
+ clearHistoryBtn.addEventListener('click', handleClearHistory);
479
+ document.addEventListener('DOMContentLoaded', renderHistory);
480
+
481
  checkFormState();
482
  </script>
483
  </body>