| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>PixelWizard AI Image Editor ✨</title> |
| <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> |
| <script src="https://unpkg.com/feather-icons"></script> |
| <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script> |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> |
| <style> |
| body { |
| font-family: 'Inter', sans-serif; |
| background-color: #111827; |
| color: #f3f4f6; |
| } |
| .dropzone { |
| border: 2px dashed #06b6d4; |
| border-radius: 0.5rem; |
| transition: all 0.3s ease; |
| } |
| .dropzone-active { |
| border-color: #0ea5e9; |
| background-color: rgba(6, 182, 212, 0.1); |
| } |
| .vanta-globe { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| z-index: -1; |
| opacity: 0.2; |
| } |
| .toast { |
| animation: slideUp 0.3s ease-out; |
| } |
| @keyframes slideUp { |
| from { transform: translateY(100%); opacity: 0; } |
| to { transform: translateY(0); opacity: 1; } |
| } |
| .image-mask { |
| position: relative; |
| } |
| .image-mask::after { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background-color: rgba(6, 182, 212, 0.3); |
| pointer-events: none; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="vanta-globe" id="vanta-globe"></div> |
| |
| <div class="min-h-screen flex flex-col"> |
| |
| <header class="bg-gray-900/80 backdrop-blur-md border-b border-gray-800 fixed w-full z-10"> |
| <div class="container mx-auto px-4 py-3 flex justify-between items-center"> |
| <h1 class="text-2xl font-bold text-cyan-400 flex items-center"> |
| <i data-feather="wand-2" class="mr-2"></i> |
| PixelWizard AI |
| </h1> |
| <a href="https://github.com" target="_blank" class="text-gray-400 hover:text-cyan-400 transition-colors"> |
| <i data-feather="github"></i> |
| </a> |
| </div> |
| </header> |
|
|
| |
| <main class="flex-grow container mx-auto px-4 pt-20 pb-8 flex flex-col lg:flex-row gap-6"> |
| |
| <div class="w-full lg:w-96 bg-gray-800/80 backdrop-blur-md rounded-xl border border-gray-700 p-6 flex flex-col"> |
| |
| <div class="space-y-6"> |
| |
| <div class="space-y-3"> |
| <h2 class="text-lg font-semibold text-gray-200">1. Upload Your Photo</h2> |
| <div id="dropzone" class="dropzone h-48 flex flex-col items-center justify-center p-4 cursor-pointer"> |
| <i data-feather="upload" class="text-cyan-400 w-8 h-8 mb-2"></i> |
| <p class="text-center text-gray-300">Drag & drop your image here</p> |
| <p class="text-xs text-gray-500 mt-1">or click to browse</p> |
| <input type="file" id="image-upload" class="hidden" accept="image/*"> |
| </div> |
| <button id="face-protect-btn" class="hidden w-full py-2 px-4 bg-gray-700 hover:bg-gray-600 text-cyan-400 rounded-lg transition-colors flex items-center justify-center"> |
| <i data-feather="shield" class="mr-2"></i> |
| Protect Faces (Manual) |
| </button> |
| <div id="face-protect-success" class="hidden text-green-400 text-sm flex items-center"> |
| <i data-feather="check-circle" class="mr-1"></i> |
| Face protection active |
| </div> |
| </div> |
|
|
| |
| <div id="reference-section" class="hidden space-y-3"> |
| <h2 class="text-lg font-semibold text-gray-200">2. Style Reference (Optional)</h2> |
| <div id="reference-dropzone" class="dropzone h-32 flex flex-col items-center justify-center p-4 cursor-pointer"> |
| <i data-feather="image" class="text-cyan-400 w-6 h-6 mb-2"></i> |
| <p class="text-center text-sm text-gray-300">Drag & drop reference image</p> |
| </div> |
| <input type="text" id="reference-name" placeholder="e.g., 'Mhomt shirt'" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-gray-200 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-cyan-500"> |
| </div> |
|
|
| |
| <div class="space-y-3"> |
| <h2 class="text-lg font-semibold text-gray-200">3. Describe Your Edits</h2> |
| <textarea id="edit-prompt" rows="4" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-gray-200 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-cyan-500" placeholder="e.g., 'Change the shirt color to blue and add a futuristic background'"></textarea> |
| </div> |
|
|
| |
| <div id="elements-section" class="hidden space-y-3"> |
| <h2 class="text-lg font-semibold text-gray-200">4. Elements to Edit (Optional)</h2> |
| <div id="analyze-prompt" class="p-4 bg-gray-700 rounded-lg text-center cursor-pointer hover:bg-gray-600 transition-colors"> |
| <p class="text-gray-300">Click on the original image to analyze elements</p> |
| </div> |
| <div id="analyzing" class="hidden p-4 bg-gray-700 rounded-lg text-center"> |
| <div class="flex items-center justify-center space-x-2"> |
| <div class="animate-spin"> |
| <i data-feather="loader"></i> |
| </div> |
| <p class="text-gray-300">Analyzing image...</p> |
| </div> |
| </div> |
| <div id="elements-list" class="hidden space-y-2"> |
| |
| </div> |
| <div id="mask-active" class="hidden text-cyan-400 text-sm flex items-center"> |
| <i data-feather="check-circle" class="mr-1"></i> |
| Precision mask is active |
| </div> |
| </div> |
|
|
| |
| <div class="space-y-3"> |
| <h2 class="text-lg font-semibold text-gray-200">5. Render Profile (Optional)</h2> |
| <select id="render-profile" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-gray-200 focus:outline-none focus:ring-2 focus:ring-cyan-500"> |
| <option value="default">Default</option> |
| <option value="iphone">iPhone 14 Pro</option> |
| <option value="pro">Professional Camera</option> |
| <option value="film">Film Look</option> |
| <option value="illustration">Illustration Style</option> |
| </select> |
| </div> |
| </div> |
|
|
| |
| <div class="mt-auto pt-6 space-y-3"> |
| <button id="optimize-prompt-btn" class="w-full py-3 px-4 border border-cyan-400 text-cyan-400 rounded-lg hover:bg-cyan-400/10 transition-colors flex items-center justify-center"> |
| <i data-feather="sparkles" class="mr-2"></i> |
| Optimize Prompt |
| </button> |
| <button id="apply-magic-btn" class="w-full py-3 px-4 bg-cyan-500 hover:bg-cyan-600 text-gray-900 font-medium rounded-lg transition-colors flex items-center justify-center"> |
| <i data-feather="zap" class="mr-2"></i> |
| Apply Magic |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div class="flex-grow bg-gray-800/80 backdrop-blur-md rounded-xl border border-gray-700 p-6 flex flex-col"> |
| <div class="flex-grow grid grid-cols-1 md:grid-cols-2 gap-4"> |
| |
| <div class="relative bg-gray-900 rounded-lg overflow-hidden"> |
| <div class="absolute top-3 left-3 z-10 flex space-x-2"> |
| <button class="p-2 bg-gray-800/80 rounded-full hover:bg-gray-700/80 transition-colors"> |
| <i data-feather="zoom-in" class="w-4 h-4 text-gray-300"></i> |
| </button> |
| <button class="p-2 bg-gray-800/80 rounded-full hover:bg-gray-700/80 transition-colors"> |
| <i data-feather="zoom-out" class="w-4 h-4 text-gray-300"></i> |
| </button> |
| <button class="p-2 bg-gray-800/80 rounded-full hover:bg-gray-700/80 transition-colors"> |
| <i data-feather="refresh-cw" class="w-4 h-4 text-gray-300"></i> |
| </button> |
| <button id="mask-toggle" class="p-2 bg-gray-800/80 rounded-full hover:bg-gray-700/80 transition-colors"> |
| <i data-feather="edit-3" class="w-4 h-4 text-gray-300"></i> |
| </button> |
| </div> |
| <div id="original-container" class="h-full flex items-center justify-center"> |
| <p class="text-gray-500">Original image will appear here</p> |
| </div> |
| <canvas id="mask-canvas" class="absolute inset-0 w-full h-full hidden"></canvas> |
| </div> |
|
|
| |
| <div class="relative bg-gray-900 rounded-lg overflow-hidden"> |
| <div id="edited-container" class="h-full flex items-center justify-center"> |
| <p class="text-gray-500">Edited image will appear here</p> |
| </div> |
| <div id="processing" class="hidden absolute inset-0 flex items-center justify-center bg-gray-900/80"> |
| <div class="text-center"> |
| <div class="animate-spin mx-auto mb-3"> |
| <i data-feather="loader" class="w-8 h-8 text-cyan-400"></i> |
| </div> |
| <p class="text-gray-300">Creating magic...</p> |
| <p class="text-xs text-gray-500">This may take a moment</p> |
| </div> |
| </div> |
| <button id="download-btn" class="hidden absolute bottom-3 right-3 p-2 bg-cyan-500 hover:bg-cyan-600 rounded-full transition-colors"> |
| <i data-feather="download" class="w-4 h-4 text-gray-900"></i> |
| </button> |
| </div> |
| </div> |
| </div> |
| </main> |
|
|
| |
| <div id="mask-tools" class="fixed inset-0 bg-gray-900/90 backdrop-blur-md z-20 hidden"> |
| <div class="absolute top-4 left-1/2 transform -translate-x-1/2 bg-gray-800 rounded-full px-4 py-2 flex space-x-3"> |
| <button id="brush-tool" class="p-2 bg-cyan-500/20 rounded-full hover:bg-cyan-500/30 transition-colors text-cyan-400"> |
| <i data-feather="edit-2" class="w-4 h-4"></i> |
| </button> |
| <button id="erase-tool" class="p-2 bg-gray-700 rounded-full hover:bg-gray-600 transition-colors text-gray-300"> |
| <i data-feather="eraser" class="w-4 h-4"></i> |
| </button> |
| <div class="flex items-center space-x-2"> |
| <i data-feather="minimize-2" class="w-4 h-4 text-gray-400"></i> |
| <input type="range" id="brush-size" min="1" max="50" value="10" class="w-24"> |
| <i data-feather="maximize-2" class="w-4 h-4 text-gray-400"></i> |
| </div> |
| <button id="clear-mask" class="p-2 bg-gray-700 rounded-full hover:bg-gray-600 transition-colors text-gray-300"> |
| <i data-feather="trash-2" class="w-4 h-4"></i> |
| </button> |
| <button id="save-mask" class="p-2 bg-cyan-500 rounded-full hover:bg-cyan-600 transition-colors text-gray-900"> |
| <i data-feather="check" class="w-4 h-4"></i> |
| </button> |
| <button id="cancel-mask" class="p-2 bg-gray-700 rounded-full hover:bg-gray-600 transition-colors text-gray-300"> |
| <i data-feather="x" class="w-4 h-4"></i> |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div id="toast" class="fixed bottom-4 left-4 hidden"> |
| <div class="bg-gray-800 border rounded-lg shadow-lg px-4 py-3 flex items-start max-w-xs"> |
| <div class="flex-grow"> |
| <div class="flex items-center"> |
| <i data-feather="alert-circle" class="text-red-400 mr-2"></i> |
| <p id="toast-message" class="text-sm text-gray-200">Error message here</p> |
| </div> |
| </div> |
| <button id="close-toast" class="ml-3 text-gray-400 hover:text-gray-300"> |
| <i data-feather="x" class="w-4 h-4"></i> |
| </button> |
| </div> |
| </div> |
| </div> |
| <script> |
| |
| function applyEdits() { |
| const originalImg = document.querySelector('#original-container img'); |
| const prompt = document.getElementById('edit-prompt').value.toLowerCase(); |
| |
| if (!originalImg) { |
| showToast('Please upload an image first', 'error'); |
| return false; |
| } |
| |
| const editedImg = originalImg.cloneNode(true); |
| let hasChanges = false; |
| |
| |
| if (prompt.includes('color') || prompt.includes('colour')) { |
| if (prompt.includes('blue')) { |
| editedImg.style.filter = 'hue-rotate(180deg)'; |
| hasChanges = true; |
| } else if (prompt.includes('red')) { |
| editedImg.style.filter = 'hue-rotate(-90deg)'; |
| hasChanges = true; |
| } else if (prompt.includes('green')) { |
| editedImg.style.filter = 'hue-rotate(90deg)'; |
| hasChanges = true; |
| } |
| } |
| |
| if (prompt.includes('brighten') || prompt.includes('lighten')) { |
| editedImg.style.filter = (editedImg.style.filter || '') + ' brightness(1.2)'; |
| hasChanges = true; |
| } |
| |
| if (prompt.includes('darken')) { |
| editedImg.style.filter = (editedImg.style.filter || '') + ' brightness(0.8)'; |
| hasChanges = true; |
| } |
| |
| if (prompt.includes('blur') || prompt.includes('background')) { |
| editedImg.style.filter = (editedImg.style.filter || '') + ' blur(4px)'; |
| hasChanges = true; |
| } |
| |
| if (!hasChanges) { |
| editedImg.style.transform = 'scaleX(-1)'; |
| } |
| |
| return editedImg; |
| } |
| |
| |
| VANTA.GLOBE({ |
| el: "#vanta-globe", |
| mouseControls: true, |
| touchControls: true, |
| gyroControls: false, |
| minHeight: 200.00, |
| minWidth: 200.00, |
| scale: 1.00, |
| scaleMobile: 1.00, |
| color: 0x06b6d4, |
| backgroundColor: 0x111827, |
| size: 0.8 |
| }); |
| |
| |
| document.addEventListener('DOMContentLoaded', function() { |
| feather.replace(); |
| }); |
| |
| |
| document.getElementById('image-upload').addEventListener('change', function(e) { |
| const file = e.target.files[0]; |
| if (file) { |
| const reader = new FileReader(); |
| reader.onload = function(event) { |
| const img = document.createElement('img'); |
| img.src = event.target.result; |
| img.className = 'max-w-full max-h-full'; |
| |
| const container = document.getElementById('original-container'); |
| container.innerHTML = ''; |
| container.appendChild(img); |
| |
| |
| document.getElementById('face-protect-btn').classList.remove('hidden'); |
| document.getElementById('reference-section').classList.remove('hidden'); |
| document.getElementById('elements-section').classList.remove('hidden'); |
| |
| |
| showToast('Image uploaded successfully!'); |
| }; |
| reader.readAsDataURL(file); |
| } |
| }); |
| |
| document.getElementById('dropzone').addEventListener('click', function() { |
| document.getElementById('image-upload').click(); |
| }); |
| |
| document.getElementById('face-protect-btn').addEventListener('click', function() { |
| this.classList.add('hidden'); |
| document.getElementById('face-protect-success').classList.remove('hidden'); |
| showToast('Face protection activated'); |
| }); |
| document.getElementById('apply-magic-btn').addEventListener('click', function() { |
| document.getElementById('processing').classList.remove('hidden'); |
| document.getElementById('edited-container').innerHTML = '<p class="text-gray-500">Edited image will appear here</p>'; |
| |
| setTimeout(function() { |
| document.getElementById('processing').classList.add('hidden'); |
| |
| const editedImg = applyEdits(); |
| if (editedImg) { |
| document.getElementById('edited-container').innerHTML = ''; |
| document.getElementById('edited-container').appendChild(editedImg); |
| document.getElementById('download-btn').classList.remove('hidden'); |
| showToast('Edits applied successfully!'); |
| } |
| }, 2000); |
| }); |
| document.getElementById('mask-toggle').addEventListener('click', function() { |
| document.getElementById('mask-tools').classList.remove('hidden'); |
| document.body.style.overflow = 'hidden'; |
| }); |
| |
| document.getElementById('cancel-mask').addEventListener('click', function() { |
| document.getElementById('mask-tools').classList.add('hidden'); |
| document.body.style.overflow = ''; |
| }); |
| |
| document.getElementById('save-mask').addEventListener('click', function() { |
| document.getElementById('mask-tools').classList.add('hidden'); |
| document.body.style.overflow = ''; |
| document.getElementById('elements-list').innerHTML = ''; |
| document.getElementById('mask-active').classList.remove('hidden'); |
| showToast('Precision mask saved'); |
| }); |
| |
| document.getElementById('download-btn').addEventListener('click', function() { |
| showToast('Download started'); |
| }); |
| document.getElementById('optimize-prompt-btn').addEventListener('click', function() { |
| const prompt = document.getElementById('edit-prompt').value; |
| if (prompt.trim() === '') { |
| showToast('Please enter a prompt first', 'error'); |
| return; |
| } |
| |
| showToast('Optimizing your prompt...'); |
| setTimeout(function() { |
| let optimized = `Professional edit: ${prompt}. `; |
| |
| if (prompt.toLowerCase().includes('color') || prompt.toLowerCase().includes('colour')) { |
| optimized += 'Using precise color adjustments. '; |
| } |
| if (prompt.toLowerCase().includes('background')) { |
| optimized += 'With enhanced background separation. '; |
| } |
| if (prompt.toLowerCase().includes('face') || prompt.toLowerCase().includes('portrait')) { |
| optimized += 'Maintaining natural skin tones. '; |
| } |
| |
| optimized += 'Preserving original composition and lighting.'; |
| document.getElementById('edit-prompt').value = optimized; |
| showToast('Prompt optimized!'); |
| }, 1500); |
| }); |
| |
| function showToast(message, type = 'success') { |
| const toast = document.getElementById('toast'); |
| const toastMessage = document.getElementById('toast-message'); |
| const toastIcon = document.querySelector('#toast i'); |
| |
| toastMessage.textContent = message; |
| |
| if (type === 'error') { |
| toastIcon.setAttribute('data-feather', 'alert-triangle'); |
| toastIcon.classList.remove('text-green-400', 'text-yellow-400'); |
| toastIcon.classList.add('text-red-400'); |
| toast.classList.remove('border-green-500'); |
| toast.classList.add('border-red-500'); |
| } else if (type === 'warning') { |
| toastIcon.setAttribute('data-feather', 'alert-circle'); |
| toastIcon.classList.remove('text-green-400', 'text-red-400'); |
| toastIcon.classList.add('text-yellow-400'); |
| toast.classList.remove('border-green-500', 'border-red-500'); |
| toast.classList.add('border-yellow-500'); |
| } else { |
| toastIcon.setAttribute('data-feather', 'check-circle'); |
| toastIcon.classList.remove('text-red-400', 'text-yellow-400'); |
| toastIcon.classList.add('text-green-400'); |
| toast.classList.remove('border-red-500', 'border-yellow-500'); |
| toast.classList.add('border-green-500'); |
| } |
| feather.replace(); |
| toast.classList.remove('hidden'); |
| |
| setTimeout(function() { |
| toast.classList.add('hidden'); |
| }, 5000); |
| } |
| |
| document.getElementById('close-toast').addEventListener('click', function() { |
| document.getElementById('toast').classList.add('hidden'); |
| }); |
| |
| |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { |
| document.getElementById('dropzone').addEventListener(eventName, preventDefaults, false); |
| }); |
| |
| function preventDefaults(e) { |
| e.preventDefault(); |
| e.stopPropagation(); |
| } |
| |
| ['dragenter', 'dragover'].forEach(eventName => { |
| document.getElementById('dropzone').addEventListener(eventName, highlight, false); |
| }); |
| |
| ['dragleave', 'drop'].forEach(eventName => { |
| document.getElementById('dropzone').addEventListener(eventName, unhighlight, false); |
| }); |
| |
| function highlight() { |
| document.getElementById('dropzone').classList.add('dropzone-active'); |
| } |
| |
| function unhighlight() { |
| document.getElementById('dropzone').classList.remove('dropzone-active'); |
| } |
| |
| document.getElementById('dropzone').addEventListener('drop', handleDrop, false); |
| |
| function handleDrop(e) { |
| const dt = e.dataTransfer; |
| const files = dt.files; |
| document.getElementById('image-upload').files = files; |
| |
| |
| const event = new Event('change'); |
| document.getElementById('image-upload').dispatchEvent(event); |
| } |
| </script> |
| </body> |
| </html> |
|
|