akhaliq HF Staff commited on
Commit
5cf1e3e
·
1 Parent(s): bd32a18

Optimize index.html for mobile and declutter options

Browse files
Files changed (1) hide show
  1. index.html +367 -356
index.html CHANGED
@@ -2,7 +2,7 @@
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>Bonsai — Create stunning images with AI</title>
7
  <!-- Google Fonts -->
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
@@ -10,251 +10,353 @@
10
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet">
11
 
12
  <style>
13
- /* CSS Variables Supporting Seamless Theme Switching */
14
  :root {
15
  --bg-main: #e8e6e2; /* Warm beige backdrop */
16
- --bg-gradient: radial-gradient(circle at 50% 50%, rgba(240, 238, 234, 0.8) 0%, rgba(232, 230, 226, 0.9) 100%);
17
- --bg-card: rgba(243, 241, 237, 0.85); /* Frosted warm-gray card */
18
  --bg-input: #faf9f7; /* Off-white inputs */
19
- --border-color: rgba(0, 0, 0, 0.06);
20
  --border-focus: rgba(99, 102, 241, 0.3);
21
  --text-primary: #2d2d34; /* Soft dark slate */
22
  --text-secondary: #6e6e76; /* Muted slate */
23
  --text-muted: #8e8e96;
24
  --accent: #5e60e6;
25
- --accent-glow: rgba(99, 102, 241, 0.15);
26
- --btn-primary: #7d808c; /* Warm solid grey */
27
- --btn-primary-hover: #5d606c;
28
  --btn-primary-text: #ffffff;
29
  --btn-secondary: rgba(0, 0, 0, 0.03);
30
  --btn-secondary-hover: rgba(0, 0, 0, 0.06);
31
- --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.03);
32
- --shadow-md: 0 10px 40px rgba(0, 0, 0, 0.06);
33
  --font-title: 'Outfit', sans-serif;
34
  --font-body: 'Inter', sans-serif;
35
- --transition-speed: 0.4s;
36
  }
37
 
38
  body.dark-theme {
39
  --bg-main: #0c0c0e; /* Deep rich dark slate */
40
- --bg-gradient: radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.06) 0%, transparent 40%),
41
- radial-gradient(circle at 90% 80%, rgba(168, 85, 247, 0.06) 0%, transparent 40%);
42
- --bg-card: rgba(22, 22, 26, 0.75); /* Frosted dark card */
43
  --bg-input: #17171e; /* Deep dark inputs */
44
- --border-color: rgba(255, 255, 255, 0.06);
45
- --border-focus: rgba(168, 85, 247, 0.4);
46
  --text-primary: #f5f5f7; /* Crisp white */
47
  --text-secondary: #9494a3; /* Soft slate gray */
48
  --text-muted: #5e5e6c;
49
  --accent: #a855f7; /* Vibrant purple */
50
- --accent-glow: rgba(168, 85, 247, 0.2);
51
  --btn-primary: linear-gradient(135deg, #6366f1, #a855f7); /* Premium gradient */
52
  --btn-primary-hover: linear-gradient(135deg, #4f46e5, #9333ea);
53
  --btn-primary-text: #ffffff;
54
  --btn-secondary: rgba(255, 255, 255, 0.03);
55
- --btn-secondary-hover: rgba(255, 255, 255, 0.08);
56
- --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.3);
57
- --shadow-md: 0 12px 40px rgba(0, 0, 0, 0.5);
58
  }
59
 
60
  * {
61
  box-sizing: border-box;
62
  margin: 0;
63
  padding: 0;
 
64
  }
65
 
66
  body {
67
  background-color: var(--bg-main);
68
  background-image: var(--bg-gradient);
69
  color: var(--text-primary);
70
- font-family: var(--font-family-body);
71
  min-height: 100vh;
72
  display: flex;
73
  flex-direction: column;
74
  transition: background-color var(--transition-speed) ease, color var(--transition-speed) ease;
 
75
  }
76
 
77
  /* Header Navigation */
78
  header {
79
- padding: 1.5rem 3rem;
80
  display: flex;
81
  align-items: center;
82
  justify-content: space-between;
83
- max-width: 1400px;
84
  width: 100%;
85
  margin: 0 auto;
86
  }
87
 
88
  @media (max-width: 768px) {
89
  header {
90
- padding: 1.5rem 1.5rem;
91
  }
92
  }
93
 
94
  .logo-container {
95
  display: flex;
96
  align-items: center;
97
- gap: 0.6rem;
98
  cursor: pointer;
99
  }
100
 
101
  .logo-symbol {
102
  display: flex;
103
  flex-direction: column;
104
- gap: 4px;
105
  }
106
 
107
  .logo-bar {
108
- width: 24px;
109
- height: 3px;
110
  background-color: var(--text-primary);
111
  border-radius: 2px;
112
  transition: background-color var(--transition-speed) ease;
113
  }
114
 
115
- .logo-bar:nth-child(2) {
116
- width: 16px;
117
- }
118
-
119
- .logo-bar:nth-child(3) {
120
- width: 20px;
121
- }
122
 
123
  .logo-text {
124
  font-family: var(--font-title);
125
  font-weight: 700;
126
- font-size: 1.65rem;
127
  letter-spacing: -0.03em;
128
  }
129
 
130
- /* Elegant Pill Toggle Button for Themes */
131
  .theme-toggle {
132
  background: var(--bg-card);
133
  border: 1px solid var(--border-color);
134
  color: var(--text-primary);
135
- padding: 0.5rem 1rem;
136
  border-radius: 9999px;
137
- font-size: 0.75rem;
138
  font-weight: 600;
139
  letter-spacing: 0.05em;
140
  text-transform: uppercase;
141
  cursor: pointer;
142
- transition: all 0.3s ease;
143
  display: flex;
144
  align-items: center;
145
- gap: 0.4rem;
146
  box-shadow: var(--shadow-sm);
147
  }
148
 
149
  .theme-toggle:hover {
150
  border-color: var(--border-focus);
151
- transform: translateY(-1px);
152
  }
153
 
154
  /* Centered Headline Section */
155
  .hero {
156
  text-align: center;
157
- margin: 2rem auto 2.5rem;
158
- padding: 0 1.5rem;
159
- max-width: 800px;
160
  }
161
 
162
  .hero h1 {
163
  font-family: var(--font-title);
164
- font-weight: 700;
165
- font-size: 2.75rem;
166
  letter-spacing: -0.02em;
167
  color: var(--text-primary);
168
- line-height: 1.15;
169
  transition: color var(--transition-speed) ease;
170
  }
171
 
172
- @media (max-width: 768px) {
173
  .hero h1 {
174
- font-size: 2rem;
175
  }
176
  }
177
 
178
  /* Central Layout Container */
179
  .container {
180
- max-width: 1100px;
181
  width: 100%;
182
  margin: 0 auto;
183
- padding: 0 2rem;
184
  display: flex;
185
  flex-direction: column;
186
- gap: 2rem;
187
  flex: 1;
188
  }
189
 
190
- @media (max-width: 768px) {
191
- .container {
192
- padding: 0 1rem;
193
- }
194
- }
195
-
196
- /* Frosted Glass Card Panel */
197
  .card-panel {
198
  background: var(--bg-card);
199
  border: 1px solid var(--border-color);
200
- border-radius: 1.75rem;
201
  backdrop-filter: blur(20px);
202
- padding: 2rem;
203
  box-shadow: var(--shadow-md);
204
  display: flex;
205
  flex-direction: column;
206
- gap: 1.5rem;
207
  transition: background-color var(--transition-speed) ease, border-color var(--transition-speed) ease, box-shadow var(--transition-speed) ease;
208
  }
209
 
210
- /* Large Prompt Area */
211
- .prompt-container {
212
- position: relative;
213
- }
214
-
215
  textarea {
216
  width: 100%;
217
- height: 120px;
218
  background-color: var(--bg-input);
219
  border: 1px solid var(--border-color);
220
- border-radius: 1.25rem;
221
  color: var(--text-primary);
222
- padding: 1.25rem;
223
  font-family: var(--font-body);
224
- font-size: 1.05rem;
225
- line-height: 1.5;
226
  resize: none;
227
  outline: none;
228
  transition: all 0.3s ease;
229
- box-shadow: inset 0 2px 4px rgba(0,0,0,0.01);
230
  }
231
 
232
  textarea:focus {
233
  border-color: var(--border-focus);
234
- box-shadow: 0 0 12px var(--accent-glow);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  }
236
 
237
- /* Horizontal Controls Dock */
238
- .controls-dock {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  display: grid;
240
- grid-template-columns: repeat(4, minmax(110px, 1fr)) 160px;
241
  gap: 0.75rem;
242
- align-items: end;
243
  }
244
 
245
- @media (max-width: 900px) {
246
- .controls-dock {
247
- grid-template-columns: 1fr 1fr;
248
- }
249
- .generate-col {
250
- grid-column: 1 / -1;
251
  }
252
  }
253
 
254
  .control-col {
255
  display: flex;
256
  flex-direction: column;
257
- gap: 0.4rem;
258
  }
259
 
260
  .control-label {
@@ -262,53 +364,54 @@
262
  font-weight: 700;
263
  color: var(--text-secondary);
264
  text-transform: uppercase;
265
- letter-spacing: 0.08em;
266
- padding-left: 0.25rem;
267
  }
268
 
269
- select, input[type="number"], .input-group-field {
270
  width: 100%;
271
  background: var(--bg-input);
272
  border: 1px solid var(--border-color);
273
- border-radius: 0.75rem;
274
  color: var(--text-primary);
275
- padding: 0.75rem 1rem;
276
  font-family: var(--font-body);
277
- font-size: 0.9rem;
278
  font-weight: 500;
279
  outline: none;
280
- transition: all 0.3s ease;
281
- height: 44px;
282
  }
283
 
284
- select:focus, input[type="number"]:focus, .input-group-field:focus {
285
  border-color: var(--border-focus);
286
  }
287
 
288
- /* Seed Dice Combined Field */
289
- .seed-input-group {
290
  display: flex;
 
291
  background: var(--bg-input);
292
  border: 1px solid var(--border-color);
293
- border-radius: 0.75rem;
 
294
  overflow: hidden;
295
- height: 44px;
296
- transition: border-color 0.3s ease;
297
  }
298
 
299
- .seed-input-group:focus-within {
300
  border-color: var(--border-focus);
301
  }
302
 
303
- .seed-input-group input {
304
  border: none;
305
  background: transparent;
306
  flex: 1;
307
- padding: 0.75rem 0.5rem 0.75rem 1rem;
308
  outline: none;
309
  color: var(--text-primary);
310
  font-family: var(--font-body);
311
- font-size: 0.9rem;
312
  font-weight: 500;
313
  width: 100%;
314
  }
@@ -316,134 +419,44 @@
316
  .dice-btn {
317
  background: transparent;
318
  border: none;
319
- padding: 0 0.75rem;
320
  color: var(--text-secondary);
321
  cursor: pointer;
322
  display: flex;
323
  align-items: center;
324
  justify-content: center;
325
- transition: color 0.2s ease;
326
  }
327
 
328
  .dice-btn:hover {
329
  color: var(--text-primary);
330
  }
331
 
332
- /* Combined Steps and Toggle Field */
333
- .steps-input-group {
334
- display: flex;
335
- align-items: center;
336
- background: var(--bg-input);
337
- border: 1px solid var(--border-color);
338
- border-radius: 0.75rem;
339
- height: 44px;
340
- overflow: hidden;
341
- padding-right: 0.5rem;
342
- transition: border-color 0.3s ease;
343
- }
344
-
345
- .steps-input-group:focus-within {
346
- border-color: var(--border-focus);
347
- }
348
-
349
- .steps-input-group input {
350
- border: none;
351
- background: transparent;
352
- flex: 1;
353
- padding: 0.75rem 0.5rem 0.75rem 1rem;
354
- outline: none;
355
- color: var(--text-primary);
356
- font-family: var(--font-body);
357
- font-size: 0.9rem;
358
- font-weight: 500;
359
- width: 100%;
360
- }
361
-
362
  .toggle-pill {
363
  background: var(--text-primary);
364
  color: var(--bg-main);
365
- padding: 0.3rem 0.6rem;
366
  border-radius: 9999px;
367
- font-size: 0.65rem;
368
  font-weight: 700;
369
- cursor: pointer;
370
  text-transform: uppercase;
371
- letter-spacing: 0.05em;
372
- transition: all 0.3s ease;
373
  }
374
 
375
- /* Generate CTA Button */
376
- .generate-btn {
377
- background: var(--btn-primary);
378
- color: var(--btn-primary-text);
379
- border: none;
380
- height: 44px;
381
- border-radius: 0.75rem;
382
- font-family: var(--font-title);
383
- font-size: 1rem;
384
- font-weight: 600;
385
- cursor: pointer;
386
- transition: all 0.3s ease;
387
- display: flex;
388
- align-items: center;
389
- justify-content: center;
390
- width: 100%;
391
- box-shadow: var(--shadow-sm);
392
- }
393
-
394
- .generate-btn:hover:not(:disabled) {
395
- background: var(--btn-primary-hover);
396
- transform: translateY(-1px);
397
- }
398
-
399
- .generate-btn:disabled {
400
- opacity: 0.6;
401
- cursor: not-allowed;
402
- }
403
-
404
- /* Pill Prompt Tags */
405
- .pill-prompts {
406
- display: flex;
407
- flex-wrap: wrap;
408
- gap: 0.5rem;
409
- margin-top: 0.5rem;
410
- }
411
-
412
- .prompt-pill {
413
- background: var(--btn-secondary);
414
- border: 1px solid var(--border-color);
415
- color: var(--text-secondary);
416
- padding: 0.5rem 1.1rem;
417
- border-radius: 9999px;
418
- font-size: 0.85rem;
419
- cursor: pointer;
420
- transition: all 0.25s ease;
421
- white-space: nowrap;
422
- overflow: hidden;
423
- text-overflow: ellipsis;
424
- max-width: 100%;
425
- }
426
-
427
- .prompt-pill:hover {
428
- border-color: var(--border-focus);
429
- background: var(--btn-secondary-hover);
430
- color: var(--text-primary);
431
- transform: translateY(-1px);
432
- }
433
-
434
- /* Large Preview/Viewport Card */
435
  .viewport-card {
436
  background: var(--bg-card);
437
  border: 1px solid var(--border-color);
438
- border-radius: 1.75rem;
439
  backdrop-filter: blur(20px);
440
- min-height: 450px;
441
  display: flex;
442
  flex-direction: column;
443
  position: relative;
444
  overflow: hidden;
445
  box-shadow: var(--shadow-md);
446
- padding: 1.5rem;
447
  transition: background-color var(--transition-speed) ease, border-color var(--transition-speed) ease, box-shadow var(--transition-speed) ease;
448
  }
449
 
@@ -453,7 +466,12 @@
453
  align-items: center;
454
  justify-content: center;
455
  position: relative;
456
- min-height: 380px;
 
 
 
 
 
457
  }
458
 
459
  .viewport-placeholder {
@@ -461,36 +479,37 @@
461
  flex-direction: column;
462
  align-items: center;
463
  color: var(--text-muted);
464
- gap: 1rem;
465
  text-align: center;
466
- max-width: 400px;
 
467
  }
468
 
469
  .viewport-placeholder svg {
470
- width: 3.5rem;
471
- height: 3.5rem;
472
  opacity: 0.3;
473
  }
474
 
475
  .viewport-placeholder h3 {
476
  font-family: var(--font-title);
477
- font-size: 1.25rem;
478
  color: var(--text-secondary);
479
  }
480
 
481
  .viewport-placeholder p {
482
- font-size: 0.85rem;
483
  }
484
 
485
  .preview-img {
486
  max-width: 100%;
487
- max-height: 550px;
488
  object-fit: contain;
489
- border-radius: 0.75rem;
490
- box-shadow: 0 4px 20px rgba(0,0,0,0.15);
491
  opacity: 0;
492
  transform: scale(0.98);
493
- transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1);
494
  }
495
 
496
  .preview-img.loaded {
@@ -498,7 +517,7 @@
498
  transform: scale(1);
499
  }
500
 
501
- /* Dynamic Queue Overlay */
502
  .overlay {
503
  position: absolute;
504
  top: 0;
@@ -506,12 +525,12 @@
506
  right: 0;
507
  bottom: 0;
508
  background: var(--bg-card);
509
- backdrop-filter: blur(8px);
510
  display: flex;
511
  flex-direction: column;
512
  align-items: center;
513
  justify-content: center;
514
- gap: 1.25rem;
515
  z-index: 10;
516
  opacity: 0;
517
  pointer-events: none;
@@ -524,49 +543,45 @@
524
  }
525
 
526
  .spinner {
527
- width: 3.5rem;
528
- height: 3.5rem;
529
- border: 3.5px solid rgba(0, 0, 0, 0.05);
530
  border-top-color: var(--accent);
531
  border-radius: 50%;
532
  animation: spin 1s linear infinite;
533
- filter: drop-shadow(0 0 8px var(--accent-glow));
534
  }
535
 
536
  body.dark-theme .spinner {
537
  border-color: rgba(255, 255, 255, 0.05);
538
  }
539
 
540
- @keyframes spin {
541
- to { transform: rotate(360deg); }
542
- }
543
-
544
  .status-msg {
545
  font-family: var(--font-title);
546
- font-size: 1.2rem;
547
  font-weight: 500;
548
  color: var(--text-primary);
549
  }
550
 
551
  .status-timer {
552
- font-size: 0.85rem;
553
  color: var(--text-secondary);
554
  }
555
 
556
- /* Bottom Status Footer (Metrics & Downloads) */
557
  .viewport-footer {
558
  display: flex;
559
  align-items: center;
560
  justify-content: space-between;
561
  border-top: 1px solid var(--border-color);
562
- padding-top: 1.25rem;
563
- margin-top: 1rem;
564
  }
565
 
566
  .metrics-summary {
567
  display: flex;
568
- gap: 1.5rem;
569
- font-size: 0.8rem;
570
  color: var(--text-secondary);
571
  }
572
 
@@ -579,15 +594,15 @@
579
  background: var(--btn-secondary);
580
  border: 1px solid var(--border-color);
581
  color: var(--text-primary);
582
- padding: 0.5rem 1.2rem;
583
  border-radius: 9999px;
584
- font-size: 0.85rem;
585
  font-weight: 600;
586
  cursor: pointer;
587
  transition: all 0.2s ease;
588
  display: flex;
589
  align-items: center;
590
- gap: 0.4rem;
591
  }
592
 
593
  .download-btn:hover:not(:disabled) {
@@ -600,56 +615,53 @@
600
  cursor: not-allowed;
601
  }
602
 
603
- /* History Drawer */
604
  .history-section {
605
  display: flex;
606
  flex-direction: column;
607
- gap: 0.75rem;
608
- margin: 0.5rem auto 3rem;
609
- max-width: 1100px;
610
  width: 100%;
611
- padding: 0 2rem;
612
- }
613
-
614
- @media (max-width: 768px) {
615
- .history-section {
616
- padding: 0 1rem;
617
- }
618
  }
619
 
620
  .history-label {
621
- font-size: 0.7rem;
622
  font-weight: 700;
623
  color: var(--text-secondary);
624
  text-transform: uppercase;
625
- letter-spacing: 0.08em;
626
- padding-left: 0.5rem;
627
  }
628
 
629
  .history-drawer {
630
- display: grid;
631
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
632
- gap: 0.75rem;
633
- max-height: 130px;
634
- overflow-y: auto;
635
- padding-right: 0.5rem;
 
 
 
 
636
  }
637
 
638
  .history-card {
639
  background: var(--bg-card);
640
  border: 1px solid var(--border-color);
641
- border-radius: 1rem;
642
- padding: 0.75rem 1.1rem;
643
- font-size: 0.8rem;
644
  color: var(--text-secondary);
645
  cursor: pointer;
646
- transition: all 0.25s ease;
647
- overflow: hidden;
648
- display: -webkit-box;
649
- -webkit-line-clamp: 3;
650
- -webkit-box-orient: vertical;
651
- line-height: 1.4;
652
  backdrop-filter: blur(10px);
 
 
 
 
653
  }
654
 
655
  .history-card:hover {
@@ -659,10 +671,10 @@
659
  }
660
 
661
  .history-empty {
662
- font-size: 0.8rem;
663
  color: var(--text-secondary);
664
  font-style: italic;
665
- padding-left: 0.5rem;
666
  }
667
  </style>
668
  </head>
@@ -678,7 +690,6 @@
678
  <span class="logo-text">Bonsai</span>
679
  </div>
680
 
681
- <!-- Toggle Button -->
682
  <button id="theme-btn" class="theme-toggle">
683
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
684
  <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
@@ -694,80 +705,91 @@
694
  <div class="container">
695
  <!-- Main Form Panel -->
696
  <section class="card-panel">
697
- <div class="prompt-container">
698
- <textarea id="prompt" placeholder="Describe what you want to create..."></textarea>
 
 
 
 
 
 
 
 
699
  </div>
700
 
701
- <!-- Align Controls and Generate button in one single row -->
702
- <div class="controls-dock">
703
- <!-- Model Column -->
704
- <div class="control-col">
705
- <span class="control-label">Model</span>
706
- <select id="backend">
707
- <option value="bonsai-ternary-gemlite">Bonsai · Ternary</option>
708
- <option value="bonsai-binary-gemlite">Bonsai · Binary</option>
709
- </select>
710
- </div>
 
 
 
711
 
712
- <!-- Resolution Column -->
713
- <div class="control-col">
714
- <span class="control-label">Resolution</span>
715
- <select id="resolution">
716
- <option value="512x512" selected>1:1 — 512 × 512</option>
717
- <option value="576x384">3:2 — 576 × 384</option>
718
- <option value="384x576">2:3 — 384 × 576</option>
719
- <option value="768x448">16:9 — 768 × 448</option>
720
- <option value="448x768">9:16 — 448 × 768</option>
721
- </select>
722
- </div>
723
 
724
- <!-- Seed Column with Embedded dice button -->
725
- <div class="control-col">
726
- <span class="control-label">Seed</span>
727
- <div class="seed-input-group">
728
- <input type="number" id="seed" value="42" min="0" max="999999999">
729
- <button id="dice-btn" class="dice-btn" title="Randomize Seed">
730
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
731
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
732
- <circle cx="8" cy="8" r="1.5"/>
733
- <circle cx="16" cy="8" r="1.5"/>
734
- <circle cx="8" cy="16" r="1.5"/>
735
- <circle cx="16" cy="16" r="1.5"/>
736
- </svg>
737
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
738
  </div>
739
- </div>
740
 
741
- <!-- Steps Column with Single batch badge -->
742
- <div class="control-col">
743
- <span class="control-label">Steps</span>
744
- <div class="steps-input-group">
745
- <input type="number" id="steps" value="4" min="1" max="20">
746
- <span class="toggle-pill" title="Execution Mode">Single</span>
 
747
  </div>
748
  </div>
749
 
750
- <!-- Guidance and hidden fields -->
751
  <input type="hidden" id="guidance" value="1.0">
752
  <input type="hidden" id="width" value="512">
753
  <input type="hidden" id="height" value="512">
754
-
755
- <!-- Generate Column -->
756
- <div class="control-col generate-col">
757
- <button id="generate-btn" class="generate-btn">Generate</button>
758
- </div>
759
- </div>
760
-
761
- <!-- Quick Pill sample tags -->
762
- <div class="pill-prompts">
763
- <button class="prompt-pill" data-prompt="A bonsai tree in a quiet ceramic studio, soft morning light, shallow depth of field">A bonsai tree in a quiet ceramic studio, soft morning light, shallow depth of field</button>
764
- <button class="prompt-pill" data-prompt="A tiny moss garden on a windowsill, rain on the glass, warm film photography">A tiny moss garden on a windowsill, rain on the glass, warm film photography</button>
765
- <button class="prompt-pill" data-prompt="A red fox curled beneath paper lanterns, ink wash style, gentle shadows">A red fox curled beneath paper lanterns, ink wash style, gentle shadows</button>
766
- <button class="prompt-pill" data-prompt="A glass terrarium city at night, miniature streets, glowing storefronts">A glass terrarium city at night, miniature streets, glowing storefronts</button>
767
  </div>
768
  </section>
769
 
770
- <!-- Preview Viewport Panel -->
771
  <section class="viewport-card">
772
  <!-- Loading Queue Overlay -->
773
  <div id="loader-overlay" class="overlay">
@@ -777,7 +799,6 @@
777
  </div>
778
 
779
  <div class="viewport-body">
780
- <!-- Placeholder frame -->
781
  <div id="placeholder" class="viewport-placeholder">
782
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
783
  <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
@@ -788,7 +809,6 @@
788
  <p>Image results will render here after clicking generate.</p>
789
  </div>
790
 
791
- <!-- Active image -->
792
  <img id="preview-image" class="preview-img" alt="Bonsai Generation Result">
793
  </div>
794
 
@@ -800,7 +820,7 @@
800
  </div>
801
 
802
  <button id="download-btn" class="download-btn" disabled>
803
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
804
  <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/>
805
  </svg>
806
  Download PNG
@@ -821,7 +841,7 @@
821
  <script type="module">
822
  import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
823
 
824
- // Global DOM bindings
825
  const promptInput = document.getElementById('prompt');
826
  const widthInput = document.getElementById('width');
827
  const heightInput = document.getElementById('height');
@@ -845,6 +865,9 @@
845
 
846
  const promptPills = document.querySelectorAll('.prompt-pill');
847
  const themeBtn = document.getElementById('theme-btn');
 
 
 
848
 
849
  // Dynamic State
850
  let client = null;
@@ -852,12 +875,17 @@
852
  let timerInterval = null;
853
  let elapsed = 0.0;
854
 
855
- // Light/Dark Theme Switcher logic
 
 
 
 
 
 
856
  themeBtn.addEventListener('click', () => {
857
  const isDark = document.body.classList.toggle('dark-theme');
858
  localStorage.setItem('bonsai_theme', isDark ? 'dark' : 'light');
859
 
860
- // Render correct label/icon
861
  if (isDark) {
862
  themeBtn.innerHTML = `
863
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
@@ -875,7 +903,6 @@
875
  }
876
  });
877
 
878
- // Initialize theme on start
879
  function initTheme() {
880
  const saved = localStorage.getItem('bonsai_theme');
881
  if (saved === 'dark') {
@@ -889,13 +916,11 @@
889
  }
890
  }
891
 
892
- // Initialize Gradio Client connection
893
  async function connectGradio() {
894
  try {
895
  client = await Client.connect(window.location.origin);
896
- console.log("Gradio client connected successfully.");
897
 
898
- // Set custom backends from endpoint if dynamically returned
899
  const backendInfo = await client.predict("/backends");
900
  if (backendInfo && backendInfo.data && backendInfo.data[0]) {
901
  const info = backendInfo.data[0];
@@ -913,28 +938,23 @@
913
  }
914
  }
915
 
916
- // Dice button seed randomization
917
  diceBtn.addEventListener('click', () => {
918
  seedInput.value = Math.floor(Math.random() * 1000000000);
919
  });
920
 
921
- // Parse resolution selection
922
  resolutionSelect.addEventListener('change', (e) => {
923
  const [w, h] = e.target.value.split('x');
924
  widthInput.value = w;
925
  heightInput.value = h;
926
  });
927
 
928
- // Pill Prompt clicking
929
  promptPills.forEach(pill => {
930
  pill.addEventListener('click', () => {
931
  promptInput.value = pill.dataset.prompt;
932
- // Soft hover styling focus
933
  promptInput.focus();
934
  });
935
  });
936
 
937
- // Timer overlays
938
  function startTimer() {
939
  elapsed = 0.0;
940
  statusTimer.textContent = `Elapsed: 0.0s`;
@@ -949,21 +969,19 @@
949
  clearInterval(timerInterval);
950
  }
951
 
952
- // Export/Download PNG
953
  downloadBtn.addEventListener('click', () => {
954
  if (!previewImage.src) return;
955
  const link = document.createElement('a');
956
  link.href = previewImage.src;
957
  const ts = new Date().toISOString().slice(0, 19).replace(/[-:]/g, "").replace("T", "_");
958
- link.download = `bonsai_artwork_${ts}.png`;
959
  document.body.appendChild(link);
960
  link.click();
961
  document.body.removeChild(link);
962
  });
963
 
964
- // Prompt history persistence
965
  function getHistory() {
966
- const h = localStorage.getItem('bonsai_prompt_history_dock');
967
  return h ? JSON.parse(h) : [];
968
  }
969
 
@@ -972,8 +990,8 @@
972
  let list = getHistory();
973
  list = list.filter(item => item !== text);
974
  list.unshift(text);
975
- list = list.slice(0, 12); // Limit history size
976
- localStorage.setItem('bonsai_prompt_history_dock', JSON.stringify(list));
977
  renderHistory();
978
  }
979
 
@@ -981,7 +999,7 @@
981
  const list = getHistory();
982
  historyDrawer.innerHTML = '';
983
  if (list.length === 0) {
984
- historyDrawer.innerHTML = '<span class="history-empty">History is currently empty. Generates will appear here.</span>';
985
  return;
986
  }
987
  list.forEach(p => {
@@ -996,7 +1014,6 @@
996
  });
997
  }
998
 
999
- // Action generate trigger
1000
  async function startGenerate() {
1001
  const promptVal = promptInput.value.trim();
1002
  if (!promptVal) {
@@ -1011,7 +1028,6 @@
1011
  downloadBtn.disabled = true;
1012
  previewImage.classList.remove('loaded');
1013
 
1014
- // Show queue loader
1015
  statusTitle.textContent = "Connecting to Blackwell GPU queue...";
1016
  loaderOverlay.classList.add('active');
1017
  placeholder.style.display = 'none';
@@ -1024,7 +1040,6 @@
1024
  const backendVal = backendInput.value;
1025
 
1026
  try {
1027
- // Submit to the gradio.Server ZeroGPU queue
1028
  statusTitle.textContent = "Generating artwork...";
1029
  const result = await client.predict("/generate", {
1030
  prompt: promptVal,
@@ -1047,16 +1062,13 @@
1047
  metricLatency.textContent = '—';
1048
  metricVram.textContent = '—';
1049
  } else {
1050
- // Success render
1051
  previewImage.src = `data:image/png;base64,${data.image_b64}`;
1052
  previewImage.classList.add('loaded');
1053
  downloadBtn.disabled = false;
1054
 
1055
- // Show metrics
1056
  metricLatency.textContent = `${data.wall_seconds} s`;
1057
  metricVram.textContent = `${data.peak_memory_mb} MB`;
1058
 
1059
- // Save history
1060
  saveHistory(promptVal);
1061
  }
1062
  } catch (err) {
@@ -1075,7 +1087,6 @@
1075
 
1076
  generateBtn.addEventListener('click', startGenerate);
1077
 
1078
- // Bootstrap on DOM load
1079
  document.addEventListener('DOMContentLoaded', () => {
1080
  initTheme();
1081
  renderHistory();
 
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>Bonsai — Create stunning images with AI</title>
7
  <!-- Google Fonts -->
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
 
10
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet">
11
 
12
  <style>
 
13
  :root {
14
  --bg-main: #e8e6e2; /* Warm beige backdrop */
15
+ --bg-gradient: radial-gradient(circle at 50% 50%, rgba(240, 238, 234, 0.85) 0%, rgba(232, 230, 226, 0.95) 100%);
16
+ --bg-card: rgba(243, 241, 237, 0.9); /* Frosted warm-gray card */
17
  --bg-input: #faf9f7; /* Off-white inputs */
18
+ --border-color: rgba(0, 0, 0, 0.05);
19
  --border-focus: rgba(99, 102, 241, 0.3);
20
  --text-primary: #2d2d34; /* Soft dark slate */
21
  --text-secondary: #6e6e76; /* Muted slate */
22
  --text-muted: #8e8e96;
23
  --accent: #5e60e6;
24
+ --accent-glow: rgba(99, 102, 241, 0.12);
25
+ --btn-primary: #3c3c43; /* Elegant charcoal */
26
+ --btn-primary-hover: #1e1e24;
27
  --btn-primary-text: #ffffff;
28
  --btn-secondary: rgba(0, 0, 0, 0.03);
29
  --btn-secondary-hover: rgba(0, 0, 0, 0.06);
30
+ --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.02);
31
+ --shadow-md: 0 10px 30px rgba(0, 0, 0, 0.04);
32
  --font-title: 'Outfit', sans-serif;
33
  --font-body: 'Inter', sans-serif;
34
+ --transition-speed: 0.35s;
35
  }
36
 
37
  body.dark-theme {
38
  --bg-main: #0c0c0e; /* Deep rich dark slate */
39
+ --bg-gradient: radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.05) 0%, transparent 40%),
40
+ radial-gradient(circle at 90% 80%, rgba(168, 85, 247, 0.05) 0%, transparent 40%);
41
+ --bg-card: rgba(22, 22, 26, 0.8); /* Frosted dark card */
42
  --bg-input: #17171e; /* Deep dark inputs */
43
+ --border-color: rgba(255, 255, 255, 0.05);
44
+ --border-focus: rgba(168, 85, 247, 0.35);
45
  --text-primary: #f5f5f7; /* Crisp white */
46
  --text-secondary: #9494a3; /* Soft slate gray */
47
  --text-muted: #5e5e6c;
48
  --accent: #a855f7; /* Vibrant purple */
49
+ --accent-glow: rgba(168, 85, 247, 0.15);
50
  --btn-primary: linear-gradient(135deg, #6366f1, #a855f7); /* Premium gradient */
51
  --btn-primary-hover: linear-gradient(135deg, #4f46e5, #9333ea);
52
  --btn-primary-text: #ffffff;
53
  --btn-secondary: rgba(255, 255, 255, 0.03);
54
+ --btn-secondary-hover: rgba(255, 255, 255, 0.06);
55
+ --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.2);
56
+ --shadow-md: 0 12px 36px rgba(0, 0, 0, 0.45);
57
  }
58
 
59
  * {
60
  box-sizing: border-box;
61
  margin: 0;
62
  padding: 0;
63
+ -webkit-tap-highlight-color: transparent;
64
  }
65
 
66
  body {
67
  background-color: var(--bg-main);
68
  background-image: var(--bg-gradient);
69
  color: var(--text-primary);
70
+ font-family: var(--font-body);
71
  min-height: 100vh;
72
  display: flex;
73
  flex-direction: column;
74
  transition: background-color var(--transition-speed) ease, color var(--transition-speed) ease;
75
+ padding-bottom: 2rem;
76
  }
77
 
78
  /* Header Navigation */
79
  header {
80
+ padding: 1.25rem 2rem;
81
  display: flex;
82
  align-items: center;
83
  justify-content: space-between;
84
+ max-width: 1000px;
85
  width: 100%;
86
  margin: 0 auto;
87
  }
88
 
89
  @media (max-width: 768px) {
90
  header {
91
+ padding: 1rem 1.25rem;
92
  }
93
  }
94
 
95
  .logo-container {
96
  display: flex;
97
  align-items: center;
98
+ gap: 0.5rem;
99
  cursor: pointer;
100
  }
101
 
102
  .logo-symbol {
103
  display: flex;
104
  flex-direction: column;
105
+ gap: 3px;
106
  }
107
 
108
  .logo-bar {
109
+ width: 20px;
110
+ height: 2.5px;
111
  background-color: var(--text-primary);
112
  border-radius: 2px;
113
  transition: background-color var(--transition-speed) ease;
114
  }
115
 
116
+ .logo-bar:nth-child(2) { width: 13px; }
117
+ .logo-bar:nth-child(3) { width: 17px; }
 
 
 
 
 
118
 
119
  .logo-text {
120
  font-family: var(--font-title);
121
  font-weight: 700;
122
+ font-size: 1.4rem;
123
  letter-spacing: -0.03em;
124
  }
125
 
 
126
  .theme-toggle {
127
  background: var(--bg-card);
128
  border: 1px solid var(--border-color);
129
  color: var(--text-primary);
130
+ padding: 0.4rem 0.8rem;
131
  border-radius: 9999px;
132
+ font-size: 0.7rem;
133
  font-weight: 600;
134
  letter-spacing: 0.05em;
135
  text-transform: uppercase;
136
  cursor: pointer;
137
+ transition: all 0.2s ease;
138
  display: flex;
139
  align-items: center;
140
+ gap: 0.3rem;
141
  box-shadow: var(--shadow-sm);
142
  }
143
 
144
  .theme-toggle:hover {
145
  border-color: var(--border-focus);
 
146
  }
147
 
148
  /* Centered Headline Section */
149
  .hero {
150
  text-align: center;
151
+ margin: 1rem auto 1.5rem;
152
+ padding: 0 1.25rem;
153
+ max-width: 600px;
154
  }
155
 
156
  .hero h1 {
157
  font-family: var(--font-title);
158
+ font-weight: 600;
159
+ font-size: 2rem;
160
  letter-spacing: -0.02em;
161
  color: var(--text-primary);
162
+ line-height: 1.2;
163
  transition: color var(--transition-speed) ease;
164
  }
165
 
166
+ @media (max-width: 480px) {
167
  .hero h1 {
168
+ font-size: 1.6rem;
169
  }
170
  }
171
 
172
  /* Central Layout Container */
173
  .container {
174
+ max-width: 800px;
175
  width: 100%;
176
  margin: 0 auto;
177
+ padding: 0 1.25rem;
178
  display: flex;
179
  flex-direction: column;
180
+ gap: 1.25rem;
181
  flex: 1;
182
  }
183
 
184
+ /* Decluttered Card Panel */
 
 
 
 
 
 
185
  .card-panel {
186
  background: var(--bg-card);
187
  border: 1px solid var(--border-color);
188
+ border-radius: 1.5rem;
189
  backdrop-filter: blur(20px);
190
+ padding: 1.25rem;
191
  box-shadow: var(--shadow-md);
192
  display: flex;
193
  flex-direction: column;
194
+ gap: 1rem;
195
  transition: background-color var(--transition-speed) ease, border-color var(--transition-speed) ease, box-shadow var(--transition-speed) ease;
196
  }
197
 
198
+ /* Prompt Area */
 
 
 
 
199
  textarea {
200
  width: 100%;
201
+ height: 90px;
202
  background-color: var(--bg-input);
203
  border: 1px solid var(--border-color);
204
+ border-radius: 1rem;
205
  color: var(--text-primary);
206
+ padding: 1rem;
207
  font-family: var(--font-body);
208
+ font-size: 0.95rem;
209
+ line-height: 1.45;
210
  resize: none;
211
  outline: none;
212
  transition: all 0.3s ease;
 
213
  }
214
 
215
  textarea:focus {
216
  border-color: var(--border-focus);
217
+ box-shadow: 0 0 8px var(--accent-glow);
218
+ }
219
+
220
+ /* Swipeable Prompt Pills (Horizontal Scroll) */
221
+ .pill-prompts-container {
222
+ width: 100%;
223
+ overflow-x: auto;
224
+ white-space: nowrap;
225
+ -webkit-overflow-scrolling: touch;
226
+ padding-bottom: 0.25rem;
227
+ margin-top: -0.25rem;
228
+ }
229
+
230
+ .pill-prompts-container::-webkit-scrollbar {
231
+ display: none;
232
+ }
233
+
234
+ .pill-prompts {
235
+ display: inline-flex;
236
+ gap: 0.4rem;
237
+ }
238
+
239
+ .prompt-pill {
240
+ background: var(--btn-secondary);
241
+ border: 1px solid var(--border-color);
242
+ color: var(--text-secondary);
243
+ padding: 0.4rem 0.85rem;
244
+ border-radius: 9999px;
245
+ font-size: 0.75rem;
246
+ cursor: pointer;
247
+ transition: all 0.2s ease;
248
+ font-weight: 500;
249
+ }
250
+
251
+ .prompt-pill:hover {
252
+ border-color: var(--border-focus);
253
+ background: var(--btn-secondary-hover);
254
+ color: var(--text-primary);
255
+ }
256
+
257
+ /* Simplified Controls bar */
258
+ .action-dock {
259
+ display: flex;
260
+ align-items: center;
261
+ gap: 0.5rem;
262
+ width: 100%;
263
+ }
264
+
265
+ .options-btn {
266
+ background: var(--bg-input);
267
+ border: 1px solid var(--border-color);
268
+ color: var(--text-primary);
269
+ height: 40px;
270
+ padding: 0 0.85rem;
271
+ border-radius: 0.75rem;
272
+ cursor: pointer;
273
+ transition: all 0.2s ease;
274
+ display: flex;
275
+ align-items: center;
276
+ justify-content: center;
277
+ gap: 0.35rem;
278
+ font-size: 0.8rem;
279
+ font-weight: 600;
280
+ white-space: nowrap;
281
+ box-shadow: var(--shadow-sm);
282
+ }
283
+
284
+ .options-btn:hover {
285
+ border-color: var(--border-focus);
286
+ }
287
+
288
+ .options-btn svg {
289
+ transition: transform 0.3s ease;
290
  }
291
 
292
+ .options-btn.active svg {
293
+ transform: rotate(45deg);
294
+ }
295
+
296
+ .generate-btn {
297
+ background: var(--btn-primary);
298
+ color: var(--btn-primary-text);
299
+ border: none;
300
+ height: 40px;
301
+ border-radius: 0.75rem;
302
+ font-family: var(--font-title);
303
+ font-size: 0.95rem;
304
+ font-weight: 600;
305
+ cursor: pointer;
306
+ transition: all 0.25s ease;
307
+ display: flex;
308
+ align-items: center;
309
+ justify-content: center;
310
+ flex: 1;
311
+ box-shadow: var(--shadow-sm);
312
+ }
313
+
314
+ .generate-btn:hover:not(:disabled) {
315
+ background: var(--btn-primary-hover);
316
+ transform: translateY(-0.5px);
317
+ }
318
+
319
+ .generate-btn:disabled {
320
+ opacity: 0.5;
321
+ cursor: not-allowed;
322
+ }
323
+
324
+ /* Collapsible Options Drawer Accordion */
325
+ .options-drawer {
326
+ max-height: 0;
327
+ overflow: hidden;
328
+ transition: max-height var(--transition-speed) cubic-bezier(0.16, 1, 0.3, 1), opacity var(--transition-speed) ease;
329
+ opacity: 0;
330
+ display: flex;
331
+ flex-direction: column;
332
+ gap: 0.85rem;
333
+ pointer-events: none;
334
+ }
335
+
336
+ .options-drawer.open {
337
+ max-height: 380px; /* Fits content smoothly */
338
+ opacity: 1;
339
+ border-top: 1px solid var(--border-color);
340
+ padding-top: 1rem;
341
+ pointer-events: auto;
342
+ }
343
+
344
+ .drawer-grid {
345
  display: grid;
346
+ grid-template-columns: 1fr 1fr;
347
  gap: 0.75rem;
 
348
  }
349
 
350
+ @media (max-width: 480px) {
351
+ .drawer-grid {
352
+ grid-template-columns: 1fr;
 
 
 
353
  }
354
  }
355
 
356
  .control-col {
357
  display: flex;
358
  flex-direction: column;
359
+ gap: 0.35rem;
360
  }
361
 
362
  .control-label {
 
364
  font-weight: 700;
365
  color: var(--text-secondary);
366
  text-transform: uppercase;
367
+ letter-spacing: 0.06em;
368
+ padding-left: 0.2rem;
369
  }
370
 
371
+ select, input[type="number"] {
372
  width: 100%;
373
  background: var(--bg-input);
374
  border: 1px solid var(--border-color);
375
+ border-radius: 0.6rem;
376
  color: var(--text-primary);
377
+ padding: 0.6rem 0.8rem;
378
  font-family: var(--font-body);
379
+ font-size: 0.85rem;
380
  font-weight: 500;
381
  outline: none;
382
+ transition: all 0.2s ease;
383
+ height: 38px;
384
  }
385
 
386
+ select:focus, input[type="number"]:focus {
387
  border-color: var(--border-focus);
388
  }
389
 
390
+ /* Combined Seed and Steps Groups */
391
+ .seed-input-group, .steps-input-group {
392
  display: flex;
393
+ align-items: center;
394
  background: var(--bg-input);
395
  border: 1px solid var(--border-color);
396
+ border-radius: 0.6rem;
397
+ height: 38px;
398
  overflow: hidden;
399
+ transition: border-color 0.2s ease;
 
400
  }
401
 
402
+ .seed-input-group:focus-within, .steps-input-group:focus-within {
403
  border-color: var(--border-focus);
404
  }
405
 
406
+ .seed-input-group input, .steps-input-group input {
407
  border: none;
408
  background: transparent;
409
  flex: 1;
410
+ padding: 0.6rem 0.5rem 0.6rem 0.8rem;
411
  outline: none;
412
  color: var(--text-primary);
413
  font-family: var(--font-body);
414
+ font-size: 0.85rem;
415
  font-weight: 500;
416
  width: 100%;
417
  }
 
419
  .dice-btn {
420
  background: transparent;
421
  border: none;
422
+ padding: 0 0.6rem;
423
  color: var(--text-secondary);
424
  cursor: pointer;
425
  display: flex;
426
  align-items: center;
427
  justify-content: center;
428
+ transition: color 0.15s ease;
429
  }
430
 
431
  .dice-btn:hover {
432
  color: var(--text-primary);
433
  }
434
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  .toggle-pill {
436
  background: var(--text-primary);
437
  color: var(--bg-main);
438
+ padding: 0.2rem 0.5rem;
439
  border-radius: 9999px;
440
+ font-size: 0.6rem;
441
  font-weight: 700;
442
+ margin-right: 0.5rem;
443
  text-transform: uppercase;
444
+ letter-spacing: 0.04em;
 
445
  }
446
 
447
+ /* Preview Viewport Card */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
448
  .viewport-card {
449
  background: var(--bg-card);
450
  border: 1px solid var(--border-color);
451
+ border-radius: 1.5rem;
452
  backdrop-filter: blur(20px);
453
+ min-height: 380px;
454
  display: flex;
455
  flex-direction: column;
456
  position: relative;
457
  overflow: hidden;
458
  box-shadow: var(--shadow-md);
459
+ padding: 1.25rem;
460
  transition: background-color var(--transition-speed) ease, border-color var(--transition-speed) ease, box-shadow var(--transition-speed) ease;
461
  }
462
 
 
466
  align-items: center;
467
  justify-content: center;
468
  position: relative;
469
+ min-height: 300px;
470
+ }
471
+
472
+ @media (max-width: 480px) {
473
+ .viewport-card { min-height: 280px; }
474
+ .viewport-body { min-height: 220px; }
475
  }
476
 
477
  .viewport-placeholder {
 
479
  flex-direction: column;
480
  align-items: center;
481
  color: var(--text-muted);
482
+ gap: 0.75rem;
483
  text-align: center;
484
+ max-width: 320px;
485
+ padding: 1rem;
486
  }
487
 
488
  .viewport-placeholder svg {
489
+ width: 2.75rem;
490
+ height: 2.75rem;
491
  opacity: 0.3;
492
  }
493
 
494
  .viewport-placeholder h3 {
495
  font-family: var(--font-title);
496
+ font-size: 1.1rem;
497
  color: var(--text-secondary);
498
  }
499
 
500
  .viewport-placeholder p {
501
+ font-size: 0.8rem;
502
  }
503
 
504
  .preview-img {
505
  max-width: 100%;
506
+ max-height: 480px;
507
  object-fit: contain;
508
+ border-radius: 0.6rem;
509
+ box-shadow: 0 4px 15px rgba(0,0,0,0.12);
510
  opacity: 0;
511
  transform: scale(0.98);
512
+ transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
513
  }
514
 
515
  .preview-img.loaded {
 
517
  transform: scale(1);
518
  }
519
 
520
+ /* Loading Queue Overlay */
521
  .overlay {
522
  position: absolute;
523
  top: 0;
 
525
  right: 0;
526
  bottom: 0;
527
  background: var(--bg-card);
528
+ backdrop-filter: blur(6px);
529
  display: flex;
530
  flex-direction: column;
531
  align-items: center;
532
  justify-content: center;
533
+ gap: 1rem;
534
  z-index: 10;
535
  opacity: 0;
536
  pointer-events: none;
 
543
  }
544
 
545
  .spinner {
546
+ width: 2.75rem;
547
+ height: 2.75rem;
548
+ border: 3px solid rgba(0, 0, 0, 0.05);
549
  border-top-color: var(--accent);
550
  border-radius: 50%;
551
  animation: spin 1s linear infinite;
552
+ filter: drop-shadow(0 0 6px var(--accent-glow));
553
  }
554
 
555
  body.dark-theme .spinner {
556
  border-color: rgba(255, 255, 255, 0.05);
557
  }
558
 
 
 
 
 
559
  .status-msg {
560
  font-family: var(--font-title);
561
+ font-size: 1.05rem;
562
  font-weight: 500;
563
  color: var(--text-primary);
564
  }
565
 
566
  .status-timer {
567
+ font-size: 0.8rem;
568
  color: var(--text-secondary);
569
  }
570
 
571
+ /* Footer metrics & actions */
572
  .viewport-footer {
573
  display: flex;
574
  align-items: center;
575
  justify-content: space-between;
576
  border-top: 1px solid var(--border-color);
577
+ padding-top: 1rem;
578
+ margin-top: 0.75rem;
579
  }
580
 
581
  .metrics-summary {
582
  display: flex;
583
+ gap: 1.25rem;
584
+ font-size: 0.75rem;
585
  color: var(--text-secondary);
586
  }
587
 
 
594
  background: var(--btn-secondary);
595
  border: 1px solid var(--border-color);
596
  color: var(--text-primary);
597
+ padding: 0.45rem 1rem;
598
  border-radius: 9999px;
599
+ font-size: 0.8rem;
600
  font-weight: 600;
601
  cursor: pointer;
602
  transition: all 0.2s ease;
603
  display: flex;
604
  align-items: center;
605
+ gap: 0.3rem;
606
  }
607
 
608
  .download-btn:hover:not(:disabled) {
 
615
  cursor: not-allowed;
616
  }
617
 
618
+ /* Prompt History Drawer */
619
  .history-section {
620
  display: flex;
621
  flex-direction: column;
622
+ gap: 0.5rem;
623
+ margin: 0 auto;
624
+ max-width: 800px;
625
  width: 100%;
626
+ padding: 0 1.25rem;
 
 
 
 
 
 
627
  }
628
 
629
  .history-label {
630
+ font-size: 0.65rem;
631
  font-weight: 700;
632
  color: var(--text-secondary);
633
  text-transform: uppercase;
634
+ letter-spacing: 0.06em;
635
+ padding-left: 0.35rem;
636
  }
637
 
638
  .history-drawer {
639
+ display: flex;
640
+ gap: 0.5rem;
641
+ overflow-x: auto;
642
+ white-space: nowrap;
643
+ -webkit-overflow-scrolling: touch;
644
+ padding-bottom: 0.25rem;
645
+ }
646
+
647
+ .history-drawer::-webkit-scrollbar {
648
+ display: none;
649
  }
650
 
651
  .history-card {
652
  background: var(--bg-card);
653
  border: 1px solid var(--border-color);
654
+ border-radius: 0.85rem;
655
+ padding: 0.6rem 0.9rem;
656
+ font-size: 0.75rem;
657
  color: var(--text-secondary);
658
  cursor: pointer;
659
+ transition: all 0.2s ease;
 
 
 
 
 
660
  backdrop-filter: blur(10px);
661
+ display: inline-block;
662
+ max-width: 240px;
663
+ overflow: hidden;
664
+ text-overflow: ellipsis;
665
  }
666
 
667
  .history-card:hover {
 
671
  }
672
 
673
  .history-empty {
674
+ font-size: 0.75rem;
675
  color: var(--text-secondary);
676
  font-style: italic;
677
+ padding-left: 0.35rem;
678
  }
679
  </style>
680
  </head>
 
690
  <span class="logo-text">Bonsai</span>
691
  </div>
692
 
 
693
  <button id="theme-btn" class="theme-toggle">
694
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
695
  <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
 
705
  <div class="container">
706
  <!-- Main Form Panel -->
707
  <section class="card-panel">
708
+ <textarea id="prompt" placeholder="Describe what you want to create..."></textarea>
709
+
710
+ <!-- Swipeable Sample Prompts -->
711
+ <div class="pill-prompts-container">
712
+ <div class="pill-prompts">
713
+ <button class="prompt-pill" data-prompt="A bonsai tree in a quiet ceramic studio, soft morning light, shallow depth of field">A bonsai tree</button>
714
+ <button class="prompt-pill" data-prompt="A tiny moss garden on a windowsill, rain on the glass, warm film photography">A moss garden</button>
715
+ <button class="prompt-pill" data-prompt="A red fox curled beneath paper lanterns, ink wash style, gentle shadows">A red fox</button>
716
+ <button class="prompt-pill" data-prompt="A glass terrarium city at night, miniature streets, glowing storefronts">A terrarium city</button>
717
+ </div>
718
  </div>
719
 
720
+ <!-- Simplified Horizontal Dock -->
721
+ <div class="action-dock">
722
+ <!-- Advanced Toggle Button -->
723
+ <button id="options-btn" class="options-btn">
724
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
725
+ <circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
726
+ </svg>
727
+ Options
728
+ </button>
729
+
730
+ <!-- Main Generate Action Button -->
731
+ <button id="generate-btn" class="generate-btn">Generate</button>
732
+ </div>
733
 
734
+ <!-- Collapsible Settings Drawer -->
735
+ <div id="options-drawer" class="options-drawer">
736
+ <div class="drawer-grid">
737
+ <!-- Model Field -->
738
+ <div class="control-col">
739
+ <span class="control-label">Model</span>
740
+ <select id="backend">
741
+ <option value="bonsai-ternary-gemlite">Bonsai · Ternary</option>
742
+ <option value="bonsai-binary-gemlite">Bonsai · Binary</option>
743
+ </select>
744
+ </div>
745
 
746
+ <!-- Resolution Field -->
747
+ <div class="control-col">
748
+ <span class="control-label">Resolution</span>
749
+ <select id="resolution">
750
+ <option value="512x512" selected>1:1 — 512 × 512</option>
751
+ <option value="576x384">3:2 — 576 × 384</option>
752
+ <option value="384x576">2:3 — 384 × 576</option>
753
+ <option value="768x448">16:9 — 768 × 448</option>
754
+ <option value="448x768">9:16 — 448 × 768</option>
755
+ </select>
756
+ </div>
757
+
758
+ <!-- Seed Field -->
759
+ <div class="control-col">
760
+ <span class="control-label">Seed</span>
761
+ <div class="seed-input-group">
762
+ <input type="number" id="seed" value="42" min="0" max="999999999">
763
+ <button id="dice-btn" class="dice-btn" title="Randomize Seed">
764
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
765
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
766
+ <circle cx="8" cy="8" r="1.5"/>
767
+ <circle cx="16" cy="8" r="1.5"/>
768
+ <circle cx="8" cy="16" r="1.5"/>
769
+ <circle cx="16" cy="16" r="1.5"/>
770
+ </svg>
771
+ </button>
772
+ </div>
773
  </div>
 
774
 
775
+ <!-- Steps Field -->
776
+ <div class="control-col">
777
+ <span class="control-label">Steps</span>
778
+ <div class="steps-input-group">
779
+ <input type="number" id="steps" value="4" min="1" max="20">
780
+ <span class="toggle-pill" title="Execution Mode">Single</span>
781
+ </div>
782
  </div>
783
  </div>
784
 
785
+ <!-- Hidden parameters -->
786
  <input type="hidden" id="guidance" value="1.0">
787
  <input type="hidden" id="width" value="512">
788
  <input type="hidden" id="height" value="512">
 
 
 
 
 
 
 
 
 
 
 
 
 
789
  </div>
790
  </section>
791
 
792
+ <!-- Viewport Card -->
793
  <section class="viewport-card">
794
  <!-- Loading Queue Overlay -->
795
  <div id="loader-overlay" class="overlay">
 
799
  </div>
800
 
801
  <div class="viewport-body">
 
802
  <div id="placeholder" class="viewport-placeholder">
803
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
804
  <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
 
809
  <p>Image results will render here after clicking generate.</p>
810
  </div>
811
 
 
812
  <img id="preview-image" class="preview-img" alt="Bonsai Generation Result">
813
  </div>
814
 
 
820
  </div>
821
 
822
  <button id="download-btn" class="download-btn" disabled>
823
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
824
  <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/>
825
  </svg>
826
  Download PNG
 
841
  <script type="module">
842
  import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
843
 
844
+ // DOM elements
845
  const promptInput = document.getElementById('prompt');
846
  const widthInput = document.getElementById('width');
847
  const heightInput = document.getElementById('height');
 
865
 
866
  const promptPills = document.querySelectorAll('.prompt-pill');
867
  const themeBtn = document.getElementById('theme-btn');
868
+
869
+ const optionsBtn = document.getElementById('options-btn');
870
+ const optionsDrawer = document.getElementById('options-drawer');
871
 
872
  // Dynamic State
873
  let client = null;
 
875
  let timerInterval = null;
876
  let elapsed = 0.0;
877
 
878
+ // Settings drawer toggle
879
+ optionsBtn.addEventListener('click', () => {
880
+ const isOpen = optionsDrawer.classList.toggle('open');
881
+ optionsBtn.classList.toggle('active', isOpen);
882
+ });
883
+
884
+ // Theme switcher
885
  themeBtn.addEventListener('click', () => {
886
  const isDark = document.body.classList.toggle('dark-theme');
887
  localStorage.setItem('bonsai_theme', isDark ? 'dark' : 'light');
888
 
 
889
  if (isDark) {
890
  themeBtn.innerHTML = `
891
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
 
903
  }
904
  });
905
 
 
906
  function initTheme() {
907
  const saved = localStorage.getItem('bonsai_theme');
908
  if (saved === 'dark') {
 
916
  }
917
  }
918
 
 
919
  async function connectGradio() {
920
  try {
921
  client = await Client.connect(window.location.origin);
922
+ console.log("Connected to Gradio Server.");
923
 
 
924
  const backendInfo = await client.predict("/backends");
925
  if (backendInfo && backendInfo.data && backendInfo.data[0]) {
926
  const info = backendInfo.data[0];
 
938
  }
939
  }
940
 
 
941
  diceBtn.addEventListener('click', () => {
942
  seedInput.value = Math.floor(Math.random() * 1000000000);
943
  });
944
 
 
945
  resolutionSelect.addEventListener('change', (e) => {
946
  const [w, h] = e.target.value.split('x');
947
  widthInput.value = w;
948
  heightInput.value = h;
949
  });
950
 
 
951
  promptPills.forEach(pill => {
952
  pill.addEventListener('click', () => {
953
  promptInput.value = pill.dataset.prompt;
 
954
  promptInput.focus();
955
  });
956
  });
957
 
 
958
  function startTimer() {
959
  elapsed = 0.0;
960
  statusTimer.textContent = `Elapsed: 0.0s`;
 
969
  clearInterval(timerInterval);
970
  }
971
 
 
972
  downloadBtn.addEventListener('click', () => {
973
  if (!previewImage.src) return;
974
  const link = document.createElement('a');
975
  link.href = previewImage.src;
976
  const ts = new Date().toISOString().slice(0, 19).replace(/[-:]/g, "").replace("T", "_");
977
+ link.download = `bonsai_art_${ts}.png`;
978
  document.body.appendChild(link);
979
  link.click();
980
  document.body.removeChild(link);
981
  });
982
 
 
983
  function getHistory() {
984
+ const h = localStorage.getItem('bonsai_history_mobile');
985
  return h ? JSON.parse(h) : [];
986
  }
987
 
 
990
  let list = getHistory();
991
  list = list.filter(item => item !== text);
992
  list.unshift(text);
993
+ list = list.slice(0, 12);
994
+ localStorage.setItem('bonsai_history_mobile', JSON.stringify(list));
995
  renderHistory();
996
  }
997
 
 
999
  const list = getHistory();
1000
  historyDrawer.innerHTML = '';
1001
  if (list.length === 0) {
1002
+ historyDrawer.innerHTML = '<span class="history-empty">History is currently empty.</span>';
1003
  return;
1004
  }
1005
  list.forEach(p => {
 
1014
  });
1015
  }
1016
 
 
1017
  async function startGenerate() {
1018
  const promptVal = promptInput.value.trim();
1019
  if (!promptVal) {
 
1028
  downloadBtn.disabled = true;
1029
  previewImage.classList.remove('loaded');
1030
 
 
1031
  statusTitle.textContent = "Connecting to Blackwell GPU queue...";
1032
  loaderOverlay.classList.add('active');
1033
  placeholder.style.display = 'none';
 
1040
  const backendVal = backendInput.value;
1041
 
1042
  try {
 
1043
  statusTitle.textContent = "Generating artwork...";
1044
  const result = await client.predict("/generate", {
1045
  prompt: promptVal,
 
1062
  metricLatency.textContent = '—';
1063
  metricVram.textContent = '—';
1064
  } else {
 
1065
  previewImage.src = `data:image/png;base64,${data.image_b64}`;
1066
  previewImage.classList.add('loaded');
1067
  downloadBtn.disabled = false;
1068
 
 
1069
  metricLatency.textContent = `${data.wall_seconds} s`;
1070
  metricVram.textContent = `${data.peak_memory_mb} MB`;
1071
 
 
1072
  saveHistory(promptVal);
1073
  }
1074
  } catch (err) {
 
1087
 
1088
  generateBtn.addEventListener('click', startGenerate);
1089
 
 
1090
  document.addEventListener('DOMContentLoaded', () => {
1091
  initTheme();
1092
  renderHistory();