Abhi1907 commited on
Commit
2998a40
·
verified ·
1 Parent(s): f3a64d3

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +754 -19
index.html CHANGED
@@ -1,19 +1,754 @@
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>Z-Image Turbo | AI Generator</title>
7
+ <!-- Import Remix Icon -->
8
+ <link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
9
+ <!-- Import Google Fonts -->
10
+ <link rel="preconnect" href="https://fonts.googleapis.com">
11
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
13
+
14
+ <style>
15
+ /* --- CSS VARIABLES & RESET --- */
16
+ :root {
17
+ --bg-dark: #0f1115;
18
+ --bg-panel: rgba(23, 25, 30, 0.75);
19
+ --bg-input: rgba(0, 0, 0, 0.3);
20
+ --primary: #6366f1;
21
+ --primary-glow: rgba(99, 102, 241, 0.4);
22
+ --accent: #ec4899;
23
+ --text-main: #ffffff;
24
+ --text-muted: #9ca3af;
25
+ --border: rgba(255, 255, 255, 0.1);
26
+ --radius-lg: 16px;
27
+ --radius-md: 8px;
28
+ --radius-sm: 4px;
29
+ --font-display: 'Space Grotesk', sans-serif;
30
+ --font-body: 'Inter', sans-serif;
31
+ --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
32
+ }
33
+
34
+ * {
35
+ margin: 0;
36
+ padding: 0;
37
+ box-sizing: border-box;
38
+ }
39
+
40
+ body {
41
+ font-family: var(--font-body);
42
+ background-color: var(--bg-dark);
43
+ color: var(--text-main);
44
+ min-height: 100vh;
45
+ overflow-x: hidden;
46
+ background-image:
47
+ radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.15) 0%, transparent 40%),
48
+ radial-gradient(circle at 90% 80%, rgba(236, 72, 153, 0.15) 0%, transparent 40%);
49
+ background-attachment: fixed;
50
+ }
51
+
52
+ /* --- UTILITIES --- */
53
+ .glass {
54
+ background: var(--bg-panel);
55
+ backdrop-filter: blur(16px);
56
+ -webkit-backdrop-filter: blur(16px);
57
+ border: 1px solid var(--border);
58
+ }
59
+
60
+ .container {
61
+ max-width: 1400px;
62
+ margin: 0 auto;
63
+ padding: 0 20px;
64
+ }
65
+
66
+ .btn {
67
+ display: inline-flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ gap: 8px;
71
+ padding: 12px 24px;
72
+ border-radius: var(--radius-md);
73
+ font-weight: 600;
74
+ font-family: var(--font-display);
75
+ border: none;
76
+ cursor: pointer;
77
+ transition: var(--transition);
78
+ font-size: 0.95rem;
79
+ }
80
+
81
+ .btn-primary {
82
+ background: linear-gradient(135deg, var(--primary), #4f46e5);
83
+ color: white;
84
+ box-shadow: 0 4px 20px var(--primary-glow);
85
+ }
86
+
87
+ .btn-primary:hover {
88
+ transform: translateY(-2px);
89
+ box-shadow: 0 8px 25px var(--primary-glow);
90
+ }
91
+
92
+ .btn-primary:active {
93
+ transform: translateY(0);
94
+ }
95
+
96
+ .btn-icon {
97
+ padding: 8px;
98
+ background: rgba(255, 255, 255, 0.1);
99
+ color: var(--text-main);
100
+ border-radius: var(--radius-sm);
101
+ }
102
+
103
+ .btn-icon:hover {
104
+ background: rgba(255, 255, 255, 0.2);
105
+ }
106
+
107
+ /* --- HEADER --- */
108
+ header {
109
+ position: fixed;
110
+ top: 0;
111
+ left: 0;
112
+ width: 100%;
113
+ z-index: 100;
114
+ padding: 16px 0;
115
+ border-bottom: 1px solid var(--border);
116
+ background: rgba(15, 17, 21, 0.8);
117
+ backdrop-filter: blur(10px);
118
+ }
119
+
120
+ .header-content {
121
+ display: flex;
122
+ justify-content: space-between;
123
+ align-items: center;
124
+ }
125
+
126
+ .logo {
127
+ font-family: var(--font-display);
128
+ font-size: 1.5rem;
129
+ font-weight: 700;
130
+ background: linear-gradient(to right, #fff, #a5b4fc);
131
+ -webkit-background-clip: text;
132
+ -webkit-text-fill-color: transparent;
133
+ display: flex;
134
+ align-items: center;
135
+ gap: 10px;
136
+ }
137
+
138
+ .logo i {
139
+ color: var(--primary);
140
+ -webkit-text-fill-color: var(--primary);
141
+ }
142
+
143
+ .anycoder-link {
144
+ font-size: 0.85rem;
145
+ color: var(--text-muted);
146
+ text-decoration: none;
147
+ transition: var(--transition);
148
+ display: flex;
149
+ align-items: center;
150
+ gap: 6px;
151
+ }
152
+
153
+ .anycoder-link:hover {
154
+ color: var(--primary);
155
+ }
156
+
157
+ /* --- MAIN LAYOUT --- */
158
+ main {
159
+ padding-top: 100px;
160
+ padding-bottom: 40px;
161
+ display: grid;
162
+ grid-template-columns: 350px 1fr;
163
+ gap: 30px;
164
+ min-height: calc(100vh - 80px);
165
+ }
166
+
167
+ /* --- SIDEBAR CONTROLS --- */
168
+ .controls-panel {
169
+ border-radius: var(--radius-lg);
170
+ padding: 24px;
171
+ height: fit-content;
172
+ position: sticky;
173
+ top: 100px;
174
+ display: flex;
175
+ flex-direction: column;
176
+ gap: 24px;
177
+ }
178
+
179
+ .input-group {
180
+ display: flex;
181
+ flex-direction: column;
182
+ gap: 8px;
183
+ }
184
+
185
+ .input-label {
186
+ font-size: 0.85rem;
187
+ font-weight: 600;
188
+ color: var(--text-muted);
189
+ text-transform: uppercase;
190
+ letter-spacing: 0.5px;
191
+ display: flex;
192
+ justify-content: space-between;
193
+ }
194
+
195
+ textarea, input[type="text"], select {
196
+ width: 100%;
197
+ background: var(--bg-input);
198
+ border: 1px solid var(--border);
199
+ border-radius: var(--radius-md);
200
+ padding: 12px 16px;
201
+ color: var(--text-main);
202
+ font-family: var(--font-body);
203
+ font-size: 0.95rem;
204
+ transition: var(--transition);
205
+ resize: vertical;
206
+ }
207
+
208
+ textarea:focus, input[type="text"]:focus, select:focus {
209
+ outline: none;
210
+ border-color: var(--primary);
211
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
212
+ }
213
+
214
+ textarea {
215
+ min-height: 120px;
216
+ }
217
+
218
+ .settings-grid {
219
+ display: grid;
220
+ grid-template-columns: 1fr 1fr;
221
+ gap: 16px;
222
+ }
223
+
224
+ .range-wrap {
225
+ display: flex;
226
+ flex-direction: column;
227
+ gap: 10px;
228
+ }
229
+
230
+ input[type="range"] {
231
+ width: 100%;
232
+ height: 4px;
233
+ background: rgba(255,255,255,0.1);
234
+ border-radius: 2px;
235
+ appearance: none;
236
+ outline: none;
237
+ }
238
+
239
+ input[type="range"]::-webkit-slider-thumb {
240
+ appearance: none;
241
+ width: 16px;
242
+ height: 16px;
243
+ border-radius: 50%;
244
+ background: var(--primary);
245
+ cursor: pointer;
246
+ transition: var(--transition);
247
+ }
248
+
249
+ /* --- PREVIEW AREA --- */
250
+ .preview-area {
251
+ display: flex;
252
+ flex-direction: column;
253
+ gap: 24px;
254
+ }
255
+
256
+ .main-image-container {
257
+ width: 100%;
258
+ min-height: 500px;
259
+ border-radius: var(--radius-lg);
260
+ border: 1px solid var(--border);
261
+ background: rgba(0,0,0,0.2);
262
+ display: flex;
263
+ align-items: center;
264
+ justify-content: center;
265
+ position: relative;
266
+ overflow: hidden;
267
+ box-shadow: 0 20px 50px rgba(0,0,0,0.3);
268
+ }
269
+
270
+ .main-image {
271
+ width: 100%;
272
+ height: 100%;
273
+ object-fit: contain;
274
+ display: block;
275
+ opacity: 0;
276
+ transition: opacity 0.5s ease;
277
+ }
278
+
279
+ .main-image.loaded {
280
+ opacity: 1;
281
+ }
282
+
283
+ .placeholder-state {
284
+ text-align: center;
285
+ color: var(--text-muted);
286
+ display: flex;
287
+ flex-direction: column;
288
+ align-items: center;
289
+ gap: 16px;
290
+ }
291
+
292
+ .placeholder-state i {
293
+ font-size: 3rem;
294
+ opacity: 0.5;
295
+ }
296
+
297
+ /* Loading Spinner */
298
+ .loader {
299
+ display: none;
300
+ width: 48px;
301
+ height: 48px;
302
+ border: 5px solid #FFF;
303
+ border-bottom-color: var(--primary);
304
+ border-radius: 50%;
305
+ animation: rotation 1s linear infinite;
306
+ position: absolute;
307
+ z-index: 10;
308
+ }
309
+
310
+ @keyframes rotation {
311
+ 0% { transform: rotate(0deg); }
312
+ 100% { transform: rotate(360deg); }
313
+ }
314
+
315
+ /* Image Actions */
316
+ .image-actions {
317
+ position: absolute;
318
+ bottom: 20px;
319
+ right: 20px;
320
+ display: flex;
321
+ gap: 10px;
322
+ opacity: 0;
323
+ transform: translateY(10px);
324
+ transition: var(--transition);
325
+ }
326
+
327
+ .main-image-container:hover .image-actions {
328
+ opacity: 1;
329
+ transform: translateY(0);
330
+ }
331
+
332
+ /* History Strip */
333
+ .history-section {
334
+ margin-top: auto;
335
+ }
336
+
337
+ .history-header {
338
+ display: flex;
339
+ justify-content: space-between;
340
+ align-items: center;
341
+ margin-bottom: 16px;
342
+ }
343
+
344
+ .history-grid {
345
+ display: grid;
346
+ grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
347
+ gap: 12px;
348
+ }
349
+
350
+ .history-item {
351
+ aspect-ratio: 1;
352
+ border-radius: var(--radius-md);
353
+ overflow: hidden;
354
+ border: 1px solid transparent;
355
+ cursor: pointer;
356
+ transition: var(--transition);
357
+ position: relative;
358
+ }
359
+
360
+ .history-item img {
361
+ width: 100%;
362
+ height: 100%;
363
+ object-fit: cover;
364
+ }
365
+
366
+ .history-item:hover {
367
+ border-color: var(--primary);
368
+ transform: scale(1.05);
369
+ }
370
+
371
+ /* --- TOAST --- */
372
+ .toast-container {
373
+ position: fixed;
374
+ bottom: 30px;
375
+ right: 30px;
376
+ z-index: 1000;
377
+ display: flex;
378
+ flex-direction: column;
379
+ gap: 10px;
380
+ }
381
+
382
+ .toast {
383
+ background: var(--bg-panel);
384
+ border: 1px solid var(--border);
385
+ backdrop-filter: blur(10px);
386
+ padding: 16px 20px;
387
+ border-radius: var(--radius-md);
388
+ color: white;
389
+ display: flex;
390
+ align-items: center;
391
+ gap: 12px;
392
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
393
+ animation: slideIn 0.3s ease;
394
+ max-width: 300px;
395
+ }
396
+
397
+ .toast i {
398
+ color: var(--primary);
399
+ font-size: 1.2rem;
400
+ }
401
+
402
+ @keyframes slideIn {
403
+ from { transform: translateX(100%); opacity: 0; }
404
+ to { transform: translateX(0); opacity: 1; }
405
+ }
406
+
407
+ /* --- RESPONSIVE --- */
408
+ @media (max-width: 900px) {
409
+ main {
410
+ grid-template-columns: 1fr;
411
+ }
412
+
413
+ .controls-panel {
414
+ position: relative;
415
+ top: 0;
416
+ }
417
+ }
418
+ </style>
419
+ </head>
420
+ <body>
421
+
422
+ <!-- Header -->
423
+ <header class="glass">
424
+ <div class="container header-content">
425
+ <div class="logo">
426
+ <i class="ri-bolt-flash-fill"></i>
427
+ Z-Image Turbo
428
+ </div>
429
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
430
+ Built with anycoder <i class="ri-external-link-line"></i>
431
+ </a>
432
+ </div>
433
+ </header>
434
+
435
+ <!-- Main Application -->
436
+ <main class="container">
437
+
438
+ <!-- Sidebar Controls -->
439
+ <aside class="controls-panel glass">
440
+ <div class="input-group">
441
+ <label class="input-label">Prompt</label>
442
+ <textarea id="promptInput" placeholder="Describe the image you want to generate... e.g., A futuristic cyberpunk city with neon lights, 4k, highly detailed"></textarea>
443
+ </div>
444
+
445
+ <div class="input-group">
446
+ <label class="input-label">Negative Prompt</label>
447
+ <textarea id="negativeInput" placeholder="What to avoid? e.g., blurry, low quality, distorted" style="min-height: 60px;"></textarea>
448
+ </div>
449
+
450
+ <div class="settings-grid">
451
+ <div class="input-group">
452
+ <label class="input-label">Aspect Ratio</label>
453
+ <select id="aspectRatio">
454
+ <option value="1:1">Square (1:1)</option>
455
+ <option value="16:9">Landscape (16:9)</option>
456
+ <option value="9:16">Portrait (9:16)</option>
457
+ <option value="4:3">Classic (4:3)</option>
458
+ </select>
459
+ </div>
460
+ <div class="input-group">
461
+ <label class="input-label">Style Seed</label>
462
+ <input type="text" id="seedInput" placeholder="Random">
463
+ </div>
464
+ </div>
465
+
466
+ <div class="input-group">
467
+ <label class="input-label">Turbo Speed</label>
468
+ <div class="range-wrap">
469
+ <input type="range" min="1" max="10" value="5" id="speedRange">
470
+ </div>
471
+ </div>
472
+
473
+ <button class="btn btn-primary" id="generateBtn">
474
+ <i class="ri-magic-line"></i> Generate Image
475
+ </button>
476
+ </aside>
477
+
478
+ <!-- Preview Area -->
479
+ <section class="preview-area">
480
+ <div class="main-image-container glass" id="mainImageContainer">
481
+ <span class="loader" id="loader"></span>
482
+
483
+ <div class="placeholder-state" id="placeholderState">
484
+ <i class="ri-image-add-line"></i>
485
+ <p>Enter a prompt and hit Generate to start.</p>
486
+ </div>
487
+
488
+ <img src="" alt="Generated AI Art" class="main-image" id="mainImage">
489
+
490
+ <div class="image-actions" id="imageActions" style="display:none;">
491
+ <button class="btn btn-primary glass" id="downloadBtn" title="Download Image">
492
+ <i class="ri-download-line"></i>
493
+ </button>
494
+ <button class="btn btn-icon glass" id="fullscreenBtn" title="View Fullscreen">
495
+ <i class="ri-fullscreen-line"></i>
496
+ </button>
497
+ </div>
498
+ </div>
499
+
500
+ <!-- History Grid -->
501
+ <div class="history-section">
502
+ <div class="history-header">
503
+ <h3 style="font-family: var(--font-display);">Recent Generations</h3>
504
+ <button class="btn-icon" id="clearHistoryBtn" title="Clear History">
505
+ <i class="ri-delete-bin-line"></i>
506
+ </button>
507
+ </div>
508
+ <div class="history-grid" id="historyGrid">
509
+ <!-- History items injected here -->
510
+ </div>
511
+ </div>
512
+ </section>
513
+
514
+ </main>
515
+
516
+ <!-- Toast Container -->
517
+ <div class="toast-container" id="toastContainer"></div>
518
+
519
+ <script>
520
+ /**
521
+ * Z-Image Turbo Logic
522
+ * Handles API calls to Pollinations.ai (SDXL Turbo backend simulation)
523
+ * and manages UI state.
524
+ */
525
+
526
+ // Elements
527
+ const promptInput = document.getElementById('promptInput');
528
+ const negativeInput = document.getElementById('negativeInput');
529
+ const aspectRatioSelect = document.getElementById('aspectRatio');
530
+ const seedInput = document.getElementById('seedInput');
531
+ const generateBtn = document.getElementById('generateBtn');
532
+ const mainImage = document.getElementById('mainImage');
533
+ const loader = document.getElementById('loader');
534
+ const placeholderState = document.getElementById('placeholderState');
535
+ const imageActions = document.getElementById('imageActions');
536
+ const historyGrid = document.getElementById('historyGrid');
537
+ const downloadBtn = document.getElementById('downloadBtn');
538
+ const fullscreenBtn = document.getElementById('fullscreenBtn');
539
+ const clearHistoryBtn = document.getElementById('clearHistoryBtn');
540
+
541
+ // State
542
+ let isGenerating = false;
543
+ let currentImageUrl = '';
544
+ let history = [];
545
+
546
+ // Aspect Ratio Map (Width x Height)
547
+ const dimensions = {
548
+ '1:1': { w: 1024, h: 1024 },
549
+ '16:9': { w: 1280, h: 720 },
550
+ '9:16': { w: 720, h: 1280 },
551
+ '4:3': { w: 1024, h: 768 }
552
+ };
553
+
554
+ // --- Core Functions ---
555
+
556
+ /**
557
+ * Generates the random seed if not provided
558
+ */
559
+ const getSeed = () => {
560
+ const val = seedInput.value.trim();
561
+ return val ? val : Math.floor(Math.random() * 1000000);
562
+ };
563
+
564
+ /**
565
+ * Constructs the API URL
566
+ */
567
+ const buildApiUrl = () => {
568
+ const prompt = promptInput.value.trim();
569
+ const negative = negativeInput.value.trim();
570
+ const ratio = aspectRatioSelect.value;
571
+ const seed = getSeed();
572
+ const { w, h } = dimensions[ratio];
573
+
574
+ if (!prompt) {
575
+ showToast("Please enter a prompt first", "error");
576
+ return null;
577
+ }
578
+
579
+ // Using Pollinations.ai API (Free, No Key, SDXL Turbo compatible)
580
+ // We encode the prompt to handle special characters
581
+ const encodedPrompt = encodeURIComponent(prompt + (negative ? `, negative: ${negative}` : ''));
582
+
583
+ // Construct URL
584
+ const url = `https://image.pollinations.ai/prompt/${encodedPrompt}?width=${w}&height=${h}&seed=${seed}&nologo=true&model=flux`; // Using flux or turbo model
585
+
586
+ return url;
587
+ };
588
+
589
+ /**
590
+ * Triggers the Image Generation
591
+ */
592
+ const handleGenerate = async () => {
593
+ if (isGenerating) return;
594
+
595
+ const url = buildApiUrl();
596
+ if (!url) return;
597
+
598
+ // UI Updates for loading state
599
+ isGenerating = true;
600
+ generateBtn.innerHTML = '<i class="ri-loader-4-line ri-spin"></i> Generating...';
601
+ generateBtn.style.opacity = '0.7';
602
+ loader.style.display = 'block';
603
+ placeholderState.style.display = 'none';
604
+ mainImage.classList.remove('loaded');
605
+ imageActions.style.display = 'none';
606
+
607
+ // Preload Image
608
+ const img = new Image();
609
+ img.src = url;
610
+
611
+ img.onload = () => {
612
+ // Success
613
+ mainImage.src = url;
614
+ currentImageUrl = url;
615
+ mainImage.classList.add('loaded');
616
+
617
+ // UI Reset
618
+ loader.style.display = 'none';
619
+ imageActions.style.display = 'flex';
620
+ generateBtn.innerHTML = '<i class="ri-magic-line"></i> Generate Image';
621
+ generateBtn.style.opacity = '1';
622
+ isGenerating = false;
623
+
624
+ // Add to history
625
+ addToHistory(url, promptInput.value);
626
+ showToast("Image generated successfully!", "success");
627
+ };
628
+
629
+ img.onerror = () => {
630
+ // Error
631
+ loader.style.display = 'none';
632
+ placeholderState.style.display = 'flex';
633
+ placeholderState.innerHTML = '<i class="ri-error-warning-line" style="color:var(--accent)"></i><p>Failed to generate image. Try again.</p>';
634
+ generateBtn.innerHTML = '<i class="ri-magic-line"></i> Generate Image';
635
+ generateBtn.style.opacity = '1';
636
+ isGenerating = false;
637
+ showToast("Generation failed. Check your connection.", "error");
638
+ };
639
+ };
640
+
641
+ /**
642
+ * Adds generated image to the history grid
643
+ */
644
+ const addToHistory = (url, prompt) => {
645
+ // Prevent duplicates at the top
646
+ if (history.length > 0 && history[0].url === url) return;
647
+
648
+ history.unshift({ url, prompt });
649
+ if (history.length > 8) history.pop(); // Keep last 8
650
+
651
+ renderHistory();
652
+ };
653
+
654
+ /**
655
+ * Renders the history grid
656
+ */
657
+ const renderHistory = () => {
658
+ historyGrid.innerHTML = '';
659
+ history.forEach((item) => {
660
+ const div = document.createElement('div');
661
+ div.className = 'history-item';
662
+ div.innerHTML = `<img src="${item.url}" alt="History Item">`;
663
+ div.title = item.prompt;
664
+
665
+ div.onclick = () => {
666
+ // Load history item to main view
667
+ mainImage.src = item.url;
668
+ currentImageUrl = item.url;
669
+ mainImage.classList.add('loaded');
670
+ placeholderState.style.display = 'none';
671
+ imageActions.style.display = 'flex';
672
+ promptInput.value = item.prompt;
673
+ };
674
+
675
+ historyGrid.appendChild(div);
676
+ });
677
+ };
678
+
679
+ /**
680
+ * Downloads the current image
681
+ */
682
+ const handleDownload = async () => {
683
+ if (!currentImageUrl) return;
684
+
685
+ try {
686
+ showToast("Downloading...", "success");
687
+ const response = await fetch(currentImageUrl);
688
+ const blob = await response.blob();
689
+ const url = window.URL.createObjectURL(blob);
690
+ const a = document.createElement('a');
691
+ a.style.display = 'none';
692
+ a.href = url;
693
+ a.download = `z-turbo-${Date.now()}.jpg`;
694
+ document.body.appendChild(a);
695
+ a.click();
696
+ window.URL.revokeObjectURL(url);
697
+ document.body.removeChild(a);
698
+ } catch (err) {
699
+ showToast("Download failed", "error");
700
+ }
701
+ };
702
+
703
+ /**
704
+ * Displays a toast notification
705
+ */
706
+ const showToast = (message, type = 'success') => {
707
+ const toast = document.createElement('div');
708
+ toast.className = 'toast';
709
+ const icon = type === 'success' ? 'ri-checkbox-circle-line' : 'ri-error-warning-line';
710
+
711
+ toast.innerHTML = `<i class="${icon}"></i> <span>${message}</span>`;
712
+
713
+ const container = document.getElementById('toastContainer');
714
+ container.appendChild(toast);
715
+
716
+ // Remove after 3 seconds
717
+ setTimeout(() => {
718
+ toast.style.opacity = '0';
719
+ toast.style.transform = 'translateY(20px)';
720
+ setTimeout(() => toast.remove(), 300);
721
+ }, 3000);
722
+ };
723
+
724
+ // --- Event Listeners ---
725
+
726
+ generateBtn.addEventListener('click', handleGenerate);
727
+
728
+ // Allow Ctrl+Enter to generate
729
+ document.addEventListener('keydown', (e) => {
730
+ if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
731
+ handleGenerate();
732
+ }
733
+ });
734
+
735
+ downloadBtn.addEventListener('click', handleDownload);
736
+
737
+ fullscreenBtn.addEventListener('click', () => {
738
+ if (mainImage.src) {
739
+ window.open(currentImageUrl, '_blank');
740
+ }
741
+ });
742
+
743
+ clearHistoryBtn.addEventListener('click', () => {
744
+ history = [];
745
+ renderHistory();
746
+ showToast("History cleared", "success");
747
+ });
748
+
749
+ // Initialize empty state
750
+ renderHistory();
751
+
752
+ </script>
753
+ </body>
754
+ </html>