Elias207 commited on
Commit
cb8089b
·
verified ·
1 Parent(s): 78344c2

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +221 -264
index.html CHANGED
@@ -48,18 +48,14 @@
48
  }
49
  .container { max-width: 820px; width: 100%; }
50
 
51
- /* --- START: Header Animation Styles --- */
52
  header {
 
53
  text-align: center;
54
  margin-bottom: 2.5rem;
 
55
  animation: fadeIn 0.8s 0.1s ease-out backwards;
56
- position: relative; /* Needed for positioning the canvas */
57
- padding: 2.5rem 1rem; /* Add padding for animation space */
58
- background-color: var(--panel-bg);
59
- border-radius: var(--radius-card);
60
- overflow: hidden; /* Keep animation inside the header */
61
- border: 1px solid var(--panel-border);
62
- box-shadow: var(--shadow-lg);
63
  }
64
  #neural-network-canvas {
65
  position: absolute;
@@ -67,13 +63,13 @@
67
  left: 0;
68
  width: 100%;
69
  height: 100%;
70
- z-index: 0; /* Place canvas behind the text */
71
  }
72
- header h1, header .subtitle {
73
- position: relative; /* Ensure text is on top of the canvas */
74
- z-index: 1;
75
  }
76
- /* --- END: Header Animation Styles --- */
77
 
