inoculatemedia commited on
Commit
d3a63d2
·
verified ·
1 Parent(s): 3dd7a1f

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +772 -19
index.html CHANGED
@@ -1,19 +1,772 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Image Sequence Generator</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
+ <style>
9
+ :root {
10
+ --primary-color: #6c5ce7;
11
+ --secondary-color: #a29bfe;
12
+ --accent-color: #fd79a8;
13
+ --dark-color: #2d3436;
14
+ --light-color: #f5f6fa;
15
+ --success-color: #00b894;
16
+ --warning-color: #fdcb6e;
17
+ --danger-color: #d63031;
18
+ }
19
+
20
+ * {
21
+ margin: 0;
22
+ padding: 0;
23
+ box-sizing: border-box;
24
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
25
+ }
26
+
27
+ body {
28
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
29
+ min-height: 100vh;
30
+ display: flex;
31
+ flex-direction: column;
32
+ align-items: center;
33
+ color: var(--dark-color);
34
+ line-height: 1.6;
35
+ }
36
+
37
+ .header {
38
+ width: 100%;
39
+ background: white;
40
+ box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
41
+ padding: 1rem 2rem;
42
+ display: flex;
43
+ justify-content: space-between;
44
+ align-items: center;
45
+ position: sticky;
46
+ top: 0;
47
+ z-index: 100;
48
+ }
49
+
50
+ .logo {
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 10px;
54
+ text-decoration: none;
55
+ color: var(--dark-color);
56
+ }
57
+
58
+ .logo-icon {
59
+ font-size: 1.5rem;
60
+ color: var(--primary-color);
61
+ }
62
+
63
+ .logo-text {
64
+ font-weight: 700;
65
+ font-size: 1.2rem;
66
+ }
67
+
68
+ .built-with {
69
+ font-size: 0.8rem;
70
+ color: var(--dark-color);
71
+ }
72
+
73
+ .built-with a {
74
+ color: var(--primary-color);
75
+ text-decoration: none;
76
+ font-weight: 600;
77
+ }
78
+
79
+ .main-container {
80
+ width: 90%;
81
+ max-width: 1200px;
82
+ margin: 2rem auto;
83
+ display: grid;
84
+ grid-template-columns: 1fr 1fr;
85
+ gap: 2rem;
86
+ }
87
+
88
+ .controls {
89
+ background: white;
90
+ border-radius: 15px;
91
+ padding: 2rem;
92
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
93
+ }
94
+
95
+ .preview {
96
+ background: white;
97
+ border-radius: 15px;
98
+ padding: 2rem;
99
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
100
+ display: flex;
101
+ flex-direction: column;
102
+ gap: 1rem;
103
+ }
104
+
105
+ .section-title {
106
+ font-size: 1.5rem;
107
+ font-weight: 700;
108
+ margin-bottom: 1.5rem;
109
+ color: var(--primary-color);
110
+ display: flex;
111
+ align-items: center;
112
+ gap: 10px;
113
+ }
114
+
115
+ .form-group {
116
+ margin-bottom: 1.5rem;
117
+ }
118
+
119
+ .form-group label {
120
+ display: block;
121
+ margin-bottom: 0.5rem;
122
+ font-weight: 600;
123
+ color: var(--dark-color);
124
+ }
125
+
126
+ .form-control {
127
+ width: 100%;
128
+ padding: 0.8rem;
129
+ border: 2px solid #e0e0e0;
130
+ border-radius: 8px;
131
+ font-size: 1rem;
132
+ transition: all 0.3s ease;
133
+ }
134
+
135
+ .form-control:focus {
136
+ outline: none;
137
+ border-color: var(--primary-color);
138
+ box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.2);
139
+ }
140
+
141
+ .btn {
142
+ display: inline-block;
143
+ padding: 0.8rem 1.5rem;
144
+ background: var(--primary-color);
145
+ color: white;
146
+ border: none;
147
+ border-radius: 8px;
148
+ font-size: 1rem;
149
+ font-weight: 600;
150
+ cursor: pointer;
151
+ transition: all 0.3s ease;
152
+ text-align: center;
153
+ }
154
+
155
+ .btn:hover {
156
+ background: #5649c0;
157
+ transform: translateY(-2px);
158
+ }
159
+
160
+ .btn:disabled {
161
+ background: #a29bfe;
162
+ cursor: not-allowed;
163
+ transform: none;
164
+ }
165
+
166
+ .btn-secondary {
167
+ background: var(--secondary-color);
168
+ }
169
+
170
+ .btn-secondary:hover {
171
+ background: #8a82e5;
172
+ }
173
+
174
+ .btn-danger {
175
+ background: var(--danger-color);
176
+ }
177
+
178
+ .btn-danger:hover {
179
+ background: #b72424;
180
+ }
181
+
182
+ .image-buffer {
183
+ display: grid;
184
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
185
+ gap: 1rem;
186
+ margin-top: 1rem;
187
+ max-height: 400px;
188
+ overflow-y: auto;
189
+ padding: 0.5rem;
190
+ border: 2px dashed #e0e0e0;
191
+ border-radius: 8px;
192
+ }
193
+
194
+ .buffer-item {
195
+ position: relative;
196
+ aspect-ratio: 1;
197
+ border-radius: 8px;
198
+ overflow: hidden;
199
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
200
+ transition: transform 0.3s ease;
201
+ }
202
+
203
+ .buffer-item:hover {
204
+ transform: scale(1.05);
205
+ z-index: 10;
206
+ }
207
+
208
+ .buffer-item img {
209
+ width: 100%;
210
+ height: 100%;
211
+ object-fit: cover;
212
+ }
213
+
214
+ .buffer-item .remove-btn {
215
+ position: absolute;
216
+ top: 5px;
217
+ right: 5px;
218
+ background: rgba(214, 48, 49, 0.8);
219
+ color: white;
220
+ border: none;
221
+ border-radius: 50%;
222
+ width: 25px;
223
+ height: 25px;
224
+ display: flex;
225
+ align-items: center;
226
+ justify-content: center;
227
+ cursor: pointer;
228
+ opacity: 0;
229
+ transition: opacity 0.3s ease;
230
+ }
231
+
232
+ .buffer-item:hover .remove-btn {
233
+ opacity: 1;
234
+ }
235
+
236
+ .status {
237
+ display: flex;
238
+ justify-content: space-between;
239
+ align-items: center;
240
+ margin-top: 1rem;
241
+ padding: 1rem;
242
+ background: #f8f9fa;
243
+ border-radius: 8px;
244
+ }
245
+
246
+ .status-item {
247
+ text-align: center;
248
+ }
249
+
250
+ .status-label {
251
+ font-size: 0.9rem;
252
+ color: #666;
253
+ margin-bottom: 0.3rem;
254
+ }
255
+
256
+ .status-value {
257
+ font-weight: 700;
258
+ color: var(--primary-color);
259
+ }
260
+
261
+ .player-controls {
262
+ display: flex;
263
+ gap: 1rem;
264
+ margin-top: 1rem;
265
+ }
266
+
267
+ .player-display {
268
+ width: 100%;
269
+ aspect-ratio: 16/9;
270
+ background: #f0f0f0;
271
+ border-radius: 8px;
272
+ overflow: hidden;
273
+ position: relative;
274
+ display: flex;
275
+ align-items: center;
276
+ justify-content: center;
277
+ margin-top: 1rem;
278
+ }
279
+
280
+ .player-display img {
281
+ width: 100%;
282
+ height: 100%;
283
+ object-fit: contain;
284
+ display: none;
285
+ }
286
+
287
+ .player-display img.active {
288
+ display: block;
289
+ }
290
+
291
+ .empty-state {
292
+ text-align: center;
293
+ padding: 2rem;
294
+ color: #999;
295
+ }
296
+
297
+ .empty-state i {
298
+ font-size: 3rem;
299
+ margin-bottom: 1rem;
300
+ color: #ddd;
301
+ }
302
+
303
+ .progress-container {
304
+ margin-top: 1rem;
305
+ }
306
+
307
+ .progress-bar {
308
+ height: 8px;
309
+ background: #e0e0e0;
310
+ border-radius: 4px;
311
+ overflow: hidden;
312
+ }
313
+
314
+ .progress {
315
+ height: 100%;
316
+ background: var(--primary-color);
317
+ width: 0%;
318
+ transition: width 0.3s ease;
319
+ }
320
+
321
+ .settings-panel {
322
+ background: white;
323
+ border-radius: 15px;
324
+ padding: 1.5rem;
325
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
326
+ margin-top: 2rem;
327
+ }
328
+
329
+ .settings-grid {
330
+ display: grid;
331
+ grid-template-columns: 1fr 1fr;
332
+ gap: 1rem;
333
+ }
334
+
335
+ @media (max-width: 768px) {
336
+ .main-container {
337
+ grid-template-columns: 1fr;
338
+ }
339
+
340
+ .settings-grid {
341
+ grid-template-columns: 1fr;
342
+ }
343
+
344
+ .header {
345
+ flex-direction: column;
346
+ gap: 1rem;
347
+ padding: 1rem;
348
+ }
349
+ }
350
+
351
+ @media (max-width: 480px) {
352
+ .image-buffer {
353
+ grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
354
+ }
355
+
356
+ .form-control {
357
+ padding: 0.6rem;
358
+ }
359
+
360
+ .btn {
361
+ padding: 0.6rem 1rem;
362
+ }
363
+ }
364
+ </style>
365
+ </head>
366
+ <body>
367
+ <header class="header">
368
+ <a href="#" class="logo">
369
+ <i class="fas fa-images logo-icon"></i>
370
+ <span class="logo-text">Image Sequence Generator</span>
371
+ </a>
372
+ <div class="built-with">
373
+ Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a>
374
+ </div>
375
+ </header>
376
+
377
+ <div class="main-container">
378
+ <div class="controls">
379
+ <h2 class="section-title">
380
+ <i class="fas fa-cog"></i>
381
+ Generation Controls
382
+ </h2>
383
+
384
+ <div class="form-group">
385
+ <label for="prompt">Prompt</label>
386
+ <input type="text" id="prompt" class="form-control" placeholder="Describe the image you want to generate">
387
+ </div>
388
+
389
+ <div class="form-group">
390
+ <label for="negative-prompt">Negative Prompt</label>
391
+ <input type="text" id="negative-prompt" class="form-control" placeholder="What you don't want to see">
392
+ </div>
393
+
394
+ <div class="settings-panel">
395
+ <h3 class="section-title" style="font-size: 1.2rem; margin-bottom: 1rem;">
396
+ <i class="fas fa-sliders-h"></i>
397
+ Advanced Settings
398
+ </h3>
399
+
400
+ <div class="settings-grid">
401
+ <div class="form-group">
402
+ <label for="steps">Steps</label>
403
+ <input type="number" id="steps" class="form-control" value="4" min="1" max="20">
404
+ </div>
405
+
406
+ <div class="form-group">
407
+ <label for="guidance-scale">Guidance Scale</label>
408
+ <input type="number" id="guidance-scale" class="form-control" value="0" min="0" max="20" step="0.1">
409
+ </div>
410
+
411
+ <div class="form-group">
412
+ <label for="seed">Seed</label>
413
+ <input type="number" id="seed" class="form-control" value="-1" min="-1">
414
+ </div>
415
+
416
+ <div class="form-group">
417
+ <label for="batch-size">Batch Size</label>
418
+ <input type="number" id="batch-size" class="form-control" value="1" min="1" max="4">
419
+ </div>
420
+ </div>
421
+ </div>
422
+
423
+ <button id="generate-btn" class="btn">
424
+ <i class="fas fa-magic"></i> Generate Image
425
+ </button>
426
+
427
+ <div class="progress-container">
428
+ <div class="progress-bar">
429
+ <div class="progress" id="progress-bar"></div>
430
+ </div>
431
+ </div>
432
+ </div>
433
+
434
+ <div class="preview">
435
+ <h2 class="section-title">
436
+ <i class="fas fa-film"></i>
437
+ Image Buffer & Player
438
+ </h2>
439
+
440
+ <div class="status">
441
+ <div class="status-item">
442
+ <div class="status-label">Images in Buffer</div>
443
+ <div class="status-value" id="buffer-count">0</div>
444
+ </div>
445
+ <div class="status-item">
446
+ <div class="status-label">Required for Playback</div>
447
+ <div class="status-value" id="required-count">10</div>
448
+ </div>
449
+ <div class="status-item">
450
+ <div class="status-label">Playback Speed</div>
451
+ <div class="status-value">30 FPS</div>
452
+ </div>
453
+ </div>
454
+
455
+ <div class="player-controls">
456
+ <button id="clear-buffer" class="btn btn-secondary">
457
+ <i class="fas fa-trash"></i> Clear Buffer
458
+ </button>
459
+ <button id="play-sequence" class="btn" disabled>
460
+ <i class="fas fa-play"></i> Play Sequence
461
+ </button>
462
+ <button id="stop-sequence" class="btn btn-danger" disabled>
463
+ <i class="fas fa-stop"></i> Stop
464
+ </button>
465
+ </div>
466
+
467
+ <div class="player-display" id="player-display">
468
+ <div class="empty-state">
469
+ <i class="fas fa-image"></i>
470
+ <p>No images in buffer yet</p>
471
+ </div>
472
+ </div>
473
+
474
+ <h3 style="margin-top: 1.5rem; font-size: 1.1rem; color: var(--dark-color);">
475
+ <i class="fas fa-inbox"></i> Image Buffer
476
+ </h3>
477
+
478
+ <div class="image-buffer" id="image-buffer">
479
+ <!-- Images will be added here -->
480
+ </div>
481
+ </div>
482
+ </div>
483
+
484
+ <script>
485
+ // Configuration
486
+ const config = {
487
+ requiredImages: 10,
488
+ playbackFPS: 30,
489
+ apiUrl: "https://diffusers-unofficial-sdxl-turbo-i2i-t2i.hf.space" // Placeholder for actual API
490
+ };
491
+
492
+ // State
493
+ let state = {
494
+ images: [],
495
+ isPlaying: false,
496
+ currentFrame: 0,
497
+ playbackInterval: null,
498
+ isGenerating: false
499
+ };
500
+
501
+ // DOM Elements
502
+ const elements = {
503
+ generateBtn: document.getElementById('generate-btn'),
504
+ playBtn: document.getElementById('play-sequence'),
505
+ stopBtn: document.getElementById('stop-sequence'),
506
+ clearBtn: document.getElementById('clear-buffer'),
507
+ imageBuffer: document.getElementById('image-buffer'),
508
+ playerDisplay: document.getElementById('player-display'),
509
+ bufferCount: document.getElementById('buffer-count'),
510
+ requiredCount: document.getElementById('required-count'),
511
+ progressBar: document.getElementById('progress-bar'),
512
+ promptInput: document.getElementById('prompt'),
513
+ negativePromptInput: document.getElementById('negative-prompt'),
514
+ stepsInput: document.getElementById('steps'),
515
+ guidanceScaleInput: document.getElementById('guidance-scale'),
516
+ seedInput: document.getElementById('seed'),
517
+ batchSizeInput: document.getElementById('batch-size')
518
+ };
519
+
520
+ // Initialize the application
521
+ function init() {
522
+ // Set required count display
523
+ elements.requiredCount.textContent = config.requiredImages;
524
+
525
+ // Set up event listeners
526
+ elements.generateBtn.addEventListener('click', generateImage);
527
+ elements.playBtn.addEventListener('click', playSequence);
528
+ elements.stopBtn.addEventListener('click', stopSequence);
529
+ elements.clearBtn.addEventListener('click', clearBuffer);
530
+
531
+ // Update UI based on initial state
532
+ updateUI();
533
+ }
534
+
535
+ // Generate a new image
536
+ async function generateImage() {
537
+ if (state.isGenerating) return;
538
+
539
+ const prompt = elements.promptInput.value.trim();
540
+ if (!prompt) {
541
+ alert('Please enter a prompt');
542
+ return;
543
+ }
544
+
545
+ state.isGenerating = true;
546
+ updateUI();
547
+
548
+ // Simulate image generation (in a real app, this would call the actual API)
549
+ try {
550
+ // Show progress
551
+ let progress = 0;
552
+ const progressInterval = setInterval(() => {
553
+ progress += Math.random() * 10;
554
+ if (progress > 90) progress = 90;
555
+ elements.progressBar.style.width = `${progress}%`;
556
+ }, 300);
557
+
558
+ // Simulate API call delay
559
+ await new Promise(resolve => setTimeout(resolve, 2000 + Math.random() * 3000));
560
+
561
+ // Generate a random image (placeholder for actual API response)
562
+ const imageData = generateRandomImageData();
563
+
564
+ // Add to buffer
565
+ addImageToBuffer(imageData);
566
+
567
+ // Complete progress
568
+ elements.progressBar.style.width = '100%';
569
+ clearInterval(progressInterval);
570
+
571
+ // Reset progress after a short delay
572
+ setTimeout(() => {
573
+ elements.progressBar.style.width = '0%';
574
+ }, 500);
575
+ } catch (error) {
576
+ console.error('Error generating image:', error);
577
+ alert('Failed to generate image. Please try again.');
578
+ } finally {
579
+ state.isGenerating = false;
580
+ updateUI();
581
+ }
582
+ }
583
+
584
+ // Generate random image data (placeholder for actual API response)
585
+ function generateRandomImageData() {
586
+ const canvas = document.createElement('canvas');
587
+ canvas.width = 512;
588
+ canvas.height = 512;
589
+ const ctx = canvas.getContext('2d');
590
+
591
+ // Generate random colors
592
+ const colors = [];
593
+ for (let i = 0; i < 5; i++) {
594
+ colors.push(`hsl(${Math.random() * 360}, 70%, 60%)`);
595
+ }
596
+
597
+ // Create a gradient pattern
598
+ const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
599
+ colors.forEach((color, i) => {
600
+ gradient.addColorStop(i / (colors.length - 1), color);
601
+ });
602
+
603
+ // Fill with gradient
604
+ ctx.fillStyle = gradient;
605
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
606
+
607
+ // Add some random shapes
608
+ for (let i = 0; i < 10; i++) {
609
+ ctx.fillStyle = `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.7)`;
610
+ const size = 50 + Math.random() * 100;
611
+ const x = Math.random() * (canvas.width - size);
612
+ const y = Math.random() * (canvas.height - size);
613
+
614
+ if (Math.random() > 0.5) {
615
+ ctx.beginPath();
616
+ ctx.arc(x + size/2, y + size/2, size/2, 0, Math.PI * 2);
617
+ ctx.fill();
618
+ } else {
619
+ ctx.fillRect(x, y, size, size);
620
+ }
621
+ }
622
+
623
+ // Add text (the prompt)
624
+ const prompt = elements.promptInput.value.trim();
625
+ if (prompt) {
626
+ ctx.font = 'bold 24px Arial';
627
+ ctx.fillStyle = 'white';
628
+ ctx.textAlign = 'center';
629
+ ctx.textBaseline = 'middle';
630
+ ctx.fillText(prompt.substring(0, 30) + (prompt.length > 30 ? '...' : ''), canvas.width/2, canvas.height/2);
631
+ }
632
+
633
+ return canvas.toDataURL('image/png');
634
+ }
635
+
636
+ // Add image to buffer
637
+ function addImageToBuffer(imageData) {
638
+ state.images.push(imageData);
639
+ updateBufferDisplay();
640
+ updateUI();
641
+ }
642
+
643
+ // Update the buffer display
644
+ function updateBufferDisplay() {
645
+ elements.imageBuffer.innerHTML = '';
646
+
647
+ if (state.images.length === 0) {
648
+ elements.imageBuffer.innerHTML = `
649
+ <div class="empty-state" style="grid-column: 1 / -1;">
650
+ <i class="fas fa-inbox"></i>
651
+ <p>No images in buffer</p>
652
+ </div>
653
+ `;
654
+ return;
655
+ }
656
+
657
+ state.images.forEach((imageData, index) => {
658
+ const bufferItem = document.createElement('div');
659
+ bufferItem.className = 'buffer-item';
660
+ bufferItem.innerHTML = `
661
+ <img src="${imageData}" alt="Generated image ${index + 1}">
662
+ <button class="remove-btn" data-index="${index}">
663
+ <i class="fas fa-times"></i>
664
+ </button>
665
+ `;
666
+ elements.imageBuffer.appendChild(bufferItem);
667
+ });
668
+
669
+ // Add event listeners to remove buttons
670
+ document.querySelectorAll('.remove-btn').forEach(btn => {
671
+ btn.addEventListener('click', (e) => {
672
+ const index = parseInt(e.target.closest('.remove-btn').dataset.index);
673
+ removeImageFromBuffer(index);
674
+ });
675
+ });
676
+ }
677
+
678
+ // Remove image from buffer
679
+ function removeImageFromBuffer(index) {
680
+ state.images.splice(index, 1);
681
+ updateBufferDisplay();
682
+ updateUI();
683
+ }
684
+
685
+ // Clear the entire buffer
686
+ function clearBuffer() {
687
+ state.images = [];
688
+ updateBufferDisplay();
689
+ updateUI();
690
+ stopSequence();
691
+ }
692
+
693
+ // Play the image sequence
694
+ function playSequence() {
695
+ if (state.isPlaying || state.images.length < config.requiredImages) return;
696
+
697
+ state.isPlaying = true;
698
+ state.currentFrame = 0;
699
+ updateUI();
700
+
701
+ // Clear player display
702
+ elements.playerDisplay.innerHTML = '';
703
+ state.images.forEach((imageData, index) => {
704
+ const img = document.createElement('img');
705
+ img.src = imageData;
706
+ img.alt = `Frame ${index + 1}`;
707
+ if (index === 0) img.classList.add('active');
708
+ elements.playerDisplay.appendChild(img);
709
+ });
710
+
711
+ // Start playback
712
+ const frameDuration = 1000 / config.playbackFPS;
713
+ state.playbackInterval = setInterval(() => {
714
+ const images = elements.playerDisplay.querySelectorAll('img');
715
+ images.forEach(img => img.classList.remove('active'));
716
+
717
+ if (state.currentFrame >= state.images.length) {
718
+ state.currentFrame = 0;
719
+ }
720
+
721
+ images[state.currentFrame].classList.add('active');
722
+ state.currentFrame++;
723
+ }, frameDuration);
724
+ }
725
+
726
+ // Stop the image sequence
727
+ function stopSequence() {
728
+ if (!state.isPlaying) return;
729
+
730
+ clearInterval(state.playbackInterval);
731
+ state.isPlaying = false;
732
+ state.currentFrame = 0;
733
+ updateUI();
734
+
735
+ // Clear player display
736
+ elements.playerDisplay.innerHTML = `
737
+ <div class="empty-state">
738
+ <i class="fas fa-image"></i>
739
+ <p>Playback stopped</p>
740
+ </div>
741
+ `;
742
+ }
743
+
744
+ // Update UI based on current state
745
+ function updateUI() {
746
+ // Update buffer count
747
+ elements.bufferCount.textContent = state.images.length;
748
+
749
+ // Update button states
750
+ elements.playBtn.disabled = state.images.length < config.requiredImages || state.isPlaying;
751
+ elements.stopBtn.disabled = !state.isPlaying;
752
+ elements.generateBtn.disabled = state.isGenerating;
753
+ elements.generateBtn.textContent = state.isGenerating ?
754
+ '<i class="fas fa-spinner fa-spin"></i> Generating...' :
755
+ '<i class="fas fa-magic"></i> Generate Image';
756
+
757
+ // Update player display if not playing
758
+ if (!state.isPlaying && state.images.length === 0) {
759
+ elements.playerDisplay.innerHTML = `
760
+ <div class="empty-state">
761
+ <i class="fas fa-image"></i>
762
+ <p>No images in buffer yet</p>
763
+ </div>
764
+ `;
765
+ }
766
+ }
767
+
768
+ // Initialize the app when DOM is loaded
769
+ document.addEventListener('DOMContentLoaded', init);
770
+ </script>
771
+ </body>
772
+ </html>