|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Image Sequence Generator</title> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
:root { |
|
|
--primary-color: #6c5ce7; |
|
|
--secondary-color: #a29bfe; |
|
|
--accent-color: #fd79a8; |
|
|
--dark-color: #2d3436; |
|
|
--light-color: #f5f6fa; |
|
|
--success-color: #00b894; |
|
|
--warning-color: #fdcb6e; |
|
|
--danger-color: #d63031; |
|
|
} |
|
|
|
|
|
* { |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
box-sizing: border-box; |
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
|
|
} |
|
|
|
|
|
body { |
|
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); |
|
|
min-height: 100vh; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
align-items: center; |
|
|
color: var(--dark-color); |
|
|
line-height: 1.6; |
|
|
} |
|
|
|
|
|
.header { |
|
|
width: 100%; |
|
|
background: white; |
|
|
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1); |
|
|
padding: 1rem 2rem; |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
position: sticky; |
|
|
top: 0; |
|
|
z-index: 100; |
|
|
} |
|
|
|
|
|
.logo { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 10px; |
|
|
text-decoration: none; |
|
|
color: var(--dark-color); |
|
|
} |
|
|
|
|
|
.logo-icon { |
|
|
font-size: 1.5rem; |
|
|
color: var(--primary-color); |
|
|
} |
|
|
|
|
|
.logo-text { |
|
|
font-weight: 700; |
|
|
font-size: 1.2rem; |
|
|
} |
|
|
|
|
|
.built-with { |
|
|
font-size: 0.8rem; |
|
|
color: var(--dark-color); |
|
|
} |
|
|
|
|
|
.built-with a { |
|
|
color: var(--primary-color); |
|
|
text-decoration: none; |
|
|
font-weight: 600; |
|
|
} |
|
|
|
|
|
.main-container { |
|
|
width: 90%; |
|
|
max-width: 1200px; |
|
|
margin: 2rem auto; |
|
|
display: grid; |
|
|
grid-template-columns: 1fr 1fr; |
|
|
gap: 2rem; |
|
|
} |
|
|
|
|
|
.controls { |
|
|
background: white; |
|
|
border-radius: 15px; |
|
|
padding: 2rem; |
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); |
|
|
} |
|
|
|
|
|
.preview { |
|
|
background: white; |
|
|
border-radius: 15px; |
|
|
padding: 2rem; |
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
gap: 1rem; |
|
|
} |
|
|
|
|
|
.section-title { |
|
|
font-size: 1.5rem; |
|
|
font-weight: 700; |
|
|
margin-bottom: 1.5rem; |
|
|
color: var(--primary-color); |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 10px; |
|
|
} |
|
|
|
|
|
.form-group { |
|
|
margin-bottom: 1.5rem; |
|
|
} |
|
|
|
|
|
.form-group label { |
|
|
display: block; |
|
|
margin-bottom: 0.5rem; |
|
|
font-weight: 600; |
|
|
color: var(--dark-color); |
|
|
} |
|
|
|
|
|
.form-control { |
|
|
width: 100%; |
|
|
padding: 0.8rem; |
|
|
border: 2px solid #e0e0e0; |
|
|
border-radius: 8px; |
|
|
font-size: 1rem; |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
|
|
|
.form-control:focus { |
|
|
outline: none; |
|
|
border-color: var(--primary-color); |
|
|
box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.2); |
|
|
} |
|
|
|
|
|
.btn { |
|
|
display: inline-block; |
|
|
padding: 0.8rem 1.5rem; |
|
|
background: var(--primary-color); |
|
|
color: white; |
|
|
border: none; |
|
|
border-radius: 8px; |
|
|
font-size: 1rem; |
|
|
font-weight: 600; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s ease; |
|
|
text-align: center; |
|
|
} |
|
|
|
|
|
.btn:hover { |
|
|
background: #5649c0; |
|
|
transform: translateY(-2px); |
|
|
} |
|
|
|
|
|
.btn:disabled { |
|
|
background: #a29bfe; |
|
|
cursor: not-allowed; |
|
|
transform: none; |
|
|
} |
|
|
|
|
|
.btn-secondary { |
|
|
background: var(--secondary-color); |
|
|
} |
|
|
|
|
|
.btn-secondary:hover { |
|
|
background: #8a82e5; |
|
|
} |
|
|
|
|
|
.btn-danger { |
|
|
background: var(--danger-color); |
|
|
} |
|
|
|
|
|
.btn-danger:hover { |
|
|
background: #b72424; |
|
|
} |
|
|
|
|
|
.image-buffer { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); |
|
|
gap: 1rem; |
|
|
margin-top: 1rem; |
|
|
max-height: 400px; |
|
|
overflow-y: auto; |
|
|
padding: 0.5rem; |
|
|
border: 2px dashed #e0e0e0; |
|
|
border-radius: 8px; |
|
|
} |
|
|
|
|
|
.buffer-item { |
|
|
position: relative; |
|
|
aspect-ratio: 1; |
|
|
border-radius: 8px; |
|
|
overflow: hidden; |
|
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); |
|
|
transition: transform 0.3s ease; |
|
|
} |
|
|
|
|
|
.buffer-item:hover { |
|
|
transform: scale(1.05); |
|
|
z-index: 10; |
|
|
} |
|
|
|
|
|
.buffer-item img { |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
object-fit: cover; |
|
|
} |
|
|
|
|
|
.buffer-item .remove-btn { |
|
|
position: absolute; |
|
|
top: 5px; |
|
|
right: 5px; |
|
|
background: rgba(214, 48, 49, 0.8); |
|
|
color: white; |
|
|
border: none; |
|
|
border-radius: 50%; |
|
|
width: 25px; |
|
|
height: 25px; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
cursor: pointer; |
|
|
opacity: 0; |
|
|
transition: opacity 0.3s ease; |
|
|
} |
|
|
|
|
|
.buffer-item:hover .remove-btn { |
|
|
opacity: 1; |
|
|
} |
|
|
|
|
|
.status { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
margin-top: 1rem; |
|
|
padding: 1rem; |
|
|
background: #f8f9fa; |
|
|
border-radius: 8px; |
|
|
} |
|
|
|
|
|
.status-item { |
|
|
text-align: center; |
|
|
} |
|
|
|
|
|
.status-label { |
|
|
font-size: 0.9rem; |
|
|
color: #666; |
|
|
margin-bottom: 0.3rem; |
|
|
} |
|
|
|
|
|
.status-value { |
|
|
font-weight: 700; |
|
|
color: var(--primary-color); |
|
|
} |
|
|
|
|
|
.player-controls { |
|
|
display: flex; |
|
|
gap: 1rem; |
|
|
margin-top: 1rem; |
|
|
} |
|
|
|
|
|
.player-display { |
|
|
width: 100%; |
|
|
aspect-ratio: 16/9; |
|
|
background: #f0f0f0; |
|
|
border-radius: 8px; |
|
|
overflow: hidden; |
|
|
position: relative; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
margin-top: 1rem; |
|
|
} |
|
|
|
|
|
.player-display img { |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
object-fit: contain; |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.player-display img.active { |
|
|
display: block; |
|
|
} |
|
|
|
|
|
.empty-state { |
|
|
text-align: center; |
|
|
padding: 2rem; |
|
|
color: #999; |
|
|
} |
|
|
|
|
|
.empty-state i { |
|
|
font-size: 3rem; |
|
|
margin-bottom: 1rem; |
|
|
color: #ddd; |
|
|
} |
|
|
|
|
|
.progress-container { |
|
|
margin-top: 1rem; |
|
|
} |
|
|
|
|
|
.progress-bar { |
|
|
height: 8px; |
|
|
background: #e0e0e0; |
|
|
border-radius: 4px; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.progress { |
|
|
height: 100%; |
|
|
background: var(--primary-color); |
|
|
width: 0%; |
|
|
transition: width 0.3s ease; |
|
|
} |
|
|
|
|
|
.settings-panel { |
|
|
background: white; |
|
|
border-radius: 15px; |
|
|
padding: 1.5rem; |
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); |
|
|
margin-top: 2rem; |
|
|
} |
|
|
|
|
|
.settings-grid { |
|
|
display: grid; |
|
|
grid-template-columns: 1fr 1fr; |
|
|
gap: 1rem; |
|
|
} |
|
|
|
|
|
@media (max-width: 768px) { |
|
|
.main-container { |
|
|
grid-template-columns: 1fr; |
|
|
} |
|
|
|
|
|
.settings-grid { |
|
|
grid-template-columns: 1fr; |
|
|
} |
|
|
|
|
|
.header { |
|
|
flex-direction: column; |
|
|
gap: 1rem; |
|
|
padding: 1rem; |
|
|
} |
|
|
} |
|
|
|
|
|
@media (max-width: 480px) { |
|
|
.image-buffer { |
|
|
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); |
|
|
} |
|
|
|
|
|
.form-control { |
|
|
padding: 0.6rem; |
|
|
} |
|
|
|
|
|
.btn { |
|
|
padding: 0.6rem 1rem; |
|
|
} |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<header class="header"> |
|
|
<a href="#" class="logo"> |
|
|
<i class="fas fa-images logo-icon"></i> |
|
|
<span class="logo-text">Image Sequence Generator</span> |
|
|
</a> |
|
|
<div class="built-with"> |
|
|
Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
<div class="main-container"> |
|
|
<div class="controls"> |
|
|
<h2 class="section-title"> |
|
|
<i class="fas fa-cog"></i> |
|
|
Generation Controls |
|
|
</h2> |
|
|
|
|
|
<div class="form-group"> |
|
|
<label for="prompt">Prompt</label> |
|
|
<input type="text" id="prompt" class="form-control" placeholder="Describe the image you want to generate"> |
|
|
</div> |
|
|
|
|
|
<div class="form-group"> |
|
|
<label for="negative-prompt">Negative Prompt</label> |
|
|
<input type="text" id="negative-prompt" class="form-control" placeholder="What you don't want to see"> |
|
|
</div> |
|
|
|
|
|
<div class="settings-panel"> |
|
|
<h3 class="section-title" style="font-size: 1.2rem; margin-bottom: 1rem;"> |
|
|
<i class="fas fa-sliders-h"></i> |
|
|
Advanced Settings |
|
|
</h3> |
|
|
|
|
|
<div class="settings-grid"> |
|
|
<div class="form-group"> |
|
|
<label for="steps">Steps</label> |
|
|
<input type="number" id="steps" class="form-control" value="4" min="1" max="20"> |
|
|
</div> |
|
|
|
|
|
<div class="form-group"> |
|
|
<label for="guidance-scale">Guidance Scale</label> |
|
|
<input type="number" id="guidance-scale" class="form-control" value="0" min="0" max="20" step="0.1"> |
|
|
</div> |
|
|
|
|
|
<div class="form-group"> |
|
|
<label for="seed">Seed</label> |
|
|
<input type="number" id="seed" class="form-control" value="-1" min="-1"> |
|
|
</div> |
|
|
|
|
|
<div class="form-group"> |
|
|
<label for="batch-size">Batch Size</label> |
|
|
<input type="number" id="batch-size" class="form-control" value="1" min="1" max="4"> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<button id="generate-btn" class="btn"> |
|
|
<i class="fas fa-magic"></i> Generate Image |
|
|
</button> |
|
|
|
|
|
<div class="progress-container"> |
|
|
<div class="progress-bar"> |
|
|
<div class="progress" id="progress-bar"></div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="preview"> |
|
|
<h2 class="section-title"> |
|
|
<i class="fas fa-film"></i> |
|
|
Image Buffer & Player |
|
|
</h2> |
|
|
|
|
|
<div class="status"> |
|
|
<div class="status-item"> |
|
|
<div class="status-label">Images in Buffer</div> |
|
|
<div class="status-value" id="buffer-count">0</div> |
|
|
</div> |
|
|
<div class="status-item"> |
|
|
<div class="status-label">Required for Playback</div> |
|
|
<div class="status-value" id="required-count">10</div> |
|
|
</div> |
|
|
<div class="status-item"> |
|
|
<div class="status-label">Playback Speed</div> |
|
|
<div class="status-value">30 FPS</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="player-controls"> |
|
|
<button id="clear-buffer" class="btn btn-secondary"> |
|
|
<i class="fas fa-trash"></i> Clear Buffer |
|
|
</button> |
|
|
<button id="play-sequence" class="btn" disabled> |
|
|
<i class="fas fa-play"></i> Play Sequence |
|
|
</button> |
|
|
<button id="stop-sequence" class="btn btn-danger" disabled> |
|
|
<i class="fas fa-stop"></i> Stop |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="player-display" id="player-display"> |
|
|
<div class="empty-state"> |
|
|
<i class="fas fa-image"></i> |
|
|
<p>No images in buffer yet</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<h3 style="margin-top: 1.5rem; font-size: 1.1rem; color: var(--dark-color);"> |
|
|
<i class="fas fa-inbox"></i> Image Buffer |
|
|
</h3> |
|
|
|
|
|
<div class="image-buffer" id="image-buffer"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
const config = { |
|
|
requiredImages: 10, |
|
|
playbackFPS: 30, |
|
|
apiUrl: "https://diffusers-unofficial-sdxl-turbo-i2i-t2i.hf.space" |
|
|
}; |
|
|
|
|
|
|
|
|
let state = { |
|
|
images: [], |
|
|
isPlaying: false, |
|
|
currentFrame: 0, |
|
|
playbackInterval: null, |
|
|
isGenerating: false |
|
|
}; |
|
|
|
|
|
|
|
|
const elements = { |
|
|
generateBtn: document.getElementById('generate-btn'), |
|
|
playBtn: document.getElementById('play-sequence'), |
|
|
stopBtn: document.getElementById('stop-sequence'), |
|
|
clearBtn: document.getElementById('clear-buffer'), |
|
|
imageBuffer: document.getElementById('image-buffer'), |
|
|
playerDisplay: document.getElementById('player-display'), |
|
|
bufferCount: document.getElementById('buffer-count'), |
|
|
requiredCount: document.getElementById('required-count'), |
|
|
progressBar: document.getElementById('progress-bar'), |
|
|
promptInput: document.getElementById('prompt'), |
|
|
negativePromptInput: document.getElementById('negative-prompt'), |
|
|
stepsInput: document.getElementById('steps'), |
|
|
guidanceScaleInput: document.getElementById('guidance-scale'), |
|
|
seedInput: document.getElementById('seed'), |
|
|
batchSizeInput: document.getElementById('batch-size') |
|
|
}; |
|
|
|
|
|
|
|
|
function init() { |
|
|
|
|
|
elements.requiredCount.textContent = config.requiredImages; |
|
|
|
|
|
|
|
|
elements.generateBtn.addEventListener('click', generateImage); |
|
|
elements.playBtn.addEventListener('click', playSequence); |
|
|
elements.stopBtn.addEventListener('click', stopSequence); |
|
|
elements.clearBtn.addEventListener('click', clearBuffer); |
|
|
|
|
|
|
|
|
updateUI(); |
|
|
} |
|
|
|
|
|
|
|
|
async function generateImage() { |
|
|
if (state.isGenerating) return; |
|
|
|
|
|
const prompt = elements.promptInput.value.trim(); |
|
|
if (!prompt) { |
|
|
alert('Please enter a prompt'); |
|
|
return; |
|
|
} |
|
|
|
|
|
state.isGenerating = true; |
|
|
updateUI(); |
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
let progress = 0; |
|
|
const progressInterval = setInterval(() => { |
|
|
progress += Math.random() * 10; |
|
|
if (progress > 90) progress = 90; |
|
|
elements.progressBar.style.width = `${progress}%`; |
|
|
}, 300); |
|
|
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 2000 + Math.random() * 3000)); |
|
|
|
|
|
|
|
|
const imageData = generateRandomImageData(); |
|
|
|
|
|
|
|
|
addImageToBuffer(imageData); |
|
|
|
|
|
|
|
|
elements.progressBar.style.width = '100%'; |
|
|
clearInterval(progressInterval); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
elements.progressBar.style.width = '0%'; |
|
|
}, 500); |
|
|
} catch (error) { |
|
|
console.error('Error generating image:', error); |
|
|
alert('Failed to generate image. Please try again.'); |
|
|
} finally { |
|
|
state.isGenerating = false; |
|
|
updateUI(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function generateRandomImageData() { |
|
|
const canvas = document.createElement('canvas'); |
|
|
canvas.width = 512; |
|
|
canvas.height = 512; |
|
|
const ctx = canvas.getContext('2d'); |
|
|
|
|
|
|
|
|
const colors = []; |
|
|
for (let i = 0; i < 5; i++) { |
|
|
colors.push(`hsl(${Math.random() * 360}, 70%, 60%)`); |
|
|
} |
|
|
|
|
|
|
|
|
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height); |
|
|
colors.forEach((color, i) => { |
|
|
gradient.addColorStop(i / (colors.length - 1), color); |
|
|
}); |
|
|
|
|
|
|
|
|
ctx.fillStyle = gradient; |
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
|
|
|
for (let i = 0; i < 10; i++) { |
|
|
ctx.fillStyle = `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.7)`; |
|
|
const size = 50 + Math.random() * 100; |
|
|
const x = Math.random() * (canvas.width - size); |
|
|
const y = Math.random() * (canvas.height - size); |
|
|
|
|
|
if (Math.random() > 0.5) { |
|
|
ctx.beginPath(); |
|
|
ctx.arc(x + size/2, y + size/2, size/2, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
} else { |
|
|
ctx.fillRect(x, y, size, size); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const prompt = elements.promptInput.value.trim(); |
|
|
if (prompt) { |
|
|
ctx.font = 'bold 24px Arial'; |
|
|
ctx.fillStyle = 'white'; |
|
|
ctx.textAlign = 'center'; |
|
|
ctx.textBaseline = 'middle'; |
|
|
ctx.fillText(prompt.substring(0, 30) + (prompt.length > 30 ? '...' : ''), canvas.width/2, canvas.height/2); |
|
|
} |
|
|
|
|
|
return canvas.toDataURL('image/png'); |
|
|
} |
|
|
|
|
|
|
|
|
function addImageToBuffer(imageData) { |
|
|
state.images.push(imageData); |
|
|
updateBufferDisplay(); |
|
|
updateUI(); |
|
|
} |
|
|
|
|
|
|
|
|
function updateBufferDisplay() { |
|
|
elements.imageBuffer.innerHTML = ''; |
|
|
|
|
|
if (state.images.length === 0) { |
|
|
elements.imageBuffer.innerHTML = ` |
|
|
<div class="empty-state" style="grid-column: 1 / -1;"> |
|
|
<i class="fas fa-inbox"></i> |
|
|
<p>No images in buffer</p> |
|
|
</div> |
|
|
`; |
|
|
return; |
|
|
} |
|
|
|
|
|
state.images.forEach((imageData, index) => { |
|
|
const bufferItem = document.createElement('div'); |
|
|
bufferItem.className = 'buffer-item'; |
|
|
bufferItem.innerHTML = ` |
|
|
<img src="${imageData}" alt="Generated image ${index + 1}"> |
|
|
<button class="remove-btn" data-index="${index}"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
`; |
|
|
elements.imageBuffer.appendChild(bufferItem); |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.remove-btn').forEach(btn => { |
|
|
btn.addEventListener('click', (e) => { |
|
|
const index = parseInt(e.target.closest('.remove-btn').dataset.index); |
|
|
removeImageFromBuffer(index); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function removeImageFromBuffer(index) { |
|
|
state.images.splice(index, 1); |
|
|
updateBufferDisplay(); |
|
|
updateUI(); |
|
|
} |
|
|
|
|
|
|
|
|
function clearBuffer() { |
|
|
state.images = []; |
|
|
updateBufferDisplay(); |
|
|
updateUI(); |
|
|
stopSequence(); |
|
|
} |
|
|
|
|
|
|
|
|
function playSequence() { |
|
|
if (state.isPlaying || state.images.length < config.requiredImages) return; |
|
|
|
|
|
state.isPlaying = true; |
|
|
state.currentFrame = 0; |
|
|
updateUI(); |
|
|
|
|
|
|
|
|
elements.playerDisplay.innerHTML = ''; |
|
|
state.images.forEach((imageData, index) => { |
|
|
const img = document.createElement('img'); |
|
|
img.src = imageData; |
|
|
img.alt = `Frame ${index + 1}`; |
|
|
if (index === 0) img.classList.add('active'); |
|
|
elements.playerDisplay.appendChild(img); |
|
|
}); |
|
|
|
|
|
|
|
|
const frameDuration = 1000 / config.playbackFPS; |
|
|
state.playbackInterval = setInterval(() => { |
|
|
const images = elements.playerDisplay.querySelectorAll('img'); |
|
|
images.forEach(img => img.classList.remove('active')); |
|
|
|
|
|
if (state.currentFrame >= state.images.length) { |
|
|
state.currentFrame = 0; |
|
|
} |
|
|
|
|
|
images[state.currentFrame].classList.add('active'); |
|
|
state.currentFrame++; |
|
|
}, frameDuration); |
|
|
} |
|
|
|
|
|
|
|
|
function stopSequence() { |
|
|
if (!state.isPlaying) return; |
|
|
|
|
|
clearInterval(state.playbackInterval); |
|
|
state.isPlaying = false; |
|
|
state.currentFrame = 0; |
|
|
updateUI(); |
|
|
|
|
|
|
|
|
elements.playerDisplay.innerHTML = ` |
|
|
<div class="empty-state"> |
|
|
<i class="fas fa-image"></i> |
|
|
<p>Playback stopped</p> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
|
|
|
function updateUI() { |
|
|
|
|
|
elements.bufferCount.textContent = state.images.length; |
|
|
|
|
|
|
|
|
elements.playBtn.disabled = state.images.length < config.requiredImages || state.isPlaying; |
|
|
elements.stopBtn.disabled = !state.isPlaying; |
|
|
elements.generateBtn.disabled = state.isGenerating; |
|
|
elements.generateBtn.textContent = state.isGenerating ? |
|
|
'<i class="fas fa-spinner fa-spin"></i> Generating...' : |
|
|
'<i class="fas fa-magic"></i> Generate Image'; |
|
|
|
|
|
|
|
|
if (!state.isPlaying && state.images.length === 0) { |
|
|
elements.playerDisplay.innerHTML = ` |
|
|
<div class="empty-state"> |
|
|
<i class="fas fa-image"></i> |
|
|
<p>No images in buffer yet</p> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', init); |
|
|
</script> |
|
|
</body> |
|
|
</html> |