// State Management
const AppState = {
currentImage: null,
currentVideo: null,
isGenerating: false,
history: JSON.parse(localStorage.getItem('generationHistory') || '[]'),
settings: {
width: 1024,
height: 1024,
steps: 30,
cfgScale: 7.5,
seed: -1,
motionStrength: 5,
videoDuration: 4
}
};
// Utility Functions
const Utils = {
generateId: () => Math.random().toString(36).substr(2, 9),
async hashString(str) {
const encoder = new TextEncoder();
const data = encoder.encode(str);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
},
getRandomSeed() {
return Math.floor(Math.random() * 2147483647);
},
formatDate(date) {
return new Intl.DateTimeFormat('en-US', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
}).format(date);
},
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
};
// API Simulation (Mock)
const APIService = {
async generateImage(prompt, negativePrompt, settings) {
// Simulate API delay
await new Promise(resolve => setTimeout(resolve, 2000 + Math.random() * 2000));
// Generate deterministic seed from prompt for consistent demo results
const seed = settings.seed > 0 ? settings.seed : Math.abs(Utils.hashString(prompt).split('').reduce((a,b)=>a+b.charCodeAt(0),0));
// Use static.photos with random category based on prompt content
const categories = ['technology', 'abstract', 'nature', 'people', 'cityscape', 'minimal'];
const category = categories[seed % categories.length];
const dimensions = settings.width > settings.height ? '1200x630' : settings.width < settings.height ? '640x360' : '1024x576';
return {
id: Utils.generateId(),
url: `http://static.photos/${category}/${dimensions}/${seed % 999}`,
prompt: prompt,
negativePrompt: negativePrompt,
settings: {...settings},
timestamp: new Date(),
seed: seed,
type: 'image'
};
},
async generateVideo(imageUrl, prompt, settings) {
await new Promise(resolve => setTimeout(resolve, 3000 + Math.random() * 3000));
const seed = Utils.getRandomSeed();
return {
id: Utils.generateId(),
imageUrl: imageUrl,
videoUrl: `http://static.photos/technology/640x360/${seed % 999}`, // Simulated video thumbnail
prompt: prompt,
settings: {...settings},
timestamp: new Date(),
duration: settings.videoDuration || 4,
type: 'video'
};
}
};
// UI Components Logic
const UI = {
showLoading(element, text = 'Generating...') {
element.innerHTML = `
`;
},
showError(element, message) {
element.innerHTML = `
`;
feather.replace();
},
createImageCard(item) {
return `
${item.type === 'video' ? `
VIDEO
` : ''}
${item.prompt}
${Utils.formatDate(new Date(item.timestamp))}
Seed: ${item.seed || 'N/A'}
`;
}
};
// Main Application Logic
class DreamMachineApp {
constructor() {
this.controls = null;
this.preview = null;
this.init();
}
init() {
this.waitForComponents().then(() => {
this.bindEvents();
this.loadHistory();
});
}
async waitForComponents() {
return new Promise((resolve) => {
const check = () => {
this.controls = document.querySelector('generator-controls');
this.preview = document.querySelector('generator-preview');
if (this.controls && this.preview) {
resolve();
} else {
setTimeout(check, 50);
}
};
check();
});
}
bindEvents() {
// Listen for custom events from web components
document.addEventListener('generate-image', () => this.generateImage());
document.addEventListener('generate-video', () => this.generateVideoFromCurrent());
document.addEventListener('enhance-prompt', () => this.enhancePrompt());
document.addEventListener('ratio-change', (e) => this.setAspectRatio(e.detail.ratio));
// Settings changes from shadow DOM - use capture to catch all
document.addEventListener('input', (e) => {
if (e.target.hasAttribute('data-setting')) {
const key = e.target.getAttribute('data-setting');
const value = e.target.type === 'checkbox' ? e.target.checked : (parseFloat(e.target.value) || e.target.value);
AppState.settings[key] = value;
}
}, true);
document.addEventListener('change', (e) => {
if (e.target.hasAttribute('data-setting')) {
const key = e.target.getAttribute('data-setting');
const value = e.target.type === 'checkbox' ? e.target.checked : (parseFloat(e.target.value) || e.target.value);
AppState.settings[key] = value;
}
}, true);
}
setAspectRatio(ratio) {
const ratios = {
'1:1': [1024, 1024],
'16:9': [1024, 576],
'9:16': [576, 1024],
'4:3': [1024, 768],
'3:4': [768, 1024]
};
const [w, h] = ratios[ratio] || ratios['1:1'];
AppState.settings.width = w;
AppState.settings.height = h;
}
async generateImage() {
if (!this.controls || !this.preview) return;
const prompt = this.controls.getPrompt();
if (!prompt.trim()) {
// Visual feedback for empty prompt
this.controls.shadowRoot.getElementById('prompt-input')?.classList.add('border-red-500');
setTimeout(() => {
this.controls.shadowRoot.getElementById('prompt-input')?.classList.remove('border-red-500');
}, 2000);
return;
}
const negativePrompt = this.controls.getNegativePrompt();
AppState.isGenerating = true;
this.preview.setLoading(true, 'Generating Image...');
try {
const result = await APIService.generateImage(prompt, negativePrompt, AppState.settings);
AppState.currentImage = result;
// Save to history
AppState.history.unshift(result);
localStorage.setItem('generationHistory', JSON.stringify(AppState.history));
this.renderPreview(result);
this.updateGallery();
this.preview.showVideoControls(true);
} catch (error) {
this.preview.setContent(`
Generation failed. Please try again.
`);
console.error('Generation error:', error);
} finally {
AppState.isGenerating = false;
}
}
renderPreview(item) {
if (!this.preview) return;
const content = `
${item.width || 1024}x${item.height || 1024}
Prompt: ${item.prompt.substring(0, 100)}${item.prompt.length > 100 ? '...' : ''}
`;
this.preview.setContent(content);
}
async generateVideoFromCurrent() {
if (!AppState.currentImage || !this.preview) return;
this.preview.setLoading(true, 'Generating Video... This may take a minute');
try {
const result = await APIService.generateVideo(
AppState.currentImage.url,
AppState.currentImage.prompt + ', cinematic motion, smooth animation',
AppState.settings
);
AppState.currentVideo = result;
// Update history
AppState.history.unshift(result);
localStorage.setItem('generationHistory', JSON.stringify(AppState.history));
this.renderVideoPreview(result);
this.updateGallery();
} catch (error) {
this.preview.setContent(`
`);
}
}
renderVideoPreview(item) {
if (!this.preview) return;
const content = `
MP4
${item.duration}s @ 24fps
`;
this.preview.setContent(content);
}
animateImage(id) {
const item = AppState.history.find(h => h.id === id);
if (item && item.type === 'image') {
AppState.currentImage = item;
this.generateVideoFromCurrent();
}
}
downloadImage(url) {
const a = document.createElement('a');
a.href = url;
a.download = `dream-machine-${Date.now()}.png`;
a.target = '_blank';
a.click();
}
deleteItem(id) {
AppState.history = AppState.history.filter(h => h.id !== id);
localStorage.setItem('generationHistory', JSON.stringify(AppState.history));
this.updateGallery();
}
updateGallery() {
const gallery = document.querySelector('gallery-grid');
if (!gallery) return;
gallery.updateCount(AppState.history.length);
if (AppState.history.length === 0) {
gallery.setContent(`
No generations yet
Your creations will be saved here automatically
`);
} else {
const cards = AppState.history.map(item => this.createImageCard(item)).join('');
gallery.setContent(cards);
}
}
createImageCard(item) {
const isVideo = item.type === 'video';
return `
${isVideo ? `
` : ''}
${item.prompt}
${Utils.formatDate(new Date(item.timestamp))}
Seed: ${item.seed || 'N/A'}
`;
}
loadHistory() {
this.updateGallery();
}
enhancePrompt() {
if (!this.controls) return;
const current = this.controls.getPrompt();
const enhancers = [
'8k resolution, photorealistic, highly detailed, professional photography',
'cinematic lighting, unreal engine 5, octane render',
'masterpiece, best quality, intricate details',
'trending on artstation, sharp focus, vivid colors'
];
const randomEnhancer = enhancers[Math.floor(Math.random() * enhancers.length)];
const newValue = current ? `${current}, ${randomEnhancer}` : randomEnhancer;
this.controls.setPrompt(newValue);
// Visual feedback
const input = this.controls.shadowRoot.getElementById('prompt-input');
if (input) {
input.style.boxShadow = '0 0 0 2px #06b6d4';
setTimeout(() => input.style.boxShadow = '', 500);
}
}
clearHistory() {
AppState.history = [];
localStorage.removeItem('generationHistory');
this.updateGallery();
}
}
// Initialize
const app = new DreamMachineApp();
// Expose to window for onclick handlers
window.app = app;