Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AI Image Studio - Professional Image Generation</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| :root { | |
| --primary: #6366f1; | |
| --primary-dark: #4f46e5; | |
| --secondary: #f59e0b; | |
| --accent: #ec4899; | |
| --bg-primary: #0f0f23; | |
| --bg-secondary: #1a1a35; | |
| --bg-card: rgba(255, 255, 255, 0.08); | |
| --text-primary: #ffffff; | |
| --text-secondary: #a1a1aa; | |
| --border: rgba(255, 255, 255, 0.1); | |
| --glass-bg: rgba(255, 255, 255, 0.05); | |
| --glass-border: rgba(255, 255, 255, 0.1); | |
| --shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); | |
| --gradient: linear-gradient(135deg, var(--primary), var(--accent)); | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| background: var(--bg-primary); | |
| color: var(--text-primary); | |
| line-height: 1.6; | |
| overflow-x: hidden; | |
| } | |
| /* Animated background */ | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: | |
| radial-gradient(circle at 20% 50%, rgba(120, 119, 198, 0.3) 0%, transparent 50%), | |
| radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%), | |
| radial-gradient(circle at 40% 80%, rgba(120, 219, 255, 0.3) 0%, transparent 50%); | |
| animation: backgroundShift 20s ease-in-out infinite; | |
| z-index: -1; | |
| } | |
| @keyframes backgroundShift { | |
| 0%, 100% { opacity: 0.3; transform: scale(1); } | |
| 50% { opacity: 0.5; transform: scale(1.1); } | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 2rem; | |
| min-height: 100vh; | |
| } | |
| /* Header */ | |
| .header { | |
| text-align: center; | |
| margin-bottom: 4rem; | |
| animation: fadeInDown 1s ease-out; | |
| } | |
| .header h1 { | |
| font-size: clamp(2.5rem, 5vw, 4rem); | |
| font-weight: 700; | |
| background: var(--gradient); | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| margin-bottom: 1rem; | |
| animation: glow 3s ease-in-out infinite alternate; | |
| } | |
| @keyframes glow { | |
| from { filter: drop-shadow(0 0 20px rgba(99, 102, 241, 0.3)); } | |
| to { filter: drop-shadow(0 0 30px rgba(236, 72, 153, 0.4)); } | |
| } | |
| .header p { | |
| font-size: 1.25rem; | |
| color: var(--text-secondary); | |
| max-width: 600px; | |
| margin: 0 auto; | |
| } | |
| /* Main content grid */ | |
| .main-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1.5fr; | |
| gap: 3rem; | |
| align-items: start; | |
| } | |
| /* Form card */ | |
| .form-card { | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(20px); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 20px; | |
| padding: 2.5rem; | |
| box-shadow: var(--shadow); | |
| position: sticky; | |
| top: 2rem; | |
| animation: slideInLeft 1s ease-out 0.3s both; | |
| } | |
| .form-title { | |
| font-size: 1.5rem; | |
| font-weight: 600; | |
| margin-bottom: 2rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| } | |
| .form-title::before { | |
| content: '✨'; | |
| font-size: 1.25rem; | |
| } | |
| .form-group { | |
| margin-bottom: 1.5rem; | |
| } | |
| .form-group label { | |
| display: block; | |
| font-weight: 500; | |
| margin-bottom: 0.5rem; | |
| color: var(--text-primary); | |
| } | |
| .input-wrapper { | |
| position: relative; | |
| } | |
| input, select { | |
| width: 100%; | |
| padding: 1rem 1.25rem; | |
| background: rgba(255, 255, 255, 0.05); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| color: var(--text-primary); | |
| font-size: 1rem; | |
| transition: all 0.3s ease; | |
| backdrop-filter: blur(10px); | |
| } | |
| input:focus, select:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); | |
| transform: translateY(-2px); | |
| } | |
| input::placeholder { | |
| color: var(--text-secondary); | |
| } | |
| /* Generate button */ | |
| .generate-btn { | |
| width: 100%; | |
| padding: 1.25rem 2rem; | |
| background: var(--gradient); | |
| border: none; | |
| border-radius: 12px; | |
| color: white; | |
| font-size: 1.1rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .generate-btn::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: -100%; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); | |
| transition: left 0.5s; | |
| } | |
| .generate-btn:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 15px 35px rgba(99, 102, 241, 0.3); | |
| } | |
| .generate-btn:hover::before { | |
| left: 100%; | |
| } | |
| .generate-btn:active { | |
| transform: translateY(-1px); | |
| } | |
| .generate-btn:disabled { | |
| opacity: 0.6; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| /* Gallery section */ | |
| .gallery-section { | |
| animation: slideInRight 1s ease-out 0.6s both; | |
| } | |
| .gallery-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 2rem; | |
| } | |
| .gallery-title { | |
| font-size: 1.5rem; | |
| font-weight: 600; | |
| } | |
| .image-count { | |
| background: var(--glass-bg); | |
| padding: 0.5rem 1rem; | |
| border-radius: 20px; | |
| font-size: 0.875rem; | |
| color: var(--text-secondary); | |
| } | |
| /* Gallery grid */ | |
| #gallery { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 1.5rem; | |
| } | |
| .image-card { | |
| background: var(--glass-bg); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 16px; | |
| overflow: hidden; | |
| transition: all 0.3s ease; | |
| animation: fadeInUp 0.6s ease-out; | |
| } | |
| .image-card:hover { | |
| transform: translateY(-8px); | |
| box-shadow: var(--shadow); | |
| } | |
| .image-card img { | |
| width: 100%; | |
| height: 250px; | |
| object-fit: cover; | |
| transition: transform 0.3s ease; | |
| } | |
| .image-card:hover img { | |
| transform: scale(1.05); | |
| } | |
| .image-info { | |
| padding: 1rem; | |
| } | |
| .image-actions { | |
| display: flex; | |
| gap: 0.5rem; | |
| margin-top: 0.75rem; | |
| } | |
| .action-btn { | |
| flex: 1; | |
| padding: 0.5rem; | |
| background: rgba(255, 255, 255, 0.1); | |
| border: none; | |
| border-radius: 8px; | |
| color: var(--text-primary); | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| font-size: 0.875rem; | |
| } | |
| .action-btn:hover { | |
| background: var(--primary); | |
| } | |
| /* Loading state */ | |
| .loading { | |
| display: none; | |
| text-align: center; | |
| padding: 3rem; | |
| } | |
| .loading.active { | |
| display: block; | |
| } | |
| .spinner { | |
| width: 60px; | |
| height: 60px; | |
| border: 3px solid rgba(99, 102, 241, 0.3); | |
| border-top: 3px solid var(--primary); | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| margin: 0 auto 1rem; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| /* Error message */ | |
| #error { | |
| background: rgba(239, 68, 68, 0.1); | |
| border: 1px solid rgba(239, 68, 68, 0.3); | |
| color: #fca5a5; | |
| padding: 1rem; | |
| border-radius: 12px; | |
| margin-top: 1rem; | |
| display: none; | |
| } | |
| #error.show { | |
| display: block; | |
| animation: shake 0.5s ease-out; | |
| } | |
| @keyframes shake { | |
| 0%, 100% { transform: translateX(0); } | |
| 25% { transform: translateX(-5px); } | |
| 75% { transform: translateX(5px); } | |
| } | |
| /* Empty state */ | |
| .empty-state { | |
| text-align: center; | |
| padding: 4rem 2rem; | |
| color: var(--text-secondary); | |
| } | |
| .empty-state-icon { | |
| font-size: 4rem; | |
| margin-bottom: 1rem; | |
| opacity: 0.3; | |
| } | |
| /* Animations */ | |
| @keyframes fadeInDown { | |
| from { | |
| opacity: 0; | |
| transform: translateY(-30px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| @keyframes slideInLeft { | |
| from { | |
| opacity: 0; | |
| transform: translateX(-50px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateX(0); | |
| } | |
| } | |
| @keyframes slideInRight { | |
| from { | |
| opacity: 0; | |
| transform: translateX(50px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateX(0); | |
| } | |
| } | |
| @keyframes fadeInUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(30px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| /* Responsive design */ | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 1rem; | |
| } | |
| .main-grid { | |
| grid-template-columns: 1fr; | |
| gap: 2rem; | |
| } | |
| .form-card { | |
| position: static; | |
| padding: 1.5rem; | |
| } | |
| .header h1 { | |
| font-size: 2.5rem; | |
| } | |
| #gallery { | |
| grid-template-columns: 1fr; | |
| } | |
| .gallery-header { | |
| flex-direction: column; | |
| gap: 1rem; | |
| text-align: center; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .form-card { | |
| padding: 1rem; | |
| } | |
| input, select, .generate-btn { | |
| padding: 0.875rem 1rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <!-- Header --> | |
| <div class="header"> | |
| <h1>AI Image Studio</h1> | |
| <p>Transform your imagination into stunning visuals with cutting-edge AI technology</p> | |
| </div> | |
| <!-- Main content grid --> | |
| <div class="main-grid"> | |
| <!-- Form section --> | |
| <div class="form-card"> | |
| <h2 class="form-title">Create Your Vision</h2> | |
| <form id="generate-form"> | |
| <div class="form-group"> | |
| <label for="prompt">Describe Your Image</label> | |
| <div class="input-wrapper"> | |
| <input type="text" id="prompt" name="prompt" placeholder="A serene mountain landscape at sunset with vibrant colors..." required> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="num_images">Number of Images</label> | |
| <select id="num_images" name="num_images" required> | |
| <option value="1">1 Image</option> | |
| <option value="2">2 Images</option> | |
| <option value="3">3 Images</option> | |
| <option value="4">4 Images</option> | |
| <option value="5">5 Images</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label for="aspect_ratio">Aspect Ratio</label> | |
| <select id="aspect_ratio" name="aspect_ratio" required> | |
| <option value="1:1">Square (1:1)</option> | |
| <option value="4:3">Standard (4:3)</option> | |
| <option value="16:9">Widescreen (16:9)</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label for="model">AI Model</label> | |
| <select id="model" name="model" required> | |
| <option value="stable_diffusion">Stable Diffusion</option> | |
| <option value="locked_model" disabled>Premium Model (Coming Soon)</option> | |
| </select> | |
| </div> | |
| <button type="submit" class="generate-btn" id="generateBtn"> | |
| <span id="btnText">Generate Images</span> | |
| </button> | |
| </form> | |
| <div id="error"></div> | |
| </div> | |
| <!-- Gallery section --> | |
| <div class="gallery-section"> | |
| <div class="gallery-header"> | |
| <h2 class="gallery-title">Generated Images</h2> | |
| <div class="image-count" id="imageCount">0 images</div> | |
| </div> | |
| <div class="loading" id="loading"> | |
| <div class="spinner"></div> | |
| <p>Creating your masterpiece...</p> | |
| </div> | |
| <div id="gallery"> | |
| <div class="empty-state"> | |
| <div class="empty-state-icon">🎨</div> | |
| <h3>Ready to Create</h3> | |
| <p>Your generated images will appear here. Start by describing what you'd like to see!</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| let imageCounter = 0; | |
| function updateImageCount() { | |
| const count = document.querySelectorAll('.image-card').length; | |
| document.getElementById('imageCount').textContent = `${count} image${count !== 1 ? 's' : ''}`; | |
| } | |
| function showError(message) { | |
| const errorDiv = document.getElementById('error'); | |
| errorDiv.textContent = message; | |
| errorDiv.classList.add('show'); | |
| setTimeout(() => errorDiv.classList.remove('show'), 5000); | |
| } | |
| function setLoading(isLoading) { | |
| const loadingDiv = document.getElementById('loading'); | |
| const generateBtn = document.getElementById('generateBtn'); | |
| const btnText = document.getElementById('btnText'); | |
| if (isLoading) { | |
| loadingDiv.classList.add('active'); | |
| generateBtn.disabled = true; | |
| btnText.textContent = 'Generating...'; | |
| } else { | |
| loadingDiv.classList.remove('active'); | |
| generateBtn.disabled = false; | |
| btnText.textContent = 'Generate Images'; | |
| } | |
| } | |
| function createImageCard(src, prompt) { | |
| const card = document.createElement('div'); | |
| card.className = 'image-card'; | |
| card.style.animationDelay = `${imageCounter * 0.1}s`; | |
| card.innerHTML = ` | |
| <img src="${src}" alt="Generated Image" loading="lazy"> | |
| <div class="image-info"> | |
| <p style="font-size: 0.875rem; color: var(--text-secondary); margin-bottom: 0.5rem;">${prompt.substring(0, 60)}${prompt.length > 60 ? '...' : ''}</p> | |
| <div class="image-actions"> | |
| <button class="action-btn" onclick="downloadImage('${src}')">Download</button> | |
| <button class="action-btn" onclick="shareImage('${src}')">Share</button> | |
| </div> | |
| </div> | |
| `; | |
| imageCounter++; | |
| return card; | |
| } | |
| function downloadImage(src) { | |
| const link = document.createElement('a'); | |
| link.href = src; | |
| link.download = `ai-generated-image-${Date.now()}.png`; | |
| link.click(); | |
| } | |
| function shareImage(src) { | |
| if (navigator.share) { | |
| navigator.share({ | |
| title: 'AI Generated Image', | |
| text: 'Check out this AI-generated image!', | |
| url: src | |
| }); | |
| } else { | |
| navigator.clipboard.writeText(src).then(() => { | |
| // Could add a toast notification here | |
| console.log('Image URL copied to clipboard'); | |
| }); | |
| } | |
| } | |
| document.getElementById('generate-form').addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const form = e.target; | |
| const formData = new FormData(form); | |
| const gallery = document.getElementById('gallery'); | |
| const prompt = formData.get('prompt'); | |
| // Clear previous errors | |
| document.getElementById('error').classList.remove('show'); | |
| // Remove empty state if it exists | |
| const emptyState = gallery.querySelector('.empty-state'); | |
| if (emptyState) { | |
| emptyState.remove(); | |
| } | |
| setLoading(true); | |
| try { | |
| const response = await fetch('/generate', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const result = await response.json(); | |
| if (response.ok) { | |
| result.images.forEach((src, index) => { | |
| setTimeout(() => { | |
| const imageCard = createImageCard(src, prompt); | |
| gallery.appendChild(imageCard); | |
| updateImageCount(); | |
| }, index * 200); | |
| }); | |
| } else { | |
| showError(result.error || 'Failed to generate images. Please try again.'); | |
| } | |
| } catch (err) { | |
| showError(`Network error: ${err.message}`); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }); | |
| // Initialize | |
| updateImageCount(); | |
| // Add some interactive polish | |
| document.querySelectorAll('input, select').forEach(element => { | |
| element.addEventListener('focus', (e) => { | |
| e.target.parentElement.style.transform = 'scale(1.02)'; | |
| }); | |
| element.addEventListener('blur', (e) => { | |
| e.target.parentElement.style.transform = 'scale(1)'; | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |