artydev commited on
Commit
a9d40c7
·
verified ·
1 Parent(s): 23c44d0

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1536 -29
index.html CHANGED
@@ -1,30 +1,1537 @@
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>HELLO</h1>
12
- <h1>Welcome to your static Space!</h1>
13
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
14
- <p>
15
- Also don't forget to check the
16
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
17
- </p>
18
- </div>
19
- <script>
20
- function hideHeader () {
21
- const header = document.querySelector("header");
22
- header.style.display = "none";
23
- alert("Hiding")
24
- }
25
-
26
- setTimeout(hideHeader, 2000)
27
-
28
- </script>
29
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <!-- Title will be set dynamically -->
8
+ <title>GenImage</title>
9
+ <style>
10
+ /* --- CONFIGURATION & SETUP --- */
11
+ :root {
12
+ --primary: #667eea;
13
+ --secondary: #764ba2;
14
+ --bg-dark: #0f0f23;
15
+ --bg-medium: #1a1a2e;
16
+ --bg-light: #2a2a3a;
17
+ --text-primary: #ffffff;
18
+ --text-secondary: #aaaaaa;
19
+ --success: #48bb78;
20
+ --error: #f56565;
21
+ --border: rgba(255, 255, 255, 0.1);
22
+ }
23
+
24
+ * {
25
+ box-sizing: border-box;
26
+ margin: 0;
27
+ padding: 0;
28
+ }
29
+
30
+ body {
31
+ font-family: 'Segoe UI', Arial, sans-serif;
32
+ background: var(--bg-dark);
33
+ color: var(--text-primary);
34
+ line-height: 1.6;
35
+ -webkit-font-smoothing: antialiased;
36
+ }
37
+
38
+ /* --- MAIN LAYOUT & HEADER --- */
39
+ .app-layout {
40
+ display: flex;
41
+ flex-direction: column;
42
+ min-height: 100vh;
43
+ max-width: 1400px;
44
+ margin: 0 auto;
45
+ padding: 2rem;
46
+ }
47
+
48
+ .header {
49
+ text-align: center;
50
+ margin-bottom: 2rem;
51
+ position: relative;
52
+ }
53
+
54
+ h1 {
55
+ font-size: 2.5rem;
56
+ font-weight: 600;
57
+ background: linear-gradient(90deg, var(--primary), var(--secondary));
58
+ -webkit-background-clip: text;
59
+ background-clip: text;
60
+ color: transparent;
61
+ margin-bottom: 0.5rem;
62
+ }
63
+
64
+ .tagline {
65
+ color: var(--text-secondary);
66
+ font-size: 1.1rem;
67
+ }
68
+
69
+ /* --- LANGUAGE SWITCHER --- */
70
+ .language-switcher {
71
+ position: absolute;
72
+ top: 0;
73
+ right: 0;
74
+ z-index: 100;
75
+ }
76
+
77
+ .lang-switcher-toggle {
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 0.5rem;
81
+ background: var(--bg-medium);
82
+ border: 1px solid var(--border);
83
+ color: var(--text-primary);
84
+ font-weight: 600;
85
+ padding: 0.5rem 1rem;
86
+ border-radius: 12px;
87
+ cursor: pointer;
88
+ transition: all 0.2s;
89
+ }
90
+
91
+ .lang-switcher-toggle:hover {
92
+ border-color: var(--primary);
93
+ }
94
+
95
+ .lang-switcher-toggle .arrow {
96
+ font-size: 0.8em;
97
+ transition: transform 0.2s;
98
+ }
99
+
100
+ .lang-switcher-toggle.open .arrow {
101
+ transform: rotate(180deg);
102
+ }
103
+
104
+ .lang-switcher-menu {
105
+ position: absolute;
106
+ top: calc(100% + 5px);
107
+ right: 0;
108
+ background: var(--bg-light);
109
+ border: 1px solid var(--border);
110
+ border-radius: 12px;
111
+ padding: 0.5rem;
112
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
113
+ display: flex;
114
+ flex-direction: column;
115
+ gap: 0.25rem;
116
+ opacity: 0;
117
+ transform: translateY(-10px);
118
+ pointer-events: none;
119
+ transition: all 0.2s ease-out;
120
+ }
121
+
122
+ .lang-switcher-menu.open {
123
+ opacity: 1;
124
+ transform: translateY(0);
125
+ pointer-events: all;
126
+ }
127
+
128
+ .lang-switcher-option {
129
+ background: none;
130
+ border: none;
131
+ color: var(--text-secondary);
132
+ font-weight: 600;
133
+ padding: 0.5rem 1rem;
134
+ border-radius: 8px;
135
+ cursor: pointer;
136
+ transition: all 0.2s;
137
+ text-align: left;
138
+ }
139
+
140
+ .lang-switcher-option:hover {
141
+ background: var(--primary);
142
+ color: var(--text-primary);
143
+ }
144
+
145
+ .lang-switcher-option.active {
146
+ color: var(--primary);
147
+ }
148
+
149
+ /* --- CONTROL PANEL & FORM --- */
150
+ .control-panel {
151
+ background: var(--bg-medium);
152
+ border: 1px solid var(--border);
153
+ border-radius: 16px;
154
+ padding: 1.5rem;
155
+ margin-bottom: 1.5rem;
156
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
157
+ }
158
+
159
+ .prompt-form {
160
+ display: flex;
161
+ flex-direction: column;
162
+ gap: 1rem;
163
+ }
164
+
165
+ .input-group {
166
+ position: relative;
167
+ }
168
+
169
+ .input-group input {
170
+ width: 100%;
171
+ background: var(--bg-light);
172
+ border: 2px solid var(--border);
173
+ border-radius: 12px;
174
+ padding: 1rem 1.5rem;
175
+ color: var(--text-primary);
176
+ font-size: 1rem;
177
+ transition: all 0.3s ease;
178
+ }
179
+
180
+ .input-group input:focus {
181
+ outline: none;
182
+ border-color: var(--primary);
183
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.3);
184
+ }
185
+
186
+ /* --- NEW TOOLBOX STYLES --- */
187
+ .toolbox-bar {
188
+ display: flex;
189
+ align-items: center;
190
+ flex-wrap: wrap;
191
+ gap: 1.5rem;
192
+ padding-top: 0.5rem;
193
+ border-top: 1px solid var(--border);
194
+ }
195
+
196
+ .toolbox-item {
197
+ display: flex;
198
+ align-items: center;
199
+ gap: 0.5rem;
200
+ }
201
+
202
+ .toolbox-item label {
203
+ font-size: 0.9rem;
204
+ color: var(--text-secondary);
205
+ white-space: nowrap;
206
+ }
207
+
208
+ .toolbox-item input {
209
+ width: 80px;
210
+ background: var(--bg-light);
211
+ border: 1px solid var(--border);
212
+ border-radius: 8px;
213
+ padding: 0.5rem;
214
+ color: var(--text-primary);
215
+ text-align: center;
216
+ }
217
+
218
+ .toolbox-item input:focus {
219
+ outline: none;
220
+ border-color: var(--primary);
221
+ }
222
+
223
+ .toolbox-actions {
224
+ display: flex;
225
+ gap: 0.75rem;
226
+ margin-left: auto;
227
+ /* Push buttons to the right */
228
+ }
229
+
230
+ /* --- BUTTONS --- */
231
+ .btn {
232
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
233
+ color: white;
234
+ border: none;
235
+ padding: 0.75rem 1.5rem;
236
+ font-size: 1rem;
237
+ font-weight: 600;
238
+ border-radius: 10px;
239
+ cursor: pointer;
240
+ transition: all 0.3s ease;
241
+ display: flex;
242
+ align-items: center;
243
+ justify-content: center;
244
+ gap: 0.5rem;
245
+ }
246
+
247
+ .btn:hover {
248
+ transform: translateY(-2px);
249
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
250
+ }
251
+
252
+ .btn-outline {
253
+ background: transparent;
254
+ border: 2px solid var(--primary);
255
+ color: var(--primary);
256
+ padding: 0.625rem 1.5rem;
257
+ /* Adjust padding for border */
258
+ }
259
+
260
+ .btn-outline:hover {
261
+ background: var(--primary);
262
+ color: white;
263
+ }
264
+
265
+ .btn-danger {
266
+ background: transparent;
267
+ border: 1px solid var(--error);
268
+ color: var(--error);
269
+ padding: 0.5rem 1rem;
270
+ border-radius: 12px;
271
+ font-size: 0.9rem;
272
+ }
273
+
274
+ .btn-danger:hover {
275
+ background: var(--error);
276
+ color: white;
277
+ transform: translateY(-2px);
278
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
279
+ }
280
+
281
+
282
+ .btn-action {
283
+ background: transparent;
284
+ border: 1px solid #667EEA;
285
+ color: #667EEA;
286
+ padding: 0.5rem 1rem;
287
+ border-radius: 12px;
288
+ font-size: 0.9rem;
289
+ }
290
+
291
+ .btn-action:hover {
292
+ background: #667EEA;
293
+ color: white;
294
+ transform: translateY(-2px);
295
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
296
+ }
297
+
298
+ /* --- TOOLBAR, TABS & BANNER --- */
299
+ .gallery-toolbar {
300
+
301
+
302
+ }
303
+
304
+ .banner {
305
+ flex-grow: 1;
306
+ display: flex;
307
+ align-items: center;
308
+ justify-content: space-between;
309
+ background: linear-gradient(90deg, var(--primary), var(--secondary));
310
+ padding: 0.75rem 1.5rem;
311
+ border-radius: 12px;
312
+ font-size: 0.95rem;
313
+ font-weight: 500;
314
+ }
315
+
316
+ .banner-content {
317
+ display: flex;
318
+ align-items: center;
319
+ gap: 0.75rem;
320
+ }
321
+
322
+ .banner-close {
323
+ background: none;
324
+ border: none;
325
+ color: rgba(255, 255, 255, 0.7);
326
+ font-size: 1.2rem;
327
+ cursor: pointer;
328
+ transition: color 0.2s;
329
+ padding: 0.25rem;
330
+ line-height: 1;
331
+ }
332
+
333
+ .banner-close:hover {
334
+ color: white;
335
+ }
336
+
337
+ .tabs {
338
+ display: flex;
339
+ gap: 0.5rem;
340
+ margin-bottom: 1.5rem;
341
+ border-bottom: 1px solid var(--border);
342
+ }
343
+
344
+ .tab {
345
+ padding: 0.75rem 1.25rem;
346
+ border-radius: 8px 8px 0 0;
347
+ cursor: pointer;
348
+ border: none;
349
+ background: none;
350
+ color: var(--text-secondary);
351
+ transition: all 0.2s;
352
+ border-bottom: 2px solid transparent;
353
+ }
354
+
355
+ .tab:hover {
356
+ background: var(--bg-light);
357
+ }
358
+
359
+ .tab.active {
360
+ font-weight: 600;
361
+ color: var(--text-primary);
362
+ border-bottom-color: var(--primary);
363
+ }
364
+
365
+ /* --- GALLERY & IMAGES --- */
366
+ .image-gallery {
367
+ display: grid;
368
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
369
+ gap: 1.5rem;
370
+ }
371
+
372
+ .image-card {
373
+ background: var(--bg-medium);
374
+ border: 1px solid var(--border);
375
+ border-radius: 16px;
376
+ overflow: hidden;
377
+ transition: transform 0.3s, box-shadow 0.3s;
378
+ display: flex;
379
+ flex-direction: column;
380
+ position: relative;
381
+ }
382
+
383
+ .image-card:hover {
384
+ transform: translateY(-5px);
385
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
386
+ }
387
+
388
+ .image-card.featured {
389
+ grid-column: span 2;
390
+ }
391
+
392
+ .image-display {
393
+ aspect-ratio: 1/1;
394
+ position: relative;
395
+ background: var(--bg-light);
396
+ cursor: pointer;
397
+ display: flex;
398
+ align-items: center;
399
+ justify-content: center;
400
+ }
401
+
402
+ .image-display img {
403
+ width: 100%;
404
+ height: 100%;
405
+ object-fit: cover;
406
+ transition: opacity 0.3s;
407
+ }
408
+
409
+ .image-info {
410
+ padding: 1rem;
411
+ flex-grow: 1;
412
+ display: flex;
413
+ flex-direction: column;
414
+ }
415
+
416
+ .image-prompt {
417
+ font-size: 0.95rem;
418
+ margin-bottom: 0.75rem;
419
+ display: -webkit-box;
420
+
421
+ -webkit-box-orient: vertical;
422
+ overflow: hidden;
423
+ flex-grow: 1;
424
+ }
425
+
426
+ .image-meta {
427
+ display: flex;
428
+ justify-content: space-between;
429
+ font-size: 0.8rem;
430
+ color: var(--text-secondary);
431
+ }
432
+
433
+ .image-actions {
434
+ position: absolute;
435
+ top: 10px;
436
+ right: 10px;
437
+ display: flex;
438
+ gap: 0.5rem;
439
+ z-index: 10;
440
+ }
441
+
442
+ .action-btn {
443
+ width: 32px;
444
+ height: 32px;
445
+ border-radius: 50%;
446
+ background: rgba(0, 0, 0, 0.6);
447
+ color: white;
448
+ border: 1px solid rgba(255, 255, 255, 0.1);
449
+ display: flex;
450
+ align-items: center;
451
+ justify-content: center;
452
+ cursor: pointer;
453
+ transition: all 0.2s;
454
+ }
455
+
456
+ .action-btn:hover {
457
+ transform: scale(1.1);
458
+ background: rgba(0, 0, 0, 0.8);
459
+ }
460
+
461
+ .action-btn.delete:hover {
462
+ background: rgba(255, 0, 0, 0.8);
463
+ }
464
+
465
+ .spinner {
466
+ width: 40px;
467
+ height: 40px;
468
+ border: 4px solid rgba(255, 255, 255, 0.1);
469
+ border-top: 4px solid var(--primary);
470
+ border-radius: 50%;
471
+ animation: spin 1s linear infinite;
472
+ position: relative;
473
+
474
+ }
475
+
476
+ .empty-state {
477
+ display: flex;
478
+ flex-direction: column;
479
+ align-items: center;
480
+ justify-content: center;
481
+ text-align: center;
482
+ gap: 1rem;
483
+ padding: 3rem;
484
+ grid-column: 1 / -1;
485
+ color: var(--text-secondary);
486
+ border: 2px dashed var(--border);
487
+ border-radius: 16px;
488
+ }
489
+
490
+ .empty-state-icon {
491
+ font-size: 3rem;
492
+ }
493
+
494
+ /* --- OVERLAYS (TOASTS & LIGHTBOX) --- */
495
+ .toast-container {
496
+ position: fixed;
497
+ bottom: 20px;
498
+ right: 20px;
499
+ z-index: 1000;
500
+ display: flex;
501
+ flex-direction: column;
502
+ gap: 1rem;
503
+ }
504
+
505
+ .toast {
506
+ display: flex;
507
+ align-items: center;
508
+ gap: 1rem;
509
+ padding: 1rem 1.5rem;
510
+ border-radius: 10px;
511
+ background: var(--bg-medium);
512
+ border-left: 4px solid var(--primary);
513
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
514
+ transform: translateX(calc(100% + 20px));
515
+ opacity: 0;
516
+ animation: toast-in 0.5s forwards cubic-bezier(0.25, 1, 0.5, 1);
517
+ }
518
+
519
+ .toast.success {
520
+ border-left-color: var(--success);
521
+ }
522
+
523
+ .toast.error {
524
+ border-left-color: var(--error);
525
+ }
526
+
527
+ .lightbox {
528
+ position: fixed;
529
+ top: 0;
530
+ left: 0;
531
+ width: 100%;
532
+ height: 100%;
533
+ background: rgba(0, 0, 0, 0.9);
534
+ z-index: 2000;
535
+ display: flex;
536
+ align-items: center;
537
+ justify-content: center;
538
+ opacity: 0;
539
+ pointer-events: none;
540
+ transition: opacity 0.3s;
541
+ }
542
+
543
+ .lightbox.active {
544
+ opacity: 1;
545
+ pointer-events: all;
546
+ }
547
+
548
+ .lightbox-content {
549
+ position: relative;
550
+ max-width: 90%;
551
+ max-height: 90%;
552
+ }
553
+
554
+ .lightbox-image {
555
+ max-width: 100%;
556
+ max-height: 90vh;
557
+ object-fit: contain;
558
+ }
559
+
560
+ .lightbox-close {
561
+ position: absolute;
562
+ top: -10px;
563
+ right: -10px;
564
+ background: var(--bg-dark);
565
+ border: 1px solid var(--border);
566
+ color: white;
567
+ font-size: 1.2rem;
568
+ width: 40px;
569
+ height: 40px;
570
+ border-radius: 50%;
571
+ cursor: pointer;
572
+ transition: transform 0.2s;
573
+ }
574
+
575
+ .lightbox-close:hover {
576
+ transform: scale(1.1);
577
+ }
578
+
579
+ /* --- ANIMATIONS & MEDIA QUERIES --- */
580
+ @keyframes spin {
581
+ to {
582
+ transform: rotate(360deg);
583
+ }
584
+ }
585
+
586
+ @keyframes toast-in {
587
+ to {
588
+ transform: translateX(0);
589
+ opacity: 1;
590
+ }
591
+ }
592
+
593
+ @media (max-width: 860px) {
594
+ .language-switcher {
595
+ position: static;
596
+ justify-content: center;
597
+ margin-bottom: 1.5rem;
598
+ }
599
+
600
+ .header {
601
+ padding-top: 0;
602
+ }
603
+
604
+
605
+
606
+ .toolbox-actions {
607
+ margin-left: 0;
608
+ width: 100%;
609
+ }
610
+
611
+ .toolbox-actions .btn {
612
+ flex-grow: 1;
613
+ }
614
+ }
615
+
616
+ @media (max-width: 768px) {
617
+ .app-layout {
618
+ padding: 1rem;
619
+ }
620
+
621
+ .image-gallery {
622
+ grid-template-columns: 1fr;
623
+ }
624
+
625
+
626
+ .image-card.featured {
627
+ grid-column: span 1;
628
+ }
629
+ }
630
+
631
+
632
+ </style>
633
+ </head>
634
+
635
+ <body>
636
+ <div id="app"></div>
637
+ <script src="https://unpkg.com/juris@0.88"></script>
638
+ <script>
639
+ // --- I18N SYSTEM ---
640
+ const translations = {
641
+ it: {
642
+ langName: "Italiano",
643
+ flag: "🇮🇹 ",
644
+ appTitle: "GenImage",
645
+ headerTitle: "GenImagini",
646
+ tagline: "Trasforma la tua immaginazione in immagini straordinarie",
647
+ bannerText: "⭐ Sblocca le funzionalità Pro per generazioni illimitate e modelli avanzati!",
648
+ promptLabel: "Prompt AI",
649
+ // ADD THIS LINE
650
+ selectModelLabel: "Seleziona Modello",
651
+ promptPlaceholder: "Descrivi cosa vuoi generare...",
652
+ widthLabel: "Larghezza",
653
+ heightLabel: "Altezza",
654
+ seedLabel: "Seme",
655
+ generateButton: "Genera ✨",
656
+ randomizeButton: "Casuale",
657
+ galleryTab: "La tua Galleria",
658
+ historyTab: "Cronologia Prompt",
659
+ emptyGalleryTitle: "La tua galleria è vuota",
660
+ emptyGalleryDesc: "Inserisci un prompt e fai clic su Genera per creare la tua prima immagine.",
661
+ metaSeed: "Seme: {seed}",
662
+ metaRandom: "Casuale",
663
+ failedToLoad: "⚠️ Caricamento fallito",
664
+ toastHistoryFail: "Impossibile caricare la cronologia",
665
+ toastImageSuccess: "Immagine generata con successo!",
666
+ toastImageFail: "Impossibile generare l'immagine",
667
+ toastEnterPrompt: "Per favore, inserisci un prompt",
668
+ toastImageRemoved: "Immagine rimossa",
669
+ toastSettingsLoaded: "Impostazioni del prompt caricate",
670
+ toastRestoring: "Ripristino di {count} immagini dalla cronologia...",
671
+ toastNewSeed: "Nuovo seme: {seed}",
672
+ deleteTooltip: "Elimina Immagine",
673
+ zoomTooltip: "Visualizza a Schermo Intero",
674
+ clearAllButton: "Cancella Tutto",
675
+ clearAllConfirm: "Sei sicuro di voler svuotare l'intera galleria e la cronologia? Questa azione non può essere annullata.",
676
+ toastGalleryCleared: "Galleria e cronologia svuotate.",
677
+ clearPrompt: "Cancella Prompt"
678
+ },
679
+ en: {
680
+ langName: "English",
681
+ flag:"🇬🇧 ",
682
+ clearPrompt: "Clear Prompt",
683
+ appTitle: "GenImage",
684
+ headerTitle: "GenImages",
685
+ tagline: "Transform your imagination into stunning visuals",
686
+ bannerText: "⭐ Unlock Pro features for unlimited generations and advanced models!",
687
+ promptLabel: "AI Prompt",
688
+ // ADD THIS LINE
689
+ selectModelLabel: "Select Model",
690
+ promptPlaceholder: "Describe what you want to generate...",
691
+ widthLabel: "Width",
692
+ heightLabel: "Height",
693
+ seedLabel: "Seed",
694
+ generateButton: "Generate ✨",
695
+ randomizeButton: "Randomize",
696
+ galleryTab: "Your Gallery",
697
+ historyTab: "Prompt History",
698
+ emptyGalleryTitle: "Your gallery is empty",
699
+ emptyGalleryDesc: "Enter a prompt and click Generate to create your first image.",
700
+ metaSeed: "Seed: {seed}",
701
+ metaRandom: "Random",
702
+ failedToLoad: "⚠️ Failed to load",
703
+ toastHistoryFail: "Failed to load history",
704
+ toastImageSuccess: "Image generated successfully!",
705
+ toastImageFail: "Failed to generate image",
706
+ toastEnterPrompt: "Please enter a prompt",
707
+ toastImageRemoved: "Image removed",
708
+ toastSettingsLoaded: "Prompt settings loaded",
709
+ toastRestoring: "Restoring {count} images from history...",
710
+ toastNewSeed: "New seed: {seed}",
711
+ deleteTooltip: "Delete Image",
712
+ zoomTooltip: "View Fullscreen",
713
+ clearAllButton: "Clear All",
714
+ clearAllConfirm: "Are you sure you want to clear the entire gallery and history? This cannot be undone.",
715
+ toastGalleryCleared: "Gallery and history cleared.",
716
+ },
717
+ fr: {
718
+ langName: "Français",
719
+ flag: '🇫🇷 ',
720
+ appTitle: "GenImage",
721
+ headerTitle: "GenImages",
722
+ tagline: "Mettez vos idées en images",
723
+ bannerText: "⭐ Débloquez les fonctionnalités Pro pour des générations illimitées et des modèles avancés !",
724
+ promptLabel: "Prompt IA",
725
+ // ADD THIS LINE
726
+ selectModelLabel: "Sélectionner un modèle",
727
+ promptPlaceholder: "Décrivez ce que vous voulez générer...",
728
+ widthLabel: "Largeur",
729
+ heightLabel: "Hauteur",
730
+ seedLabel: "Graine",
731
+ generateButton: "Générer ✨",
732
+ randomizeButton: "Aléatoire",
733
+ galleryTab: "Votre Galerie",
734
+ historyTab: "Historique des Prompts",
735
+ emptyGalleryTitle: "Votre galerie est vide",
736
+ emptyGalleryDesc: "Entrez un prompt et cliquez sur Générer pour créer votre première image.",
737
+ metaSeed: "Graine : {seed}",
738
+ metaRandom: "Aléatoire",
739
+ failedToLoad: "⚠️ Échec du chargement",
740
+ toastHistoryFail: "Échec du chargement de l'historique",
741
+ toastImageSuccess: "Image générée avec succès !",
742
+ toastImageFail: "Échec de la génération de l'image",
743
+ toastEnterPrompt: "Veuillez entrer un prompt",
744
+ toastImageRemoved: "Image supprimée",
745
+ toastSettingsLoaded: "Paramètres du prompt chargés",
746
+ toastRestoring: "Restauration de {count} images de l'historique...",
747
+ toastNewSeed: "Nouvelle graine : {seed}",
748
+ deleteTooltip: "Supprimer l'image",
749
+ zoomTooltip: "Voir en plein écran",
750
+ clearAllButton: "Tout Effacer",
751
+ clearAllConfirm: "Êtes-vous sûr de vouloir vider toute la galerie et l'historique ? Cette action est irréversible.",
752
+ toastGalleryCleared: "Galerie et historique vidés.",
753
+ clearPrompt: "Effacer le Prompt"
754
+ },
755
+ };
756
+
757
+ const I18nManager = (props, { getState, setState, subscribe }) => {
758
+ const t = (key, params = {}) => {
759
+ const lang = getState('ui.lang', 'en');
760
+ let str = translations[lang]?.[key] || translations['en'][key] || key;
761
+ for (const p in params) {
762
+ str = str.replace(`{${p}}`, params[p]);
763
+ }
764
+ return str;
765
+ };
766
+
767
+ return {
768
+ api: { t },
769
+ hooks: {
770
+ onRegister: () => {
771
+ subscribe('ui.lang', (lang) => {
772
+ document.title = t('appTitle');
773
+ });
774
+ }
775
+ }
776
+ };
777
+ };
778
+
779
+ // Utility functions
780
+ const uniqueId = () => `${Date.now()}-${Math.floor(Math.random() * 1e9)}`;
781
+ const debounce = (fn, delay) => {
782
+ let timeout;
783
+ return (...args) => {
784
+ clearTimeout(timeout);
785
+ timeout = setTimeout(() => fn(...args), delay);
786
+ };
787
+ };
788
+
789
+ const sanitizeInput = (str) => {
790
+ const div = document.createElement('div');
791
+ div.textContent = str;
792
+ return div.innerHTML.replace(/[/?#]/g, ' ');
793
+ };
794
+
795
+ // Toast System
796
+ const ToastSystem = (props, { getState, setState }) => {
797
+ const showToast = (message, type = 'info', duration = 5000) => {
798
+ const id = uniqueId();
799
+ const toast = { id, message, type };
800
+ setState('toasts', [...getState('toasts', []), toast]);
801
+
802
+ setTimeout(() => {
803
+ const toasts = getState('toasts', []).filter(t => t.id !== id);
804
+ setState('toasts', toasts);
805
+ }, duration);
806
+ };
807
+
808
+ return {
809
+ api: { showToast },
810
+ hooks: { onRegister: () => setState('toasts', []) }
811
+ };
812
+ };
813
+
814
+
815
+
816
+ // Prompt History Manager
817
+ const PromptHistoryManager = (props, { getState, setState, headless }) => {
818
+ const { t } = headless.I18nManager;
819
+ const save = () => {
820
+ const history = getState('promptHistory', []).map(item => ({
821
+ id: item.id, prompt: item.prompt, seed: item.seed,
822
+ width: item.width, height: item.height, createdAt: item.createdAt,
823
+ model: item.model // <--- ADD THIS LINE
824
+ }));
825
+ localStorage.setItem('juris_history', JSON.stringify(history));
826
+ };
827
+
828
+ const load = () => {
829
+ try {
830
+ const stored = localStorage.getItem('juris_history');
831
+ if (stored) setState('promptHistory', JSON.parse(stored));
832
+ } catch {
833
+ headless.ToastSystem.showToast(t('toastHistoryFail'), "error");
834
+ setState('promptHistory', []);
835
+ }
836
+ };
837
+
838
+ const clearHistory = () => {
839
+ setState('promptHistory', []);
840
+ localStorage.removeItem('juris_history');
841
+ };
842
+
843
+ return {
844
+ hooks: { onRegister: load },
845
+ api: {
846
+ add: (item) => {
847
+ // Ensure the 'item' passed here includes the model
848
+ let history = [item, ...getState('promptHistory', [])].slice(0, 50);
849
+ setState('promptHistory', history);
850
+ save();
851
+ },
852
+ remove: (id) => {
853
+ const newHistory = getState('promptHistory', []).filter(i => i.id !== id);
854
+ setState('promptHistory', newHistory);
855
+ save();
856
+ },
857
+ clearAll: () => {
858
+ if (confirm(t('clearAllConfirm'))) {
859
+ clearHistory();
860
+ setState('ui.imagesList', []);
861
+ setState('ui.imagesById', {});
862
+ headless.ToastSystem.showToast(t('toastGalleryCleared'), 'info');
863
+ }
864
+ }
865
+ }
866
+ };
867
+ };
868
+
869
+ // Image Generator Service
870
+ const ImageGenerator = (props, { getState, setState, headless }) => {
871
+ const { t } = headless.I18nManager;
872
+
873
+ // IMPORTANT: generateSingleImage needs to receive the model.
874
+ // The 'model' variable outside this function is the *current* app model,
875
+ // not necessarily the one saved with the history item.
876
+ const generateSingleImage = async (imageId, prompt, seed, width, height, modelUsed) => { // Added modelUsed parameter
877
+ try {
878
+ const encodedPrompt = encodeURIComponent(sanitizeInput(prompt));
879
+ // Use modelUsed here, which comes from the history item or current selection
880
+ let url = `https://image.pollinations.ai/prompt/${encodedPrompt}?nologo=true&enhance=true&model=${modelUsed}&width=${width}&height=${height}`;
881
+ if (seed) url += `&seed=${seed}`;
882
+
883
+ await new Promise((resolve, reject) => {
884
+ const img = new Image();
885
+ img.onload = () => resolve(url);
886
+ img.onerror = () => reject(new Error("Image generation failed"));
887
+ img.src = url;
888
+ });
889
+
890
+ setState(`ui.imagesById.${imageId}`, {
891
+ ...getState(`ui.imagesById.${imageId}`),
892
+ status: "loaded",
893
+ imageUrl: url,
894
+ });
895
+
896
+ headless.ToastSystem.showToast(t('toastImageSuccess'), "success");
897
+ } catch (error) {
898
+ console.error("Image generation error:", error);
899
+ setState(`ui.imagesById.${imageId}.status`, "error");
900
+ headless.ToastSystem.showToast(t('toastImageFail'), "error");
901
+ }
902
+ };
903
+
904
+ const generateQueued = debounce(async () => {
905
+ const queue = getState('ui.generationQueue', []);
906
+ if (queue.length === 0) return;
907
+ // The task now contains all parameters including modelUsed
908
+ const [task, ...remaining] = queue;
909
+ setState('ui.generationQueue', remaining);
910
+ await generateSingleImage(...task); // Pass all task elements
911
+ generateQueued();
912
+ }, 300);
913
+
914
+ return {
915
+ api: {
916
+ generate: async () => {
917
+ const prompt = sanitizeInput(getState("ui.prompt", "").trim());
918
+ const seed = getState("ui.seed", "");
919
+ const width = Math.min(2048, Math.max(64, getState("ui.width", "512")));
920
+ const height = Math.min(2048, Math.max(64, getState("ui.height", "512")));
921
+ const currentModel = getState("model", "flux"); // Get the currently selected model
922
+
923
+ if (!prompt) {
924
+ headless.ToastSystem.showToast(t('toastEnterPrompt'), "error");
925
+ return;
926
+ }
927
+
928
+ const imageId = uniqueId();
929
+ const newImage = {
930
+ id: imageId, status: "loading", prompt, seed, width, height, imageUrl: null,
931
+ historyId: uniqueId(), createdAt: new Date().toISOString(),
932
+ model: currentModel // <--- SAVE THE MODEL HERE FOR NEW GENERATIONS
933
+ };
934
+
935
+ setState(`ui.imagesById.${imageId}`, newImage);
936
+ setState("ui.imagesList", [imageId, ...getState("ui.imagesList", [])]);
937
+ headless.PromptHistoryManager.add(newImage);
938
+
939
+ // Pass all parameters including currentModel to the queue
940
+ setState('ui.generationQueue', [...getState('ui.generationQueue', []), [imageId, prompt, seed, width, height, currentModel]]);
941
+ generateQueued();
942
+ },
943
+
944
+ regenerateFromHistory: () => {
945
+ const history = getState('promptHistory', []);
946
+ if (!history || history.length === 0) return;
947
+ const count = Math.min(50, history.length);
948
+
949
+ history.slice(0, count).forEach(item => {
950
+ const id = uniqueId();
951
+ // Ensure 'item.model' exists, default if not (for old history entries)
952
+ const modelToUse = item.model || getState("model", "flux");
953
+
954
+ const newImage = {
955
+ id, status: "loading", prompt: item.prompt, seed: item.seed, width: item.width,
956
+ height: item.height, imageUrl: null, historyId: item.id,
957
+ createdAt: item.createdAt || new Date().toISOString(),
958
+ model: modelToUse // <--- Store the model from history with the new image
959
+ };
960
+
961
+ setState(`ui.imagesById.${id}`, newImage);
962
+ setState("ui.imagesList", [...getState("ui.imagesList", []), id]);
963
+ // Pass the model from the history item to the queue
964
+ setState('ui.generationQueue', [...getState('ui.generationQueue', []), [id, item.prompt, item.seed, item.width, item.height, modelToUse]]);
965
+ });
966
+
967
+ generateQueued();
968
+ headless.ToastSystem.showToast(t('toastRestoring', { count }), "info");
969
+ }
970
+ }
971
+ };
972
+ };
973
+ // Image Display Component
974
+ // Image Display Component
975
+ const ImageDisplay = (props, { getState, setState, headless }) => ({
976
+ render: () => {
977
+ const { t } = headless.I18nManager;
978
+ const img = getState(`ui.imagesById.${props.id}`);
979
+ if (!img) return "ignore";
980
+
981
+ return {
982
+ div: {
983
+ className: { "image-card": true, "featured": props.featured },
984
+ children: [
985
+ {
986
+ div: {
987
+ className: "image-display",
988
+ children: [
989
+ {
990
+ div: {
991
+ className: "image-actions",
992
+ children: [
993
+ {
994
+ button: {
995
+ className: "action-btn delete", text: "✕", title: () => t('deleteTooltip'),
996
+ onclick: () => {
997
+ headless.PromptHistoryManager.remove(img.historyId);
998
+ const ids = getState("ui.imagesList", []);
999
+ setState("ui.imagesList", ids.filter((id) => id !== props.id));
1000
+ const newMap = { ...getState("ui.imagesById") };
1001
+ delete newMap[props.id];
1002
+ setState("ui.imagesById", newMap);
1003
+ headless.ToastSystem.showToast(t('toastImageRemoved'), "info");
1004
+ }
1005
+ }
1006
+ },
1007
+
1008
+ ]
1009
+ }
1010
+ },
1011
+ {
1012
+ div: {
1013
+ style: { width: '100%', height: '100%' },
1014
+ onclick: () => {
1015
+ setState("ui.prompt", img.prompt);
1016
+ setState("ui.seed", img.seed);
1017
+ setState("ui.width", img.width);
1018
+ setState("ui.height", img.height);
1019
+ setState("model", img.model || "flux"); // <--- LOAD THE MODEL HERE
1020
+ headless.ToastSystem.showToast(t('toastSettingsLoaded'), "info");
1021
+ },
1022
+ children: () => {
1023
+ if (img.status === "loading") return [
1024
+ {
1025
+ div:
1026
+ {
1027
+ style: { display: 'grid', height: '100%', placeItems: 'center' },
1028
+ children: [{ div: { className: "spinner" } }]
1029
+ }
1030
+ }
1031
+ ];
1032
+ if (img.status === "error") return [{ div: { text: t('failedToLoad') } }];
1033
+ return [{ img: { src: img.imageUrl, alt: img.prompt, loading: "lazy" } }];
1034
+ }
1035
+ }
1036
+ }
1037
+ ]
1038
+ }
1039
+ },
1040
+ {
1041
+ div: {
1042
+ className: "image-info",
1043
+ children: [
1044
+ { div: { className: "image-prompt", text: img.prompt } },
1045
+ {
1046
+ div: {
1047
+ className: "image-meta",
1048
+ children: [
1049
+ { span: { text: `${img.width}x${img.height}` } },
1050
+ { span: { text: () => img.seed ? t('metaSeed', { seed: img.seed }) : t('metaRandom') } },
1051
+ { span: { text: `Model: ${img.model || "N/A"}` } } // <--- DISPLAY THE MODEL HERE
1052
+ ]
1053
+ }
1054
+ }
1055
+ ]
1056
+ }
1057
+ }
1058
+ ]
1059
+ }
1060
+ };
1061
+ }
1062
+ });
1063
+
1064
+ // Toast Display Component
1065
+ const ToastDisplay = (props, { getState }) => ({
1066
+ render: () => ({
1067
+ div: {
1068
+ className: "toast-container",
1069
+ children: () => getState('toasts', []).map(toast => ({
1070
+ div: {
1071
+ key: toast.id, className: `toast ${toast.type}`,
1072
+ children: [
1073
+ { div: { text: toast.type === 'success' ? '✓' : '⚠' } },
1074
+ { div: { text: toast.message } }
1075
+ ]
1076
+ }
1077
+ }))
1078
+ }
1079
+ })
1080
+ });
1081
+
1082
+ // Gallery Controls Component
1083
+ // Display : None
1084
+ const GalleryControls = (props, { getState, setState, headless }) => ({
1085
+ render: () => {
1086
+ const { t } = headless.I18nManager;
1087
+ return {
1088
+ div: {
1089
+ style: {display: "none"},
1090
+ className: "tabs",
1091
+ children: [
1092
+ {
1093
+ button: {
1094
+ className: { "tab": true, "active": getState('ui.activeTab', 'images') === 'images' },
1095
+ text: () => t('galleryTab'),
1096
+ onclick: () => setState('ui.activeTab', 'images')
1097
+ }
1098
+ },
1099
+ {
1100
+ button: {
1101
+ className: { "tab": true, "active": getState('ui.activeTab') === 'history' },
1102
+ text: () => t('historyTab'),
1103
+ onclick: () => setState('ui.activeTab', 'history')
1104
+ }
1105
+ }
1106
+ ]
1107
+ }
1108
+ }
1109
+ }
1110
+ });
1111
+
1112
+
1113
+ const SelectModels = (props, { getState, setState, headless }) => { // Ensure 'headless' is destructured here
1114
+ const { t } = headless.I18nManager; // Get the translation function
1115
+
1116
+ return {
1117
+ div: { // Wrap the select in a div to hold the label and select
1118
+ className: "input-group", // Reusing input-group for consistent styling if needed
1119
+ children: [
1120
+ {
1121
+ label: {
1122
+ style: {
1123
+ color: "var(--primary)",
1124
+ display: "block",
1125
+ marginBottom: "0.5rem", // Add some space below the label
1126
+ },
1127
+ text: () => t('selectModelLabel') // Use the translated label
1128
+ }
1129
+ },
1130
+ {
1131
+ select: {
1132
+ // Add inline styles here
1133
+ style: {
1134
+ background: "#1A1A2E", // Dark background
1135
+ color: "var(--text-primary)", // Text color
1136
+ fontSize: "1rem",
1137
+ border: "1px solid rgba(255,255,255,0.5)", // Border like the textarea
1138
+ borderRadius: "5px",
1139
+ padding: "0.5rem 1rem", // Padding
1140
+ appearance: "none", // Remove default select arrow for custom styling
1141
+ cursor: "pointer",
1142
+ outline: "none", // Remove outline on focus
1143
+ backgroundImage: 'url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23FFFFFF%22%20d%3D%22M287%20197.4l-14.3-14.3c-2.1-2.1-4.7-3.3-7.7-3.3s-5.6%201.2-7.7%203.3L146.2%20275.9%2034.7%20164.4c-2.1-2.1-4.7-3.3-7.7-3.3s-5.6%201.2-7.7%203.3L5%20178.7c-2.1%202.1-3.3%204.7-3.3%207.7s1.2%205.6%203.3%207.7l136.2%20136.2c2.1%202.1%204.7%203.3%207.7%203.3s5.6-1.2%207.7-3.3L287%20212.8c2.1-2.1%203.3-4.7%203.3-7.7s-1.2-5.6-3.3-7.7z%22%2F%3E%3C%2Fsvg%3E")',
1144
+ backgroundRepeat: 'no-repeat',
1145
+ backgroundPosition: 'right 0.7em top 50%',
1146
+ backgroundSize: '0.65em auto',
1147
+ paddingRight: '2.5em',
1148
+ minWidth: '100px',
1149
+ width: '100%' // Make it take full available width in its container
1150
+ },
1151
+ onchange: (e) => setState("model", e.target.value),
1152
+ value: () => getState("model", "flux"),
1153
+ children: [
1154
+
1155
+ {
1156
+ option: {
1157
+ text: "Flux",
1158
+ value: "flux"
1159
+ }
1160
+ },
1161
+ {
1162
+ option: {
1163
+ text: "Turbo",
1164
+ value: "turbo"
1165
+ }
1166
+ },
1167
+ ]
1168
+ }
1169
+ }
1170
+ ]
1171
+ }
1172
+ }
1173
+ }
1174
+ // Component for just the main prompt input
1175
+ // Component for just the main prompt input
1176
+ const PromptInput = (props, { getState, setState, headless }) => {
1177
+ const { t } = headless.I18nManager;
1178
+ return {
1179
+ div: {
1180
+ className: "input-group",
1181
+ children: [
1182
+ // This div now only contains the 'AI Prompt' label
1183
+ {
1184
+ div: {
1185
+ style: { display: "flex", gap: "2rem", flexDirection: "column"}, // Changed
1186
+ }
1187
+ },
1188
+ { SelectModels: {} },
1189
+ {
1190
+ div: {
1191
+ style: {
1192
+ position: "relative",
1193
+ top: "0.5rem",
1194
+ color: "var(--primary)",
1195
+ },
1196
+ text: t("promptLabel")
1197
+ }
1198
+ },
1199
+ {
1200
+ textarea: {
1201
+ style: { width: "100%",
1202
+ maxWidth: '100%',
1203
+ minWidth: '100%',
1204
+ display: "flex",
1205
+ "flex-direction": "column",
1206
+ "background": "#1A1A2E",
1207
+ "color": "var(--text-primary)",
1208
+ "font-size": "1rem",
1209
+ "border": "1px solid rgba(255,255,255,0.5)",
1210
+ "border-radius": "5px",
1211
+ "padding": "1rem",
1212
+ spellcheck: "false" ,
1213
+ autocorrect:"off",
1214
+ marginTop: "1rem" // Add some space above the textarea
1215
+ },
1216
+ rows: "5",
1217
+ value: () => getState("ui.prompt", ""),
1218
+ oninput: e => setState("ui.prompt", e.target.value)
1219
+ }
1220
+ }
1221
+ ]
1222
+ }
1223
+ }
1224
+ };
1225
+ // Component for the new toolbox bar
1226
+ const Toolbox = (props, { getState, setState, headless }) => {
1227
+
1228
+ const { t } = headless.I18nManager;
1229
+ return {
1230
+ div: {
1231
+ className: "toolbox-bar",
1232
+ children: [
1233
+ { div: {
1234
+ className: "toolbox-item",
1235
+ style: () => ({ display: 'flex', 'flex-direction': getState("ui.isMobile") ? 'column' : 'row' }),
1236
+ children: [
1237
+ { label: { text: () => t('widthLabel') } },
1238
+ { input: {
1239
+ type: "number", min: "64", max: "2048", step: "16",
1240
+ value: () => getState("ui.width", "512"),
1241
+ oninput: e => setState("ui.width", e.target.value) } }] }
1242
+ },
1243
+ { div: {
1244
+ className: "toolbox-item",
1245
+ style: () => ({ display: 'flex', 'flex-direction': getState("ui.isMobile") ? 'column' : 'row' }),
1246
+ children: [
1247
+ { label: { text: () => t('heightLabel') } },
1248
+ { input: {
1249
+ type: "number", min: "64", max: "2048", step: "16",
1250
+ value: () => getState("ui.height", "512"),
1251
+ oninput: e => setState("ui.height", e.target.value) } }] } },
1252
+ { div: {
1253
+ className: "toolbox-item",
1254
+ style: () => ({ display: 'flex', 'flex-direction': getState("ui.isMobile") ? 'column' : 'row' }),
1255
+ children: [
1256
+ { label: { text: () => t('seedLabel') } },
1257
+ { input: {
1258
+ type: "number",
1259
+ value: () => getState("ui.seed", ""),
1260
+ oninput: e => setState("ui.seed", e.target.value) } }]
1261
+ } },
1262
+ {
1263
+ div: {
1264
+ className: 'toolbox-actions',
1265
+ children: [
1266
+ { button: {
1267
+ type: "button",
1268
+ className: "btn btn-outline",
1269
+ text: () => t('randomizeButton'),
1270
+ onclick: () => {
1271
+ const newSeed = Math.floor(Math.random() * 1000000);
1272
+ setState("ui.seed", newSeed);
1273
+ headless.ToastSystem.showToast(t('toastNewSeed', { seed: newSeed }), "info"); }
1274
+ } },
1275
+ { button: { type: "submit", className: "btn", text: () => t('generateButton') } }
1276
+ ]
1277
+ }
1278
+ }
1279
+ ]
1280
+ }
1281
+ }
1282
+ };
1283
+
1284
+ // Main control panel component that composes the others
1285
+ const ControlPanel = (props, { headless }) => {
1286
+ return {
1287
+ div: {
1288
+ className: "control-panel",
1289
+ children: [{
1290
+ form: {
1291
+ className: "prompt-form",
1292
+ onsubmit: (e) => { e.preventDefault(); headless.ImageGenerator?.generate(); },
1293
+ children: [
1294
+ { PromptInput: {} },
1295
+ { Toolbox: {} }
1296
+ ]
1297
+ }
1298
+ }]
1299
+ }
1300
+ }
1301
+ };
1302
+
1303
+ // Gallery Component
1304
+ const Gallery = (props, { getState, headless }) => ({
1305
+ render: () => {
1306
+ const { t } = headless.I18nManager;
1307
+ const images = getState("ui.imagesList", []);
1308
+ if (images.length === 0) {
1309
+ return {
1310
+ div: {
1311
+ className: "empty-state",
1312
+ children: [
1313
+ { div: { className: "empty-state-icon", text: "🖼️" } },
1314
+ { h3: { text: () => t('emptyGalleryTitle') } },
1315
+ { p: { text: () => t('emptyGalleryDesc') } }
1316
+ ]
1317
+ }
1318
+ };
1319
+ }
1320
+ return {
1321
+ div: {
1322
+ className: "image-gallery",
1323
+ children: images.map((id, index) => ({ ImageDisplay: { key: id, id, featured: index === 0 } }))
1324
+ }
1325
+ };
1326
+ }
1327
+ });
1328
+
1329
+ // Language Switcher Component
1330
+ const LanguageSwitcher = (props, { getState, setState }) => {
1331
+ const currentLang = getState('ui.lang', 'en');
1332
+ return {
1333
+ div: {
1334
+ children: [
1335
+ {
1336
+ div: {
1337
+ style: {
1338
+ display: "flex",
1339
+ gap: "1rem",
1340
+ justifyContent: "center",
1341
+ position: "absolute",
1342
+ right: 0,
1343
+ top: "-0.5rem"
1344
+ },
1345
+ children: Object.keys(translations).map(langCode => ({
1346
+ button: {
1347
+ style: {
1348
+ border:"none",
1349
+ cursor: "pointer",
1350
+
1351
+ background: "rgba(255,255,255,0.121)",
1352
+ border: "1px solid rgba(255, 255, 255, 0.4)",
1353
+ fontSize: "2rem",
1354
+ width: "3rem"
1355
+ },
1356
+ key: langCode,
1357
+ text: translations[langCode].flag ,
1358
+ onclick: () => {
1359
+ setState('ui.lang', langCode);
1360
+ // setState('ui.langSwitcherOpen', false);
1361
+ }
1362
+ }
1363
+ }))
1364
+ }
1365
+ }
1366
+ ]
1367
+ }
1368
+ }
1369
+ };
1370
+
1371
+ // Banner Component
1372
+ const Banner = (props, { getState, setState, headless }) => ({
1373
+ render: () => {
1374
+ const { t } = headless.I18nManager;
1375
+ if (!getState('ui.isBannerVisible', true)) return "ignore";
1376
+
1377
+ return {
1378
+ div: {
1379
+ className: 'banner',
1380
+ children: [
1381
+ {
1382
+ div: {
1383
+ className: 'banner-content',
1384
+ children: [{ span: { text: () => t('bannerText') } }]
1385
+ }
1386
+ },
1387
+ {
1388
+ button: {
1389
+ className: 'banner-close',
1390
+ text: '✕',
1391
+ onclick: () => setState('ui.isBannerVisible', false)
1392
+ }
1393
+ }
1394
+ ]
1395
+ }
1396
+ }
1397
+ }
1398
+ });
1399
+
1400
+ // Toolbar Component
1401
+ const Toolbar = (props, { setState, headless }) => ({
1402
+ render: () => {
1403
+ const { t } = headless.I18nManager;
1404
+ return {
1405
+ div: {
1406
+ className: 'gallery-toolbar',
1407
+ style: {
1408
+ display: "flex",
1409
+ gap: "1rem",
1410
+ "justify-content": "center",
1411
+ "align-items": "center", padding: "0 0.5rem",
1412
+ "margin-bottom": "1.5rem"
1413
+ },
1414
+ children: [
1415
+ {
1416
+ button: {
1417
+ className: 'btn btn-action',
1418
+ text: t("clearPrompt"),
1419
+ onclick: () => setState("ui.prompt", "")
1420
+ }
1421
+ },
1422
+ {
1423
+ button: {
1424
+ className: 'btn btn-danger',
1425
+ text: () => t('clearAllButton'),
1426
+ onclick: () => headless.PromptHistoryManager?.clearAll()
1427
+ }
1428
+ }
1429
+ ]
1430
+ }
1431
+ }
1432
+ }
1433
+ });
1434
+
1435
+ // App Component
1436
+ const App = (props, { headless }) => ({
1437
+ render: () => {
1438
+ const { t } = headless.I18nManager;
1439
+ return {
1440
+ div: {
1441
+ className: "app-layout",
1442
+ children: [
1443
+ {
1444
+ div: {
1445
+ className: "header",
1446
+ children: [
1447
+ { LanguageSwitcher: {} },
1448
+ { h1:
1449
+ {
1450
+ style: { 'margin-top': '2rem'},
1451
+ text: () => t('headerTitle')
1452
+ } },
1453
+ { p: { className: "tagline", text: () => t('tagline') } }
1454
+ ]
1455
+ }
1456
+ },
1457
+ { ControlPanel: {} }, // Use the new main ControlPanel
1458
+ { Toolbar: {} },
1459
+ //{ GalleryControls: {} },
1460
+ { Gallery: {} },
1461
+ // { ToastDisplay: {} },
1462
+
1463
+ ]
1464
+ }
1465
+ }
1466
+ }
1467
+ });
1468
+
1469
+ // Initialize Juris App
1470
+ const app = new Juris({
1471
+ states: {
1472
+ ui: {
1473
+ lang: 'fr',
1474
+ isMobile: 'false',
1475
+ langSwitcherOpen: false,
1476
+ isBannerVisible: true,
1477
+ prompt: "Oil thick past paint of Paris in winter",
1478
+ seed: "57811",
1479
+ width: "1024",
1480
+ height: "1024",
1481
+ imagesList: [],
1482
+ imagesById: {},
1483
+ generationQueue: [],
1484
+ activeTab: "images",
1485
+ lightbox: { activeImage: null },
1486
+ },
1487
+ model: "flux",
1488
+ promptHistory: [],
1489
+ toasts: [],
1490
+ },
1491
+ components: {
1492
+ App, ControlPanel, PromptInput, Toolbox, // Register the new components
1493
+ GalleryControls, Gallery, ImageDisplay, ToastDisplay,
1494
+ LanguageSwitcher, Toolbar, Banner, SelectModels
1495
+ },
1496
+ headlessComponents: {
1497
+ I18nManager: { fn: I18nManager, options: { autoInit: true } },
1498
+ ImageGenerator: { fn: ImageGenerator, options: { autoInit: true } },
1499
+ PromptHistoryManager: { fn: PromptHistoryManager, options: { autoInit: true } },
1500
+ ToastSystem: { fn: ToastSystem, options: { autoInit: true } },
1501
+ },
1502
+ layout: { App: {} },
1503
+ });
1504
+
1505
+ app.render();
1506
+
1507
+ // Restore history on load
1508
+ setTimeout(() => {
1509
+ if (app.headlessAPIs.ImageGenerator) {
1510
+ app.headlessAPIs.ImageGenerator.regenerateFromHistory();
1511
+ }
1512
+ }, 500);
1513
+
1514
+ // Keyboard shortcuts
1515
+ document.addEventListener('keydown', (e) => {
1516
+ if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
1517
+ if (e.key === 'Escape') app.setState('ui.lightbox.activeImage', null);
1518
+ if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
1519
+ e.preventDefault();
1520
+ app.headlessAPIs.ImageGenerator?.generate();
1521
+ }
1522
+ });
1523
+
1524
+
1525
+ if (window.matchMedia("(max-width: 640px)").matches) {
1526
+ app.setState("ui.isMobile", true);
1527
+ }
1528
+ else {
1529
+ app.setState("ui.isMobile", false);
1530
+ }
1531
+
1532
+
1533
+
1534
+ </script>
1535
+ </body>
1536
+
1537
  </html>