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 Generator</title> | |
| <!-- Import Remix Icon --> | |
| <link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet"> | |
| <!-- Import Google Fonts --> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet"> | |
| <style> | |
| /* --- CSS VARIABLES & RESET --- */ | |
| :root { | |
| --bg-dark: #0f1115; | |
| --bg-panel: rgba(23, 25, 30, 0.75); | |
| --bg-input: rgba(0, 0, 0, 0.3); | |
| --primary: #6366f1; | |
| --primary-glow: rgba(99, 102, 241, 0.4); | |
| --accent: #ec4899; | |
| --text-main: #ffffff; | |
| --text-muted: #9ca3af; | |
| --border: rgba(255, 255, 255, 0.1); | |
| --radius-lg: 16px; | |
| --radius-md: 8px; | |
| --radius-sm: 4px; | |
| --font-display: 'Space Grotesk', sans-serif; | |
| --font-body: 'Inter', sans-serif; | |
| --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: var(--font-body); | |
| background-color: var(--bg-dark); | |
| color: var(--text-main); | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| background-image: | |
| radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.15) 0%, transparent 40%), | |
| radial-gradient(circle at 90% 80%, rgba(236, 72, 153, 0.15) 0%, transparent 40%); | |
| background-attachment: fixed; | |
| } | |
| /* --- UTILITIES --- */ | |
| .glass { | |
| background: var(--bg-panel); | |
| backdrop-filter: blur(16px); | |
| -webkit-backdrop-filter: blur(16px); | |
| border: 1px solid var(--border); | |
| } | |
| .container { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| padding: 0 20px; | |
| } | |
| .btn { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 8px; | |
| padding: 12px 24px; | |
| border-radius: var(--radius-md); | |
| font-weight: 600; | |
| font-family: var(--font-display); | |
| border: none; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| font-size: 0.95rem; | |
| } | |
| .btn-primary { | |
| background: linear-gradient(135deg, var(--primary), #4f46e5); | |
| color: white; | |
| box-shadow: 0 4px 20px var(--primary-glow); | |
| } | |
| .btn-primary:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 25px var(--primary-glow); | |
| } | |
| .btn-primary:active { | |
| transform: translateY(0); | |
| } | |
| .btn-icon { | |
| padding: 8px; | |
| background: rgba(255, 255, 255, 0.1); | |
| color: var(--text-main); | |
| border-radius: var(--radius-sm); | |
| } | |
| .btn-icon:hover { | |
| background: rgba(255, 255, 255, 0.2); | |
| } | |
| /* --- HEADER --- */ | |
| header { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| z-index: 100; | |
| padding: 16px 0; | |
| border-bottom: 1px solid var(--border); | |
| background: rgba(15, 17, 21, 0.8); | |
| backdrop-filter: blur(10px); | |
| } | |
| .header-content { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .logo { | |
| font-family: var(--font-display); | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| background: linear-gradient(to right, #fff, #a5b4fc); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .logo i { | |
| color: var(--primary); | |
| -webkit-text-fill-color: var(--primary); | |
| } | |
| .anycoder-link { | |
| font-size: 0.85rem; | |
| color: var(--text-muted); | |
| text-decoration: none; | |
| transition: var(--transition); | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .anycoder-link:hover { | |
| color: var(--primary); | |
| } | |
| /* --- MAIN LAYOUT --- */ | |
| main { | |
| padding-top: 100px; | |
| padding-bottom: 40px; | |
| display: grid; | |
| grid-template-columns: 350px 1fr; | |
| gap: 30px; | |
| min-height: calc(100vh - 80px); | |
| } | |
| /* --- SIDEBAR CONTROLS --- */ | |
| .controls-panel { | |
| border-radius: var(--radius-lg); | |
| padding: 24px; | |
| height: fit-content; | |
| position: sticky; | |
| top: 100px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 24px; | |
| } | |
| .input-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| } | |
| .input-label { | |
| font-size: 0.85rem; | |
| font-weight: 600; | |
| color: var(--text-muted); | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| display: flex; | |
| justify-content: space-between; | |
| } | |
| textarea, input[type="text"], select { | |
| width: 100%; | |
| background: var(--bg-input); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-md); | |
| padding: 12px 16px; | |
| color: var(--text-main); | |
| font-family: var(--font-body); | |
| font-size: 0.95rem; | |
| transition: var(--transition); | |
| resize: vertical; | |
| } | |
| textarea:focus, input[type="text"]:focus, select:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2); | |
| } | |
| textarea { | |
| min-height: 120px; | |
| } | |
| .settings-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 16px; | |
| } | |
| .range-wrap { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| input[type="range"] { | |
| width: 100%; | |
| height: 4px; | |
| background: rgba(255,255,255,0.1); | |
| border-radius: 2px; | |
| appearance: none; | |
| outline: none; | |
| } | |
| input[type="range"]::-webkit-slider-thumb { | |
| appearance: none; | |
| width: 16px; | |
| height: 16px; | |
| border-radius: 50%; | |
| background: var(--primary); | |
| cursor: pointer; | |
| transition: var(--transition); | |
| } | |
| /* --- PREVIEW AREA --- */ | |
| .preview-area { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 24px; | |
| } | |
| .main-image-container { | |
| width: 100%; | |
| min-height: 500px; | |
| border-radius: var(--radius-lg); | |
| border: 1px solid var(--border); | |
| background: rgba(0,0,0,0.2); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| position: relative; | |
| overflow: hidden; | |
| box-shadow: 0 20px 50px rgba(0,0,0,0.3); | |
| } | |
| .main-image { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: contain; | |
| display: block; | |
| opacity: 0; | |
| transition: opacity 0.5s ease; | |
| } | |
| .main-image.loaded { | |
| opacity: 1; | |
| } | |
| .placeholder-state { | |
| text-align: center; | |
| color: var(--text-muted); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 16px; | |
| } | |
| .placeholder-state i { | |
| font-size: 3rem; | |
| opacity: 0.5; | |
| } | |
| /* Loading Spinner */ | |
| .loader { | |
| display: none; | |
| width: 48px; | |
| height: 48px; | |
| border: 5px solid #FFF; | |
| border-bottom-color: var(--primary); | |
| border-radius: 50%; | |
| animation: rotation 1s linear infinite; | |
| position: absolute; | |
| z-index: 10; | |
| } | |
| @keyframes rotation { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| /* Image Actions */ | |
| .image-actions { | |
| position: absolute; | |
| bottom: 20px; | |
| right: 20px; | |
| display: flex; | |
| gap: 10px; | |
| opacity: 0; | |
| transform: translateY(10px); | |
| transition: var(--transition); | |
| } | |
| .main-image-container:hover .image-actions { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| /* History Strip */ | |
| .history-section { | |
| margin-top: auto; | |
| } | |
| .history-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 16px; | |
| } | |
| .history-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); | |
| gap: 12px; | |
| } | |
| .history-item { | |
| aspect-ratio: 1; | |
| border-radius: var(--radius-md); | |
| overflow: hidden; | |
| border: 1px solid transparent; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| position: relative; | |
| } | |
| .history-item img { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; | |
| } | |
| .history-item:hover { | |
| border-color: var(--primary); | |
| transform: scale(1.05); | |
| } | |
| /* --- TOAST --- */ | |
| .toast-container { | |
| position: fixed; | |
| bottom: 30px; | |
| right: 30px; | |
| z-index: 1000; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| .toast { | |
| background: var(--bg-panel); | |
| border: 1px solid var(--border); | |
| backdrop-filter: blur(10px); | |
| padding: 16px 20px; | |
| border-radius: var(--radius-md); | |
| color: white; | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.5); | |
| animation: slideIn 0.3s ease; | |
| max-width: 300px; | |
| } | |
| .toast i { | |
| color: var(--primary); | |
| font-size: 1.2rem; | |
| } | |
| @keyframes slideIn { | |
| from { transform: translateX(100%); opacity: 0; } | |
| to { transform: translateX(0); opacity: 1; } | |
| } | |
| /* --- RESPONSIVE --- */ | |
| @media (max-width: 900px) { | |
| main { | |
| grid-template-columns: 1fr; | |
| } | |
| .controls-panel { | |
| position: relative; | |
| top: 0; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Header --> | |
| <header class="glass"> | |
| <div class="container header-content"> | |
| <div class="logo"> | |
| <i class="ri-bolt-flash-fill"></i> | |
| Z-Image Turbo | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link"> | |
| Built with anycoder <i class="ri-external-link-line"></i> | |
| </a> | |
| </div> | |
| </header> | |
| <!-- Main Application --> | |
| <main class="container"> | |
| <!-- Sidebar Controls --> | |
| <aside class="controls-panel glass"> | |
| <div class="input-group"> | |
| <label class="input-label">Prompt</label> | |
| <textarea id="promptInput" placeholder="Describe the image you want to generate... e.g., A futuristic cyberpunk city with neon lights, 4k, highly detailed"></textarea> | |
| </div> | |
| <div class="input-group"> | |
| <label class="input-label">Negative Prompt</label> | |
| <textarea id="negativeInput" placeholder="What to avoid? e.g., blurry, low quality, distorted" style="min-height: 60px;"></textarea> | |
| </div> | |
| <div class="settings-grid"> | |
| <div class="input-group"> | |
| <label class="input-label">Aspect Ratio</label> | |
| <select id="aspectRatio"> | |
| <option value="1:1">Square (1:1)</option> | |
| <option value="16:9">Landscape (16:9)</option> | |
| <option value="9:16">Portrait (9:16)</option> | |
| <option value="4:3">Classic (4:3)</option> | |
| </select> | |
| </div> | |
| <div class="input-group"> | |
| <label class="input-label">Style Seed</label> | |
| <input type="text" id="seedInput" placeholder="Random"> | |
| </div> | |
| </div> | |
| <div class="input-group"> | |
| <label class="input-label">Turbo Speed</label> | |
| <div class="range-wrap"> | |
| <input type="range" min="1" max="10" value="5" id="speedRange"> | |
| </div> | |
| </div> | |
| <button class="btn btn-primary" id="generateBtn"> | |
| <i class="ri-magic-line"></i> Generate Image | |
| </button> | |
| </aside> | |
| <!-- Preview Area --> | |
| <section class="preview-area"> | |
| <div class="main-image-container glass" id="mainImageContainer"> | |
| <span class="loader" id="loader"></span> | |
| <div class="placeholder-state" id="placeholderState"> | |
| <i class="ri-image-add-line"></i> | |
| <p>Enter a prompt and hit Generate to start.</p> | |
| </div> | |
| <img src="" alt="Generated AI Art" class="main-image" id="mainImage"> | |
| <div class="image-actions" id="imageActions" style="display:none;"> | |
| <button class="btn btn-primary glass" id="downloadBtn" title="Download Image"> | |
| <i class="ri-download-line"></i> | |
| </button> | |
| <button class="btn btn-icon glass" id="fullscreenBtn" title="View Fullscreen"> | |
| <i class="ri-fullscreen-line"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- History Grid --> | |
| <div class="history-section"> | |
| <div class="history-header"> | |
| <h3 style="font-family: var(--font-display);">Recent Generations</h3> | |
| <button class="btn-icon" id="clearHistoryBtn" title="Clear History"> | |
| <i class="ri-delete-bin-line"></i> | |
| </button> | |
| </div> | |
| <div class="history-grid" id="historyGrid"> | |
| <!-- History items injected here --> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| <!-- Toast Container --> | |
| <div class="toast-container" id="toastContainer"></div> | |
| <script> | |
| /** | |
| * Z-Image Turbo Logic | |
| * Handles API calls to Pollinations.ai (SDXL Turbo backend simulation) | |
| * and manages UI state. | |
| */ | |
| // Elements | |
| const promptInput = document.getElementById('promptInput'); | |
| const negativeInput = document.getElementById('negativeInput'); | |
| const aspectRatioSelect = document.getElementById('aspectRatio'); | |
| const seedInput = document.getElementById('seedInput'); | |
| const generateBtn = document.getElementById('generateBtn'); | |
| const mainImage = document.getElementById('mainImage'); | |
| const loader = document.getElementById('loader'); | |
| const placeholderState = document.getElementById('placeholderState'); | |
| const imageActions = document.getElementById('imageActions'); | |
| const historyGrid = document.getElementById('historyGrid'); | |
| const downloadBtn = document.getElementById('downloadBtn'); | |
| const fullscreenBtn = document.getElementById('fullscreenBtn'); | |
| const clearHistoryBtn = document.getElementById('clearHistoryBtn'); | |
| // State | |
| let isGenerating = false; | |
| let currentImageUrl = ''; | |
| let history = []; | |
| // Aspect Ratio Map (Width x Height) | |
| const dimensions = { | |
| '1:1': { w: 1024, h: 1024 }, | |
| '16:9': { w: 1280, h: 720 }, | |
| '9:16': { w: 720, h: 1280 }, | |
| '4:3': { w: 1024, h: 768 } | |
| }; | |
| // --- Core Functions --- | |
| /** | |
| * Generates the random seed if not provided | |
| */ | |
| const getSeed = () => { | |
| const val = seedInput.value.trim(); | |
| return val ? val : Math.floor(Math.random() * 1000000); | |
| }; | |
| /** | |
| * Constructs the API URL | |
| */ | |
| const buildApiUrl = () => { | |
| const prompt = promptInput.value.trim(); | |
| const negative = negativeInput.value.trim(); | |
| const ratio = aspectRatioSelect.value; | |
| const seed = getSeed(); | |
| const { w, h } = dimensions[ratio]; | |
| if (!prompt) { | |
| showToast("Please enter a prompt first", "error"); | |
| return null; | |
| } | |
| // Using Pollinations.ai API (Free, No Key, SDXL Turbo compatible) | |
| // We encode the prompt to handle special characters | |
| const encodedPrompt = encodeURIComponent(prompt + (negative ? `, negative: ${negative}` : '')); | |
| // Construct URL | |
| const url = `https://image.pollinations.ai/prompt/${encodedPrompt}?width=${w}&height=${h}&seed=${seed}&nologo=true&model=flux`; // Using flux or turbo model | |
| return url; | |
| }; | |
| /** | |
| * Triggers the Image Generation | |
| */ | |
| const handleGenerate = async () => { | |
| if (isGenerating) return; | |
| const url = buildApiUrl(); | |
| if (!url) return; | |
| // UI Updates for loading state | |
| isGenerating = true; | |
| generateBtn.innerHTML = '<i class="ri-loader-4-line ri-spin"></i> Generating...'; | |
| generateBtn.style.opacity = '0.7'; | |
| loader.style.display = 'block'; | |
| placeholderState.style.display = 'none'; | |
| mainImage.classList.remove('loaded'); | |
| imageActions.style.display = 'none'; | |
| // Preload Image | |
| const img = new Image(); | |
| img.src = url; | |
| img.onload = () => { | |
| // Success | |
| mainImage.src = url; | |
| currentImageUrl = url; | |
| mainImage.classList.add('loaded'); | |
| // UI Reset | |
| loader.style.display = 'none'; | |
| imageActions.style.display = 'flex'; | |
| generateBtn.innerHTML = '<i class="ri-magic-line"></i> Generate Image'; | |
| generateBtn.style.opacity = '1'; | |
| isGenerating = false; | |
| // Add to history | |
| addToHistory(url, promptInput.value); | |
| showToast("Image generated successfully!", "success"); | |
| }; | |
| img.onerror = () => { | |
| // Error | |
| loader.style.display = 'none'; | |
| placeholderState.style.display = 'flex'; | |
| placeholderState.innerHTML = '<i class="ri-error-warning-line" style="color:var(--accent)"></i><p>Failed to generate image. Try again.</p>'; | |
| generateBtn.innerHTML = '<i class="ri-magic-line"></i> Generate Image'; | |
| generateBtn.style.opacity = '1'; | |
| isGenerating = false; | |
| showToast("Generation failed. Check your connection.", "error"); | |
| }; | |
| }; | |
| /** | |
| * Adds generated image to the history grid | |
| */ | |
| const addToHistory = (url, prompt) => { | |
| // Prevent duplicates at the top | |
| if (history.length > 0 && history[0].url === url) return; | |
| history.unshift({ url, prompt }); | |
| if (history.length > 8) history.pop(); // Keep last 8 | |
| renderHistory(); | |
| }; | |
| /** | |
| * Renders the history grid | |
| */ | |
| const renderHistory = () => { | |
| historyGrid.innerHTML = ''; | |
| history.forEach((item) => { | |
| const div = document.createElement('div'); | |
| div.className = 'history-item'; | |
| div.innerHTML = `<img src="${item.url}" alt="History Item">`; | |
| div.title = item.prompt; | |
| div.onclick = () => { | |
| // Load history item to main view | |
| mainImage.src = item.url; | |
| currentImageUrl = item.url; | |
| mainImage.classList.add('loaded'); | |
| placeholderState.style.display = 'none'; | |
| imageActions.style.display = 'flex'; | |
| promptInput.value = item.prompt; | |
| }; | |
| historyGrid.appendChild(div); | |
| }); | |
| }; | |
| /** | |
| * Downloads the current image | |
| */ | |
| const handleDownload = async () => { | |
| if (!currentImageUrl) return; | |
| try { | |
| showToast("Downloading...", "success"); | |
| const response = await fetch(currentImageUrl); | |
| 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-turbo-${Date.now()}.jpg`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| window.URL.revokeObjectURL(url); | |
| document.body.removeChild(a); | |
| } catch (err) { | |
| showToast("Download failed", "error"); | |
| } | |
| }; | |
| /** | |
| * Displays a toast notification | |
| */ | |
| const showToast = (message, type = 'success') => { | |
| const toast = document.createElement('div'); | |
| toast.className = 'toast'; | |
| const icon = type === 'success' ? 'ri-checkbox-circle-line' : 'ri-error-warning-line'; | |
| toast.innerHTML = `<i class="${icon}"></i> <span>${message}</span>`; | |
| const container = document.getElementById('toastContainer'); | |
| container.appendChild(toast); | |
| // Remove after 3 seconds | |
| setTimeout(() => { | |
| toast.style.opacity = '0'; | |
| toast.style.transform = 'translateY(20px)'; | |
| setTimeout(() => toast.remove(), 300); | |
| }, 3000); | |
| }; | |
| // --- Event Listeners --- | |
| generateBtn.addEventListener('click', handleGenerate); | |
| // Allow Ctrl+Enter to generate | |
| document.addEventListener('keydown', (e) => { | |
| if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { | |
| handleGenerate(); | |
| } | |
| }); | |
| downloadBtn.addEventListener('click', handleDownload); | |
| fullscreenBtn.addEventListener('click', () => { | |
| if (mainImage.src) { | |
| window.open(currentImageUrl, '_blank'); | |
| } | |
| }); | |
| clearHistoryBtn.addEventListener('click', () => { | |
| history = []; | |
| renderHistory(); | |
| showToast("History cleared", "success"); | |
| }); | |
| // Initialize empty state | |
| renderHistory(); | |
| </script> | |
| </body> | |
| </html> |