akhaliq HF Staff commited on
Commit
a888964
·
1 Parent(s): 89d8565

refactor: update UI to Apple-inspired aesthetic with system fonts and dark mode support

Browse files
Files changed (1) hide show
  1. index.html +158 -318
index.html CHANGED
@@ -2,303 +2,189 @@
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>ERNIE Image Turbo</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com">
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
10
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
 
12
  <style>
13
  :root {
14
- --bg-base: #0f172a;
15
- --bg-secondary: rgba(30, 41, 59, 0.7);
16
- --primary: #6366f1;
17
- --primary-hover: #4f46e5;
18
- --accent: #ec4899;
19
- --text-main: #f8fafc;
20
- --text-muted: #94a3b8;
21
- --border: rgba(255, 255, 255, 0.1);
22
- --glass-bg: rgba(15, 23, 42, 0.6);
23
- --glass-border: rgba(255, 255, 255, 0.08);
24
- --glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
 
 
 
 
 
 
 
 
 
 
25
  }
26
 
27
  * {
28
  box-sizing: border-box;
29
  margin: 0;
30
  padding: 0;
 
 
31
  }
32
 
33
  body {
34
- font-family: 'Inter', sans-serif;
35
- background-color: var(--bg-base);
36
- color: var(--text-main);
37
  min-height: 100vh;
38
  display: flex;
39
- background-image:
40
- radial-gradient(circle at 15% 50%, rgba(99, 102, 241, 0.15), transparent 25%),
41
- radial-gradient(circle at 85% 30%, rgba(236, 72, 153, 0.15), transparent 25%);
42
- background-attachment: fixed;
43
- overflow-x: hidden;
44
  }
45
 
46
  main {
47
  display: flex;
48
  width: 100%;
49
- height: 100vh;
50
- padding: 1rem;
51
- gap: 1rem;
 
 
 
 
 
 
 
 
 
 
52
  }
53
 
54
- /* ----- SIDEBAR CONTROLS ----- */
55
  .sidebar {
56
- width: 360px;
57
- background: var(--glass-bg);
58
- backdrop-filter: blur(20px);
59
- -webkit-backdrop-filter: blur(20px);
60
- border: 1px solid var(--glass-border);
61
- border-radius: 24px;
62
- padding: 1.5rem;
63
  display: flex;
64
  flex-direction: column;
65
  gap: 1.5rem;
66
- box-shadow: var(--glass-shadow);
67
- overflow-y: auto;
68
  flex-shrink: 0;
69
  z-index: 2;
70
  }
71
 
72
- /* Custom Scrollbar for Sidebar */
73
- .sidebar::-webkit-scrollbar {
74
- width: 6px;
75
- }
76
- .sidebar::-webkit-scrollbar-track {
77
- background: rgba(255, 255, 255, 0.02);
78
- }
79
- .sidebar::-webkit-scrollbar-thumb {
80
- background: rgba(255, 255, 255, 0.1);
81
- border-radius: 10px;
82
- }
83
-
84
- .header {
85
- margin-bottom: 0.5rem;
86
- }
87
-
88
  .header h1 {
89
- font-family: 'Outfit', sans-serif;
90
- font-size: 1.8rem;
91
- font-weight: 700;
92
- background: linear-gradient(135deg, #a5b4fc 0%, #fbcfe8 100%);
93
- -webkit-background-clip: text;
94
- -webkit-text-fill-color: transparent;
95
- margin-bottom: 0.25rem;
96
  letter-spacing: -0.5px;
 
97
  }
98
 
99
  .header p {
100
- color: var(--text-muted);
101
- font-size: 0.875rem;
 
102
  }
103
 
104
  .control-group {
105
  display: flex;
106
  flex-direction: column;
107
- gap: 0.5rem;
108
- }
109
-
110
- label {
111
- font-size: 0.875rem;
112
- font-weight: 500;
113
- color: var(--text-main);
114
- display: flex;
115
- justify-content: space-between;
116
- }
117
-
118
- .label-val {
119
- color: var(--primary);
120
- font-family: 'Outfit', sans-serif;
121
- font-weight: 600;
122
  }
123
 
124
  textarea {
 
125
  width: 100%;
126
- height: 120px;
127
- background: rgba(0, 0, 0, 0.2);
128
- border: 1px solid var(--border);
129
- border-radius: 12px;
130
- padding: 1rem;
131
- color: var(--text-main);
132
  font-family: inherit;
133
- font-size: 0.95rem;
 
134
  resize: none;
135
- transition: all 0.3s ease;
136
  }
137
 
138
  textarea:focus {
139
  outline: none;
140
- border-color: var(--primary);
141
- box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
142
  }
143
 
144
- /* Custom Sliders */
145
- input[type="range"] {
146
- -webkit-appearance: none;
147
- width: 100%;
148
- background: transparent;
149
- height: 6px;
150
- border-radius: 3px;
151
- }
152
-
153
- input[type="range"]::-webkit-slider-runnable-track {
154
- width: 100%;
155
- height: 6px;
156
- background: rgba(255, 255, 255, 0.1);
157
- border-radius: 3px;
158
- }
159
-
160
- input[type="range"]::-webkit-slider-thumb {
161
- -webkit-appearance: none;
162
- height: 18px;
163
- width: 18px;
164
- border-radius: 50%;
165
- background: var(--primary);
166
- margin-top: -6px;
167
- cursor: pointer;
168
- box-shadow: 0 0 10px rgba(99, 102, 241, 0.5);
169
- transition: transform 0.2s;
170
- }
171
-
172
- input[type="range"]::-webkit-slider-thumb:hover {
173
- transform: scale(1.2);
174
- }
175
-
176
- /* Custom Checkbox */
177
- .checkbox-container {
178
- display: flex;
179
- align-items: center;
180
- gap: 0.75rem;
181
- cursor: pointer;
182
- user-select: none;
183
- }
184
-
185
- .checkbox-container input {
186
- display: none;
187
- }
188
-
189
- .checkmark {
190
- width: 20px;
191
- height: 20px;
192
- background: rgba(0, 0, 0, 0.2);
193
- border: 1px solid var(--border);
194
- border-radius: 6px;
195
- display: flex;
196
- align-items: center;
197
- justify-content: center;
198
- transition: all 0.2s ease;
199
- }
200
-
201
- .checkbox-container input:checked ~ .checkmark {
202
- background: var(--primary);
203
- border-color: var(--primary);
204
- }
205
-
206
- .checkmark i {
207
- color: white;
208
- font-size: 12px;
209
- opacity: 0;
210
- transform: scale(0.5);
211
- transition: all 0.2s ease;
212
- }
213
-
214
- .checkbox-container input:checked ~ .checkmark i {
215
- opacity: 1;
216
- transform: scale(1);
217
  }
218
 
219
  .btn-generate {
220
- background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%);
221
- color: white;
222
  border: none;
223
- padding: 1rem;
224
- border-radius: 12px;
225
- font-family: 'Outfit', sans-serif;
226
- font-size: 1.1rem;
227
  font-weight: 600;
228
  cursor: pointer;
229
- transition: all 0.3s ease;
230
- box-shadow: 0 4px 15px rgba(236, 72, 153, 0.3);
231
- margin-top: auto;
232
- position: relative;
233
- overflow: hidden;
234
  display: flex;
235
  justify-content: center;
236
  align-items: center;
237
  gap: 0.5rem;
238
- }
239
-
240
- .btn-generate::before {
241
- content: '';
242
- position: absolute;
243
- top: 0;
244
- left: -100%;
245
- width: 100%;
246
- height: 100%;
247
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
248
- transition: left 0.5s ease;
249
  }
250
 
251
  .btn-generate:hover {
252
- transform: translateY(-2px);
253
- box-shadow: 0 6px 20px rgba(236, 72, 153, 0.4);
254
- }
255
-
256
- .btn-generate:hover::before {
257
- left: 100%;
258
  }
259
 
260
  .btn-generate:disabled {
261
- opacity: 0.7;
262
  cursor: not-allowed;
263
  transform: none;
264
  }
265
 
266
- /* ----- WORKSPACE (CANVAS) ----- */
267
  .workspace {
268
  flex: 1;
269
- background: var(--glass-bg);
270
- backdrop-filter: blur(20px);
271
- -webkit-backdrop-filter: blur(20px);
272
- border: 1px solid var(--glass-border);
273
- border-radius: 24px;
274
  display: flex;
275
  align-items: center;
276
  justify-content: center;
277
- position: relative;
278
- box-shadow: var(--glass-shadow);
279
  overflow: hidden;
280
- padding: 1rem;
281
  z-index: 1;
282
  }
283
 
284
  .image-container {
285
- position: relative;
286
  width: 100%;
287
  height: 100%;
288
  display: flex;
289
  align-items: center;
290
  justify-content: center;
 
291
  }
292
 
293
  #result-image {
294
  max-width: 100%;
295
  max-height: 100%;
296
  object-fit: contain;
297
- border-radius: 12px;
298
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
299
- transition: opacity 0.5s ease, transform 0.5s ease;
300
  opacity: 0;
301
- transform: scale(0.95);
 
 
302
  }
303
 
304
  #result-image.loaded {
@@ -313,42 +199,40 @@
313
  align-items: center;
314
  justify-content: center;
315
  gap: 1rem;
316
- color: var(--text-muted);
317
  text-align: center;
318
- pointer-events: none;
319
- transition: opacity 0.3s ease;
320
  }
321
 
322
  .empty-state i {
323
- font-size: 4rem;
324
- background: linear-gradient(45deg, var(--primary), var(--accent));
325
- -webkit-background-clip: text;
326
- -webkit-text-fill-color: transparent;
327
  opacity: 0.5;
 
328
  }
329
 
330
  .empty-state h3 {
331
- font-family: 'Outfit', sans-serif;
332
- font-size: 1.5rem;
333
  font-weight: 500;
 
334
  }
335
 
336
  /* Loading Overlay */
337
  .loading-overlay {
338
  position: absolute;
339
  inset: 0;
340
- background: rgba(15, 23, 42, 0.8);
341
- backdrop-filter: blur(8px);
 
342
  display: flex;
343
  flex-direction: column;
344
  align-items: center;
345
  justify-content: center;
346
- gap: 1.5rem;
347
  opacity: 0;
348
  pointer-events: none;
349
  transition: opacity 0.3s ease;
350
- border-radius: 20px;
351
  z-index: 10;
 
352
  }
353
 
354
  .loading-overlay.active {
@@ -357,10 +241,10 @@
357
  }
358
 
359
  .spinner {
360
- width: 60px;
361
- height: 60px;
362
- border: 4px solid rgba(255, 255, 255, 0.1);
363
- border-left-color: var(--primary);
364
  border-radius: 50%;
365
  animation: spin 1s linear infinite;
366
  }
@@ -370,33 +254,34 @@
370
  }
371
 
372
  .loading-text {
373
- font-family: 'Outfit', sans-serif;
374
- font-size: 1.2rem;
375
  font-weight: 500;
376
- color: white;
377
- letter-spacing: 1px;
378
  animation: pulse 1.5s ease-in-out infinite;
379
  }
380
 
381
  @keyframes pulse {
382
  0%, 100% { opacity: 1; }
383
- 50% { opacity: 0.5; }
384
  }
385
 
 
386
  .error-message {
387
  position: absolute;
388
  top: 2rem;
389
  left: 50%;
390
  transform: translateX(-50%) translateY(-20px);
391
- background: rgba(239, 68, 68, 0.9);
392
  color: white;
393
- padding: 1rem 2rem;
394
- border-radius: 12px;
395
  font-weight: 500;
 
396
  opacity: 0;
397
  pointer-events: none;
398
- transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
399
- box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
400
  z-index: 20;
401
  display: flex;
402
  align-items: center;
@@ -412,21 +297,23 @@
412
  position: absolute;
413
  bottom: 2rem;
414
  right: 2rem;
415
- background: rgba(255, 255, 255, 0.1);
416
- backdrop-filter: blur(10px);
417
- border: 1px solid var(--border);
418
- color: white;
419
- width: 50px;
420
- height: 50px;
 
421
  border-radius: 50%;
422
  display: flex;
423
  align-items: center;
424
  justify-content: center;
425
  cursor: pointer;
426
- transition: all 0.3s ease;
427
  opacity: 0;
428
  pointer-events: none;
429
  z-index: 5;
 
430
  }
431
 
432
  .download-btn.visible {
@@ -435,28 +322,37 @@
435
  }
436
 
437
  .download-btn:hover {
438
- background: var(--primary);
439
- transform: translateY(-3px);
440
- box-shadow: 0 5px 15px rgba(99, 102, 241, 0.4);
441
  }
442
 
443
  @media (max-width: 1024px) {
 
 
 
 
444
  main {
445
  flex-direction: column-reverse; /* Image on top, controls on bottom */
446
- padding: 0.5rem;
447
- gap: 0.5rem;
448
  height: 100dvh;
449
  }
450
  .sidebar {
451
  width: 100%;
452
- height: 50vh;
453
- max-height: none;
454
- border-radius: 20px 20px 0 0;
455
- padding-bottom: 2rem; /* Give breathing room at bottom */
 
 
456
  }
457
  .workspace {
458
- height: 50vh;
459
- border-radius: 0 0 20px 20px;
 
 
 
 
 
460
  }
461
  }
462
  </style>
@@ -465,57 +361,23 @@
465
 
466
  <main>
467
  <!-- Sidebar Controls -->
468
- <aside class="sidebar">
469
  <div class="header">
470
  <h1>ERNIE Image Turbo</h1>
471
- <p>High-Fidelity Gen-AI</p>
472
- </div>
473
-
474
- <div class="control-group">
475
- <label for="prompt">Prompt</label>
476
- <textarea id="prompt" placeholder="A futuristic city with flying cars at sunset, highly detailed, cinematic lighting..."></textarea>
477
- </div>
478
-
479
- <div class="control-group">
480
- <label>
481
- Width
482
- <span class="label-val" id="width-val">1024</span>
483
- </label>
484
- <input type="range" id="width" min="512" max="1536" step="64" value="1024">
485
  </div>
486
 
487
  <div class="control-group">
488
- <label>
489
- Height
490
- <span class="label-val" id="height-val">1024</span>
491
- </label>
492
- <input type="range" id="height" min="512" max="1536" step="64" value="1024">
493
- </div>
494
-
495
- <div class="control-group">
496
- <label>
497
- Inference Steps
498
- <span class="label-val" id="steps-val">8</span>
499
- </label>
500
- <input type="range" id="steps" min="4" max="20" step="1" value="8">
501
- </div>
502
-
503
- <div class="control-group">
504
- <label>
505
- Guidance Scale
506
- <span class="label-val" id="guidance-val">1.0</span>
507
- </label>
508
- <input type="range" id="guidance" min="0.0" max="5.0" step="0.1" value="1.0">
509
  </div>
510
 
511
  <button class="btn-generate" id="generate-btn">
512
- <i class="fas fa-wand-magic-sparkles"></i>
513
- Generate Image
514
  </button>
515
  </aside>
516
 
517
  <!-- Canvas Workspace -->
518
- <section class="workspace">
519
  <div class="error-message" id="error-toast">
520
  <i class="fas fa-exclamation-circle"></i>
521
  <span id="error-text">An error occurred.</span>
@@ -523,15 +385,15 @@
523
 
524
  <div class="image-container">
525
  <div class="empty-state" id="empty-state">
526
- <i class="fa-regular fa-image"></i>
527
- <h3>Your masterpiece awaits</h3>
528
- <p>Enter a prompt and hit generate to begin.</p>
529
  </div>
530
  <img id="result-image" alt="Generated Image" src="" />
531
  </div>
532
 
533
  <a id="download-link" download="ernie-generation.png" class="download-btn">
534
- <i class="fas fa-download"></i>
535
  </a>
536
 
537
  <div class="loading-overlay" id="loading-overlay">
@@ -547,15 +409,6 @@
547
 
548
  // ----- UI Elements -----
549
  const promptInput = document.getElementById('prompt');
550
- const widthSlider = document.getElementById('width');
551
- const widthVal = document.getElementById('width-val');
552
- const heightSlider = document.getElementById('height');
553
- const heightVal = document.getElementById('height-val');
554
- const stepsSlider = document.getElementById('steps');
555
- const stepsVal = document.getElementById('steps-val');
556
- const guidanceSlider = document.getElementById('guidance');
557
- const guidanceVal = document.getElementById('guidance-val');
558
-
559
  const generateBtn = document.getElementById('generate-btn');
560
  const resultImage = document.getElementById('result-image');
561
  const emptyState = document.getElementById('empty-state');
@@ -565,18 +418,6 @@
565
  const errorText = document.getElementById('error-text');
566
  const downloadBtn = document.getElementById('download-link');
567
 
568
- // ----- Sync Sliders with Labels -----
569
- const setupSlider = (slider, label) => {
570
- slider.addEventListener('input', (e) => {
571
- label.textContent = e.target.value;
572
- });
573
- };
574
-
575
- setupSlider(widthSlider, widthVal);
576
- setupSlider(heightSlider, heightVal);
577
- setupSlider(stepsSlider, stepsVal);
578
- setupSlider(guidanceSlider, guidanceVal);
579
-
580
  const showError = (message) => {
581
  errorText.textContent = message;
582
  errorToast.classList.add('visible');
@@ -599,18 +440,17 @@
599
  loadingOverlay.classList.add('active');
600
  statusText.textContent = "Connecting to backend...";
601
 
602
- // Connect to backend (same origin as HTML is served by App)
603
  const client = await Client.connect(window.location.origin);
604
 
605
- statusText.textContent = "Generating... this may take a few moments.";
606
 
607
- // Prepare params
608
  const params = {
609
  prompt: promptStr,
610
- width: parseInt(widthSlider.value),
611
- height: parseInt(heightSlider.value),
612
- guidance_scale: parseFloat(guidanceSlider.value),
613
- num_inference_steps: parseInt(stepsSlider.value)
614
  };
615
 
616
  // Call endpoint
 
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
  <title>ERNIE Image Turbo</title>
 
 
 
7
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
 
9
  <style>
10
  :root {
11
+ --bg-body: #f5f5f7;
12
+ --bg-surface: rgba(255, 255, 255, 0.7);
13
+ --border-subtle: rgba(0, 0, 0, 0.05);
14
+ --text-primary: #1d1d1f;
15
+ --text-secondary: #86868b;
16
+ --accent: #000000;
17
+ --accent-hover: #333333;
18
+ --glass-shadow: 0 4px 24px rgba(0, 0, 0, 0.04);
19
+ }
20
+
21
+ @media (prefers-color-scheme: dark) {
22
+ :root {
23
+ --bg-body: #000000;
24
+ --bg-surface: rgba(28, 28, 30, 0.7);
25
+ --border-subtle: rgba(255, 255, 255, 0.1);
26
+ --text-primary: #f5f5f7;
27
+ --text-secondary: #86868b;
28
+ --accent: #ffffff;
29
+ --accent-hover: #e5e5e5;
30
+ --glass-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
31
+ }
32
  }
33
 
34
  * {
35
  box-sizing: border-box;
36
  margin: 0;
37
  padding: 0;
38
+ -webkit-font-smoothing: antialiased;
39
+ -moz-osx-font-smoothing: grayscale;
40
  }
41
 
42
  body {
43
+ font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
44
+ background-color: var(--bg-body);
45
+ color: var(--text-primary);
46
  min-height: 100vh;
47
  display: flex;
48
+ align-items: center;
49
+ justify-content: center;
50
+ padding: 2rem;
51
+ overflow: hidden;
52
+ transition: background-color 0.3s ease;
53
  }
54
 
55
  main {
56
  display: flex;
57
  width: 100%;
58
+ max-width: 1200px;
59
+ height: calc(100vh - 4rem);
60
+ gap: 2rem;
61
+ }
62
+
63
+ /* Elements */
64
+ .panel {
65
+ background: var(--bg-surface);
66
+ backdrop-filter: blur(40px);
67
+ -webkit-backdrop-filter: blur(40px);
68
+ border: 1px solid var(--border-subtle);
69
+ border-radius: 32px;
70
+ box-shadow: var(--glass-shadow);
71
  }
72
 
73
+ /* Inputs */
74
  .sidebar {
75
+ width: 320px;
76
+ padding: 2.5rem;
 
 
 
 
 
77
  display: flex;
78
  flex-direction: column;
79
  gap: 1.5rem;
 
 
80
  flex-shrink: 0;
81
  z-index: 2;
82
  }
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  .header h1 {
85
+ font-size: 1.5rem;
86
+ font-weight: 600;
 
 
 
 
 
87
  letter-spacing: -0.5px;
88
+ margin-bottom: 0.25rem;
89
  }
90
 
91
  .header p {
92
+ color: var(--text-secondary);
93
+ font-size: 0.9rem;
94
+ font-weight: 400;
95
  }
96
 
97
  .control-group {
98
  display: flex;
99
  flex-direction: column;
100
+ gap: 0.75rem;
101
+ flex: 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  }
103
 
104
  textarea {
105
+ flex: 1;
106
  width: 100%;
107
+ background: rgba(120, 120, 128, 0.05);
108
+ border: 1px solid var(--border-subtle);
109
+ border-radius: 20px;
110
+ padding: 1.25rem;
111
+ color: var(--text-primary);
 
112
  font-family: inherit;
113
+ font-size: 1.05rem;
114
+ line-height: 1.4;
115
  resize: none;
116
+ transition: all 0.2s ease;
117
  }
118
 
119
  textarea:focus {
120
  outline: none;
121
+ background: rgba(120, 120, 128, 0.1);
122
+ border-color: rgba(120, 120, 128, 0.2);
123
  }
124
 
125
+ textarea::placeholder {
126
+ color: var(--text-secondary);
127
+ font-weight: 400;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  }
129
 
130
  .btn-generate {
131
+ background-color: var(--accent);
132
+ color: var(--bg-body);
133
  border: none;
134
+ padding: 1.1rem;
135
+ border-radius: 100px; /* Pill shape */
136
+ font-size: 1.05rem;
 
137
  font-weight: 600;
138
  cursor: pointer;
139
+ transition: transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1), background-color 0.2s;
 
 
 
 
140
  display: flex;
141
  justify-content: center;
142
  align-items: center;
143
  gap: 0.5rem;
144
+ margin-top: auto;
 
 
 
 
 
 
 
 
 
 
145
  }
146
 
147
  .btn-generate:hover {
148
+ transform: scale(0.98);
149
+ background-color: var(--accent-hover);
 
 
 
 
150
  }
151
 
152
  .btn-generate:disabled {
153
+ opacity: 0.5;
154
  cursor: not-allowed;
155
  transform: none;
156
  }
157
 
158
+ /* Workspace */
159
  .workspace {
160
  flex: 1;
161
+ position: relative;
 
 
 
 
162
  display: flex;
163
  align-items: center;
164
  justify-content: center;
165
+ padding: 2rem;
 
166
  overflow: hidden;
 
167
  z-index: 1;
168
  }
169
 
170
  .image-container {
 
171
  width: 100%;
172
  height: 100%;
173
  display: flex;
174
  align-items: center;
175
  justify-content: center;
176
+ position: relative;
177
  }
178
 
179
  #result-image {
180
  max-width: 100%;
181
  max-height: 100%;
182
  object-fit: contain;
183
+ border-radius: 16px;
 
 
184
  opacity: 0;
185
+ transform: scale(0.98);
186
+ transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
187
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
188
  }
189
 
190
  #result-image.loaded {
 
199
  align-items: center;
200
  justify-content: center;
201
  gap: 1rem;
202
+ color: var(--text-secondary);
203
  text-align: center;
204
+ transition: opacity 0.4s ease;
 
205
  }
206
 
207
  .empty-state i {
208
+ font-size: 3rem;
 
 
 
209
  opacity: 0.5;
210
+ margin-bottom: 0.5rem;
211
  }
212
 
213
  .empty-state h3 {
214
+ font-size: 1.2rem;
 
215
  font-weight: 500;
216
+ color: var(--text-primary);
217
  }
218
 
219
  /* Loading Overlay */
220
  .loading-overlay {
221
  position: absolute;
222
  inset: 0;
223
+ background: var(--bg-surface);
224
+ backdrop-filter: blur(20px);
225
+ -webkit-backdrop-filter: blur(20px);
226
  display: flex;
227
  flex-direction: column;
228
  align-items: center;
229
  justify-content: center;
230
+ gap: 1.2rem;
231
  opacity: 0;
232
  pointer-events: none;
233
  transition: opacity 0.3s ease;
 
234
  z-index: 10;
235
+ border-radius: inherit;
236
  }
237
 
238
  .loading-overlay.active {
 
241
  }
242
 
243
  .spinner {
244
+ width: 40px;
245
+ height: 40px;
246
+ border: 3px solid var(--border-subtle);
247
+ border-top-color: var(--text-primary);
248
  border-radius: 50%;
249
  animation: spin 1s linear infinite;
250
  }
 
254
  }
255
 
256
  .loading-text {
257
+ font-size: 1rem;
 
258
  font-weight: 500;
259
+ color: var(--text-primary);
260
+ letter-spacing: -0.2px;
261
  animation: pulse 1.5s ease-in-out infinite;
262
  }
263
 
264
  @keyframes pulse {
265
  0%, 100% { opacity: 1; }
266
+ 50% { opacity: 0.6; }
267
  }
268
 
269
+ /* Error */
270
  .error-message {
271
  position: absolute;
272
  top: 2rem;
273
  left: 50%;
274
  transform: translateX(-50%) translateY(-20px);
275
+ background: #ff3b30;
276
  color: white;
277
+ padding: 1rem 1.5rem;
278
+ border-radius: 100px;
279
  font-weight: 500;
280
+ font-size: 0.95rem;
281
  opacity: 0;
282
  pointer-events: none;
283
+ transition: all 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
284
+ box-shadow: 0 4px 16px rgba(255, 59, 48, 0.4);
285
  z-index: 20;
286
  display: flex;
287
  align-items: center;
 
297
  position: absolute;
298
  bottom: 2rem;
299
  right: 2rem;
300
+ background: var(--bg-surface);
301
+ backdrop-filter: blur(20px);
302
+ -webkit-backdrop-filter: blur(20px);
303
+ border: 1px solid var(--border-subtle);
304
+ color: var(--text-primary);
305
+ width: 44px;
306
+ height: 44px;
307
  border-radius: 50%;
308
  display: flex;
309
  align-items: center;
310
  justify-content: center;
311
  cursor: pointer;
312
+ transition: all 0.2s ease;
313
  opacity: 0;
314
  pointer-events: none;
315
  z-index: 5;
316
+ box-shadow: var(--glass-shadow);
317
  }
318
 
319
  .download-btn.visible {
 
322
  }
323
 
324
  .download-btn:hover {
325
+ transform: scale(1.05);
 
 
326
  }
327
 
328
  @media (max-width: 1024px) {
329
+ body {
330
+ padding: 0;
331
+ align-items: flex-end;
332
+ }
333
  main {
334
  flex-direction: column-reverse; /* Image on top, controls on bottom */
335
+ padding: 0;
336
+ gap: 0;
337
  height: 100dvh;
338
  }
339
  .sidebar {
340
  width: 100%;
341
+ height: 45vh;
342
+ border-radius: 32px 32px 0 0;
343
+ padding-bottom: 2.5rem;
344
+ border-bottom: none;
345
+ border-left: none;
346
+ border-right: none;
347
  }
348
  .workspace {
349
+ height: 55vh;
350
+ border-radius: 0;
351
+ border-top: none;
352
+ border-left: none;
353
+ border-right: none;
354
+ background: transparent;
355
+ box-shadow: none;
356
  }
357
  }
358
  </style>
 
361
 
362
  <main>
363
  <!-- Sidebar Controls -->
364
+ <aside class="sidebar panel">
365
  <div class="header">
366
  <h1>ERNIE Image Turbo</h1>
367
+ <p>High-Fidelity Generation</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  </div>
369
 
370
  <div class="control-group">
371
+ <textarea id="prompt" placeholder="A futuristic city with flying cars at sunset, highly detailed..."></textarea>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  </div>
373
 
374
  <button class="btn-generate" id="generate-btn">
375
+ Generate
 
376
  </button>
377
  </aside>
378
 
379
  <!-- Canvas Workspace -->
380
+ <section class="workspace panel">
381
  <div class="error-message" id="error-toast">
382
  <i class="fas fa-exclamation-circle"></i>
383
  <span id="error-text">An error occurred.</span>
 
385
 
386
  <div class="image-container">
387
  <div class="empty-state" id="empty-state">
388
+ <i class="fa-solid fa-wand-magic-sparkles"></i>
389
+ <h3>Imagine anything</h3>
390
+ <p>Enter a prompt and hit generate</p>
391
  </div>
392
  <img id="result-image" alt="Generated Image" src="" />
393
  </div>
394
 
395
  <a id="download-link" download="ernie-generation.png" class="download-btn">
396
+ <i class="fas fa-arrow-down"></i>
397
  </a>
398
 
399
  <div class="loading-overlay" id="loading-overlay">
 
409
 
410
  // ----- UI Elements -----
411
  const promptInput = document.getElementById('prompt');
 
 
 
 
 
 
 
 
 
412
  const generateBtn = document.getElementById('generate-btn');
413
  const resultImage = document.getElementById('result-image');
414
  const emptyState = document.getElementById('empty-state');
 
418
  const errorText = document.getElementById('error-text');
419
  const downloadBtn = document.getElementById('download-link');
420
 
 
 
 
 
 
 
 
 
 
 
 
 
421
  const showError = (message) => {
422
  errorText.textContent = message;
423
  errorToast.classList.add('visible');
 
440
  loadingOverlay.classList.add('active');
441
  statusText.textContent = "Connecting to backend...";
442
 
 
443
  const client = await Client.connect(window.location.origin);
444
 
445
+ statusText.textContent = "Generating...";
446
 
447
+ // Hardcoded defaults for clean UI
448
  const params = {
449
  prompt: promptStr,
450
+ width: 1024,
451
+ height: 1024,
452
+ guidance_scale: 1.0,
453
+ num_inference_steps: 8
454
  };
455
 
456
  // Call endpoint