78
  h1 {
79
  font-size: 2.8rem;
@@ -165,7 +161,6 @@
165
  .text-overlay { position: absolute; top: 45%; left: 50%; transform: translate(-50%, -50%); font-size: 24px; font-weight: 700; text-shadow: 0 0 20px rgba(56, 189, 248, 0.8), 0 0 30px rgba(187, 134, 252, 0.5); animation: glow-text 7s infinite ease-in-out; }
166
  @keyframes glow-text { 0% { opacity: 0.7; text-shadow: 0 0 20px rgba(56, 189, 248, 0.8); } 50% { opacity: 1; text-shadow: 0 0 30px rgba(56, 189, 248, 1), 0 0 40px rgba(187, 134, 252, 0.7); } 100% { opacity: 0.7; text-shadow: 0 0 20px rgba(56, 189, 248, 0.8); } }
167
  .progress-bar { position: absolute; bottom: 0; left: 0; width: 0%; height: 6px; background: linear-gradient(to right, #38bdf8, #bb86fc, #facc15); animation: progress 7s infinite linear; }
168
- @keyframes progress { 0% { width: 0%; } 100% { width: 100%; } }
169
  /* --- END: AI Animation Styles --- */
170
 
171
  #result-grid { display: none; grid-template-columns: repeat(2, 1fr); gap: 1.5rem; width: 100%; }
@@ -239,7 +234,9 @@
239
  <body>
240
  <div class="container">
241
  <header>
 
242
  <canvas id="neural-network-canvas"></canvas>
 
243
  <h1>فتوشاپ هوش مصنوعی ✨</h1>
244
  <p class="subtitle">تصاویر خود را با قدرت هوش مصنوعی و به زبان ساده ویرایش کنید</p>
245
  </header>
@@ -341,77 +338,222 @@
341
  <!-- END: Custom Confirmation Modal HTML -->
342
 
343
  <script>
344
- document.addEventListener('DOMContentLoaded', () => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
 
346
- // --- START: Neural Network Header Animation ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  const canvas = document.getElementById('neural-network-canvas');
348
- const headerElement = document.querySelector('header');
349
  const ctx = canvas.getContext('2d');
350
- let particlesArray;
351
 
352
- const style = getComputedStyle(document.documentElement);
353
- const primaryColor = style.getPropertyValue('--accent-primary').trim();
354
- const secondaryColor = style.getPropertyValue('--accent-secondary').trim();
 
 
 
 
 
355
 
356
  function resizeCanvas() {
357
- canvas.width = headerElement.offsetWidth;
358
- canvas.height = headerElement.offsetHeight;
 
359
  }
360
 
361
  class Particle {
362
- constructor(x, y, directionX, directionY, size, color) {
363
- this.x = x; this.y = y; this.directionX = directionX;
364
- this.directionY = directionY; this.size = size; this.color = color;
 
 
 
365
  }
 
 
 
 
 
 
 
 
 
366
  draw() {
367
  ctx.beginPath();
368
- ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2, false);
369
- ctx.fillStyle = this.color;
370
  ctx.fill();
371
  }
372
- update() {
373
- if (this.x > canvas.width || this.x < 0) this.directionX = -this.directionX;
374
- if (this.y > canvas.height || this.y < 0) this.directionY = -this.directionY;
375
- this.x += this.directionX;
376
- this.y += this.directionY;
377
- this.draw();
378
- }
379
  }
380
 
381
  function init() {
382
- resizeCanvas();
383
- particlesArray = [];
384
- let numberOfParticles = (canvas.height * canvas.width) / 9000;
385
- for (let i = 0; i < numberOfParticles; i++) {
386
- let size = (Math.random() * 2) + 1;
387
- let x = (Math.random() * ((innerWidth - size * 2) - (size * 2)) + size * 2);
388
- let y = (Math.random() * ((innerHeight - size * 2) - (size * 2)) + size * 2);
389
- let directionX = (Math.random() * .5) - .25;
390
- let directionY = (Math.random() * .5) - .25;
391
- let color = Math.random() > 0.5 ? primaryColor : secondaryColor;
392
- particlesArray.push(new Particle(x, y, directionX, directionY, size, color));
393
  }
394
  }
395
-
396
- function connect() {
397
- let opacityValue = 1;
398
- for (let a = 0; a < particlesArray.length; a++) {
399
- for (let b = a; b < particlesArray.length; b++) {
400
- let distance = ((particlesArray[a].x - particlesArray[b].x) * (particlesArray[a].x - particlesArray[b].x)) +
401
- ((particlesArray[a].y - particlesArray[b].y) * (particlesArray[a].y - particlesArray[b].y));
402
- if (distance < (canvas.width / 7) * (canvas.height / 7)) {
403
- opacityValue = 1 - (distance / 20000);
404
- let dx = particlesArray[a].x - particlesArray[b].x;
405
- let dy = particlesArray[a].y - particlesArray[b].y;
406
- let gradient = ctx.createLinearGradient(particlesArray[a].x, particlesArray[a].y, particlesArray[b].x, particlesArray[b].y);
407
- gradient.addColorStop(0, particlesArray[a].color);
408
- gradient.addColorStop(1, particlesArray[b].color);
409
- ctx.strokeStyle = gradient;
410
- ctx.lineWidth = 1;
411
- ctx.globalAlpha = opacityValue;
412
  ctx.beginPath();
413
- ctx.moveTo(particlesArray[a].x, particlesArray[a].y);
414
- ctx.lineTo(particlesArray[b].x, particlesArray[b].y);
 
 
 
415
  ctx.stroke();
416
  }
417
  }
@@ -420,210 +562,25 @@
420
  }
421
 
422
  function animate() {
 
 
 
 
 
 
 
 
 
423
  requestAnimationFrame(animate);
424
- ctx.clearRect(0, 0, innerWidth, innerHeight);
425
- for (let i = 0; i < particlesArray.length; i++) {
426
- particlesArray[i].update();
427
- }
428
- connect();
429
  }
430
-
431
- window.addEventListener('resize', () => {
432
- clearTimeout(window.resizedFinished);
433
- window.resizedFinished = setTimeout(function(){
434
- init();
435
- }, 250);
436
- });
437
-
438
- init();
439
- animate();
440
- // --- END: Neural Network Header Animation ---
441
-
442
- // --- 1. ELEMENT SELECTORS ---
443
- const API_URL = 'https://alfa-editor-worker.onrender.com/api/edit';
444
- const uploadArea = document.getElementById('upload-area');
445
- const fileInput = document.getElementById('file-input');
446
- const previewImage = document.getElementById('preview-image-main');
447
- const removeFileBtn = document.getElementById('remove-file-btn-main');
448
- const promptInput = document.getElementById('prompt-input');
449
- const submitBtn = document.getElementById('submit-btn');
450
- const resultContainer = document.getElementById('result-container');
451
- const resultGrid = document.getElementById('result-grid');
452
- const errorMessage = document.getElementById('error-message');
453
- const lightbox = document.getElementById('lightbox');
454
- const lightboxImg = document.getElementById('lightbox-img');
455
- const lightboxClose = document.getElementById('lightbox-close');
456
- const lightboxDownload = document.getElementById('lightbox-download');
457
- const lightboxNext = document.getElementById('lightbox-next');
458
- const historyGrid = document.getElementById('history-grid');
459
- const clearHistoryBtn = document.getElementById('clear-history-btn');
460
- // New selectors for confirmation modal
461
- const confirmationModal = document.getElementById('confirmation-modal');
462
- const modalMessageText = document.getElementById('modal-message-text');
463
- const modalConfirmBtn = document.getElementById('modal-confirm-btn');
464
- const modalCancelBtn = document.getElementById('modal-cancel-btn');
465
-
466
- // --- 2. STATE VARIABLES ---
467
- let uploadedFile = null;
468
- let currentLightboxUrl = null;
469
- let currentLightboxGroup = [];
470
- let currentLightboxIndex = 0;
471
-
472
- // --- 3. FUNCTION DEFINITIONS ---
473
-
474
- // --- START: Custom Confirmation Modal Functions ---
475
- const showConfirmationModal = (message, onConfirm) => {
476
- modalMessageText.textContent = message;
477
- confirmationModal.classList.add('visible');
478
 
479
- modalConfirmBtn.onclick = () => {
480
- onConfirm();
481
- hideConfirmationModal();
482
- };
483
-
484
- modalCancelBtn.onclick = () => {
485
- hideConfirmationModal();
486
- };
487
- };
488
-
489
- const hideConfirmationModal = () => {
490
- confirmationModal.classList.remove('visible');
491
- modalConfirmBtn.onclick = null; // Clean up listeners
492
- modalCancelBtn.onclick = null;
493
- };
494
- // --- END: Custom Confirmation Modal Functions ---
495
-
496
- const resetUploader = () => { uploadedFile = null; fileInput.value = ''; previewImage.src = ''; uploadArea.classList.remove('has-file'); checkFormState(); };
497
- const handleFile = (file) => {
498
- if (!file || !file.type.startsWith('image/')) { displayError('لطفا یک فایل تصویری معتبر انتخاب کنید.'); return; }
499
- uploadedFile = file;
500
- const reader = new FileReader();
501
- reader.onload = (e) => { previewImage.src = e.target.result; };
502
- reader.readAsDataURL(file);
503
- uploadArea.classList.add('has-file');
504
- checkFormState();
505
- clearResult();
506
- };
507
- const checkFormState = () => { submitBtn.disabled = !uploadedFile || promptInput.value.trim() === ''; };
508
- const setLoading = (isLoading) => {
509
- const btnSpinner = submitBtn.querySelector('.spinner');
510
- const btnText = document.getElementById('btn-text');
511
- const btnIcon = submitBtn.querySelector('svg');
512
- if (isLoading) {
513
- resultContainer.classList.remove('has-content'); resultContainer.classList.add('loading');
514
- btnSpinner.style.display = 'inline-block'; btnIcon.style.display = 'none';
515
- btnText.textContent = 'در حال پردازش...'; submitBtn.disabled = true; resultGrid.innerHTML = ''; errorMessage.style.display = 'none';
516
- } else {
517
- resultContainer.classList.remove('loading'); btnSpinner.style.display = 'none'; btnIcon.style.display = 'inline-block';
518
- btnText.textContent = 'خلق کن'; checkFormState();
519
- }
520
- };
521
- const displayResult = (imageUrls) => {
522
- resultGrid.innerHTML = '';
523
- imageUrls.forEach((url) => {
524
- const img = document.createElement('img'); img.src = url; img.alt = 'تصویر ویرایش شده';
525
- img.addEventListener('click', () => openLightbox(url, imageUrls));
526
- resultGrid.appendChild(img);
527
- });
528
- resultContainer.classList.add('has-content');
529
- };
530
- const clearResult = () => { resultGrid.innerHTML = ''; errorMessage.style.display = 'none'; resultContainer.classList.remove('has-content'); };
531
- const displayError = (message) => { errorMessage.textContent = message; errorMessage.style.display = 'block'; };
532
- const updateLightboxImage = () => { const newUrl = currentLightboxGroup[currentLightboxIndex]; lightboxImg.src = newUrl; currentLightboxUrl = newUrl; };
533
- const openLightbox = (clickedUrl, urlGroup) => {
534
- currentLightboxGroup = urlGroup; currentLightboxIndex = urlGroup.indexOf(clickedUrl);
535
- updateLightboxImage(); lightboxNext.style.display = urlGroup.length > 1 ? 'flex' : 'none'; lightbox.classList.add('visible');
536
- };
537
- const closeLightbox = () => { lightbox.classList.remove('visible'); currentLightboxGroup = []; };
538
- const handleDownload = async () => {
539
- if (!currentLightboxUrl) return;
540
- const spinner = lightboxDownload.querySelector('.spinner'); const icon = lightboxDownload.querySelector('svg');
541
- spinner.style.display = 'block'; icon.style.display = 'none'; lightboxDownload.disabled = true;
542
- try {
543
- const response = await fetch(currentLightboxUrl);
544
- if (!response.ok) throw new Error('Network response was not ok.');
545
- const blob = await response.blob(); const objectUrl = URL.createObjectURL(blob);
546
- const a = document.createElement('a'); a.href = objectUrl; a.download = `ai-edited-image-${Date.now()}.png`;
547
- document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(objectUrl);
548
- } catch (e) { console.error('Download failed:', e); alert('خطا در دانلود تصویر. لطفا دوباره تلاش کنید.'); }
549
- finally { spinner.style.display = 'none'; icon.style.display = 'block'; lightboxDownload.disabled = false; }
550
- };
551
- const getHistory = () => JSON.parse(localStorage.getItem('aiPhotoshopHistory') || '[]');
552
- const saveHistory = (history) => { localStorage.setItem('aiPhotoshopHistory', JSON.stringify(history)); renderHistory(); };
553
- const addToHistory = (item) => { let history = getHistory(); history.unshift(item); if (history.length > 15) history.pop(); saveHistory(history); };
554
-
555
- const handleClearHistory = () => {
556
- showConfirmationModal(
557
- 'آیا از پاک کردن تمام تاریخچه مطمئن هستید؟ این عمل غیرقابل بازگشت است.',
558
- () => { saveHistory([]); }
559
- );
560
- };
561
 
562
- const handleDeleteItem = (index) => {
563
- showConfirmationModal(
564
- 'آیا از حذف این مورد از تاریخچه مطمئن هستید؟',
565
- () => {
566
- let history = getHistory();
567
- history.splice(index, 1);
568
- saveHistory(history);
569
- }
570
- );
571
- };
572
-
573
- const renderHistory = () => {
574
- const history = getHistory(); historyGrid.innerHTML = '';
575
- clearHistoryBtn.style.display = history.length > 0 ? 'flex' : 'none';
576
- history.forEach((item, index) => {
577
- const card = document.createElement('div'); card.className = 'history-item';
578
- const deleteBtn = document.createElement('button'); deleteBtn.className = 'history-delete-btn';
579
- deleteBtn.title = 'حذف این مورد';
580
- deleteBtn.innerHTML = `<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>`;
581
- deleteBtn.onclick = () => handleDeleteItem(index);
582
- card.appendChild(deleteBtn);
583
- const imageGrid = document.createElement('div'); imageGrid.className = 'history-item-grid';
584
- item.urls.forEach(url => {
585
- const img = document.createElement('img'); img.src = url; img.alt = 'تصویر از تاریخچه';
586
- img.addEventListener('click', () => openLightbox(url, item.urls));
587
- imageGrid.appendChild(img);
588
- });
589
- card.appendChild(imageGrid); historyGrid.appendChild(card);
590
- });
591
- };
592
-
593
- // --- 4. EVENT LISTENERS ---
594
- renderHistory();
595
- checkFormState();
596
- removeFileBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); resetUploader(); });
597
- fileInput.addEventListener('change', (e) => handleFile(e.target.files[0]));
598
- ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(e => { uploadArea.addEventListener(e, p => { p.preventDefault(); p.stopPropagation(); }); });
599
- ['dragenter', 'dragover'].forEach(e => { uploadArea.addEventListener(e, () => { if (!uploadArea.classList.contains('has-file')) uploadArea.classList.add('drag-over'); }); });
600
- ['dragleave', 'drop'].forEach(e => { uploadArea.addEventListener(e, () => uploadArea.classList.remove('drag-over')); });
601
- uploadArea.addEventListener('drop', e => { if (!uploadArea.classList.contains('has-file')) handleFile(e.dataTransfer.files[0]); });
602
- promptInput.addEventListener('input', checkFormState);
603
- submitBtn.addEventListener('click', async () => {
604
- if (submitBtn.disabled) return;
605
- setLoading(true);
606
- const formData = new FormData(); formData.append('image', uploadedFile); formData.append('prompt', promptInput.value.trim());
607
- try {
608
- const response = await fetch(API_URL, { method: 'POST', body: formData });
609
- if (!response.ok) {
610
- const errorData = await response.json().catch(() => ({ error: `خطای سرور: ${response.statusText}` }));
611
- throw new Error(errorData.error || `یک خطای ناشناخته رخ داد.`);
612
- }
613
- const result = await response.json();
614
- if (result.image_urls && Array.isArray(result.image_urls) && result.image_urls.length > 0) {
615
- displayResult(result.image_urls);
616
- addToHistory({ prompt: promptInput.value.trim(), urls: result.image_urls });
617
- } else { throw new Error('پاسخ معتبری از سرور دریافت نشد.'); }
618
- } catch (error) { displayError(error.message); }
619
- finally { setLoading(false); }
620
- });
621
- lightboxClose.addEventListener('click', closeLightbox);
622
- lightbox.addEventListener('click', (e) => { if (e.target === lightbox) closeLightbox(); });
623
- lightboxDownload.addEventListener('click', handleDownload);
624
- lightboxNext.addEventListener('click', () => { currentLightboxIndex = (currentLightboxIndex + 1) % currentLightboxGroup.length; updateLightboxImage(); });
625
- clearHistoryBtn.addEventListener('click', handleClearHistory);
626
  });
627
  </script>
 
628
  </body>
629
  </html>
 
48
  }
49
  .container { max-width: 820px; width: 100%; }
50
 
51
+ /* START: Changes for Header Animation */
52
  header {
53
+ position: relative; /* Required for positioning the canvas */
54
  text-align: center;
55
  margin-bottom: 2.5rem;
56
+ padding: 2rem 0; /* Add some padding for the animation to breathe */
57
  animation: fadeIn 0.8s 0.1s ease-out backwards;
58
+ overflow: hidden; /* Keeps particles inside the header */
 
 
 
 
 
 
59
  }
60
  #neural-network-canvas {
61
  position: absolute;
 
63
  left: 0;
64
  width: 100%;
65
  height: 100%;
66
+ z-index: 1; /* Behind the text */
67
  }
68
+ h1, .subtitle {
69
+ position: relative; /* To ensure they stay on top of the canvas */
70
+ z-index: 2;
71
  }
72
+ /* END: Changes for Header Animation */
73
 
74
  h1 {
75
  font-size: 2.8rem;
 
161
  .text-overlay { position: absolute; top: 45%; left: 50%; transform: translate(-50%, -50%); font-size: 24px; font-weight: 700; text-shadow: 0 0 20px rgba(56, 189, 248, 0.8), 0 0 30px rgba(187, 134, 252, 0.5); animation: glow-text 7s infinite ease-in-out; }
162
  @keyframes glow-text { 0% { opacity: 0.7; text-shadow: 0 0 20px rgba(56, 189, 248, 0.8); } 50% { opacity: 1; text-shadow: 0 0 30px rgba(56, 189, 248, 1), 0 0 40px rgba(187, 134, 252, 0.7); } 100% { opacity: 0.7; text-shadow: 0 0 20px rgba(56, 189, 248, 0.8); } }
163
  .progress-bar { position: absolute; bottom: 0; left: 0; width: 0%; height: 6px; background: linear-gradient(to right, #38bdf8, #bb86fc, #facc15); animation: progress 7s infinite linear; }
 
164
  /* --- END: AI Animation Styles --- */
165
 
166
  #result-grid { display: none; grid-template-columns: repeat(2, 1fr); gap: 1.5rem; width: 100%; }
 
234
  <body>
235
  <div class="container">
236
  <header>
237
+ <!-- START: Added Canvas for Animation -->
238
  <canvas id="neural-network-canvas"></canvas>
239
+ <!-- END: Added Canvas for Animation -->
240
  <h1>فتوشاپ هوش مصنوعی ✨</h1>
241
  <p class="subtitle">تصاویر خود را با قدرت هوش مصنوعی و به زبان ساده ویرایش کنید</p>
242
  </header>
 
338
  <!-- END: Custom Confirmation Modal HTML -->
339
 
340
  <script>
341
+ // --- 1. ELEMENT SELECTORS ---
342
+ const API_URL = 'https://alfa-editor-worker.onrender.com/api/edit';
343
+ const uploadArea = document.getElementById('upload-area');
344
+ const fileInput = document.getElementById('file-input');
345
+ const previewImage = document.getElementById('preview-image-main');
346
+ const removeFileBtn = document.getElementById('remove-file-btn-main');
347
+ const promptInput = document.getElementById('prompt-input');
348
+ const submitBtn = document.getElementById('submit-btn');
349
+ const resultContainer = document.getElementById('result-container');
350
+ const resultGrid = document.getElementById('result-grid');
351
+ const errorMessage = document.getElementById('error-message');
352
+ const lightbox = document.getElementById('lightbox');
353
+ const lightboxImg = document.getElementById('lightbox-img');
354
+ const lightboxClose = document.getElementById('lightbox-close');
355
+ const lightboxDownload = document.getElementById('lightbox-download');
356
+ const lightboxNext = document.getElementById('lightbox-next');
357
+ const historyGrid = document.getElementById('history-grid');
358
+ const clearHistoryBtn = document.getElementById('clear-history-btn');
359
+ const confirmationModal = document.getElementById('confirmation-modal');
360
+ const modalMessageText = document.getElementById('modal-message-text');
361
+ const modalConfirmBtn = document.getElementById('modal-confirm-btn');
362
+ const modalCancelBtn = document.getElementById('modal-cancel-btn');
363
 
364
+ // --- 2. STATE VARIABLES ---
365
+ let uploadedFile = null;
366
+ let currentLightboxUrl = null;
367
+ let currentLightboxGroup = [];
368
+ let currentLightboxIndex = 0;
369
+
370
+ // --- 3. FUNCTION DEFINITIONS ---
371
+
372
+ const showConfirmationModal = (message, onConfirm) => { /* ... (rest of the code is unchanged) ... */ };
373
+ const hideConfirmationModal = () => { /* ... */ };
374
+ const resetUploader = () => { uploadedFile = null; fileInput.value = ''; previewImage.src = ''; uploadArea.classList.remove('has-file'); checkFormState(); };
375
+ const handleFile = (file) => {
376
+ if (!file || !file.type.startsWith('image/')) { displayError('لطفا یک فایل تصویری معتبر انتخاب کنید.'); return; }
377
+ uploadedFile = file;
378
+ const reader = new FileReader();
379
+ reader.onload = (e) => { previewImage.src = e.target.result; };
380
+ reader.readAsDataURL(file);
381
+ uploadArea.classList.add('has-file');
382
+ checkFormState();
383
+ clearResult();
384
+ };
385
+ const checkFormState = () => { submitBtn.disabled = !uploadedFile || promptInput.value.trim() === ''; };
386
+ const setLoading = (isLoading) => {
387
+ const btnSpinner = submitBtn.querySelector('.spinner');
388
+ const btnText = document.getElementById('btn-text');
389
+ const btnIcon = submitBtn.querySelector('svg');
390
+ if (isLoading) {
391
+ resultContainer.classList.remove('has-content'); resultContainer.classList.add('loading');
392
+ btnSpinner.style.display = 'inline-block'; btnIcon.style.display = 'none';
393
+ btnText.textContent = 'در حال پردازش...'; submitBtn.disabled = true; resultGrid.innerHTML = ''; errorMessage.style.display = 'none';
394
+ } else {
395
+ resultContainer.classList.remove('loading'); btnSpinner.style.display = 'none'; btnIcon.style.display = 'inline-block';
396
+ btnText.textContent = 'خلق کن'; checkFormState();
397
+ }
398
+ };
399
+ const displayResult = (imageUrls) => {
400
+ resultGrid.innerHTML = '';
401
+ imageUrls.forEach((url) => {
402
+ const img = document.createElement('img'); img.src = url; img.alt = 'تصویر ویرایش شده';
403
+ img.addEventListener('click', () => openLightbox(url, imageUrls));
404
+ resultGrid.appendChild(img);
405
+ });
406
+ resultContainer.classList.add('has-content');
407
+ };
408
+ const clearResult = () => { resultGrid.innerHTML = ''; errorMessage.style.display = 'none'; resultContainer.classList.remove('has-content'); };
409
+ const displayError = (message) => { errorMessage.textContent = message; errorMessage.style.display = 'block'; };
410
+ const updateLightboxImage = () => { const newUrl = currentLightboxGroup[currentLightboxIndex]; lightboxImg.src = newUrl; currentLightboxUrl = newUrl; };
411
+ const openLightbox = (clickedUrl, urlGroup) => {
412
+ currentLightboxGroup = urlGroup; currentLightboxIndex = urlGroup.indexOf(clickedUrl);
413
+ updateLightboxImage(); lightboxNext.style.display = urlGroup.length > 1 ? 'flex' : 'none'; lightbox.classList.add('visible');
414
+ };
415
+ const closeLightbox = () => { lightbox.classList.remove('visible'); currentLightboxGroup = []; };
416
+ const handleDownload = async () => {
417
+ if (!currentLightboxUrl) return;
418
+ const spinner = lightboxDownload.querySelector('.spinner'); const icon = lightboxDownload.querySelector('svg');
419
+ spinner.style.display = 'block'; icon.style.display = 'none'; lightboxDownload.disabled = true;
420
+ try {
421
+ const response = await fetch(currentLightboxUrl);
422
+ if (!response.ok) throw new Error('Network response was not ok.');
423
+ const blob = await response.blob(); const objectUrl = URL.createObjectURL(blob);
424
+ const a = document.createElement('a'); a.href = objectUrl; a.download = `ai-edited-image-${Date.now()}.png`;
425
+ document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(objectUrl);
426
+ } catch (e) { console.error('Download failed:', e); alert('خطا در دانلود تصویر. لطفا دوباره تلاش کنید.'); }
427
+ finally { spinner.style.display = 'none'; icon.style.display = 'block'; lightboxDownload.disabled = false; }
428
+ };
429
+ const getHistory = () => JSON.parse(localStorage.getItem('aiPhotoshopHistory') || '[]');
430
+ const saveHistory = (history) => { localStorage.setItem('aiPhotoshopHistory', JSON.stringify(history)); renderHistory(); };
431
+ const addToHistory = (item) => { let history = getHistory(); history.unshift(item); if (history.length > 15) history.pop(); saveHistory(history); };
432
+
433
+ const handleClearHistory = () => { showConfirmationModal( 'آیا از پاک کردن تمام تاریخچه مطمئن هستید؟ این عمل غیرقابل بازگشت است.', () => { saveHistory([]); }); };
434
+ const handleDeleteItem = (index) => { showConfirmationModal( 'آیا از حذف این مورد از تاریخچه مطمئن هستید؟', () => { let history = getHistory(); history.splice(index, 1); saveHistory(history); }); };
435
+ const renderHistory = () => {
436
+ const history = getHistory(); historyGrid.innerHTML = '';
437
+ clearHistoryBtn.style.display = history.length > 0 ? 'flex' : 'none';
438
+ history.forEach((item, index) => {
439
+ const card = document.createElement('div'); card.className = 'history-item';
440
+ const deleteBtn = document.createElement('button'); deleteBtn.className = 'history-delete-btn';
441
+ deleteBtn.title = 'حذف این مورد';
442
+ deleteBtn.innerHTML = `<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>`;
443
+ deleteBtn.onclick = () => handleDeleteItem(index);
444
+ card.appendChild(deleteBtn);
445
+ const imageGrid = document.createElement('div'); imageGrid.className = 'history-item-grid';
446
+ item.urls.forEach(url => {
447
+ const img = document.createElement('img'); img.src = url; img.alt = 'تصویر از تاریخچه';
448
+ img.addEventListener('click', () => openLightbox(url, item.urls));
449
+ imageGrid.appendChild(img);
450
+ });
451
+ card.appendChild(imageGrid); historyGrid.appendChild(card);
452
+ });
453
+ };
454
+
455
+ // --- 4. EVENT LISTENERS ---
456
+ document.addEventListener('DOMContentLoaded', () => { renderHistory(); checkFormState(); });
457
+ removeFileBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); resetUploader(); });
458
+ fileInput.addEventListener('change', (e) => handleFile(e.target.files[0]));
459
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(e => { uploadArea.addEventListener(e, p => { p.preventDefault(); p.stopPropagation(); }); });
460
+ ['dragenter', 'dragover'].forEach(e => { uploadArea.addEventListener(e, () => { if (!uploadArea.classList.contains('has-file')) uploadArea.classList.add('drag-over'); }); });
461
+ ['dragleave', 'drop'].forEach(e => { uploadArea.addEventListener(e, () => uploadArea.classList.remove('drag-over')); });
462
+ uploadArea.addEventListener('drop', e => { if (!uploadArea.classList.contains('has-file')) handleFile(e.dataTransfer.files[0]); });
463
+ promptInput.addEventListener('input', checkFormState);
464
+ submitBtn.addEventListener('click', async () => {
465
+ if (submitBtn.disabled) return;
466
+ setLoading(true);
467
+ const formData = new FormData(); formData.append('image', uploadedFile); formData.append('prompt', promptInput.value.trim());
468
+ try {
469
+ const response = await fetch(API_URL, { method: 'POST', body: formData });
470
+ if (!response.ok) {
471
+ const errorData = await response.json().catch(() => ({ error: `خطای سرور: ${response.statusText}` }));
472
+ throw new Error(errorData.error || `یک خطای ناشناخته رخ داد.`);
473
+ }
474
+ const result = await response.json();
475
+ if (result.image_urls && Array.isArray(result.image_urls) && result.image_urls.length > 0) {
476
+ displayResult(result.image_urls);
477
+ addToHistory({ prompt: promptInput.value.trim(), urls: result.image_urls });
478
+ } else { throw new Error('پاسخ معتبری از سرور دریافت نشد.'); }
479
+ } catch (error) { displayError(error.message); }
480
+ finally { setLoading(false); }
481
+ });
482
+ lightboxClose.addEventListener('click', closeLightbox);
483
+ lightbox.addEventListener('click', (e) => { if (e.target === lightbox) closeLightbox(); });
484
+ lightboxDownload.addEventListener('click', handleDownload);
485
+ lightboxNext.addEventListener('click', () => { currentLightboxIndex = (currentLightboxIndex + 1) % currentLightboxGroup.length; updateLightboxImage(); });
486
+ clearHistoryBtn.addEventListener('click', handleClearHistory);
487
+ </script>
488
+
489
+ <!-- START: Added JavaScript for Header Animation -->
490
+ <script>
491
+ document.addEventListener('DOMContentLoaded', () => {
492
  const canvas = document.getElementById('neural-network-canvas');
493
+ const header = canvas.parentElement;
494
  const ctx = canvas.getContext('2d');
 
495
 
496
+ let particles = [];
497
+ const particleCount = 60;
498
+ const maxDistance = 120;
499
+
500
+ // Get colors from CSS variables for consistency
501
+ const computedStyles = getComputedStyle(document.documentElement);
502
+ const particleColor = computedStyles.getPropertyValue('--accent-primary').trim();
503
+ const lineColor = computedStyles.getPropertyValue('--text-tertiary').trim();
504
 
505
  function resizeCanvas() {
506
+ canvas.width = header.clientWidth;
507
+ canvas.height = header.clientHeight;
508
+ init(); // Re-initialize particles on resize
509
  }
510
 
511
  class Particle {
512
+ constructor() {
513
+ this.x = Math.random() * canvas.width;
514
+ this.y = Math.random() * canvas.height;
515
+ this.vx = (Math.random() - 0.5) * 0.5;
516
+ this.vy = (Math.random() - 0.5) * 0.5;
517
+ this.radius = 1.5;
518
  }
519
+
520
+ update() {
521
+ this.x += this.vx;
522
+ this.y += this.vy;
523
+
524
+ if (this.x < 0 || this.x > canvas.width) this.vx *= -1;
525
+ if (this.y < 0 || this.y > canvas.height) this.vy *= -1;
526
+ }
527
+
528
  draw() {
529
  ctx.beginPath();
530
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
531
+ ctx.fillStyle = particleColor;
532
  ctx.fill();
533
  }
 
 
 
 
 
 
 
534
  }
535
 
536
  function init() {
537
+ particles = [];
538
+ for (let i = 0; i < particleCount; i++) {
539
+ particles.push(new Particle());
 
 
 
 
 
 
 
 
540
  }
541
  }
542
+
543
+ function connectParticles() {
544
+ for (let i = 0; i < particles.length; i++) {
545
+ for (let j = i + 1; j < particles.length; j++) {
546
+ const dx = particles[i].x - particles[j].x;
547
+ const dy = particles[i].y - particles[j].y;
548
+ const distance = Math.sqrt(dx * dx + dy * dy);
549
+
550
+ if (distance < maxDistance) {
 
 
 
 
 
 
 
 
551
  ctx.beginPath();
552
+ ctx.moveTo(particles[i].x, particles[i].y);
553
+ ctx.lineTo(particles[j].x, particles[j].y);
554
+ ctx.strokeStyle = lineColor;
555
+ ctx.lineWidth = 0.5;
556
+ ctx.globalAlpha = 1 - distance / maxDistance; // Fade out with distance
557
  ctx.stroke();
558
  }
559
  }
 
562
  }
563
 
564
  function animate() {
565
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
566
+
567
+ particles.forEach(particle => {
568
+ particle.update();
569
+ particle.draw();
570
+ });
571
+
572
+ connectParticles();
573
+
574
  requestAnimationFrame(animate);
 
 
 
 
 
575
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
 
577
+ window.addEventListener('resize', resizeCanvas);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
578
 
579
+ // Initial setup
580
+ resizeCanvas();
581
+ animate();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  });
583
  </script>
584
+ <!-- END: Added JavaScript for Header Animation -->
585
  </body>
586
  </html>