AI-Powered Art/Image Generator with User Prompts: Users provide text prompts, and an AI generates unique images, with interactive controls to refine or iterate on the visuals. (DALL-E is an example - Source 6.2)
HTML for Preview
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Image Generator Mock-up</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; }
.control-panel-bg { background-color: #f9fafb; /* Tailwind gray-50 */ }
.image-display-bg { background-color: #f3f4f6; /* Tailwind gray-100 */ }
.btn-primary { background-color: #4f46e5; /* Tailwind indigo-600 */ color: white; }
.btn-primary:hover { background-color: #4338ca; /* Tailwind indigo-700 */ }
.btn-secondary { background-color: #6b7280; /* Tailwind gray-500 */ color: white; }
.btn-secondary:hover { background-color: #4b5563; /* Tailwind gray-600 */ }
/* Custom scrollbar for webkit browsers */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Loading spinner */
.spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
width: 36px;
height: 36px;
border-radius: 50%;
border-left-color: #4f46e5; /* Tailwind indigo-600 */
animation: spin 1s ease infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Ensure buttons in a group don't wrap weirdly on small screens */
.button-group > button {
min-width: max-content;
}
</style>
</head>
<body class="bg-gray-200 text-gray-800 antialiased">
<div class="container mx-auto p-4 min-h-screen flex flex-col">
<header class="mb-6 text-center">
<h1 class="text-3xl sm:text-4xl font-bold text-indigo-700">AI-Powered Art Generator</h1>
<p class="text-md sm:text-lg text-gray-600 mt-2">Craft unique visuals from your textual descriptions. (Conceptual Mock-up)</p>
</header>
<main class="flex-grow flex flex-col lg:flex-row gap-6">
<div class="lg:w-2/5 xl:w-1/3 control-panel-bg p-6 rounded-xl shadow-xl flex flex-col gap-5">
<div>
<label for="prompt" class="block text-sm font-medium text-gray-700 mb-1">Enter Your Prompt:</label>
<textarea id="prompt" name="prompt" rows="4" class="w-full p-3 border border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500 focus:border-indigo-500 transition-shadow" placeholder="e.g., A majestic lion wearing a crown, digital painting"></textarea>
</div>
<div>
<label for="style" class="block text-sm font-medium text-gray-700 mb-1">Art Style:</label>
<select id="style" name="style" class="w-full p-3 border border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500 focus:border-indigo-500 bg-white transition-shadow">
<option value="photorealistic">Photorealistic</option>
<option value="impressionistic">Impressionistic</option>
<option value="surreal">Surreal</option>
<option value="abstract">Abstract</option>
<option value="cartoon">Cartoon</option>
<option value="pixel_art">Pixel Art</option>
<option value="fantasy">Fantasy Art</option>
<option value="sci_fi">Sci-Fi Concept Art</option>
<option value="watercolor">Watercolor</option>
<option value="oil_painting">Oil Painting</option>
<option value="anime">Anime / Manga</option>
<option value="cyberpunk">Cyberpunk</option>
<option value="steampunk">Steampunk</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Aspect Ratio:</label>
<div class="grid grid-cols-3 gap-2 button-group">
<button data-ratio="1:1" class="aspect-ratio-btn p-2 border border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors">1:1</button>
<button data-ratio="16:9" class="aspect-ratio-btn p-2 border border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors">16:9</button>
<button data-ratio="4:3" class="aspect-ratio-btn p-2 border border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors">4:3</button>
<button data-ratio="3:4" class="aspect-ratio-btn p-2 border border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors">3:4</button>
<button data-ratio="9:16" class="aspect-ratio-btn p-2 border border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors">9:16</button>
<button data-ratio="custom" class="aspect-ratio-btn p-2 border border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors">Custom</button>
</div>
<div id="custom-ratio-inputs" class="hidden mt-2 flex gap-2">
<input type="number" id="custom-width" placeholder="Width (px)" class="w-1/2 p-2 border border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500 transition-shadow" min="64" max="2048">
<input type="number" id="custom-height" placeholder="Height (px)" class="w-1/2 p-2 border border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500 transition-shadow" min="64" max="2048">
</div>
</div>
<div>
<label for="negative-prompt" class="block text-sm font-medium text-gray-700 mb-1">Negative Prompt (Optional):</label>
<textarea id="negative-prompt" name="negative-prompt" rows="2" class="w-full p-3 border border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500 focus:border-indigo-500 transition-shadow" placeholder="e.g., blurry, watermark, text, ugly, deformed"></textarea>
</div>
<div class="mt-auto pt-5 border-t border-gray-300">
<p class="text-xs text-gray-500 mb-3">Note: Image generation is simulated. No actual AI is used. This is a UI concept.</p>
<button id="generateBtn" class="w-full btn-primary font-semibold py-3 px-4 rounded-lg shadow-md hover:shadow-lg transition duration-150 ease-in-out flex items-center justify-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-wand-2"><path d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72Z"/><path d="m14 7 3 3"/><path d="M5 6v4"/><path d="M19 14v4"/><path d="M10 2v2"/><path d="M7 8H3"/><path d="M17 18h-4"/></svg>
Generate Image
</button>
</div>
</div>
<div class="lg:w-3/5 xl:w-2/3 image-display-bg p-4 sm:p-6 rounded-xl shadow-xl flex flex-col items-center justify-center relative">
<div id="image-container" class="w-full max-w-3xl bg-gray-700 rounded-lg overflow-hidden flex items-center justify-center relative shadow-inner">
<img id="generatedImage" src="https://placehold.co/512x512/4B5563/9CA3AF?text=Your+AI+Art+Will+Appear+Here" alt="Generated AI Art" class="object-contain w-full h-full transition-opacity duration-500">
<div id="loading-overlay" class="absolute inset-0 bg-gray-800 bg-opacity-80 flex flex-col items-center justify-center hidden transition-opacity duration-300">
<div class="spinner"></div>
<p class="text-white mt-4 font-medium text-lg">Generating your masterpiece...</p>
</div>
<div id="placeholder-text-container" class="absolute inset-0 flex items-center justify-center pointer-events-none">
<p class="text-gray-400 text-xl text-center p-4">Your AI Art Will Appear Here</p>
</div>
</div>
<div class="mt-6 w-full max-w-3xl flex flex-col sm:flex-row gap-3 justify-center button-group">
<button id="regenerateBtn" class="flex-1 btn-secondary font-semibold py-3 px-4 rounded-lg shadow-md hover:shadow-lg transition duration-150 ease-in-out flex items-center justify-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed" disabled>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-refresh-cw"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M3 21c-1.66 0-3-1.34-3-3s1.34-3 3-3c1.06 0 1.98.54 2.5 1.35"/><path d="M21 15v4a2 2 0 0 1-2 2H5.5a2.5 2.5 0 0 1-2.5-2.5V15"/></svg>
Regenerate
</button>
<button id="downloadBtn" class="flex-1 bg-green-600 hover:bg-green-700 text-white font-semibold py-3 px-4 rounded-lg shadow-md hover:shadow-lg transition duration-150 ease-in-out flex items-center justify-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed" disabled>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>
Download
</button>
</div>
<p class="text-xs text-gray-500 mt-4 text-center">Download functionality is for demonstration purposes only.</p>
</div>
</main>
<footer class="mt-8 py-4 text-center text-sm text-gray-500 border-t border-gray-300">
<p>© <span id="currentYear"></span> AI Art Generator Mock-up. All rights reserved (conceptually).</p>
<p>This interface is a simulation and does not connect to any actual AI image generation models like DALL-E.</p>
</footer>
</div>
<script>
// --- DOM Elements ---
const promptTextarea = document.getElementById('prompt');
const styleSelect = document.getElementById('style');
const negativePromptTextarea = document.getElementById('negative-prompt');
const generateBtn = document.getElementById('generateBtn');
const regenerateBtn = document.getElementById('regenerateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const generatedImage = document.getElementById('generatedImage');
const imageContainer = document.getElementById('image-container');
const loadingOverlay = document.getElementById('loading-overlay');
const placeholderTextContainer = document.getElementById('placeholder-text-container');
const aspectRatioBtns = document.querySelectorAll('.aspect-ratio-btn');
const customRatioInputs = document.getElementById('custom-ratio-inputs');
const customWidthInput = document.getElementById('custom-width');
const customHeightInput = document.getElementById('custom-height');
const initialPlaceholderSrc = 'https://placehold.co/512x512/4B5563/9CA3AF?text=Your+AI+Art+Will+Appear+Here';
// --- State Variables ---
let currentImageSeed = null;
let currentAspectRatio = { width: 512, height: 512, ratioString: '1/1' }; // Default 1:1
// --- Helper Functions ---
function setButtonsDisabledState(disabled) {
generateBtn.disabled = disabled;
// Regenerate and Download should only be enabled if an image has been "generated"
if (currentImageSeed !== null) {
regenerateBtn.disabled = disabled;
downloadBtn.disabled = disabled;
} else {
regenerateBtn.disabled = true;
downloadBtn.disabled = true;
}
}
function showLoading(isLoading) {
if (isLoading) {
loadingOverlay.classList.remove('hidden');
placeholderTextContainer.classList.add('hidden');
generatedImage.classList.add('opacity-0');
} else {
loadingOverlay.classList.add('hidden');
generatedImage.classList.remove('opacity-0');
// Show placeholder text only if the image src is the initial one
if (generatedImage.src === initialPlaceholderSrc) {
placeholderTextContainer.classList.remove('hidden');
} else {
placeholderTextContainer.classList.add('hidden');
}
}
}
// --- Aspect Ratio Logic ---
function updateImageContainerAspectRatio() {
imageContainer.style.aspectRatio = currentAspectRatio.ratioString;
// Update placeholder if it's currently showing the default text
if (generatedImage.src === initialPlaceholderSrc || generatedImage.src.startsWith('https://placehold.co/')) {
if (placeholderTextContainer.classList.contains('hidden') && generatedImage.src !== initialPlaceholderSrc) {
// If an image was already generated, update its dimensions
const url = new URL(generatedImage.src);
const text = url.searchParams.get('text') || "Generated Art";
const colors = url.pathname.substring(1).split('/').slice(1,3).join('/'); // e.g. 374151/9ca3af
generatedImage.src = `https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/${colors}?text=${encodeURIComponent(text)}`;
} else {
// Otherwise, just update the default placeholder
generatedImage.src = `https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/4B5563/9CA3AF?text=Your+AI+Art+Will+Appear+Here`;
}
}
}
aspectRatioBtns.forEach(btn => {
btn.addEventListener('click', () => {
aspectRatioBtns.forEach(b => b.classList.remove('bg-indigo-200', 'ring-2', 'ring-indigo-500', 'font-semibold'));
btn.classList.add('bg-indigo-200', 'ring-2', 'ring-indigo-500', 'font-semibold');
const ratio = btn.dataset.ratio;
if (ratio === 'custom') {
customRatioInputs.classList.remove('hidden');
customWidthInput.value = customWidthInput.value || currentAspectRatio.width;
customHeightInput.value = customHeightInput.value || currentAspectRatio.height;
// Trigger update if custom is selected, using current input values or defaults
handleCustomRatioChange();
} else {
customRatioInputs.classList.add('hidden');
const [w, h] = ratio.split(':').map(Number);
currentAspectRatio = { width: w * 128, height: h * 128, ratioString: `${w}/${h}` }; // Scale for placeholder
if (w === 1 && h === 1) currentAspectRatio = { width: 512, height: 512, ratioString: '1/1'};
if (w === 16 && h === 9) currentAspectRatio = { width: 1024, height: 576, ratioString: '16/9'};
if (w === 4 && h === 3) currentAspectRatio = { width: 640, height: 480, ratioString: '4/3'};
if (w === 3 && h === 4) currentAspectRatio = { width: 480, height: 640, ratioString: '3/4'};
if (w === 9 && h === 16) currentAspectRatio = { width: 576, height: 1024, ratioString: '9/16'};
updateImageContainerAspectRatio();
}
});
});
function handleCustomRatioChange() {
let width = parseInt(customWidthInput.value);
let height = parseInt(customHeightInput.value);
// Basic validation and clamping
width = Math.max(64, Math.min(2048, width || currentAspectRatio.width));
height = Math.max(64, Math.min(2048, height || currentAspectRatio.height));
customWidthInput.value = width; // Update input field with validated value
customHeightInput.value = height; // Update input field with validated value
if (width > 0 && height > 0) {
currentAspectRatio = { width, height, ratioString: `${width}/${height}` };
updateImageContainerAspectRatio();
}
}
customWidthInput.addEventListener('change', handleCustomRatioChange); // Use 'change' to update when focus is lost or enter is pressed
customHeightInput.addEventListener('change', handleCustomRatioChange);
// --- Image Generation Logic ---
function simulateImageGeneration(isRegeneration = false) {
const prompt = promptTextarea.value.trim();
if (!prompt && !isRegeneration) {
// Using a more subtle way to indicate error than alert()
promptTextarea.classList.add('border-red-500', 'ring-red-500');
promptTextarea.focus();
setTimeout(() => {
promptTextarea.classList.remove('border-red-500', 'ring-red-500');
}, 2000);
return;
}
showLoading(true);
setButtonsDisabledState(true);
setTimeout(() => {
const style = styleSelect.value;
currentImageSeed = isRegeneration ? (currentImageSeed || Date.now()) + 1 : Date.now();
let placeholderText = prompt.substring(0, 25) || "Generated Image";
if (style) placeholderText += ` (${style.replace(/_/g, ' ')})`;
placeholderText += ` #${currentImageSeed % 1000}`;
const bgColor = Math.floor(Math.random()*127 + 64).toString(16).padStart(2,'0') + // R
Math.floor(Math.random()*127 + 64).toString(16).padStart(2,'0') + // G
Math.floor(Math.random()*127 + 64).toString(16).padStart(2,'0'); // B (avoiding too dark/light)
const textColor = 'FFFFFF';
generatedImage.src = `https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/${bgColor}/${textColor}?text=${encodeURIComponent(placeholderText)}`;
generatedImage.onload = () => {
showLoading(false);
setButtonsDisabledState(false);
};
generatedImage.onerror = () => {
showLoading(false);
setButtonsDisabledState(false);
// Show error in placeholder
generatedImage.src = `https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/FF0000/FFFFFF?text=Error+Loading+Image`;
placeholderTextContainer.classList.add('hidden'); // Ensure main placeholder text is hidden
};
}, 1500 + Math.random() * 1000);
}
generateBtn.addEventListener('click', () => simulateImageGeneration(false));
regenerateBtn.addEventListener('click', () => {
if (!promptTextarea.value.trim() && !currentImageSeed) {
promptTextarea.classList.add('border-red-500', 'ring-red-500');
promptTextarea.focus();
setTimeout(() => {
promptTextarea.classList.remove('border-red-500', 'ring-red-500');
}, 2000);
return;
}
simulateImageGeneration(true);
});
// --- Download Logic (Conceptual) ---
downloadBtn.addEventListener('click', () => {
if (generatedImage.src && generatedImage.src !== initialPlaceholderSrc && !generatedImage.src.includes('Error+Loading')) {
const link = document.createElement('a');
link.href = generatedImage.src;
const urlParams = new URLSearchParams(new URL(generatedImage.src).search);
const textParam = urlParams.get('text') || 'ai_art_simulation';
const filename = textParam.replace(/[^a-z0-9_]+/gi, '_').toLowerCase().substring(0,50) + '.png';
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
const originalText = downloadBtn.innerHTML;
const checkSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check"><polyline points="20 6 9 17 4 12"></polyline></svg> Downloaded!`;
downloadBtn.innerHTML = checkSVG;
setTimeout(() => { downloadBtn.innerHTML = originalText; }, 2500);
} else {
// Could show a small message here instead of alert
console.warn("No image generated to download or image is the initial placeholder/error.");
}
});
// --- Initial Setup ---
document.getElementById('currentYear').textContent = new Date().getFullYear();
// Set 1:1 as default active aspect ratio button
const defaultRatioButton = document.querySelector('.aspect-ratio-btn[data-ratio="1:1"]');
if (defaultRatioButton) {
defaultRatioButton.classList.add('bg-indigo-200', 'ring-2', 'ring-indigo-500', 'font-semibold');
}
updateImageContainerAspectRatio(); // Apply initial aspect ratio to container
showLoading(false); // Ensure correct initial visibility of placeholder/image
setButtonsDisabledState(true); // Regenerate/Download disabled initially
</script>
</body>
</html>
----------------------------------------
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Image Generator Mock-up</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; }
.control-panel-bg { background-color: #f9fafb; /* Tailwind gray-50 */ }
.image-display-bg { background-color: #f3f4f6; /* Tailwind gray-100 */ }
.btn-primary { background-color: #4f46e5; /* Tailwind indigo-600 */ color: white; }
.btn-primary:hover { background-color: #4338ca; /* Tailwind indigo-700 */ }
.btn-secondary { background-color: #6b7280; /* Tailwind gray-500 */ color: white; }
.btn-secondary:hover { background-color: #4b5563; /* Tailwind gray-600 */ }
/* Custom scrollbar for webkit browsers */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Loading spinner */
.spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
width: 36px;
height: 36px;
border-radius: 50%;
border-left-color: #4f46e5; /* Tailwind indigo-600 */
animation: spin 1s ease infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Ensure buttons in a group don't wrap weirdly on small screens */
.button-group > button {
min-width: max-content;
}
</style>
</head>
<body class="bg-gray-200 text-gray-800 antialiased">
<div class="container mx-auto p-4 min-h-screen flex flex-col">
<header class="mb-6 text-center">
<h1 class="text-3xl sm:text-4xl font-bold text-indigo-700">AI-Powered Art Generator</h1>
<p class="text-md sm:text-lg text-gray-600 mt-2">Craft unique visuals from your textual descriptions. (Conceptual Mock-up)</p>
</header>
<main class="flex-grow flex flex-col lg:flex-row gap-6">
<div class="lg:w-2/5 xl:w-1/3 control-panel-bg p-6 rounded-xl shadow-xl flex flex-col gap-5">
<div>
<label for="prompt" class="block text-sm font-medium text-gray-700 mb-1">Enter Your Prompt:</label>
<textarea id="prompt" name="prompt" rows="4" class="w-full p-3 border border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500 focus:border-indigo-500 transition-shadow" placeholder="e.g., A majestic lion wearing a crown, digital painting"></textarea>
</div>
<div>
<label for="style" class="block text-sm font-medium text-gray-700 mb-1">Art Style:</label>
<select id="style" name="style" class="w-full p-3 border border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500 focus:border-indigo-500 bg-white transition-shadow">
<option value="photorealistic">Photorealistic</option>
<option value="impressionistic">Impressionistic</option>
<option value="surreal">Surreal</option>
<option value="abstract">Abstract</option>
<option value="cartoon">Cartoon</option>
<option value="pixel_art">Pixel Art</option>
<option value="fantasy">Fantasy Art</option>
<option value="sci_fi">Sci-Fi Concept Art</option>
<option value="watercolor">Watercolor</option>
<option value="oil_painting">Oil Painting</option>
<option value="anime">Anime / Manga</option>
<option value="cyberpunk">Cyberpunk</option>
<option value="steampunk">Steampunk</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Aspect Ratio:</label>
<div class="grid grid-cols-3 gap-2 button-group">
<button data-ratio="1:1" class="aspect-ratio-btn p-2 border border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors">1:1</button>
<button data-ratio="16:9" class="aspect-ratio-btn p-2 border border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors">16:9</button>
<button data-ratio="4:3" class="aspect-ratio-btn p-2 border border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors">4:3</button>
<button data-ratio="3:4" class="aspect-ratio-btn p-2 border border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors">3:4</button>
<button data-ratio="9:16" class="aspect-ratio-btn p-2 border border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors">9:16</button>
<button data-ratio="custom" class="aspect-ratio-btn p-2 border border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors">Custom</button>
</div>
<div id="custom-ratio-inputs" class="hidden mt-2 flex gap-2">
<input type="number" id="custom-width" placeholder="Width (px)" class="w-1/2 p-2 border border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500 transition-shadow" min="64" max="2048">
<input type="number" id="custom-height" placeholder="Height (px)" class="w-1/2 p-2 border border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500 transition-shadow" min="64" max="2048">
</div>
</div>
<div>
<label for="negative-prompt" class="block text-sm font-medium text-gray-700 mb-1">Negative Prompt (Optional):</label>
<textarea id="negative-prompt" name="negative-prompt" rows="2" class="w-full p-3 border border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500 focus:border-indigo-500 transition-shadow" placeholder="e.g., blurry, watermark, text, ugly, deformed"></textarea>
</div>
<div class="mt-auto pt-5 border-t border-gray-300">
<p class="text-xs text-gray-500 mb-3">Note: Image generation is simulated. No actual AI is used. This is a UI concept.</p>
<button id="generateBtn" class="w-full btn-primary font-semibold py-3 px-4 rounded-lg shadow-md hover:shadow-lg transition duration-150 ease-in-out flex items-center justify-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-wand-2"><path d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72Z"/><path d="m14 7 3 3"/><path d="M5 6v4"/><path d="M19 14v4"/><path d="M10 2v2"/><path d="M7 8H3"/><path d="M17 18h-4"/></svg>
Generate Image
</button>
</div>
</div>
<div class="lg:w-3/5 xl:w-2/3 image-display-bg p-4 sm:p-6 rounded-xl shadow-xl flex flex-col items-center justify-center relative">
<div id="image-container" class="w-full max-w-3xl bg-gray-700 rounded-lg overflow-hidden flex items-center justify-center relative shadow-inner">
<img id="generatedImage" src="https://placehold.co/512x512/4B5563/9CA3AF?text=Your+AI+Art+Will+Appear+Here" alt="Generated AI Art" class="object-contain w-full h-full transition-opacity duration-500">
<div id="loading-overlay" class="absolute inset-0 bg-gray-800 bg-opacity-80 flex flex-col items-center justify-center hidden transition-opacity duration-300">
<div class="spinner"></div>
<p class="text-white mt-4 font-medium text-lg">Generating your masterpiece...</p>
</div>
<div id="placeholder-text-container" class="absolute inset-0 flex items-center justify-center pointer-events-none">
<p class="text-gray-400 text-xl text-center p-4">Your AI Art Will Appear Here</p>
</div>
</div>
<div class="mt-6 w-full max-w-3xl flex flex-col sm:flex-row gap-3 justify-center button-group">
<button id="regenerateBtn" class="flex-1 btn-secondary font-semibold py-3 px-4 rounded-lg shadow-md hover:shadow-lg transition duration-150 ease-in-out flex items-center justify-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed" disabled>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-refresh-cw"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M3 21c-1.66 0-3-1.34-3-3s1.34-3 3-3c1.06 0 1.98.54 2.5 1.35"/><path d="M21 15v4a2 2 0 0 1-2 2H5.5a2.5 2.5 0 0 1-2.5-2.5V15"/></svg>
Regenerate
</button>
<button id="downloadBtn" class="flex-1 bg-green-600 hover:bg-green-700 text-white font-semibold py-3 px-4 rounded-lg shadow-md hover:shadow-lg transition duration-150 ease-in-out flex items-center justify-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed" disabled>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>
Download
</button>
</div>
<p class="text-xs text-gray-500 mt-4 text-center">Download functionality is for demonstration purposes only.</p>
</div>
</main>
<footer class="mt-8 py-4 text-center text-sm text-gray-500 border-t border-gray-300">
<p>© <span id="currentYear"></span> AI Art Generator Mock-up. All rights reserved (conceptually).</p>
<p>This interface is a simulation and does not connect to any actual AI image generation models like DALL-E.</p>
</footer>
</div>
<script>
// --- DOM Elements ---
const promptTextarea = document.getElementById('prompt');
const styleSelect = document.getElementById('style');
const negativePromptTextarea = document.getElementById('negative-prompt');
const generateBtn = document.getElementById('generateBtn');
const regenerateBtn = document.getElementById('regenerateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const generatedImage = document.getElementById('generatedImage');
const imageContainer = document.getElementById('image-container');
const loadingOverlay = document.getElementById('loading-overlay');
const placeholderTextContainer = document.getElementById('placeholder-text-container');
const aspectRatioBtns = document.querySelectorAll('.aspect-ratio-btn');
const customRatioInputs = document.getElementById('custom-ratio-inputs');
const customWidthInput = document.getElementById('custom-width');
const customHeightInput = document.getElementById('custom-height');
const initialPlaceholderSrc = 'https://placehold.co/512x512/4B5563/9CA3AF?text=Your+AI+Art+Will+Appear+Here';
// --- State Variables ---
let currentImageSeed = null;
let currentAspectRatio = { width: 512, height: 512, ratioString: '1/1' }; // Default 1:1
// --- Helper Functions ---
function setButtonsDisabledState(disabled) {
generateBtn.disabled = disabled;
// Regenerate and Download should only be enabled if an image has been "generated"
if (currentImageSeed !== null) {
regenerateBtn.disabled = disabled;
downloadBtn.disabled = disabled;
} else {
regenerateBtn.disabled = true;
downloadBtn.disabled = true;
}
}
function showLoading(isLoading) {
if (isLoading) {
loadingOverlay.classList.remove('hidden');
placeholderTextContainer.classList.add('hidden');
generatedImage.classList.add('opacity-0');
} else {
loadingOverlay.classList.add('hidden');
generatedImage.classList.remove('opacity-0');
// Show placeholder text only if the image src is the initial one
if (generatedImage.src === initialPlaceholderSrc) {
placeholderTextContainer.classList.remove('hidden');
} else {
placeholderTextContainer.classList.add('hidden');
}
}
}
// --- Aspect Ratio Logic ---
function updateImageContainerAspectRatio() {
imageContainer.style.aspectRatio = currentAspectRatio.ratioString;
// Update placeholder if it's currently showing the default text
if (generatedImage.src === initialPlaceholderSrc || generatedImage.src.startsWith('https://placehold.co/')) {
if (placeholderTextContainer.classList.contains('hidden') && generatedImage.src !== initialPlaceholderSrc) {
// If an image was already generated, update its dimensions
const url = new URL(generatedImage.src);
const text = url.searchParams.get('text') || "Generated Art";
const colors = url.pathname.substring(1).split('/').slice(1,3).join('/'); // e.g. 374151/9ca3af
generatedImage.src = `https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/${colors}?text=${encodeURIComponent(text)}`;
} else {
// Otherwise, just update the default placeholder
generatedImage.src = `https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/4B5563/9CA3AF?text=Your+AI+Art+Will+Appear+Here`;
}
}
}
aspectRatioBtns.forEach(btn => {
btn.addEventListener('click', () => {
aspectRatioBtns.forEach(b => b.classList.remove('bg-indigo-200', 'ring-2', 'ring-indigo-500', 'font-semibold'));
btn.classList.add('bg-indigo-200', 'ring-2', 'ring-indigo-500', 'font-semibold');
const ratio = btn.dataset.ratio;
if (ratio === 'custom') {
customRatioInputs.classList.remove('hidden');
customWidthInput.value = customWidthInput.value || currentAspectRatio.width;
customHeightInput.value = customHeightInput.value || currentAspectRatio.height;
// Trigger update if custom is selected, using current input values or defaults
handleCustomRatioChange();
} else {
customRatioInputs.classList.add('hidden');
const [w, h] = ratio.split(':').map(Number);
currentAspectRatio = { width: w * 128, height: h * 128, ratioString: `${w}/${h}` }; // Scale for placeholder
if (w === 1 && h === 1) currentAspectRatio = { width: 512, height: 512, ratioString: '1/1'};
if (w === 16 && h === 9) currentAspectRatio = { width: 1024, height: 576, ratioString: '16/9'};
if (w === 4 && h === 3) currentAspectRatio = { width: 640, height: 480, ratioString: '4/3'};
if (w === 3 && h === 4) currentAspectRatio = { width: 480, height: 640, ratioString: '3/4'};
if (w === 9 && h === 16) currentAspectRatio = { width: 576, height: 1024, ratioString: '9/16'};
updateImageContainerAspectRatio();
}
});
});
function handleCustomRatioChange() {
let width = parseInt(customWidthInput.value);
let height = parseInt(customHeightInput.value);
// Basic validation and clamping
width = Math.max(64, Math.min(2048, width || currentAspectRatio.width));
height = Math.max(64, Math.min(2048, height || currentAspectRatio.height));
customWidthInput.value = width; // Update input field with validated value
customHeightInput.value = height; // Update input field with validated value
if (width > 0 && height > 0) {
currentAspectRatio = { width, height, ratioString: `${width}/${height}` };
updateImageContainerAspectRatio();
}
}
customWidthInput.addEventListener('change', handleCustomRatioChange); // Use 'change' to update when focus is lost or enter is pressed
customHeightInput.addEventListener('change', handleCustomRatioChange);
// --- Image Generation Logic ---
function simulateImageGeneration(isRegeneration = false) {
const prompt = promptTextarea.value.trim();
if (!prompt && !isRegeneration) {
// Using a more subtle way to indicate error than alert()
promptTextarea.classList.add('border-red-500', 'ring-red-500');
promptTextarea.focus();
setTimeout(() => {
promptTextarea.classList.remove('border-red-500', 'ring-red-500');
}, 2000);
return;
}
showLoading(true);
setButtonsDisabledState(true);
setTimeout(() => {
const style = styleSelect.value;
currentImageSeed = isRegeneration ? (currentImageSeed || Date.now()) + 1 : Date.now();
let placeholderText = prompt.substring(0, 25) || "Generated Image";
if (style) placeholderText += ` (${style.replace(/_/g, ' ')})`;
placeholderText += ` #${currentImageSeed % 1000}`;
const bgColor = Math.floor(Math.random()*127 + 64).toString(16).padStart(2,'0') + // R
Math.floor(Math.random()*127 + 64).toString(16).padStart(2,'0') + // G
Math.floor(Math.random()*127 + 64).toString(16).padStart(2,'0'); // B (avoiding too dark/light)
const textColor = 'FFFFFF';
generatedImage.src = `https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/${bgColor}/${textColor}?text=${encodeURIComponent(placeholderText)}`;
generatedImage.onload = () => {
showLoading(false);
setButtonsDisabledState(false);
};
generatedImage.onerror = () => {
showLoading(false);
setButtonsDisabledState(false);
// Show error in placeholder
generatedImage.src = `https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/FF0000/FFFFFF?text=Error+Loading+Image`;
placeholderTextContainer.classList.add('hidden'); // Ensure main placeholder text is hidden
};
}, 1500 + Math.random() * 1000);
}
generateBtn.addEventListener('click', () => simulateImageGeneration(false));
regenerateBtn.addEventListener('click', () => {
if (!promptTextarea.value.trim() && !currentImageSeed) {
promptTextarea.classList.add('border-red-500', 'ring-red-500');
promptTextarea.focus();
setTimeout(() => {
promptTextarea.classList.remove('border-red-500', 'ring-red-500');
}, 2000);
return;
}
simulateImageGeneration(true);
});
// --- Download Logic (Conceptual) ---
downloadBtn.addEventListener('click', () => {
if (generatedImage.src && generatedImage.src !== initialPlaceholderSrc && !generatedImage.src.includes('Error+Loading')) {
const link = document.createElement('a');
link.href = generatedImage.src;
const urlParams = new URLSearchParams(new URL(generatedImage.src).search);
const textParam = urlParams.get('text') || 'ai_art_simulation';
const filename = textParam.replace(/[^a-z0-9_]+/gi, '_').toLowerCase().substring(0,50) + '.png';
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
const originalText = downloadBtn.innerHTML;
const checkSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check"><polyline points="20 6 9 17 4 12"></polyline></svg> Downloaded!`;
downloadBtn.innerHTML = checkSVG;
setTimeout(() => { downloadBtn.innerHTML = originalText; }, 2500);
} else {
// Could show a small message here instead of alert
console.warn("No image generated to download or image is the initial placeholder/error.");
}
});
// --- Initial Setup ---
document.getElementById('currentYear').textContent = new Date().getFullYear();
// Set 1:1 as default active aspect ratio button
const defaultRatioButton = document.querySelector('.aspect-ratio-btn[data-ratio="1:1"]');
if (defaultRatioButton) {
defaultRatioButton.classList.add('bg-indigo-200', 'ring-2', 'ring-indigo-500', 'font-semibold');
}
updateImageContainerAspectRatio(); // Apply initial aspect ratio to container
showLoading(false); // Ensure correct initial visibility of placeholder/image
setButtonsDisabledState(true); // Regenerate/Download disabled initially
</script>
</body>
</html>
-----------------------
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Image Generator Mock-up</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; }
.control-panel-bg { background-color: #f9fafb; /* Tailwind gray-50 */ }
.image-display-bg { background-color: #f3f4f6; /* Tailwind gray-100 */ }
.btn-primary { background-color: #4f46e5; /* Tailwind indigo-600 */ color: white; }
.btn-primary:hover { background-color: #4338ca; /* Tailwind indigo-700 */ }
.btn-secondary { background-color: #6b7280; /* Tailwind gray-500 */ color: white; }
.btn-secondary:hover { background-color: #4b5563; /* Tailwind gray-600 */ }
/* Custom scrollbar for webkit browsers */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Loading spinner */
.spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
width: 36px;
height: 36px;
border-radius: 50%;
border-left-color: #4f46e5; /* Tailwind indigo-600 */
animation: spin 1s ease infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Ensure buttons in a group don't wrap weirdly on small screens */
.button-group > button {
min-width: max-content;
}
</style>
</head>
<body class="bg-gray-200 text-gray-800 antialiased">
<div class="container mx-auto p-4 min-h-screen flex flex-col">
<header class="mb-6 text-center">
<h1 class="text-3xl sm:text-4xl font-bold text-indigo-700">AI-Powered Art Generator</h1>
<p class="text-md sm:text-lg text-gray-600 mt-2">Craft unique
visuals from your textual descriptions. (Conceptual Mock-up)</p>
</header>
<main class="flex-grow flex flex-col lg:flex-row gap-6">
<div class="lg:w-2/5 xl:w-1/3 control-panel-bg p-6 rounded-xl shadow-xl flex flex-col gap-5">
<div>
<label for="prompt" class="block text-sm font-medium text-gray-700
mb-1">Enter Your Prompt:</label>
<textarea id="prompt" name="prompt" rows="4" class="w-full p-3 border
border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500
focus:border-indigo-500 transition-shadow" placeholder="e.g., A majestic
lion wearing a crown, digital painting"></textarea>
</div>
<div>
<label for="style" class="block text-sm font-medium text-gray-700 mb-1">Art Style:</label>
<select id="style" name="style" class="w-full p-3 border
border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500
focus:border-indigo-500 bg-white transition-shadow">
<option value="photorealistic">Photorealistic</option>
<option value="impressionistic">Impressionistic</option>
<option value="surreal">Surreal</option>
<option value="abstract">Abstract</option>
<option value="cartoon">Cartoon</option>
<option value="pixel_art">Pixel Art</option>
<option value="fantasy">Fantasy Art</option>
<option value="sci_fi">Sci-Fi Concept Art</option>
<option value="watercolor">Watercolor</option>
<option value="oil_painting">Oil Painting</option>
<option value="anime">Anime / Manga</option>
<option value="cyberpunk">Cyberpunk</option>
<option value="steampunk">Steampunk</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Aspect Ratio:</label>
<div class="grid grid-cols-3 gap-2 button-group">
<button data-ratio="1:1" class="aspect-ratio-btn p-2 border
border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
transition-colors">1:1</button>
<button data-ratio="16:9" class="aspect-ratio-btn p-2 border
border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
transition-colors">16:9</button>
<button data-ratio="4:3" class="aspect-ratio-btn p-2 border
border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
transition-colors">4:3</button>
<button data-ratio="3:4" class="aspect-ratio-btn p-2 border
border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
transition-colors">3:4</button>
<button data-ratio="9:16" class="aspect-ratio-btn p-2 border
border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
transition-colors">9:16</button>
<button data-ratio="custom" class="aspect-ratio-btn p-2 border
border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
transition-colors">Custom</button>
</div>
<div id="custom-ratio-inputs" class="hidden mt-2 flex gap-2">
<input type="number" id="custom-width" placeholder="Width (px)"
class="w-1/2 p-2 border border-gray-300 rounded-md focus:ring-indigo-500
focus:border-indigo-500 transition-shadow" min="64" max="2048">
<input type="number" id="custom-height" placeholder="Height (px)"
class="w-1/2 p-2 border border-gray-300 rounded-md focus:ring-indigo-500
focus:border-indigo-500 transition-shadow" min="64" max="2048">
</div>
</div>
<div>
<label for="negative-prompt" class="block text-sm font-medium
text-gray-700 mb-1">Negative Prompt (Optional):</label>
<textarea id="negative-prompt" name="negative-prompt" rows="2"
class="w-full p-3 border border-gray-300 rounded-lg shadow-sm
focus:ring-indigo-500 focus:border-indigo-500 transition-shadow"
placeholder="e.g., blurry, watermark, text, ugly,
deformed"></textarea>
</div>
<div class="mt-auto pt-5 border-t border-gray-300">
<p class="text-xs text-gray-500 mb-3">Note: Image generation is
simulated. No actual AI is used. This is a UI concept.</p>
<button id="generateBtn" class="w-full btn-primary font-semibold
py-3 px-4 rounded-lg shadow-md hover:shadow-lg transition duration-150
ease-in-out flex items-center justify-center gap-2 disabled:opacity-60
disabled:cursor-not-allowed">
<svg
xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0
24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" class="lucide
lucide-wand-2"><path d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72
0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72
0L21.64 5.36a1.2 1.2 0 0 0 0-1.72Z"/><path d="m14 7 3
3"/><path d="M5 6v4"/><path d="M19 14v4"/><path d="M10
2v2"/><path d="M7 8H3"/><path d="M17
18h-4"/></svg>
Generate Image
</button>
</div>
</div>
<div class="lg:w-3/5 xl:w-2/3 image-display-bg p-4 sm:p-6 rounded-xl
shadow-xl flex flex-col items-center justify-center relative">
<div id="image-container" class="w-full max-w-3xl bg-gray-700
rounded-lg overflow-hidden flex items-center justify-center relative
shadow-inner">
<img id="generatedImage"
src="https://placehold.co/512x512/4B5563/9CA3AF?text=Your+AI+Art+Will+Appear+Here"
alt="Generated AI Art" class="object-contain w-full h-full
transition-opacity duration-500">
<div
id="loading-overlay" class="absolute inset-0 bg-gray-800 bg-opacity-80
flex flex-col items-center justify-center hidden transition-opacity
duration-300">
<div class="spinner"></div>
<p class="text-white mt-4 font-medium text-lg">Generating your masterpiece...</p>
</div>
<div id="placeholder-text-container" class="absolute inset-0 flex
items-center justify-center pointer-events-none">
<p class="text-gray-400 text-xl text-center p-4">Your AI Art Will Appear Here</p>
</div>
</div>
<div class="mt-6 w-full max-w-3xl flex flex-col sm:flex-row gap-3 justify-center button-group">
<button id="regenerateBtn" class="flex-1 btn-secondary font-semibold
py-3 px-4 rounded-lg shadow-md hover:shadow-lg transition duration-150
ease-in-out flex items-center justify-center gap-2 disabled:opacity-60
disabled:cursor-not-allowed" disabled>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" class="lucide
lucide-refresh-cw"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1
6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M3 21c-1.66
0-3-1.34-3-3s1.34-3 3-3c1.06 0 1.98.54 2.5 1.35"/><path d="M21
15v4a2 2 0 0 1-2 2H5.5a2.5 2.5 0 0 1-2.5-2.5V15"/></svg>
Regenerate
</button>
<button id="downloadBtn" class="flex-1 bg-green-600
hover:bg-green-700 text-white font-semibold py-3 px-4 rounded-lg
shadow-md hover:shadow-lg transition duration-150 ease-in-out flex
items-center justify-center gap-2 disabled:opacity-60
disabled:cursor-not-allowed" disabled>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" class="lucide
lucide-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0
1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line
x1="12" x2="12" y1="15" y2="3"/></svg>
Download
</button>
</div>
<p class="text-xs text-gray-500 mt-4 text-center">Download
functionality is for demonstration purposes only.</p>
</div>
</main>
<footer class="mt-8 py-4 text-center text-sm text-gray-500 border-t border-gray-300">
<p>© <span id="currentYear"></span> AI Art
Generator Mock-up. All rights reserved (conceptually).</p>
<p>This interface is a simulation and does not connect to any
actual AI image generation models like DALL-E.</p>
</footer>
</div>
<script>
// --- DOM Elements ---
const promptTextarea = document.getElementById('prompt');
const styleSelect = document.getElementById('style');
const negativePromptTextarea = document.getElementById('negative-prompt');
const generateBtn = document.getElementById('generateBtn');
const regenerateBtn = document.getElementById('regenerateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const generatedImage = document.getElementById('generatedImage');
const imageContainer = document.getElementById('image-container');
const loadingOverlay = document.getElementById('loading-overlay');
const placeholderTextContainer = document.getElementById('placeholder-text-container');
const aspectRatioBtns = document.querySelectorAll('.aspect-ratio-btn');
const customRatioInputs = document.getElementById('custom-ratio-inputs');
const customWidthInput = document.getElementById('custom-width');
const customHeightInput = document.getElementById('custom-height');
const initialPlaceholderSrc = 'https://placehold.co/512x512/4B5563/9CA3AF?text=Your+AI+Art+Will+Appear+Here';
// --- State Variables ---
let currentImageSeed = null;
let currentAspectRatio = { width: 512, height: 512, ratioString: '1/1' }; // Default 1:1
// --- Helper Functions ---
function setButtonsDisabledState(disabled) {
generateBtn.disabled = disabled;
// Regenerate and Download should only be enabled if an image has been "generated"
if (currentImageSeed !== null) {
regenerateBtn.disabled = disabled;
downloadBtn.disabled = disabled;
} else {
regenerateBtn.disabled = true;
downloadBtn.disabled = true;
}
}
function showLoading(isLoading) {
if (isLoading) {
loadingOverlay.classList.remove('hidden');
placeholderTextContainer.classList.add('hidden');
generatedImage.classList.add('opacity-0');
} else {
loadingOverlay.classList.add('hidden');
generatedImage.classList.remove('opacity-0');
// Show placeholder text only if the image src is the initial one
if (generatedImage.src === initialPlaceholderSrc) {
placeholderTextContainer.classList.remove('hidden');
} else {
placeholderTextContainer.classList.add('hidden');
}
}
}
// --- Aspect Ratio Logic ---
function updateImageContainerAspectRatio() {
imageContainer.style.aspectRatio = currentAspectRatio.ratioString;
// Update placeholder if it's currently showing the default text
if (generatedImage.src === initialPlaceholderSrc || generatedImage.src.startsWith('https://placehold.co/')) {
if (placeholderTextContainer.classList.contains('hidden') &&
generatedImage.src !== initialPlaceholderSrc) {
// If an image was already generated, update its dimensions
const url = new URL(generatedImage.src);
const text = url.searchParams.get('text') || "Generated Art";
const colors = url.pathname.substring(1).split('/').slice(1,3).join('/'); // e.g. 374151/9ca3af
generatedImage.src =
`https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/${colors}?text=${encodeURIComponent(text)}`;
} else {
// Otherwise, just update the default placeholder
generatedImage.src =
`https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/4B5563/9CA3AF?text=Your+AI+Art+Will+Appear+Here`;
}
}
}
aspectRatioBtns.forEach(btn => {
btn.addEventListener('click', () => {
aspectRatioBtns.forEach(b => b.classList.remove('bg-indigo-200',
'ring-2', 'ring-indigo-500', 'font-semibold'));
btn.classList.add('bg-indigo-200', 'ring-2', 'ring-indigo-500', 'font-semibold');
const ratio = btn.dataset.ratio;
if (ratio === 'custom') {
customRatioInputs.classList.remove('hidden');
customWidthInput.value = customWidthInput.value || currentAspectRatio.width;
customHeightInput.value = customHeightInput.value || currentAspectRatio.height;
// Trigger update if custom is selected, using current input values or defaults
handleCustomRatioChange();
} else {
customRatioInputs.classList.add('hidden');
const [w, h] = ratio.split(':').map(Number);
currentAspectRatio = { width: w * 128, height: h * 128, ratioString: `${w}/${h}` }; // Scale for placeholder
if (w === 1 && h === 1) currentAspectRatio = { width: 512, height: 512, ratioString: '1/1'};
if (w === 16 && h === 9) currentAspectRatio = { width: 1024, height: 576, ratioString: '16/9'};
if (w === 4 && h === 3) currentAspectRatio = { width: 640, height: 480, ratioString: '4/3'};
if (w === 3 && h === 4) currentAspectRatio = { width: 480, height: 640, ratioString: '3/4'};
if (w === 9 && h === 16) currentAspectRatio = { width: 576, height: 1024, ratioString: '9/16'};
updateImageContainerAspectRatio();
}
});
});
function handleCustomRatioChange() {
let width = parseInt(customWidthInput.value);
let height = parseInt(customHeightInput.value);
// Basic validation and clamping
width = Math.max(64, Math.min(2048, width || currentAspectRatio.width));
height = Math.max(64, Math.min(2048, height || currentAspectRatio.height));
customWidthInput.value = width; // Update input field with validated value
customHeightInput.value = height; // Update input field with validated value
if (width > 0 && height > 0) {
currentAspectRatio = { width, height, ratioString: `${width}/${height}` };
updateImageContainerAspectRatio();
}
}
customWidthInput.addEventListener('change', handleCustomRatioChange);
// Use 'change' to update when focus is lost or enter is pressed
customHeightInput.addEventListener('change', handleCustomRatioChange);
// --- Image Generation Logic ---
function simulateImageGeneration(isRegeneration = false) {
const prompt = promptTextarea.value.trim();
if (!prompt && !isRegeneration) {
// Using a more subtle way to indicate error than alert()
promptTextarea.classList.add('border-red-500', 'ring-red-500');
promptTextarea.focus();
setTimeout(() => {
promptTextarea.classList.remove('border-red-500', 'ring-red-500');
}, 2000);
return;
}
showLoading(true);
setButtonsDisabledState(true);
setTimeout(() => {
const style = styleSelect.value;
currentImageSeed = isRegeneration ? (currentImageSeed || Date.now()) + 1 : Date.now();
let placeholderText = prompt.substring(0, 25) || "Generated Image";
if (style) placeholderText += ` (${style.replace(/_/g, ' ')})`;
placeholderText += ` #${currentImageSeed % 1000}`;
const bgColor = Math.floor(Math.random()*127 + 64).toString(16).padStart(2,'0') + // R
Math.floor(Math.random()*127 + 64).toString(16).padStart(2,'0') + // G
Math.floor(Math.random()*127 + 64).toString(16).padStart(2,'0'); // B (avoiding too dark/light)
const textColor = 'FFFFFF';
generatedImage.src =
`https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/${bgColor}/${textColor}?text=${encodeURIComponent(placeholderText)}`;
generatedImage.onload = () => {
showLoading(false);
setButtonsDisabledState(false);
};
generatedImage.onerror = () => {
showLoading(false);
setButtonsDisabledState(false);
// Show error in placeholder
generatedImage.src =
`https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/FF0000/FFFFFF?text=Error+Loading+Image`;
placeholderTextContainer.classList.add('hidden'); // Ensure main placeholder text is hidden
};
}, 1500 + Math.random() * 1000);
}
generateBtn.addEventListener('click', () => simulateImageGeneration(false));
regenerateBtn.addEventListener('click', () => {
if (!promptTextarea.value.trim() && !currentImageSeed) {
promptTextarea.classList.add('border-red-500', 'ring-red-500');
promptTextarea.focus();
setTimeout(() => {
promptTextarea.classList.remove('border-red-500', 'ring-red-500');
}, 2000);
return;
}
simulateImageGeneration(true);
});
// --- Download Logic (Conceptual) ---
downloadBtn.addEventListener('click', () => {
if (generatedImage.src && generatedImage.src !==
initialPlaceholderSrc &&
!generatedImage.src.includes('Error+Loading')) {
const link = document.createElement('a');
link.href = generatedImage.src;
const urlParams = new URLSearchParams(new URL(generatedImage.src).search);
const textParam = urlParams.get('text') || 'ai_art_simulation';
const filename = textParam.replace(/[^a-z0-9_]+/gi, '_').toLowerCase().substring(0,50) + '.png';
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
const originalText = downloadBtn.innerHTML;
const checkSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="20"
height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-check"><polyline points="20 6 9 17 4
12"></polyline></svg> Downloaded!`;
downloadBtn.innerHTML = checkSVG;
setTimeout(() => { downloadBtn.innerHTML = originalText; }, 2500);
} else {
// Could show a small message here instead of alert
console.warn("No image generated to download or image is the initial placeholder/error.");
}
});
// --- Initial Setup ---
document.getElementById('currentYear').textContent = new Date().getFullYear();
// Set 1:1 as default active aspect ratio button
const defaultRatioButton = document.querySelector('.aspect-ratio-btn[data-ratio="1:1"]');
if (defaultRatioButton) {
defaultRatioButton.classList.add('bg-indigo-200', 'ring-2', 'ring-indigo-500', 'font-semibold');
}
updateImageContainerAspectRatio(); // Apply initial aspect ratio to container
showLoading(false); // Ensure correct initial visibility of placeholder/image
setButtonsDisabledState(true); // Regenerate/Download disabled initially
</script>
</body>
</html>
Deploy
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Image Generator Mock-up</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; }
.control-panel-bg { background-color: #f9fafb; /* Tailwind gray-50 */ }
.image-display-bg { background-color: #f3f4f6; /* Tailwind gray-100 */ }
.btn-primary { background-color: #4f46e5; /* Tailwind indigo-600 */ color: white; }
.btn-primary:hover { background-color: #4338ca; /* Tailwind indigo-700 */ }
.btn-secondary { background-color: #6b7280; /* Tailwind gray-500 */ color: white; }
.btn-secondary:hover { background-color: #4b5563; /* Tailwind gray-600 */ }
/* Custom scrollbar for webkit browsers */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Loading spinner */
.spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
width: 36px;
height: 36px;
border-radius: 50%;
border-left-color: #4f46e5; /* Tailwind indigo-600 */
animation: spin 1s ease infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Ensure buttons in a group don't wrap weirdly on small screens */
.button-group > button {
min-width: max-content;
}
</style>
</head>
<body class="bg-gray-200 text-gray-800 antialiased">
<div class="container mx-auto p-4 min-h-screen flex flex-col">
<header class="mb-6 text-center">
<h1 class="text-3xl sm:text-4xl font-bold text-indigo-700">AI-Powered Art Generator</h1>
<p class="text-md sm:text-lg text-gray-600 mt-2">Craft unique
visuals from your textual descriptions. (Conceptual Mock-up)</p>
</header>
<main class="flex-grow flex flex-col lg:flex-row gap-6">
<div class="lg:w-2/5 xl:w-1/3 control-panel-bg p-6 rounded-xl shadow-xl flex flex-col gap-5">
<div>
<label for="prompt" class="block text-sm font-medium text-gray-700
mb-1">Enter Your Prompt:</label>
<textarea id="prompt" name="prompt" rows="4" class="w-full p-3 border
border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500
focus:border-indigo-500 transition-shadow" placeholder="e.g., A majestic
lion wearing a crown, digital painting"></textarea>
</div>
<div>
<label for="style" class="block text-sm font-medium text-gray-700 mb-1">Art Style:</label>
<select id="style" name="style" class="w-full p-3 border
border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500
focus:border-indigo-500 bg-white transition-shadow">
<option value="photorealistic">Photorealistic</option>
<option value="impressionistic">Impressionistic</option>
<option value="surreal">Surreal</option>
<option value="abstract">Abstract</option>
<option value="cartoon">Cartoon</option>
<option value="pixel_art">Pixel Art</option>
<option value="fantasy">Fantasy Art</option>
<option value="sci_fi">Sci-Fi Concept Art</option>
<option value="watercolor">Watercolor</option>
<option value="oil_painting">Oil Painting</option>
<option value="anime">Anime / Manga</option>
<option value="cyberpunk">Cyberpunk</option>
<option value="steampunk">Steampunk</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Aspect Ratio:</label>
<div class="grid grid-cols-3 gap-2 button-group">
<button data-ratio="1:1" class="aspect-ratio-btn p-2 border
border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
transition-colors">1:1</button>
<button data-ratio="16:9" class="aspect-ratio-btn p-2 border
border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
transition-colors">16:9</button>
<button data-ratio="4:3" class="aspect-ratio-btn p-2 border
border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
transition-colors">4:3</button>
<button data-ratio="3:4" class="aspect-ratio-btn p-2 border
border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
transition-colors">3:4</button>
<button data-ratio="9:16" class="aspect-ratio-btn p-2 border
border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
transition-colors">9:16</button>
<button data-ratio="custom" class="aspect-ratio-btn p-2 border
border-gray-300 rounded-md hover:bg-indigo-100 focus:bg-indigo-200
focus:outline-none focus:ring-2 focus:ring-indigo-500
transition-colors">Custom</button>
</div>
<div id="custom-ratio-inputs" class="hidden mt-2 flex gap-2">
<input type="number" id="custom-width" placeholder="Width (px)"
class="w-1/2 p-2 border border-gray-300 rounded-md focus:ring-indigo-500
focus:border-indigo-500 transition-shadow" min="64" max="2048">
<input type="number" id="custom-height" placeholder="Height (px)"
class="w-1/2 p-2 border border-gray-300 rounded-md focus:ring-indigo-500
focus:border-indigo-500 transition-shadow" min="64" max="2048">
</div>
</div>
<div>
<label for="negative-prompt" class="block text-sm font-medium
text-gray-700 mb-1">Negative Prompt (Optional):</label>
<textarea id="negative-prompt" name="negative-prompt" rows="2"
class="w-full p-3 border border-gray-300 rounded-lg shadow-sm
focus:ring-indigo-500 focus:border-indigo-500 transition-shadow"
placeholder="e.g., blurry, watermark, text, ugly,
deformed"></textarea>
</div>
<div class="mt-auto pt-5 border-t border-gray-300">
<p class="text-xs text-gray-500 mb-3">Note: Image generation is
simulated. No actual AI is used. This is a UI concept.</p>
<button id="generateBtn" class="w-full btn-primary font-semibold
py-3 px-4 rounded-lg shadow-md hover:shadow-lg transition duration-150
ease-in-out flex items-center justify-center gap-2 disabled:opacity-60
disabled:cursor-not-allowed">
<svg
xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0
24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" class="lucide
lucide-wand-2"><path d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72
0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72
0L21.64 5.36a1.2 1.2 0 0 0 0-1.72Z"/><path d="m14 7 3
3"/><path d="M5 6v4"/><path d="M19 14v4"/><path d="M10
2v2"/><path d="M7 8H3"/><path d="M17
18h-4"/></svg>
Generate Image
</button>
</div>
</div>
<div class="lg:w-3/5 xl:w-2/3 image-display-bg p-4 sm:p-6 rounded-xl
shadow-xl flex flex-col items-center justify-center relative">
<div id="image-container" class="w-full max-w-3xl bg-gray-700
rounded-lg overflow-hidden flex items-center justify-center relative
shadow-inner">
<img id="generatedImage"
src="https://placehold.co/512x512/4B5563/9CA3AF?text=Your+AI+Art+Will+Appear+Here"
alt="Generated AI Art" class="object-contain w-full h-full
transition-opacity duration-500">
<div
id="loading-overlay" class="absolute inset-0 bg-gray-800 bg-opacity-80
flex flex-col items-center justify-center hidden transition-opacity
duration-300">
<div class="spinner"></div>
<p class="text-white mt-4 font-medium text-lg">Generating your masterpiece...</p>
</div>
<div id="placeholder-text-container" class="absolute inset-0 flex
items-center justify-center pointer-events-none">
<p class="text-gray-400 text-xl text-center p-4">Your AI Art Will Appear Here</p>
</div>
</div>
<div class="mt-6 w-full max-w-3xl flex flex-col sm:flex-row gap-3 justify-center button-group">
<button id="regenerateBtn" class="flex-1 btn-secondary font-semibold
py-3 px-4 rounded-lg shadow-md hover:shadow-lg transition duration-150
ease-in-out flex items-center justify-center gap-2 disabled:opacity-60
disabled:cursor-not-allowed" disabled>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" class="lucide
lucide-refresh-cw"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1
6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M3 21c-1.66
0-3-1.34-3-3s1.34-3 3-3c1.06 0 1.98.54 2.5 1.35"/><path d="M21
15v4a2 2 0 0 1-2 2H5.5a2.5 2.5 0 0 1-2.5-2.5V15"/></svg>
Regenerate
</button>
<button id="downloadBtn" class="flex-1 bg-green-600
hover:bg-green-700 text-white font-semibold py-3 px-4 rounded-lg
shadow-md hover:shadow-lg transition duration-150 ease-in-out flex
items-center justify-center gap-2 disabled:opacity-60
disabled:cursor-not-allowed" disabled>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" class="lucide
lucide-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0
1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line
x1="12" x2="12" y1="15" y2="3"/></svg>
Download
</button>
</div>
<p class="text-xs text-gray-500 mt-4 text-center">Download
functionality is for demonstration purposes only.</p>
</div>
</main>
<footer class="mt-8 py-4 text-center text-sm text-gray-500 border-t border-gray-300">
<p>© <span id="currentYear"></span> AI Art
Generator Mock-up. All rights reserved (conceptually).</p>
<p>This interface is a simulation and does not connect to any
actual AI image generation models like DALL-E.</p>
</footer>
</div>
<script>
// --- DOM Elements ---
const promptTextarea = document.getElementById('prompt');
const styleSelect = document.getElementById('style');
const negativePromptTextarea = document.getElementById('negative-prompt');
const generateBtn = document.getElementById('generateBtn');
const regenerateBtn = document.getElementById('regenerateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const generatedImage = document.getElementById('generatedImage');
const imageContainer = document.getElementById('image-container');
const loadingOverlay = document.getElementById('loading-overlay');
const placeholderTextContainer = document.getElementById('placeholder-text-container');
const aspectRatioBtns = document.querySelectorAll('.aspect-ratio-btn');
const customRatioInputs = document.getElementById('custom-ratio-inputs');
const customWidthInput = document.getElementById('custom-width');
const customHeightInput = document.getElementById('custom-height');
const initialPlaceholderSrc = 'https://placehold.co/512x512/4B5563/9CA3AF?text=Your+AI+Art+Will+Appear+Here';
// --- State Variables ---
let currentImageSeed = null;
let currentAspectRatio = { width: 512, height: 512, ratioString: '1/1' }; // Default 1:1
// --- Helper Functions ---
function setButtonsDisabledState(disabled) {
generateBtn.disabled = disabled;
// Regenerate and Download should only be enabled if an image has been "generated"
if (currentImageSeed !== null) {
regenerateBtn.disabled = disabled;
downloadBtn.disabled = disabled;
} else {
regenerateBtn.disabled = true;
downloadBtn.disabled = true;
}
}
function showLoading(isLoading) {
if (isLoading) {
loadingOverlay.classList.remove('hidden');
placeholderTextContainer.classList.add('hidden');
generatedImage.classList.add('opacity-0');
} else {
loadingOverlay.classList.add('hidden');
generatedImage.classList.remove('opacity-0');
// Show placeholder text only if the image src is the initial one
if (generatedImage.src === initialPlaceholderSrc) {
placeholderTextContainer.classList.remove('hidden');
} else {
placeholderTextContainer.classList.add('hidden');
}
}
}
// --- Aspect Ratio Logic ---
function updateImageContainerAspectRatio() {
imageContainer.style.aspectRatio = currentAspectRatio.ratioString;
// Update placeholder if it's currently showing the default text
if (generatedImage.src === initialPlaceholderSrc || generatedImage.src.startsWith('https://placehold.co/')) {
if (placeholderTextContainer.classList.contains('hidden') &&
generatedImage.src !== initialPlaceholderSrc) {
// If an image was already generated, update its dimensions
const url = new URL(generatedImage.src);
const text = url.searchParams.get('text') || "Generated Art";
const colors = url.pathname.substring(1).split('/').slice(1,3).join('/'); // e.g. 374151/9ca3af
generatedImage.src =
`https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/${colors}?text=${encodeURIComponent(text)}`;
} else {
// Otherwise, just update the default placeholder
generatedImage.src =
`https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/4B5563/9CA3AF?text=Your+AI+Art+Will+Appear+Here`;
}
}
}
aspectRatioBtns.forEach(btn => {
btn.addEventListener('click', () => {
aspectRatioBtns.forEach(b => b.classList.remove('bg-indigo-200',
'ring-2', 'ring-indigo-500', 'font-semibold'));
btn.classList.add('bg-indigo-200', 'ring-2', 'ring-indigo-500', 'font-semibold');
const ratio = btn.dataset.ratio;
if (ratio === 'custom') {
customRatioInputs.classList.remove('hidden');
customWidthInput.value = customWidthInput.value || currentAspectRatio.width;
customHeightInput.value = customHeightInput.value || currentAspectRatio.height;
// Trigger update if custom is selected, using current input values or defaults
handleCustomRatioChange();
} else {
customRatioInputs.classList.add('hidden');
const [w, h] = ratio.split(':').map(Number);
currentAspectRatio = { width: w * 128, height: h * 128, ratioString: `${w}/${h}` }; // Scale for placeholder
if (w === 1 && h === 1) currentAspectRatio = { width: 512, height: 512, ratioString: '1/1'};
if (w === 16 && h === 9) currentAspectRatio = { width: 1024, height: 576, ratioString: '16/9'};
if (w === 4 && h === 3) currentAspectRatio = { width: 640, height: 480, ratioString: '4/3'};
if (w === 3 && h === 4) currentAspectRatio = { width: 480, height: 640, ratioString: '3/4'};
if (w === 9 && h === 16) currentAspectRatio = { width: 576, height: 1024, ratioString: '9/16'};
updateImageContainerAspectRatio();
}
});
});
function handleCustomRatioChange() {
let width = parseInt(customWidthInput.value);
let height = parseInt(customHeightInput.value);
// Basic validation and clamping
width = Math.max(64, Math.min(2048, width || currentAspectRatio.width));
height = Math.max(64, Math.min(2048, height || currentAspectRatio.height));
customWidthInput.value = width; // Update input field with validated value
customHeightInput.value = height; // Update input field with validated value
if (width > 0 && height > 0) {
currentAspectRatio = { width, height, ratioString: `${width}/${height}` };
updateImageContainerAspectRatio();
}
}
customWidthInput.addEventListener('change', handleCustomRatioChange);
// Use 'change' to update when focus is lost or enter is pressed
customHeightInput.addEventListener('change', handleCustomRatioChange);
// --- Image Generation Logic ---
function simulateImageGeneration(isRegeneration = false) {
const prompt = promptTextarea.value.trim();
if (!prompt && !isRegeneration) {
// Using a more subtle way to indicate error than alert()
promptTextarea.classList.add('border-red-500', 'ring-red-500');
promptTextarea.focus();
setTimeout(() => {
promptTextarea.classList.remove('border-red-500', 'ring-red-500');
}, 2000);
return;
}
showLoading(true);
setButtonsDisabledState(true);
setTimeout(() => {
const style = styleSelect.value;
currentImageSeed = isRegeneration ? (currentImageSeed || Date.now()) + 1 : Date.now();
let placeholderText = prompt.substring(0, 25) || "Generated Image";
if (style) placeholderText += ` (${style.replace(/_/g, ' ')})`;
placeholderText += ` #${currentImageSeed % 1000}`;
const bgColor = Math.floor(Math.random()*127 + 64).toString(16).padStart(2,'0') + // R
Math.floor(Math.random()*127 + 64).toString(16).padStart(2,'0') + // G
Math.floor(Math.random()*127 + 64).toString(16).padStart(2,'0'); // B (avoiding too dark/light)
const textColor = 'FFFFFF';
generatedImage.src =
`https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/${bgColor}/${textColor}?text=${encodeURIComponent(placeholderText)}`;
generatedImage.onload = () => {
showLoading(false);
setButtonsDisabledState(false);
};
generatedImage.onerror = () => {
showLoading(false);
setButtonsDisabledState(false);
// Show error in placeholder
generatedImage.src =
`https://placehold.co/${currentAspectRatio.width}x${currentAspectRatio.height}/FF0000/FFFFFF?text=Error+Loading+Image`;
placeholderTextContainer.classList.add('hidden'); // Ensure main placeholder text is hidden
};
}, 1500 + Math.random() * 1000);
}
generateBtn.addEventListener('click', () => simulateImageGeneration(false));
regenerateBtn.addEventListener('click', () => {
if (!promptTextarea.value.trim() && !currentImageSeed) {
promptTextarea.classList.add('border-red-500', 'ring-red-500');
promptTextarea.focus();
setTimeout(() => {
promptTextarea.classList.remove('border-red-500', 'ring-red-500');
}, 2000);
return;
}
simulateImageGeneration(true);
});
// --- Download Logic (Conceptual) ---
downloadBtn.addEventListener('click', () => {
if (generatedImage.src && generatedImage.src !==
initialPlaceholderSrc &&
!generatedImage.src.includes('Error+Loading')) {
const link = document.createElement('a');
link.href = generatedImage.src;
const urlParams = new URLSearchParams(new URL(generatedImage.src).search);
const textParam = urlParams.get('text') || 'ai_art_simulation';
const filename = textParam.replace(/[^a-z0-9_]+/gi, '_').toLowerCase().substring(0,50) + '.png';
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
const originalText = downloadBtn.innerHTML;
const checkSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="20"
height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-check"><polyline points="20 6 9 17 4
12"></polyline></svg> Downloaded!`;
downloadBtn.innerHTML = checkSVG;
setTimeout(() => { downloadBtn.innerHTML = originalText; }, 2500);
} else {
// Could show a small message here instead of alert
console.warn("No image generated to download or image is the initial placeholder/error.");
}
});
// --- Initial Setup ---
document.getElementById('currentYear').textContent = new Date().getFullYear();
// Set 1:1 as default active aspect ratio button
const defaultRatioButton = document.querySelector('.aspect-ratio-btn[data-ratio="1:1"]');
if (defaultRatioButton) {
defaultRatioButton.classList.add('bg-indigo-200', 'ring-2', 'ring-indigo-500', 'font-semibold');
}
updateImageContainerAspectRatio(); // Apply initial aspect ratio to container
showLoading(false); // Ensure correct initial visibility of placeholder/image
setButtonsDisabledState(true); // Regenerate/Download disabled initially
</script>
</body>
</html>
No comments:
Post a Comment