Sourav6861 commited on
Commit
95ad280
·
verified ·
1 Parent(s): cd948f7

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +511 -0
index.html ADDED
@@ -0,0 +1,511 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="utf-8"/>
6
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
7
+ <title>imgGenPro — Free AI Image Generator</title>
8
+
9
+ <!-- TailwindCSS -->
10
+ <script src="https://cdn.tailwindcss.com"></script>
11
+ <!-- Font Awesome -->
12
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"/>
13
+ </head>
14
+
15
+ <style>
16
+
17
+ .blur-nsfw {
18
+ filter: blur(12px);
19
+ position: relative;
20
+ }
21
+ .blur-nsfw::after {
22
+ content: "⚠️ NSFW — Click to reveal";
23
+ position: absolute;
24
+ top:50%; left:50%;
25
+ transform: translate(-50%, -50%);
26
+ color:white;
27
+ font-weight:bold;
28
+ }
29
+ </style>
30
+
31
+ <body class="min-h-screen bg-black text-white selection:bg-fuchsia-400/30">
32
+ <!-- Topbar -->
33
+ <header class="sticky top-0 z-50 backdrop-blur bg-black/40 border-b border-white/10">
34
+ <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
35
+ <div class="flex items-center gap-3">
36
+ <div class="w-9 h-9 rounded-xl bg-gradient-to-br from-fuchsia-500 via-purple-500 to-sky-400 glow"></div>
37
+ <span class="font-semibold tracking-wide">imgGenPro</span>
38
+ </div>
39
+ <nav class="hidden md:flex items-center gap-2">
40
+ <a class="px-3 py-2 rounded-lg hover:bg-white/5 text-sm" href="https://aniwall.free.nf" target="_blank" rel="noopener">
41
+ <i class="fa-solid fa-image mr-2"></i>Wallpapers
42
+ </a>
43
+ <button id="openSettings" class="px-3 py-2 rounded-lg hover:bg-white/5 text-sm">
44
+ <i class="fa-solid fa-gear mr-2"></i>Settings
45
+ </button>
46
+ <a class="px-3 py-2 rounded-lg hover:bg-white/5 text-sm" href="#" id="openHistory">
47
+ <i class="fa-solid fa-clock-rotate-left mr-2"></i>History
48
+ </a>
49
+ <a class="px-3 py-2 rounded-lg hover:bg-white/5 text-sm" href="#" id="clearGallery">
50
+ <i class="fa-solid fa-trash mr-2"></i>Clear
51
+ </a>
52
+ </nav>
53
+ <button id="openSettingsSm" class="md:hidden px-3 py-2 rounded-lg hover:bg-white/5">
54
+ <i class="fa-solid fa-sliders"></i>
55
+ </button>
56
+ </div>
57
+ </header>
58
+
59
+ <!-- Main -->
60
+ <main class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
61
+ <!-- Prompt card -->
62
+ <section class="rounded-2xl border border-white/10 bg-white/5 p-4 md:p-6">
63
+ <div class="flex flex-col md:flex-row gap-3">
64
+ <div class="flex-1">
65
+ <label for="prompt" class="sr-only">Prompt</label>
66
+ <div class="relative">
67
+ <textarea id="prompt" rows="2" placeholder="Describe what you want to create (e.g., 'a cyberpunk city alley with neon reflections, rainy night')"
68
+ 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>
69
+ <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">
70
+ <i class="fa-solid fa-wand-magic-sparkles mr-1"></i>Enhance
71
+ </button>
72
+ </div>
73
+
74
+ <!-- Quick styles -->
75
+ <div class="mt-3 flex flex-wrap gap-2">
76
+ <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>
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="cinematic shot, moody tone, film still">Cinema</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="photo, candid, natural look, unposed">Realistic</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="studio portrait, soft tone, subject centered">Portrait</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="anime style, vibrant cel shading">Anime</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="fantasy concept art, painterly">Fantasy</button>
82
+ </div>
83
+ </div>
84
+
85
+ <div class="w-full md:w-56 flex md:flex-col gap-2">
86
+ <button id="generateBtn"
87
+ class="flex-1 md:h-[66px] p-4 rounded-xl bg-white text-black font-semibold hover:opacity-90 active:opacity-100 transition mb-2">
88
+ <span class="inline-flex items-center gap-2"><i class="fa-solid fa-bolt"></i> Generate</span>
89
+ </button> <br>
90
+ <button id="randomPrompt" class="flex-1 md:h-[56px] rounded-xl bg-white/5 border border-white/10 hover:bg-white/10">
91
+ <i class="fa-solid fa-shuffle mr-2"></i>Random
92
+ </button>
93
+ <button id="downloadAll" class="hidden md:block h-[56px] rounded-xl bg-white/5 border border-white/10 hover:bg-white/10">
94
+ <i class="fa-solid fa-download mr-2"></i>Download All
95
+ </button>
96
+ </div>
97
+ </div>
98
+
99
+ <p class="mt-3 text-xs text-white/60">
100
+ Tip: NSFW may be filtered by models.
101
+ </p>
102
+ </section>
103
+
104
+ <!-- Gallery -->
105
+ <section class="mt-6">
106
+ <div id="gallery" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"></div>
107
+ <div id="emptyState" class="text-center py-16 text-white/50">
108
+ <i class="fa-regular fa-images text-4xl mb-3 block"></i>
109
+ <p>No images yet — start by generating something ✨</p>
110
+ </div>
111
+ </section>
112
+ </main>
113
+
114
+ <!-- Settings Drawer -->
115
+ <div id="settingsDrawer" class="fixed inset-0 hidden">
116
+ <div class="absolute inset-0 backdrop"></div>
117
+ <aside class="ml-auto h-full w-full sm:w-[420px] bg-black/90 border-l border-white/10 p-5 overflow-y-auto">
118
+ <div class="flex items-center justify-between mb-4">
119
+ <h2 class="text-lg font-semibold"><i class="fa-solid fa-sliders mr-2"></i>Settings</h2>
120
+ <button id="closeSettings" class="p-2 rounded-lg hover:bg-white/5">
121
+ <i class="fa-solid fa-xmark"></i>
122
+ </button>
123
+ </div>
124
+
125
+ <div class="space-y-4">
126
+ <label class="block text-sm">
127
+ <span class="text-white/70">Model</span>
128
+ <select id="model" class="mt-1 w-full rounded-lg bg-black/50 border border-white/10 p-2">
129
+ <!-- Popular, fast models -->
130
+ <option value="black-forest-labs/FLUX.1-schnell">FLUX.1-schnell (fast)</option>
131
+ <option value="stabilityai/stable-diffusion-xl-base-1.0">SDXL Base 1.0</option>
132
+ <option value="runwayml/stable-diffusion-v1-5">SD 1.5</option>
133
+ </select>
134
+ </label>
135
+
136
+ <div class="grid grid-cols-2 gap-3">
137
+ <label class="block text-sm">
138
+ <span class="text-white/70">Width</span>
139
+ <input id="width" type="number" min="64" max="2048" value="1024"
140
+ class="mt-1 w-full rounded-lg bg-black/50 border border-white/10 p-2">
141
+ </label>
142
+ <label class="block text-sm">
143
+ <span class="text-white/70">Height</span>
144
+ <input id="height" type="number" min="64" max="2048" value="1024"
145
+ class="mt-1 w-full rounded-lg bg-black/50 border border-white/10 p-2">
146
+ </label>
147
+ </div>
148
+
149
+ <div class="grid grid-cols-2 gap-3">
150
+ <label class="block text-sm">
151
+ <span class="text-white/70">Seed</span>
152
+ <input id="seed" type="number" value="42"
153
+ class="mt-1 w-full rounded-lg bg-black/50 border border-white/10 p-2">
154
+ </label>
155
+ <label class="flex items-center gap-2 text-sm mt-7">
156
+ <input id="randomSeed" type="checkbox" class="size-4 accent-fuchsia-500" checked>
157
+ Random seed
158
+ </label>
159
+ </div>
160
+
161
+ <label class="block text-sm">
162
+ <span class="text-white/70">Negative Prompt (optional)</span>
163
+ <input id="negPrompt" type="text" placeholder="blurry, low quality, watermark"
164
+ class="mt-1 w-full rounded-lg bg-black/50 border border-white/10 p-2">
165
+ </label>
166
+
167
+ <div class="flex items-center justify-between">
168
+ <label class="flex items-center gap-2 text-sm">
169
+ <input id="enhance" type="checkbox" class="size-4 accent-fuchsia-500">
170
+ Enhance prompt (client-side)
171
+ </label>
172
+ <button id="saveSettings" class="px-3 py-2 rounded-lg border border-white/10 hover:bg-white/5">
173
+ <i class="fa-regular fa-floppy-disk mr-2"></i>Save
174
+ </button>
175
+ </div>
176
+
177
+
178
+ </div>
179
+ </aside>
180
+ </div>
181
+
182
+ <!-- Image Modal -->
183
+ <div id="imageModal" class="fixed inset-0 hidden">
184
+ <div class="absolute inset-0 backdrop"></div>
185
+ <div class="relative z-10 h-full w-full flex items-center justify-center p-4">
186
+ <div class="max-w-5xl w-full rounded-2xl bg-black/90 border border-white/10 p-4">
187
+ <div class="flex justify-between items-center mb-3">
188
+ <h3 class="font-semibold"><i class="fa-regular fa-image mr-2"></i>Preview</h3>
189
+ <button id="closeModal" class="p-2 rounded-lg hover:bg-white/5">
190
+ <i class="fa-solid fa-xmark"></i>
191
+ </button>
192
+ </div>
193
+ <img id="modalImg" src="" alt="Preview" class="w-full rounded-xl border border-white/10">
194
+ <div class="mt-3 flex gap-2">
195
+ <button id="downloadBtn" class="px-3 py-2 rounded-lg bg-white/5 border border-white/10 hover:bg-white/10">
196
+ <i class="fa-solid fa-download mr-2"></i>Download
197
+ </button>
198
+ <button id="copyPromptBtn" class="px-3 py-2 rounded-lg bg-white/5 border border-white/10 hover:bg-white/10">
199
+ <i class="fa-solid fa-quote-left mr-2"></i>Copy Prompt
200
+ </button>
201
+ </div>
202
+ </div>
203
+ </div>
204
+ </div>
205
+
206
+ <!-- Toast -->
207
+ <div id="toast" class="fixed bottom-4 left-1/2 -translate-x-1/2 hidden">
208
+ <div class="rounded-xl bg-white/10 border border-white/10 px-4 py-2 text-sm">
209
+ <span id="toastMsg">Saved</span>
210
+ </div>
211
+ </div>
212
+
213
+
214
+
215
+
216
+
217
+ <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>
218
+
219
+ <!-- JS -->
220
+ <script>
221
+
222
+ // ------------ State ------------
223
+ const el = {
224
+ prompt: document.getElementById('prompt'),
225
+ model: document.getElementById('model'),
226
+ width: document.getElementById('width'),
227
+ height: document.getElementById('height'),
228
+ seed: document.getElementById('seed'),
229
+ randomSeed: document.getElementById('randomSeed'),
230
+ negPrompt: document.getElementById('negPrompt'),
231
+ enhance: document.getElementById('enhance'),
232
+
233
+ enhanceToggle: document.getElementById('enhanceToggle'),
234
+ generateBtn: document.getElementById('generateBtn'),
235
+ randomPrompt: document.getElementById('randomPrompt'),
236
+ downloadAll: document.getElementById('downloadAll'),
237
+
238
+ gallery: document.getElementById('gallery'),
239
+ emptyState: document.getElementById('emptyState'),
240
+
241
+ openSettings: document.getElementById('openSettings'),
242
+ openSettingsSm: document.getElementById('openSettingsSm'),
243
+ settingsDrawer: document.getElementById('settingsDrawer'),
244
+ closeSettings: document.getElementById('closeSettings'),
245
+ saveSettings: document.getElementById('saveSettings'),
246
+
247
+ openHistory: document.getElementById('openHistory'),
248
+ clearGallery: document.getElementById('clearGallery'),
249
+
250
+ imageModal: document.getElementById('imageModal'),
251
+ modalImg: document.getElementById('modalImg'),
252
+ closeModal: document.getElementById('closeModal'),
253
+ downloadBtn: document.getElementById('downloadBtn'),
254
+ copyPromptBtn: document.getElementById('copyPromptBtn'),
255
+
256
+ toast: document.getElementById('toast'),
257
+ toastMsg: document.getElementById('toastMsg'),
258
+ };
259
+
260
+ let currentStyle = "";
261
+ let history = JSON.parse(localStorage.getItem('img_history') || '[]');
262
+
263
+ // ------------ Helpers ------------
264
+ function showToast(msg){
265
+ el.toastMsg.textContent = msg;
266
+ el.toast.classList.remove('hidden');
267
+ setTimeout(()=>el.toast.classList.add('hidden'), 1600);
268
+ }
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
+
286
+ function loadSettings(){
287
+ try{
288
+ const s = JSON.parse(localStorage.getItem('img_settings')||'{}');
289
+ if(s.model) el.model.value = s.model;
290
+ if(s.width) el.width.value = s.width;
291
+ if(s.height) el.height.value = s.height;
292
+ if(s.seed !== undefined) el.seed.value = s.seed;
293
+ if(s.randomSeed !== undefined) el.randomSeed.checked = s.randomSeed;
294
+ if(s.negPrompt !== undefined) el.negPrompt.value = s.negPrompt;
295
+ if(s.enhance !== undefined) el.enhance.checked = s.enhance;
296
+ }catch(e){}
297
+ }
298
+ loadSettings();
299
+
300
+ // Random prompt pool (30 mixed)
301
+ const promptPool = [
302
+ "a cozy reading nook by a window with rain outside",
303
+ "a neon-lit ramen shop alley in Tokyo at night, reflections on wet ground",
304
+ "a majestic dragon soaring above snow-capped mountains, fantasy concept",
305
+ "a retro-futuristic living room, 80s aesthetic meets sci-fi",
306
+ "a cute corgi astronaut floating in space",
307
+ "a cyberpunk cityscape with neon signs and flying cars",
308
+ "a serene waterfall in a mystical forest, foggy morning",
309
+ "an ancient temple on a cliff during sunset, epic lighting",
310
+ "a photorealistic portrait of an old wise wizard",
311
+ "a futuristic robot chef cooking in a sleek kitchen",
312
+ "a fantasy castle floating above clouds, glowing lights",
313
+ "a retro diner interior, 50s style with neon lights",
314
+ "a giant mech battling dragons in a ruined city",
315
+ "a cozy cabin in snowy mountains with smoke from chimney",
316
+ "a glowing jellyfish underwater, ethereal lighting",
317
+ "a mystical portal opening in a dark forest",
318
+ "a cute kitten astronaut exploring Mars",
319
+ "an epic spaceship battle in outer space",
320
+ "a painterly landscape of a lavender field at sunrise",
321
+ "a realistic lion sitting on a cliff, dramatic sky",
322
+ "a neon anime girl with a futuristic city background",
323
+ "a steampunk airship flying above mountains",
324
+ "a magical library with floating books and candles",
325
+ "a cinematic scene of a samurai duel at sunset",
326
+ "a cozy coffee shop interior with warm lighting",
327
+ "a fantasy elf archer in a misty forest",
328
+ "a cyberpunk hacker in a dark room with neon monitors",
329
+ "a whimsical fairy village in the trees",
330
+ "a majestic phoenix rising from flames",
331
+ "a realistic portrait of a smiling elderly woman"
332
+ ];
333
+
334
+ function promptVariants(){ return promptPool[randInt(0, promptPool.length-1)]; }
335
+
336
+ function buildPrompt(){
337
+ let p = el.prompt.value.trim();
338
+ if(currentStyle) p += `, ${currentStyle}`;
339
+ const neg = el.negPrompt.value.trim();
340
+ return { prompt: p, negative_prompt: neg };
341
+ }
342
+
343
+ function addCardSkeleton(){
344
+ el.emptyState.classList.add('hidden');
345
+ const card = document.createElement('div');
346
+ card.className = "group relative rounded-2xl border border-white/10 bg-white/5 overflow-hidden";
347
+ card.innerHTML = `
348
+ <div class="aspect-[4/3] skeleton"></div>
349
+ <div class="absolute inset-0 flex items-center justify-center">
350
+ <div class="w-14 h-14 rounded-full skeleton"></div>
351
+ </div>
352
+ <div class="p-3 text-xs text-white/50">Generating...</div>
353
+ `;
354
+ el.gallery.prepend(card);
355
+ return card;
356
+ }
357
+
358
+ function addCard(imgUrl, meta){
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 hover:border-fuchsia-400/40 transition";
362
+ card.innerHTML = `
363
+ <img src="${imgUrl}" alt="AI image" class="w-full h-auto block">
364
+ <div class="absolute top-2 right-2 flex gap-1 opacity-0 group-hover:opacity-100 transition">
365
+ <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>
366
+ <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>
367
+ <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>
368
+ </div>
369
+ <div class="p-3 text-[11px] text-white/60 line-clamp-2">${meta.prompt}</div>
370
+ `;
371
+
372
+ const imgEl = card.querySelector("img");
373
+ if(isNSFW(meta.prompt)) imgEl.classList.add("blur-nsfw");
374
+ imgEl.addEventListener("click", () => imgEl.classList.remove("blur-nsfw"));
375
+
376
+ card.addEventListener('click', e=>{
377
+ const btn = e.target.closest('button');
378
+ if(!btn) return;
379
+ const act = btn.dataset.act;
380
+ if(act==='download'){
381
+ const a = document.createElement('a');
382
+ a.href = imgUrl; a.download = 'imggenpro.png'; a.click();
383
+ } else if(act==='open'){
384
+ openModal(imgUrl, meta.prompt);
385
+ } else if(act==='copy'){
386
+ navigator.clipboard.writeText(meta.prompt);
387
+ showToast('Prompt copied');
388
+ }
389
+ });
390
+
391
+ el.gallery.prepend(card);
392
+ return card;
393
+ }
394
+
395
+ function openModal(src, prompt){
396
+ el.modalImg.src = src;
397
+ el.copyPromptBtn.dataset.prompt = prompt;
398
+ el.imageModal.classList.remove('hidden');
399
+ }
400
+ function closeModal(){ el.imageModal.classList.add('hidden'); }
401
+
402
+ // ------------ Events ------------
403
+ document.querySelectorAll('.style-btn').forEach(b=>{
404
+ b.addEventListener('click', ()=>{
405
+ document.querySelectorAll('.style-btn').forEach(x=>x.classList.remove('ring-2','ring-fuchsia-400/60'));
406
+ b.classList.add('ring-2','ring-fuchsia-400/60');
407
+ currentStyle = b.dataset.style || "";
408
+ });
409
+ });
410
+
411
+ el.enhanceToggle.addEventListener('click', ()=>{
412
+ el.enhance.checked = !el.enhance.checked;
413
+ el.enhanceToggle.classList.toggle('bg-white/10', el.enhance.checked);
414
+ });
415
+
416
+ el.openSettings?.addEventListener('click', ()=> el.settingsDrawer.classList.remove('hidden'));
417
+ el.openSettingsSm?.addEventListener('click', ()=> el.settingsDrawer.classList.remove('hidden'));
418
+ el.closeSettings.addEventListener('click', ()=> el.settingsDrawer.classList.add('hidden'));
419
+ el.saveSettings.addEventListener('click', ()=>{ saveSettings(); el.settingsDrawer.classList.add('hidden'); });
420
+
421
+ el.openHistory?.addEventListener('click', ()=>{
422
+ if(!history.length){ showToast('No history yet'); return; }
423
+ el.gallery.innerHTML = '';
424
+ el.emptyState.classList.add('hidden');
425
+ history.forEach(h=> addCard(h.url, {prompt: h.prompt}));
426
+ });
427
+
428
+ el.clearGallery?.addEventListener('click', ()=>{
429
+ el.gallery.innerHTML = '';
430
+ el.emptyState.classList.remove('hidden');
431
+ });
432
+
433
+ el.randomPrompt.addEventListener('click', ()=>{ el.prompt.value = promptVariants(); });
434
+
435
+ el.downloadAll.addEventListener('click', ()=>{
436
+ const imgs = el.gallery.querySelectorAll('img');
437
+ if(!imgs.length){ showToast('Nothing to download'); return; }
438
+ imgs.forEach((img,i)=>{
439
+ const a = document.createElement('a');
440
+ a.href = img.src; a.download = `imggenpro_${i+1}.png`; a.click();
441
+ });
442
+ });
443
+
444
+ document.addEventListener('keydown', (e)=>{
445
+ if(e.key==='Escape'){ closeModal(); el.settingsDrawer.classList.add('hidden'); }
446
+ });
447
+
448
+ el.closeModal.addEventListener('click', closeModal);
449
+ el.downloadBtn.addEventListener('click', ()=>{
450
+ const a = document.createElement('a'); a.href = el.modalImg.src; a.download='imggenpro.png'; a.click();
451
+ });
452
+ el.copyPromptBtn.addEventListener('click', ()=>{
453
+ const p = el.copyPromptBtn.dataset.prompt || '';
454
+ navigator.clipboard.writeText(p);
455
+ showToast('Prompt copied');
456
+ });
457
+
458
+ function isNSFW(prompt){
459
+ const nsfwWords = ["bikini","nude","nsfw","sex","erotic","porn"];
460
+ return nsfwWords.some(w=>prompt.toLowerCase().includes(w));
461
+ }
462
+
463
+ async function doGenerate(){
464
+ const card = addCardSkeleton();
465
+ try{
466
+ el.generateBtn.disabled = true;
467
+ el.generateBtn.innerHTML = '<span class="inline-flex items-center gap-2"><i class="fa-solid fa-spinner animate-spin"></i> Generating…</span>';
468
+
469
+ let { prompt } = buildPrompt();
470
+ if(!prompt){ throw new Error('Please enter a prompt'); }
471
+
472
+ const model = el.model.value;
473
+
474
+ const res = await fetch('/generate', {
475
+ method: 'POST',
476
+ headers: { 'Content-Type': 'application/json' },
477
+ body: JSON.stringify({ prompt, model })
478
+ });
479
+
480
+ if(!res.ok){ throw new Error('Generation failed'); }
481
+
482
+ const blob = await res.blob();
483
+ const url = URL.createObjectURL(blob);
484
+
485
+ card.remove();
486
+ addCard(url, { prompt });
487
+ history.unshift({ url, prompt });
488
+ history = history.slice(0, 30);
489
+ localStorage.setItem('img_history', JSON.stringify(history));
490
+
491
+ } catch(err){
492
+ card.remove();
493
+ showToast(err.message);
494
+ console.error(err);
495
+ } finally{
496
+ el.generateBtn.disabled = false;
497
+ el.generateBtn.innerHTML = '<span class="inline-flex items-center gap-2"><i class="fa-solid fa-bolt"></i> Generate</span>';
498
+ }
499
+ }
500
+
501
+ el.generateBtn.addEventListener('click', doGenerate);
502
+ el.prompt.addEventListener('keydown', e=>{
503
+ if((e.ctrlKey||e.metaKey) && e.key==='Enter'){ doGenerate(); }
504
+ });
505
+ </script>
506
+
507
+
508
+
509
+
510
+ </body>
511
+ </html>