|
|
| document.addEventListener('DOMContentLoaded', () => { |
| const elements = { |
| generateBtn: document.getElementById('generate-btn'), |
| surpriseBtn: document.getElementById('surprise-btn'), |
| randomSeedBtn: document.getElementById('random-seed-btn'), |
| resultImage: document.getElementById('result-image'), |
| paletteContainer: document.getElementById('palette-container'), |
| loader: document.getElementById('loader'), |
| backgroundContainer: document.getElementById('background-container'), |
| statusDisplay: document.getElementById('status-display'), |
| styleSelect: document.getElementById('style-select'), |
| seedInput: document.getElementById('seed-input'), |
| colorInput: document.getElementById('color-input'), |
| postGenerationTools: document.getElementById('post-generation-tools'), |
| copySeedBtn: document.getElementById('copy-seed-btn'), |
| downloadBtn: document.getElementById('download-btn'), |
| copySeedFeedback: document.getElementById('copy-seed-feedback'), |
| tagline: document.getElementById('tagline'), |
| recentCreationsSection: document.getElementById('recent-creations'), |
| recentGallery: document.getElementById('recent-gallery'), |
| particlesContainer: document.getElementById('particles'), |
| }; |
|
|
| |
| const API_URL = ''; |
|
|
| |
|
|
| |
| let backgroundImages = []; |
| let recentCreations = []; |
| let statusInterval; |
| const MAX_BG_IMAGES = 6; |
| const MAX_RECENTS = 4; |
|
|
| |
| const EXAMPLE_IMAGES = [ |
| 'examples/img1.png', |
| 'examples/img2.png', |
| 'examples/img3.png', |
| 'examples/img4.png', |
| 'examples/img5.png', |
| 'examples/img6.png', |
| ]; |
|
|
| |
| function createParticles() { |
| const colors = ['#4D6473', '#A58A73', '#B59B67', '#F4C2C2', '#E6CFC1']; |
| for (let i = 0; i < 20; i++) { |
| const particle = document.createElement('div'); |
| particle.className = 'particle'; |
| particle.style.width = Math.random() * 8 + 4 + 'px'; |
| particle.style.height = particle.style.width; |
| particle.style.left = Math.random() * 100 + '%'; |
| particle.style.top = Math.random() * 100 + '%'; |
| particle.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)]; |
| particle.style.animationDelay = Math.random() * 15 + 's'; |
| particle.style.animationDuration = (Math.random() * 10 + 10) + 's'; |
| elements.particlesContainer.appendChild(particle); |
| } |
| } |
|
|
| |
| function typeText(element, text, speed = 100) { |
| let i = 0; |
| element.innerHTML = ""; |
| const typing = setInterval(() => { |
| if (i < text.length) { |
| element.innerHTML += text.charAt(i); |
| i++; |
| } else { |
| clearInterval(typing); |
| setTimeout(() => { |
| element.style.textShadow = '0 0 20px rgba(77, 100, 115, 0.5)'; |
| setTimeout(() => { |
| element.style.textShadow = 'none'; |
| }, 1000); |
| }, 500); |
| } |
| }, speed); |
| } |
|
|
| |
| function addFloatingImage(imageUrl) { |
| const img = document.createElement('img'); |
| img.src = imageUrl; |
| img.className = 'floating-bg'; |
| if (backgroundImages.length >= MAX_BG_IMAGES) { |
| const oldImg = backgroundImages.shift(); |
| oldImg.style.opacity = '0'; |
| oldImg.style.transform = 'scale(0.8)'; |
| setTimeout(() => oldImg.remove(), 1000); |
| } |
| const size = Math.random() * 180 + 120; |
| img.style.width = `${size}px`; |
| img.style.height = 'auto'; |
| img.style.top = `${Math.random() * 80 + 10}%`; |
| img.style.left = `${Math.random() * 80 + 10}%`; |
| img.style.animationDuration = `${Math.random() * 15 + 20}s`; |
| img.style.zIndex = Math.floor(Math.random() * 3) + 1; |
| elements.backgroundContainer.appendChild(img); |
| backgroundImages.push(img); |
| } |
|
|
| function addFloatingImageFromExamples() { |
| const imageUrl = EXAMPLE_IMAGES[Math.floor(Math.random() * EXAMPLE_IMAGES.length)]; |
| addFloatingImage(imageUrl); |
| } |
|
|
| function addFloatingImageAt(imageUrl, topPercent, leftPercent, cellWPercent) { |
| const img = document.createElement('img'); |
| img.src = imageUrl; |
| img.className = 'floating-bg'; |
|
|
| if (backgroundImages.length >= MAX_BG_IMAGES) { |
| const oldImg = backgroundImages.shift(); |
| oldImg.style.opacity = '0'; |
| oldImg.style.transform = 'scale(0.8)'; |
| setTimeout(() => oldImg.remove(), 1000); |
| } |
|
|
| |
| const cellWidthPx = window.innerWidth * (cellWPercent / 100); |
| const targetWidth = Math.max(120, Math.min(220, cellWidthPx * 0.6)); |
| img.style.width = `${Math.round(targetWidth)}px`; |
| img.style.height = 'auto'; |
|
|
| |
| img.dataset.topPercent = String(topPercent); |
| img.dataset.leftPercent = String(leftPercent); |
| img.style.top = `${topPercent}%`; |
| img.style.left = `${leftPercent}%`; |
| img.style.animationDuration = `${Math.random() * 15 + 20}s`; |
| img.style.zIndex = Math.floor(Math.random() * 3) + 1; |
|
|
| elements.backgroundContainer.appendChild(img); |
|
|
| |
| if (img.complete) { |
| adjustToAvoidControls(img, 8); |
| } else { |
| img.addEventListener('load', () => adjustToAvoidControls(img, 8), { once: true }); |
| } |
|
|
| backgroundImages.push(img); |
| } |
|
|
| function computeEvenPositions(count, margin = 8) { |
| const cols = Math.ceil(Math.sqrt(count)); |
| const rows = Math.ceil(count / cols); |
| const availableW = 100 - 2 * margin; |
| const availableH = 100 - 2 * margin; |
| const cellW = availableW / cols; |
| const cellH = availableH / rows; |
| const positions = []; |
| for (let i = 0; i < count; i++) { |
| const r = Math.floor(i / cols); |
| const c = i % cols; |
| let top = margin + (r + 0.5) * cellH; |
| let left = margin + (c + 0.5) * cellW; |
| const jitterX = (Math.random() - 0.5) * cellW * 0.3; |
| const jitterY = (Math.random() - 0.5) * cellH * 0.3; |
| top += jitterY; |
| left += jitterX; |
| top = Math.max(margin, Math.min(100 - margin, top)); |
| left = Math.max(margin, Math.min(100 - margin, left)); |
| positions.push({ top, left }); |
| } |
| return { positions, cellW, cellH, rows, cols }; |
| } |
|
|
| function computeOverlapArea(r1, r2) { |
| const x = Math.max(0, Math.min(r1.right, r2.right) - Math.max(r1.left, r2.left)); |
| const y = Math.max(0, Math.min(r1.bottom, r2.bottom) - Math.max(r1.top, r2.top)); |
| return x * y; |
| } |
|
|
| function adjustToAvoidControls(img, margin = 8) { |
| const controls = document.querySelector('.controls'); |
| if (!controls) return; |
|
|
| const clamp = (val, min, max) => Math.max(min, Math.min(max, val)); |
| let topP = parseFloat(img.dataset.topPercent || '50'); |
| let leftP = parseFloat(img.dataset.leftPercent || '50'); |
|
|
| const step = 3; |
| const maxTries = 25; |
|
|
| for (let i = 0; i < maxTries; i++) { |
| const imgRect = img.getBoundingClientRect(); |
| const ctrlRect = controls.getBoundingClientRect(); |
| const imgArea = imgRect.width * imgRect.height; |
| if (!imgArea) break; |
|
|
| const overlap = computeOverlapArea(imgRect, ctrlRect); |
| const ratio = overlap / imgArea; |
| if (ratio <= 0.5) break; |
|
|
| const cx = ctrlRect.left + ctrlRect.width / 2; |
| const cy = ctrlRect.top + ctrlRect.height / 2; |
| const ix = imgRect.left + imgRect.width / 2; |
| const iy = imgRect.top + imgRect.height / 2; |
|
|
| const overlapW = Math.max(0, Math.min(imgRect.right, ctrlRect.right) - Math.max(imgRect.left, ctrlRect.left)); |
| const overlapH = Math.max(0, Math.min(imgRect.bottom, ctrlRect.bottom) - Math.max(imgRect.top, ctrlRect.top)); |
|
|
| if (overlapW >= overlapH) { |
| const dir = ix < cx ? -1 : 1; |
| leftP = clamp(leftP + dir * step, margin, 100 - margin); |
| } else { |
| const dir = iy < cy ? -1 : 1; |
| topP = clamp(topP + dir * step, margin, 100 - margin); |
| } |
|
|
| img.dataset.topPercent = String(topP); |
| img.dataset.leftPercent = String(leftP); |
| img.style.top = `${topP}%`; |
| img.style.left = `${leftP}%`; |
| } |
| } |
|
|
| |
| function startStatusUpdates() { |
| const messages = [ |
| " Mixing digital paint...", |
| " Consulting the AI muse...", |
| " Channeling creative energy...", |
| " Weaving visual magic...", |
| " Launching imagination...", |
| " Almost there..." |
| ]; |
| let msgIndex = 0; |
| elements.statusDisplay.textContent = messages[msgIndex]; |
| statusInterval = setInterval(() => { |
| msgIndex = (msgIndex + 1) % messages.length; |
| elements.statusDisplay.textContent = messages[msgIndex]; |
| }, 2500); |
| } |
|
|
| function stopStatusUpdates(finalMessage) { |
| clearInterval(statusInterval); |
| elements.statusDisplay.textContent = finalMessage; |
| elements.statusDisplay.style.transform = 'scale(1.1)'; |
| elements.statusDisplay.style.color = '#4D6473'; |
| setTimeout(() => { |
| elements.statusDisplay.style.transform = 'scale(1)'; |
| elements.statusDisplay.style.color = ''; |
| }, 300); |
| } |
|
|
| |
| function renderRecents() { |
| if (recentCreations.length === 0) return; |
| elements.recentGallery.innerHTML = ""; |
| recentCreations.forEach((creation, index) => { |
| const img = document.createElement('img'); |
| img.src = creation.src; |
| img.title = `Style: ${creation.style}, Seed: ${creation.seed}`; |
| img.style.animationDelay = `${index * 0.1}s`; |
| img.addEventListener('click', () => { |
| elements.styleSelect.value = creation.style; |
| elements.seedInput.value = creation.seed; |
| elements.colorInput.value = creation.color || ""; |
| window.scrollTo({ |
| top: 0, |
| behavior: 'smooth' |
| }); |
| elements.styleSelect.style.boxShadow = '0 0 20px rgba(77, 100, 115, 0.5)'; |
| setTimeout(() => { |
| elements.styleSelect.style.boxShadow = ''; |
| }, 2000); |
| }); |
| elements.recentGallery.appendChild(img); |
| }); |
| elements.recentCreationsSection.classList.remove('hidden'); |
| } |
|
|
| |
| function displayColorPalette(colors) { |
| elements.paletteContainer.innerHTML = ''; |
| colors.forEach((color, index) => { |
| const wrapper = document.createElement('div'); |
| wrapper.className = 'color-swatch-wrapper'; |
| wrapper.style.animationDelay = `${index * 0.1}s`; |
| const swatch = document.createElement('div'); |
| swatch.className = 'color-swatch'; |
| swatch.style.backgroundColor = color; |
| const hexCode = document.createElement('span'); |
| hexCode.className = 'hex-code'; |
| hexCode.textContent = color; |
| swatch.addEventListener('click', () => { |
| navigator.clipboard.writeText(color).then(() => { |
| hexCode.textContent = 'Copied!'; |
| swatch.style.transform = 'scale(1.3) rotate(10deg)'; |
| setTimeout(() => { |
| hexCode.textContent = color; |
| swatch.style.transform = ''; |
| }, 1500); |
| }); |
| }); |
| wrapper.appendChild(swatch); |
| wrapper.appendChild(hexCode); |
| elements.paletteContainer.appendChild(wrapper); |
| }); |
| } |
|
|
| |
| async function performGeneration() { |
| elements.loader.classList.remove('hidden'); |
| elements.resultImage.classList.add('hidden'); |
| elements.paletteContainer.innerHTML = ''; |
| elements.generateBtn.disabled = true; |
| elements.surpriseBtn.disabled = true; |
| elements.postGenerationTools.classList.add('hidden'); |
| startStatusUpdates(); |
|
|
| const generationData = { |
| style: elements.styleSelect.value, |
| resolution: document.getElementById('resolution-select').value, |
| seed: parseInt(elements.seedInput.value, 10), |
| color: elements.colorInput.value, |
| steps: 4, |
| }; |
|
|
| try { |
| const response = await fetch(`${API_URL}/generate-wallpaper/`, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json' |
| }, |
| body: JSON.stringify(generationData), |
| }); |
|
|
| if (!response.ok) { |
| throw new Error(`Server error! Status: ${response.status}`); |
| } |
|
|
| const data = await response.json(); |
|
|
| |
| elements.resultImage.src = data.image; |
| elements.resultImage.classList.remove('hidden'); |
|
|
|
|
| |
| elements.postGenerationTools.classList.remove('hidden'); |
| displayColorPalette(data.palette); |
|
|
| |
| recentCreations.unshift({ ...generationData, |
| src: data.image |
| }); |
| if (recentCreations.length > MAX_RECENTS) { |
| recentCreations.pop(); |
| } |
| renderRecents(); |
|
|
| stopStatusUpdates(' Masterpiece created!'); |
|
|
| } catch (error) { |
| console.error('Generation failed:', error); |
| stopStatusUpdates(`Error: ${error.message}`); |
| } finally { |
| elements.loader.classList.add('hidden'); |
| elements.generateBtn.disabled = false; |
| elements.surpriseBtn.disabled = false; |
| } |
| } |
|
|
| |
| async function updateRandomSeed() { |
| try { |
| const response = await fetch(`${API_URL}/random-seed/`); |
| if (!response.ok) { |
| throw new Error('Failed to fetch seed from server.'); |
| } |
| const data = await response.json(); |
| elements.seedInput.value = data.seed; |
|
|
| |
| elements.seedInput.style.background = 'rgba(77, 100, 115, 0.2)'; |
| setTimeout(() => { |
| elements.seedInput.style.background = ''; |
| }, 500); |
|
|
| } catch (error) { |
| console.error('Failed to get random seed:', error); |
| elements.seedInput.value = Math.floor(Math.random() * 999999); |
| } |
| } |
|
|
| elements.generateBtn.addEventListener('click', performGeneration); |
| elements.randomSeedBtn.addEventListener('click', updateRandomSeed); |
|
|
| elements.surpriseBtn.addEventListener('click', async () => { |
| const options = elements.styleSelect.options; |
| const randomIndex = Math.floor(Math.random() * options.length); |
| elements.styleSelect.value = options[randomIndex].value; |
| elements.colorInput.value = ""; |
| await updateRandomSeed(); |
| elements.surpriseBtn.style.transform = 'rotate(360deg)'; |
| setTimeout(() => { |
| elements.surpriseBtn.style.transform = ''; |
| performGeneration(); |
| }, 500); |
| }); |
|
|
| elements.copySeedBtn.addEventListener('click', () => { |
| const seedValue = elements.seedInput.value; |
| navigator.clipboard.writeText(seedValue).then(() => { |
| elements.copySeedFeedback.textContent = 'Copied!'; |
| elements.copySeedBtn.style.background = 'rgba(77, 100, 115, 0.3)'; |
| setTimeout(() => { |
| elements.copySeedFeedback.textContent = 'Copy Seed'; |
| elements.copySeedBtn.style.background = ''; |
| }, 2000); |
| }); |
| }); |
|
|
| elements.downloadBtn.addEventListener('click', () => { |
| if (!elements.resultImage.src || elements.resultImage.src.startsWith('http')) return; |
| const link = document.createElement('a'); |
| link.href = elements.resultImage.src; |
| const style = elements.styleSelect.value.replace(/\s+/g, '-').toLowerCase(); |
| const seed = elements.seedInput.value; |
| link.download = `wallpaper-${style}-${seed}.png`; |
| document.body.appendChild(link); |
| link.click(); |
| document.body.removeChild(link); |
| elements.downloadBtn.style.transform = 'scale(1.1)'; |
| setTimeout(() => { |
| elements.downloadBtn.style.transform = ''; |
| }, 200); |
| }); |
|
|
| |
| [elements.styleSelect, elements.colorInput, elements.seedInput].forEach(input => { |
| input.addEventListener('focus', (e) => { |
| e.target.style.transform = 'translateY(-2px)'; |
| }); |
| input.addEventListener('blur', (e) => { |
| e.target.style.transform = ''; |
| }); |
| }); |
|
|
| |
| createParticles(); |
| typeText(elements.tagline, "Create unique, high-resolution wallpapers with AI magic "); |
| if (typeof initThemeSelector === 'function') { |
| initThemeSelector(); |
| } |
|
|
| |
| const layout = computeEvenPositions(EXAMPLE_IMAGES.length, 8); |
| EXAMPLE_IMAGES.forEach((src, i) => { |
| const pos = layout.positions[i]; |
| setTimeout(() => addFloatingImageAt(src, pos.top, pos.left, layout.cellW), i * 400); |
| }); |
|
|
| |
| window.addEventListener('scroll', () => { |
| elements.backgroundContainer.style.transform = 'translateY(0)'; |
| }); |
|
|
| |
| document.addEventListener('keydown', (e) => { |
| if (e.ctrlKey || e.metaKey) { |
| switch (e.key) { |
| case 'Enter': |
| e.preventDefault(); |
| performGeneration(); |
| break; |
| case 'r': |
| e.preventDefault(); |
| updateRandomSeed(); |
| break; |
| case 's': |
| e.preventDefault(); |
| elements.surpriseBtn.click(); |
| break; |
| } |
| } |
| }); |
|
|
| console.log('AI Wallpaper Generator initialized successfully!'); |
| }); |