Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Z-Image Turbo | AI Image Generator</title> | |
| <!-- Importing FontAwesome for Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| /* Color Palette - Cyberpunk/Dark Mode */ | |
| --bg-color: #0f1115; | |
| --panel-bg: #181b21; | |
| --input-bg: #22262e; | |
| --primary: #6366f1; /* Indigo */ | |
| --primary-hover: #4f46e5; | |
| --accent: #ec4899; /* Pink */ | |
| --text-main: #ffffff; | |
| --text-muted: #9ca3af; | |
| --border-color: #2d313a; | |
| --success: #10b981; | |
| --radius-md: 12px; | |
| --radius-sm: 8px; | |
| --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
| --glow: 0 0 15px rgba(99, 102, 241, 0.3); | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | |
| } | |
| body { | |
| background-color: var(--bg-color); | |
| color: var(--text-main); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| overflow-x: hidden; | |
| } | |
| /* --- Header --- */ | |
| header { | |
| background-color: rgba(15, 17, 21, 0.9); | |
| backdrop-filter: blur(10px); | |
| border-bottom: 1px solid var(--border-color); | |
| padding: 1rem 2rem; | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .brand { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| background: linear-gradient(to right, var(--primary), var(--accent)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| letter-spacing: -0.5px; | |
| } | |
| .brand i { | |
| -webkit-text-fill-color: var(--primary); | |
| font-size: 1.2rem; | |
| } | |
| .anycoder-link { | |
| font-size: 0.9rem; | |
| color: var(--text-muted); | |
| text-decoration: none; | |
| transition: color 0.3s ease; | |
| border: 1px solid var(--border-color); | |
| padding: 0.4rem 0.8rem; | |
| border-radius: var(--radius-sm); | |
| background: var(--panel-bg); | |
| } | |
| .anycoder-link:hover { | |
| color: var(--primary); | |
| border-color: var(--primary); | |
| } | |
| /* --- Main Layout --- */ | |
| main { | |
| flex: 1; | |
| display: grid; | |
| grid-template-columns: 350px 1fr; | |
| gap: 2rem; | |
| padding: 2rem; | |
| max-width: 1600px; | |
| margin: 0 auto; | |
| width: 100%; | |
| } | |
| /* --- Sidebar / Controls --- */ | |
| .controls-panel { | |
| background: var(--panel-bg); | |
| border-radius: var(--radius-md); | |
| padding: 1.5rem; | |
| border: 1px solid var(--border-color); | |
| height: fit-content; | |
| box-shadow: var(--shadow); | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1.5rem; | |
| } | |
| .control-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| } | |
| label { | |
| font-size: 0.85rem; | |
| font-weight: 600; | |
| color: var(--text-muted); | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| textarea { | |
| background: var(--input-bg); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-sm); | |
| color: var(--text-main); | |
| padding: 1rem; | |
| resize: vertical; | |
| min-height: 120px; | |
| font-size: 1rem; | |
| transition: border-color 0.3s; | |
| } | |
| textarea:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1); | |
| } | |
| select, input[type="text"] { | |
| background: var(--input-bg); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-sm); | |
| color: var(--text-main); | |
| padding: 0.75rem; | |
| font-size: 0.95rem; | |
| width: 100%; | |
| } | |
| select:focus, input[type="text"]:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| } | |
| .ratio-grid { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 0.5rem; | |
| } | |
| .ratio-btn { | |
| background: var(--input-bg); | |
| border: 1px solid var(--border-color); | |
| color: var(--text-muted); | |
| padding: 0.6rem; | |
| border-radius: var(--radius-sm); | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 4px; | |
| } | |
| .ratio-btn i { | |
| font-size: 1.2rem; | |
| } | |
| .ratio-btn span { | |
| font-size: 0.7rem; | |
| } | |
| .ratio-btn:hover { | |
| background: var(--border-color); | |
| } | |
| .ratio-btn.active { | |
| background: rgba(99, 102, 241, 0.1); | |
| border-color: var(--primary); | |
| color: var(--primary); | |
| } | |
| .generate-btn { | |
| background: linear-gradient(135deg, var(--primary), var(--accent)); | |
| color: white; | |
| border: none; | |
| padding: 1rem; | |
| border-radius: var(--radius-sm); | |
| font-size: 1rem; | |
| font-weight: 700; | |
| cursor: pointer; | |
| transition: transform 0.2s, box-shadow 0.2s; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| gap: 0.5rem; | |
| box-shadow: var(--glow); | |
| } | |
| .generate-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 0 25px rgba(99, 102, 241, 0.5); | |
| } | |
| .generate-btn:active { | |
| transform: translateY(0); | |
| } | |
| .generate-btn:disabled { | |
| opacity: 0.7; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| /* --- Preview Area --- */ | |
| .preview-panel { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1.5rem; | |
| } | |
| .image-container { | |
| background: var(--panel-bg); | |
| border-radius: var(--radius-md); | |
| border: 1px solid var(--border-color); | |
| min-height: 500px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| position: relative; | |
| overflow: hidden; | |
| box-shadow: var(--shadow); | |
| } | |
| .generated-image { | |
| max-width: 100%; | |
| max-height: 600px; | |
| object-fit: contain; | |
| opacity: 0; | |
| transition: opacity 0.5s ease; | |
| } | |
| .generated-image.loaded { | |
| opacity: 1; | |
| } | |
| .placeholder-text { | |
| position: absolute; | |
| color: var(--text-muted); | |
| text-align: center; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 1rem; | |
| } | |
| .placeholder-text i { | |
| font-size: 3rem; | |
| opacity: 0.3; | |
| } | |
| /* Loading State */ | |
| .loader-overlay { | |
| position: absolute; | |
| inset: 0; | |
| background: rgba(15, 17, 21, 0.8); | |
| display: none; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 10; | |
| backdrop-filter: blur(5px); | |
| } | |
| .loader-overlay.active { | |
| display: flex; | |
| } | |
| .spinner { | |
| width: 50px; | |
| height: 50px; | |
| border: 3px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 50%; | |
| border-top-color: var(--primary); | |
| animation: spin 1s ease-in-out infinite; | |
| margin-bottom: 1rem; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| .loading-text { | |
| font-family: monospace; | |
| color: var(--primary); | |
| font-size: 0.9rem; | |
| } | |
| /* Action Bar */ | |
| .action-bar { | |
| display: flex; | |
| justify-content: flex-end; | |
| gap: 1rem; | |
| opacity: 0.5; | |
| pointer-events: none; | |
| transition: opacity 0.3s; | |
| } | |
| .action-bar.active { | |
| opacity: 1; | |
| pointer-events: all; | |
| } | |
| .action-btn { | |
| background: var(--input-bg); | |
| border: 1px solid var(--border-color); | |
| color: var(--text-main); | |
| padding: 0.6rem 1.2rem; | |
| border-radius: var(--radius-sm); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| font-size: 0.9rem; | |
| transition: all 0.2s; | |
| } | |
| .action-btn:hover { | |
| background: var(--border-color); | |
| color: white; | |
| } | |
| .action-btn.primary { | |
| background: var(--primary); | |
| border-color: var(--primary); | |
| } | |
| .action-btn.primary:hover { | |
| background: var(--primary-hover); | |
| } | |
| /* History Grid */ | |
| .history-section h3 { | |
| margin-bottom: 1rem; | |
| font-size: 1.1rem; | |
| color: var(--text-main); | |
| } | |
| .history-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); | |
| gap: 1rem; | |
| } | |
| .history-item { | |
| aspect-ratio: 1; | |
| border-radius: var(--radius-sm); | |
| overflow: hidden; | |
| cursor: pointer; | |
| border: 2px solid transparent; | |
| transition: all 0.2s; | |
| position: relative; | |
| } | |
| .history-item:hover { | |
| transform: scale(1.05); | |
| border-color: var(--primary); | |
| } | |
| .history-item img { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; | |
| } | |
| /* Toast Notification */ | |
| .toast { | |
| position: fixed; | |
| bottom: 2rem; | |
| right: 2rem; | |
| background: var(--panel-bg); | |
| border: 1px solid var(--border-color); | |
| padding: 1rem 1.5rem; | |
| border-radius: var(--radius-sm); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5); | |
| transform: translateY(100px); | |
| opacity: 0; | |
| transition: all 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55); | |
| z-index: 1000; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.8rem; | |
| } | |
| .toast.show { | |
| transform: translateY(0); | |
| opacity: 1; | |
| } | |
| .toast i { | |
| color: var(--success); | |
| } | |
| /* Responsive */ | |
| @media (max-width: 900px) { | |
| main { | |
| grid-template-columns: 1fr; | |
| } | |
| .controls-panel { | |
| order: 2; | |
| } | |
| .preview-panel { | |
| order: 1; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="brand"> | |
| <i class="fa-solid fa-bolt"></i> | |
| Z-Image Turbo | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link"> | |
| Built with anycoder <i class="fa-solid fa-arrow-up-right-from-square" style="font-size: 0.7em; margin-left: 5px;"></i> | |
| </a> | |
| </header> | |
| <main> | |
| <!-- Sidebar Controls --> | |
| <section class="controls-panel"> | |
| <div class="control-group"> | |
| <label for="prompt">Prompt</label> | |
| <textarea id="prompt" placeholder="Describe the image you want to generate... e.g., A futuristic cyberpunk city with neon lights, 4k, highly detailed"></textarea> | |
| </div> | |
| <div class="control-group"> | |
| <label for="negative-prompt">Negative Prompt</label> | |
| <input type="text" id="negative-prompt" placeholder="blur, low quality, distorted"> | |
| </div> | |
| <div class="control-group"> | |
| <label>Aspect Ratio</label> | |
| <div class="ratio-grid"> | |
| <button class="ratio-btn active" data-ratio="1:1" data-w="1024" data-h="1024"> | |
| <i class="fa-regular fa-square"></i> | |
| <span>1:1</span> | |
| </button> | |
| <button class="ratio-btn" data-ratio="16:9" data-w="1280" data-h="720"> | |
| <i class="fa-solid fa-rectangle-wide"></i> | |
| <span>16:9</span> | |
| </button> | |
| <button class="ratio-btn" data-ratio="9:16" data-w="720" data-h="1280"> | |
| <i class="fa-solid fa-mobile-screen"></i> | |
| <span>9:16</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="control-group"> | |
| <label for="style">Style Preset</label> | |
| <select id="style"> | |
| <option value="none">None (Default)</option> | |
| <option value="cinematic">Cinematic</option> | |
| <option value="anime">Anime / Manga</option> | |
| <option value="3d-model">3D Model</option> | |
| <option value="digital-art">Digital Art</option> | |
| <option value="photographic">Photographic</option> | |
| </select> | |
| </div> | |
| <div class="control-group"> | |
| <label for="steps">Inference Steps</label> | |
| <input type="range" id="steps" min="10" max="50" value="25" style="width: 100%; accent-color: var(--primary);"> | |
| <div style="display: flex; justify-content: space-between; font-size: 0.8rem; color: var(--text-muted);"> | |
| <span>Fast</span> | |
| <span id="steps-val">25</span> | |
| <span>Quality</span> | |
| </div> | |
| </div> | |
| <button id="generate-btn" class="generate-btn"> | |
| <i class="fa-solid fa-wand-magic-sparkles"></i> Generate Image | |
| </button> | |
| </section> | |
| <!-- Preview Area --> | |
| <section class="preview-panel"> | |
| <div class="image-container" id="image-container"> | |
| <div class="placeholder-text" id="placeholder"> | |
| <i class="fa-regular fa-image"></i> | |
| <p>Enter a prompt and click Generate</p> | |
| </div> | |
| <div class="loader-overlay" id="loader"> | |
| <div class="spinner"></div> | |
| <div class="loading-text" id="loading-text">Initializing Z-Image Turbo...</div> | |
| </div> | |
| <img src="" alt="Generated AI Art" class="generated-image" id="main-image"> | |
| </div> | |
| <div class="action-bar" id="action-bar"> | |
| <button class="action-btn" id="copy-seed-btn"> | |
| <i class="fa-solid fa-fingerprint"></i> Copy Seed | |
| </button> | |
| <button class="action-btn primary" id="download-btn"> | |
| <i class="fa-solid fa-download"></i> Download HD | |
| </button> | |
| </div> | |
| <div class="history-section"> | |
| <h3>Recent Generations</h3> | |
| <div class="history-grid" id="history-grid"> | |
| <!-- History items will be injected here --> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| <!-- Toast Notification --> | |
| <div class="toast" id="toast"> | |
| <i class="fa-solid fa-circle-check"></i> | |
| <span id="toast-message">Operation successful</span> | |
| </div> | |
| <script> | |
| // DOM Elements | |
| const promptInput = document.getElementById('prompt'); | |
| const negativePromptInput = document.getElementById('negative-prompt'); | |
| const generateBtn = document.getElementById('generate-btn'); | |
| const mainImage = document.getElementById('main-image'); | |
| const loader = document.getElementById('loader'); | |
| const loadingText = document.getElementById('loading-text'); | |
| const placeholder = document.getElementById('placeholder'); | |
| const actionBar = document.getElementById('action-bar'); | |
| const downloadBtn = document.getElementById('download-btn'); | |
| const copySeedBtn = document.getElementById('copy-seed-btn'); | |
| const historyGrid = document.getElementById('history-grid'); | |
| const ratioBtns = document.querySelectorAll('.ratio-btn'); | |
| const stepsInput = document.getElementById('steps'); | |
| const stepsVal = document.getElementById('steps-val'); | |
| const styleSelect = document.getElementById('style'); | |
| const toast = document.getElementById('toast'); | |
| const toastMessage = document.getElementById('toast-message'); | |
| // State | |
| let currentSeed = ''; | |
| let currentWidth = 1024; | |
| let currentHeight = 1024; | |
| let isGenerating = false; | |
| // Event Listeners for Aspect Ratio | |
| ratioBtns.forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| ratioBtns.forEach(b => b.classList.remove('active')); | |
| btn.classList.add('active'); | |
| currentWidth = parseInt(btn.dataset.w); | |
| currentHeight = parseInt(btn.dataset.h); | |
| }); | |
| }); | |
| // Step Slider Update | |
| stepsInput.addEventListener('input', (e) => { | |
| stepsVal.textContent = e.target.value; | |
| }); | |
| // Generate Button Logic | |
| generateBtn.addEventListener('click', generateImage); | |
| // Helper: Show Toast | |
| function showToast(msg) { | |
| toastMessage.textContent = msg; | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| // Helper: Generate a random seed based on prompt + random | |
| function generateSeed(text) { | |
| const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; | |
| let result = text.replace(/[^a-zA-Z0-9]/g, '').substring(0, 10); | |
| if (result.length < 5) result += 'zimageturbo'; // Fallback for short prompts | |
| // Add randomness for unique generations of same prompt | |
| const randomPart = Math.random().toString(36).substring(2, 7); | |
| return result + randomPart; | |
| } | |
| async function generateImage() { | |
| const prompt = promptInput.value.trim(); | |
| if (!prompt) { | |
| showToast("Please enter a text prompt first."); | |
| promptInput.focus(); | |
| return; | |
| } | |
| if (isGenerating) return; | |
| isGenerating = true; | |
| // UI Updates: Start Loading | |
| generateBtn.disabled = true; | |
| generateBtn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> Generating...'; | |
| loader.classList.add('active'); | |
| placeholder.style.display = 'none'; | |
| mainImage.classList.remove('loaded'); | |
| actionBar.classList.remove('active'); | |
| // Simulate AI Steps for realism | |
| const steps = [ | |
| "Encoding text prompt...", | |
| "Initializing latent space...", | |
| "Running Z-Image Turbo diffusion...", | |
| "Denoising step 5/25...", | |
| "Denoising step 15/25...", | |
| "Upscaling result...", | |
| "Finalizing render..." | |
| ]; | |
| let stepIndex = 0; | |
| const stepInterval = setInterval(() => { | |
| if (stepIndex < steps.length) { | |
| loadingText.textContent = steps[stepIndex]; | |
| stepIndex++; | |
| } | |
| }, 400); | |
| // Calculate Seed and Construct URL | |
| // Note: Using picsum.photos as a placeholder for the actual AI generation. | |
| // In a real app, this would be a fetch() call to the Z-Image Turbo API. | |
| currentSeed = generateSeed(prompt); | |
| // We use the seed to get a deterministic image from picsum | |
| // We use the width/height settings from the UI | |
| const imageUrl = `https://picsum.photos/seed/${currentSeed}/${currentWidth}/${currentHeight}`; | |
| // Wait for simulated processing time (min 2.5s) | |
| await new Promise(r => setTimeout(r, 2500)); | |
| clearInterval(stepInterval); | |
| // Load Image | |
| mainImage.src = imageUrl; | |
| mainImage.onload = () => { | |
| // Reset UI | |
| isGenerating = false; | |
| generateBtn.disabled = false; | |
| generateBtn.innerHTML = '<i class="fa-solid fa-wand-magic-sparkles"></i> Generate Image'; | |
| loader.classList.remove('active'); | |
| mainImage.classList.add('loaded'); | |
| actionBar.classList.add('active'); | |
| loadingText.textContent = "Initializing Z-Image Turbo..."; // Reset text | |
| // Add to history | |
| addToHistory(imageUrl); | |
| showToast("Image generated successfully!"); | |
| }; | |
| mainImage.onerror = () => { | |
| isGenerating = false; | |
| generateBtn.disabled = false; | |
| generateBtn.innerHTML = '<i class="fa-solid fa-wand-magic-sparkles"></i> Generate Image'; | |
| loader.classList.remove('active'); | |
| placeholder.style.display = 'flex'; | |
| showToast("Error generating image. Try again."); | |
| }; | |
| } | |
| function addToHistory(url) { | |
| const item = document.createElement('div'); | |
| item.className = 'history-item'; | |
| item.innerHTML = `<img src="${url}" alt="History Item">`; | |
| item.addEventListener('click', () => { | |
| mainImage.src = url; | |
| mainImage.classList.add('loaded'); | |
| placeholder.style.display = 'none'; | |
| actionBar.classList.add('active'); | |
| // In a real app, we'd restore the prompt/seed data here too | |
| }); | |
| // Add to beginning | |
| historyGrid.insertBefore(item, historyGrid.firstChild); | |
| // Limit history to 8 items | |
| if (historyGrid.children.length > 8) { | |
| historyGrid.removeChild(historyGrid.lastChild); | |
| } | |
| } | |
| // Download Functionality | |
| downloadBtn.addEventListener('click', async () => { | |
| if (!mainImage.src) return; | |
| try { | |
| // Fetch blob to avoid cross-origin taint issues on some browsers | |
| const response = await fetch(mainImage.src); | |
| const blob = await response.blob(); | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.style.display = 'none'; | |
| a.href = url; | |
| a.download = `z-image-${currentSeed}.jpg`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| window.URL.revokeObjectURL(url); | |
| document.body.removeChild(a); | |
| showToast("Download started"); | |
| } catch (err) { | |
| console.error(err); | |
| // Fallback: Open in new tab | |
| window.open(mainImage.src, '_blank'); | |
| } | |
| }); | |
| // Copy Seed Functionality | |
| copySeedBtn.addEventListener('click', () => { | |
| if (!currentSeed) return; | |
| navigator.clipboard.writeText(currentSeed).then(() => { | |
| showToast("Seed copied to clipboard"); | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |