Sourav6861 commited on
Commit
b1fd0d2
·
verified ·
1 Parent(s): 8ebb1ad

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +536 -17
index.html CHANGED
@@ -1,20 +1,539 @@
1
- async function generateImage() {
2
- const prompt = document.getElementById("prompt").value;
3
- const model = document.getElementById("model").value;
4
-
5
- const res = await fetch("/generate", {
6
- method: "POST",
7
- headers: { "Content-Type": "application/json" },
8
- body: JSON.stringify({ prompt, model })
9
- });
10
-
11
- if (!res.ok) {
12
- const error = await res.json();
13
- alert("Error: " + error.error);
14
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  }
16
 
17
- const blob = await res.blob();
18
- const url = URL.createObjectURL(blob);
19
- document.getElementById("gallery").innerHTML = `<img src="${url}" class="rounded-xl border border-white/10">`;
 
 
 
 
 
20
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ <!doctype html>
4
+ <html lang="en">
5
+ <head>
6
+ <meta charset="utf-8"/>
7
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
8
+ <title>imgGenPro Free AI Image Generator</title>
9
+
10
+ <!-- TailwindCSS -->
11
+ <script src="https://cdn.tailwindcss.com"></script>
12
+ <!-- Font Awesome -->
13
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"/>
14
+ </head>
15
+
16
+ <style>
17
+
18
+ .blur-nsfw {
19
+ filter: blur(12px);
20
+ position: relative;
21
+ }
22
+ .blur-nsfw::after {
23
+ content: "⚠️ NSFW — Click to reveal";
24
+ position: absolute;
25
+ top:50%; left:50%;
26
+ transform: translate(-50%, -50%);
27
+ color:white;
28
+ font-weight:bold;
29
+ }
30
+ </style>
31
+
32
+ <body class="min-h-screen bg-black text-white selection:bg-fuchsia-400/30">
33
+ <!-- Topbar -->
34
+ <header class="sticky top-0 z-50 backdrop-blur bg-black/40 border-b border-white/10">
35
+ <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
36
+ <div class="flex items-center gap-3">
37
+ <div class="w-9 h-9 rounded-xl bg-gradient-to-br from-fuchsia-500 via-purple-500 to-sky-400 glow"></div>
38
+ <span class="font-semibold tracking-wide">imgGenPro</span>
39
+ </div>
40
+ <nav class="hidden md:flex items-center gap-2">
41
+ <a class="px-3 py-2 rounded-lg hover:bg-white/5 text-sm" href="https://aniwall.free.nf" target="_blank" rel="noopener">
42
+ <i class="fa-solid fa-image mr-2"></i>Wallpapers
43
+ </a>
44
+ <button id="openSettings" class="px-3 py-2 rounded-lg hover:bg-white/5 text-sm">
45
+ <i class="fa-solid fa-gear mr-2"></i>Settings
46
+ </button>
47
+ <a class="px-3 py-2 rounded-lg hover:bg-white/5 text-sm" href="#" id="openHistory">
48
+ <i class="fa-solid fa-clock-rotate-left mr-2"></i>History
49
+ </a>
50
+ <a class="px-3 py-2 rounded-lg hover:bg-white/5 text-sm" href="#" id="clearGallery">
51
+ <i class="fa-solid fa-trash mr-2"></i>Clear
52
+ </a>
53
+ </nav>
54
+ <button id="openSettingsSm" class="md:hidden px-3 py-2 rounded-lg hover:bg-white/5">
55
+ <i class="fa-solid fa-sliders"></i>
56
+ </button>
57
+ </div>
58
+ </header>
59
+
60
+ <!-- Main -->
61
+ <main class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
62
+ <!-- Prompt card -->
63
+ <section class="rounded-2xl border border-white/10 bg-white/5 p-4 md:p-6">
64
+ <div class="flex flex-col md:flex-row gap-3">
65
+ <div class="flex-1">
66
+ <label for="prompt" class="sr-only">Prompt</label>
67
+ <div class="relative">
68
+ <textarea id="prompt" rows="2" placeholder="Describe what you want to create (e.g., 'a cyberpunk city alley with neon reflections, rainy night')"
69
+ class="w-full resize-y rounded-xl bg-black/50 border border-white/10 focus:border-fuchsia-400/60 outline-none p-4 pr-12 placeholder-white/40"></textarea>
70
+ <button id="enhanceToggle" class="absolute right-2 top-2 text-xs px-2 py-1 rounded-lg border border-white/10 hover:bg-white/5">
71
+ <i class="fa-solid fa-wand-magic-sparkles mr-1"></i>Enhance
72
+ </button>
73
+ </div>
74
+
75
+ <!-- Quick styles -->
76
+ <div class="mt-3 flex flex-wrap gap-2">
77
+ <button class="style-btn px-3 py-1.5 rounded-lg bg-white/5 border border-white/10 text-xs hover:bg-white/10" data-style="">No style</button>
78
+ <button class="style-btn px-3 py-1.5 rounded-lg bg-white/5 border border-white/10 text-xs hover:bg-white/10" data-style="cinematic shot, moody tone, film still">Cinema</button>
79
+ <button class="style-btn px-3 py-1.5 rounded-lg bg-white/5 border border-white/10 text-xs hover:bg-white/10" data-style="photo, candid, natural look, unposed">Realistic</button>
80
+ <button class="style-btn px-3 py-1.5 rounded-lg bg-white/5 border border-white/10 text-xs hover:bg-white/10" data-style="studio portrait, soft tone, subject centered">Portrait</button>
81
+ <button class="style-btn px-3 py-1.5 rounded-lg bg-white/5 border border-white/10 text-xs hover:bg-white/10" data-style="anime style, vibrant cel shading">Anime</button>
82
+ <button class="style-btn px-3 py-1.5 rounded-lg bg-white/5 border border-white/10 text-xs hover:bg-white/10" data-style="fantasy concept art, painterly">Fantasy</button>
83
+ </div>
84
+ </div>
85
+
86
+ <div class="w-full md:w-56 flex md:flex-col gap-2">
87
+ <button id="generateBtn"
88
+ class="flex-1 md:h-[60px] p-2 rounded-xl bg-white text-black font-semibold hover:opacity-90 active:opacity-100 transition mb-2">
89
+ <span class="inline-flex items-center gap-2"><i class="fa-solid fa-bolt"></i> Generate</span>
90
+ </button>
91
+ <button id="randomPrompt" class="flex-1 md:h-[56px] rounded-xl bg-white/5 border border-white/10 hover:bg-white/10">
92
+ <i class="fa-solid fa-shuffle mr-2"></i>Random
93
+ </button>
94
+ <button id="downloadAll" class="hidden md:block h-[56px] rounded-xl bg-white/5 border border-white/10 hover:bg-white/10">
95
+ <i class="fa-solid fa-download mr-2"></i>Download All
96
+ </button>
97
+ </div>
98
+ </div>
99
+
100
+ <p class="mt-3 text-xs text-white/60">
101
+ Tip: NSFW may be filtered by models.
102
+ </p>
103
+ </section>
104
+
105
+ <!-- Gallery -->
106
+ <section class="mt-6">
107
+ <div id="gallery" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"></div>
108
+ <div id="emptyState" class="text-center py-16 text-white/50">
109
+ <i class="fa-regular fa-images text-4xl mb-3 block"></i>
110
+ <p>No images yet — start by generating something ✨</p>
111
+ </div>
112
+ </section>
113
+ </main>
114
+
115
+ <!-- Settings Drawer -->
116
+ <div id="settingsDrawer" class="fixed inset-0 hidden">
117
+ <div class="absolute inset-0 backdrop"></div>
118
+ <aside class="ml-auto h-full w-full sm:w-[420px] bg-black/90 border-l border-white/10 p-5 overflow-y-auto">
119
+ <div class="flex items-center justify-between mb-4">
120
+ <h2 class="text-lg font-semibold"><i class="fa-solid fa-sliders mr-2"></i>Settings</h2>
121
+ <button id="closeSettings" class="p-2 rounded-lg hover:bg-white/5">
122
+ <i class="fa-solid fa-xmark"></i>
123
+ </button>
124
+ </div>
125
+
126
+ <div class="space-y-4">
127
+ <label class="block text-sm">
128
+ <span class="text-white/70">Model</span>
129
+ <select id="model" class="mt-1 w-full rounded-lg bg-black/50 border border-white/10 p-2">
130
+ <!-- Popular, fast models -->
131
+ <option value="black-forest-labs/FLUX.1-schnell">FLUX.1-schnell (fast)</option>
132
+ <option value="stabilityai/stable-diffusion-xl-base-1.0">SDXL Base 1.0</option>
133
+ <option value="runwayml/stable-diffusion-v1-5">SD 1.5</option>
134
+ </select>
135
+ </label>
136
+
137
+ <div class="grid grid-cols-2 gap-3">
138
+ <label class="block text-sm">
139
+ <span class="text-white/70">Width</span>
140
+ <input id="width" type="number" min="64" max="2048" value="1024"
141
+ class="mt-1 w-full rounded-lg bg-black/50 border border-white/10 p-2">
142
+ </label>
143
+ <label class="block text-sm">
144
+ <span class="text-white/70">Height</span>
145
+ <input id="height" type="number" min="64" max="2048" value="1024"
146
+ class="mt-1 w-full rounded-lg bg-black/50 border border-white/10 p-2">
147
+ </label>
148
+ </div>
149
+
150
+ <div class="grid grid-cols-2 gap-3">
151
+ <label class="block text-sm">
152
+ <span class="text-white/70">Seed</span>
153
+ <input id="seed" type="number" value="42"
154
+ class="mt-1 w-full rounded-lg bg-black/50 border border-white/10 p-2">
155
+ </label>
156
+ <label class="flex items-center gap-2 text-sm mt-7">
157
+ <input id="randomSeed" type="checkbox" class="size-4 accent-fuchsia-500" checked>
158
+ Random seed
159
+ </label>
160
+ </div>
161
+
162
+ <label class="block text-sm">
163
+ <span class="text-white/70">Negative Prompt (optional)</span>
164
+ <input id="negPrompt" type="text" placeholder="blurry, low quality, watermark"
165
+ class="mt-1 w-full rounded-lg bg-black/50 border border-white/10 p-2">
166
+ </label>
167
+
168
+ <div class="flex items-center justify-between">
169
+ <label class="flex items-center gap-2 text-sm">
170
+ <input id="enhance" type="checkbox" class="size-4 accent-fuchsia-500">
171
+ Enhance prompt (client-side)
172
+ </label>
173
+ <button id="saveSettings" class="px-3 py-2 rounded-lg border border-white/10 hover:bg-white/5">
174
+ <i class="fa-regular fa-floppy-disk mr-2"></i>Save
175
+ </button>
176
+ </div>
177
+
178
+
179
+ </div>
180
+ </aside>
181
+ </div>
182
+
183
+ <!-- Image Modal -->
184
+ <div id="imageModal" class="fixed inset-0 hidden">
185
+ <div class="absolute inset-0 backdrop"></div>
186
+ <div class="relative z-10 h-full w-full flex items-center justify-center p-4">
187
+ <div class="max-w-5xl w-full rounded-2xl bg-black/90 border border-white/10 p-4">
188
+ <div class="flex justify-between items-center mb-3">
189
+ <h3 class="font-semibold"><i class="fa-regular fa-image mr-2"></i>Preview</h3>
190
+ <button id="closeModal" class="p-2 rounded-lg hover:bg-white/5">
191
+ <i class="fa-solid fa-xmark"></i>
192
+ </button>
193
+ </div>
194
+ <img id="modalImg" src="" alt="Preview" class="w-full rounded-xl border border-white/10">
195
+ <div class="mt-3 flex gap-2">
196
+ <button id="downloadBtn" class="px-3 py-2 rounded-lg bg-white/5 border border-white/10 hover:bg-white/10">
197
+ <i class="fa-solid fa-download mr-2"></i>Download
198
+ </button>
199
+ <button id="copyPromptBtn" class="px-3 py-2 rounded-lg bg-white/5 border border-white/10 hover:bg-white/10">
200
+ <i class="fa-solid fa-quote-left mr-2"></i>Copy Prompt
201
+ </button>
202
+ </div>
203
+ </div>
204
+ </div>
205
+ </div>
206
+
207
+ <!-- Toast -->
208
+ <div id="toast" class="fixed bottom-4 left-1/2 -translate-x-1/2 hidden">
209
+ <div class="rounded-xl bg-white/10 border border-white/10 px-4 py-2 text-sm">
210
+ <span id="toastMsg">Saved</span>
211
+ </div>
212
+ </div>
213
+
214
+
215
+
216
+
217
+
218
+ <footer class="text-center text-white-600 pt-8 border-t border-gray-200"><p>ImgGenPro &copy; 2025 - All rights reserved</p><p class="mt-2">Created by <a href="https://souravs.great-site.net"> SouRav </a></p></footer></div>
219
+
220
+ <!-- JS -->
221
+ <script>
222
+
223
+ // ------------ State ------------
224
+ const el = {
225
+ prompt: document.getElementById('prompt'),
226
+ model: document.getElementById('model'),
227
+ width: document.getElementById('width'),
228
+ height: document.getElementById('height'),
229
+ seed: document.getElementById('seed'),
230
+ randomSeed: document.getElementById('randomSeed'),
231
+ negPrompt: document.getElementById('negPrompt'),
232
+ enhance: document.getElementById('enhance'),
233
+
234
+ enhanceToggle: document.getElementById('enhanceToggle'),
235
+ generateBtn: document.getElementById('generateBtn'),
236
+ randomPrompt: document.getElementById('randomPrompt'),
237
+ downloadAll: document.getElementById('downloadAll'),
238
+
239
+ gallery: document.getElementById('gallery'),
240
+ emptyState: document.getElementById('emptyState'),
241
+
242
+ openSettings: document.getElementById('openSettings'),
243
+ openSettingsSm: document.getElementById('openSettingsSm'),
244
+ settingsDrawer: document.getElementById('settingsDrawer'),
245
+ closeSettings: document.getElementById('closeSettings'),
246
+ saveSettings: document.getElementById('saveSettings'),
247
+
248
+ openHistory: document.getElementById('openHistory'),
249
+ clearGallery: document.getElementById('clearGallery'),
250
+
251
+ imageModal: document.getElementById('imageModal'),
252
+ modalImg: document.getElementById('modalImg'),
253
+ closeModal: document.getElementById('closeModal'),
254
+ downloadBtn: document.getElementById('downloadBtn'),
255
+ copyPromptBtn: document.getElementById('copyPromptBtn'),
256
+
257
+ toast: document.getElementById('toast'),
258
+ toastMsg: document.getElementById('toastMsg'),
259
+ };
260
+
261
+ let currentStyle = "";
262
+ let history = JSON.parse(localStorage.getItem('img_history') || '[]');
263
+
264
+ // ------------ Helpers ------------
265
+ function showToast(msg){
266
+ el.toastMsg.textContent = msg;
267
+ el.toast.classList.remove('hidden');
268
+ setTimeout(()=>el.toast.classList.add('hidden'), 1600);
269
+ }
270
+ function randInt(min,max){return Math.floor(Math.random()*(max-min+1))+min;}
271
+
272
+ function saveSettings(){
273
+ const s = {
274
+ model: el.model.value,
275
+ width: +el.width.value || 1024,
276
+ height: +el.height.value || 1024,
277
+ seed: +el.seed.value || 42,
278
+ randomSeed: el.randomSeed.checked,
279
+ negPrompt: el.negPrompt.value || '',
280
+ enhance: el.enhance.checked
281
+ };
282
+ localStorage.setItem('img_settings', JSON.stringify(s));
283
+ showToast('Settings saved');
284
+ }
285
+ function loadSettings(){
286
+ try{
287
+ const s = JSON.parse(localStorage.getItem('img_settings')||'{}');
288
+ if(s.model) el.model.value = s.model;
289
+ if(s.width) el.width.value = s.width;
290
+ if(s.height) el.height.value = s.height;
291
+ if(s.seed !== undefined) el.seed.value = s.seed;
292
+ if(s.randomSeed !== undefined) el.randomSeed.checked = s.randomSeed;
293
+ if(s.negPrompt !== undefined) el.negPrompt.value = s.negPrompt;
294
+ if(s.enhance !== undefined) el.enhance.checked = s.enhance;
295
+ }catch(e){}
296
+ }
297
+ loadSettings();
298
+
299
+ function promptVariants(){
300
+ const arr = [
301
+ // Cozy / Realistic
302
+ "a cozy reading nook by a window with rain outside",
303
+ "a serene beach at sunset with bioluminescent waves",
304
+ "a cozy coffee shop interior with warm lighting and people reading",
305
+ "a retro-futuristic living room, 80s aesthetic meets sci-fi",
306
+ "a quiet snowy village at night with glowing lanterns, cinematic",
307
+ "a sunlit forest clearing with wildflowers and butterflies",
308
+ "a rustic kitchen with fresh bread on the table, morning light",
309
+
310
+ // Fantasy / Concept Art
311
+ "a majestic dragon soaring above snow-capped mountains, fantasy concept",
312
+ "a mystical castle on a cliff during a thunderstorm",
313
+ "an ancient forest with glowing magical mushrooms, misty atmosphere",
314
+ "a surreal dreamscape with floating islands and waterfalls",
315
+ "a steampunk airship flying above the clouds, dramatic lighting",
316
+ "a magical library with floating books and enchanted candles",
317
+ "a crystal cave glowing with colorful lights, fantasy setting",
318
+
319
+ // Cyberpunk / Sci-fi
320
+ "a neon-lit ramen shop alley in Tokyo at night, reflections on wet ground",
321
+ "a bustling cyberpunk city street, rain-soaked, neon reflections",
322
+ "a futuristic laboratory with floating holograms and robots",
323
+ "a sci-fi spaceship bridge overlooking a distant galaxy",
324
+ "a robotic marketplace with android vendors and glowing signs",
325
+ "a high-tech futuristic cityscape at sunset with flying cars",
326
+
327
+ // Whimsical / Cute / Anime-style
328
+ "a cute corgi astronaut floating in space",
329
+ "a portrait of a wise owl wearing a wizard hat, digital painting",
330
+ "an anime girl sitting under cherry blossoms, soft lighting",
331
+ "a chibi character baking cupcakes in a magical kitchen",
332
+ "a young mage casting a spell in a magical garden, anime style",
333
+ "a playful fox spirit dancing in a moonlit forest, anime illustration",
334
+ "an anime-style futuristic city with floating platforms and lights",
335
+ "a cat café full of cats wearing tiny costumes, cute anime style",
336
+ "a magical girl soaring through the skies with sparkling trails",
337
+ "a dragon and a young adventurer sharing tea in a forest clearing"
338
+ ];
339
+
340
+ return arr[randInt(0, arr.length-1)];
341
+ }
342
+
343
+ function buildPrompt(){
344
+ let p = el.prompt.value.trim();
345
+ if(currentStyle) p += `, ${currentStyle}`;
346
+ const neg = el.negPrompt.value.trim();
347
+ return { prompt: p, negative_prompt: neg };
348
+ }
349
+
350
+ // Simple NSFW keyword detection
351
+ const nsfwKeywords = ["bikini","nude","sex","erotic","porn"];
352
+ function isNSFW(prompt){
353
+ const lower = prompt.toLowerCase();
354
+ return nsfwKeywords.some(k => lower.includes(k));
355
+ }
356
+
357
+ // ------------ Cards ------------
358
+ function addCardSkeleton(){
359
+ el.emptyState.classList.add('hidden');
360
+ const card = document.createElement('div');
361
+ card.className = "group relative rounded-2xl border border-white/10 bg-white/5 overflow-hidden";
362
+ card.innerHTML = `
363
+ <div class="aspect-[4/3] skeleton"></div>
364
+ <div class="absolute inset-0 flex items-center justify-center">
365
+ <div class="w-14 h-14 rounded-full skeleton"></div>
366
+ </div>
367
+ <div class="p-3 text-xs text-white/50">Generating...</div>
368
+ `;
369
+ el.gallery.prepend(card);
370
+ return card;
371
+ }
372
+
373
+ function addCard(imgUrl, meta){
374
+ el.emptyState.classList.add('hidden');
375
+ const card = document.createElement('div');
376
+ card.className = "group relative rounded-2xl border border-white/10 bg-white/5 overflow-hidden hover:border-fuchsia-400/40 transition";
377
+ card.innerHTML = `
378
+ <img src="${imgUrl}" alt="AI image" class="w-full h-auto block">
379
+ <div class="absolute top-2 right-2 flex gap-1 opacity-0 group-hover:opacity-100 transition">
380
+ <button class="px-2 py-1 rounded-lg bg-black/60 hover:bg-black/80 border border-white/10 text-xs" data-act="download"><i class="fa-solid fa-download"></i></button>
381
+ <button class="px-2 py-1 rounded-lg bg-black/60 hover:bg-black/80 border border-white/10 text-xs" data-act="open"><i class="fa-regular fa-image"></i></button>
382
+ <button class="px-2 py-1 rounded-lg bg-black/60 hover:bg-black/80 border border-white/10 text-xs" data-act="copy"><i class="fa-solid fa-quote-left"></i></button>
383
+ </div>
384
+ <div class="p-3 text-[11px] text-white/60 line-clamp-2">${meta.prompt}</div>
385
+ `;
386
+
387
+ const img = card.querySelector('img');
388
+ if(isNSFW(meta.prompt)){
389
+ img.classList.add("blur-nsfw");
390
+ img.title = "⚠️ NSFW — Click to reveal";
391
+ img.addEventListener('click', ()=> img.classList.remove("blur-nsfw"));
392
+ }
393
+
394
+ card.addEventListener('click', e=>{
395
+ const btn = e.target.closest('button');
396
+ if(!btn) return;
397
+ const act = btn.dataset.act;
398
+ if(act==='download'){
399
+ const a = document.createElement('a'); a.href = imgUrl; a.download = 'imggenpro.png'; a.click();
400
+ }else if(act==='open'){
401
+ openModal(imgUrl, meta.prompt);
402
+ }else if(act==='copy'){
403
+ navigator.clipboard.writeText(meta.prompt);
404
+ showToast('Prompt copied');
405
+ }
406
+ });
407
+ el.gallery.prepend(card);
408
+ return card;
409
+ }
410
+
411
+ // ------------ Modal ------------
412
+ function openModal(src, prompt){
413
+ el.modalImg.src = src;
414
+ el.copyPromptBtn.dataset.prompt = prompt;
415
+ el.imageModal.classList.remove('hidden');
416
+ }
417
+ function closeModal(){ el.imageModal.classList.add('hidden'); }
418
+
419
+ // ------------ Events ------------
420
+ document.querySelectorAll('.style-btn').forEach(b=>{
421
+ b.addEventListener('click', ()=>{
422
+ document.querySelectorAll('.style-btn').forEach(x=>x.classList.remove('ring-2','ring-fuchsia-400/60'));
423
+ b.classList.add('ring-2','ring-fuchsia-400/60');
424
+ currentStyle = b.dataset.style || "";
425
+ });
426
+ });
427
+
428
+ el.enhanceToggle.addEventListener('click', ()=>{
429
+ el.enhance.checked = !el.enhance.checked;
430
+ el.enhanceToggle.classList.toggle('bg-white/10', el.enhance.checked);
431
+ });
432
+
433
+ el.openSettings?.addEventListener('click', ()=> el.settingsDrawer.classList.remove('hidden'));
434
+ el.openSettingsSm?.addEventListener('click', ()=> el.settingsDrawer.classList.remove('hidden'));
435
+ el.closeSettings.addEventListener('click', ()=> el.settingsDrawer.classList.add('hidden'));
436
+ el.saveSettings.addEventListener('click', ()=>{ saveSettings(); el.settingsDrawer.classList.add('hidden'); });
437
+
438
+ el.openHistory?.addEventListener('click', ()=>{
439
+ if(!history.length){ showToast('No history yet'); return; }
440
+ el.gallery.innerHTML = '';
441
+ el.emptyState.classList.add('hidden');
442
+ history.forEach(h=> addCard(h.url, {prompt: h.prompt}));
443
+ });
444
+
445
+ el.clearGallery?.addEventListener('click', ()=>{
446
+ el.gallery.innerHTML = '';
447
+ el.emptyState.classList.remove('hidden');
448
+ });
449
+
450
+ el.randomPrompt.addEventListener('click', ()=>{ el.prompt.value = promptVariants(); });
451
+
452
+ el.downloadAll.addEventListener('click', ()=>{
453
+ const imgs = el.gallery.querySelectorAll('img');
454
+ if(!imgs.length){ showToast('Nothing to download'); return; }
455
+ imgs.forEach((img,i)=>{
456
+ const a = document.createElement('a');
457
+ a.href = img.src; a.download = `imggenpro_${i+1}.png`; a.click();
458
+ });
459
+ });
460
+
461
+ el.closeModal.addEventListener('click', closeModal);
462
+ el.downloadBtn.addEventListener('click', ()=>{
463
+ const a = document.createElement('a'); a.href = el.modalImg.src; a.download='imggenpro.png'; a.click();
464
+ });
465
+ el.copyPromptBtn.addEventListener('click', ()=>{
466
+ const p = el.copyPromptBtn.dataset.prompt || '';
467
+ navigator.clipboard.writeText(p);
468
+ showToast('Prompt copied');
469
+ });
470
+
471
+ // ------------ Prompt Enhance (fake client-side) ------------
472
+ async function enhancePrompt(text){
473
+ if(text.length < 10) return text;
474
+ return text.replace(/\s+/g,' ').concat(', high detail focus, cohesive composition');
475
+ }
476
+
477
+ // ------------ Generate Flow ------------
478
+ async function doGenerate(){
479
+ const card = addCardSkeleton();
480
+ try{
481
+ el.generateBtn.disabled = true;
482
+ el.generateBtn.innerHTML = '<span class="inline-flex items-center gap-2"><i class="fa-solid fa-spinner animate-spin"></i> Generating…</span>';
483
+
484
+ let { prompt, negative_prompt } = buildPrompt();
485
+ if(!prompt){ throw new Error('Please enter a prompt'); }
486
+ if(el.enhance.checked){ prompt = await enhancePrompt(prompt); }
487
+
488
+ const model = el.model.value;
489
+ const width = Math.min(2048, Math.max(64, +el.width.value||1024));
490
+ const height = Math.min(2048, Math.max(64, +el.height.value||1024));
491
+ const seed = el.randomSeed.checked ? randInt(0, 999999) : (+el.seed.value||42);
492
+
493
+
494
+ const res = await fetch('/generate', {
495
+ method: 'POST',
496
+ headers: { 'Content-Type': 'application/json' },
497
+ body: JSON.stringify({ prompt, negative_prompt, model, width, height, seed })
498
+ });
499
+
500
+ if(!res.ok){ throw new Error('Generation failed'); }
501
+
502
+ const contentType = res.headers.get("content-type");
503
+
504
+ if (contentType.includes("application/json")) {
505
+ const data = await res.json();
506
+ throw new Error(data.error || "Model returned JSON instead of image");
507
+ } else {
508
+ const blob = await res.blob();
509
+ const url = URL.createObjectURL(blob);
510
+
511
+ card.remove();
512
+ addCard(url, { prompt });
513
+ history.unshift({ url, prompt });
514
+ history = history.slice(0, 30);
515
+ localStorage.setItem('img_history', JSON.stringify(history));
516
  }
517
 
518
+ }catch(err){
519
+ console.error(err);
520
+ card.querySelector('.p-3')?.remove();
521
+ card.innerHTML = `<div class="p-4 text-red-300 text-sm">Error: ${err.message || 'Unknown error'}</div>`;
522
+ }finally{
523
+ el.generateBtn.disabled = false;
524
+ el.generateBtn.innerHTML = '<span class="inline-flex items-center gap-2"><i class="fa-solid fa-bolt"></i> Generate</span>';
525
+ }
526
  }
527
+
528
+ // Only generate with button click now
529
+ el.generateBtn.addEventListener('click', doGenerate);
530
+
531
+ // Remove Ctrl+Enter trigger for UX clarity
532
+
533
+
534
+
535
+ </script
536
+
537
+
538
+ </body>
539
+ </html>