zakkx2000 commited on
Commit
ea451fa
·
verified ·
1 Parent(s): c430006

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +1222 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Photo Imageperfect
3
- emoji: 📊
4
- colorFrom: blue
5
- colorTo: red
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: photo-imageperfect
3
+ emoji: 🐳
4
+ colorFrom: purple
5
+ colorTo: purple
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1222 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
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>CMS - DICI/SAMU IMAGE PERFECT</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
11
+ <style>
12
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
13
+
14
+ body {
15
+ font-family: 'Inter', sans-serif;
16
+ background-color: #0f172a;
17
+ color: #e2e8f0;
18
+ }
19
+
20
+ .dropzone {
21
+ border: 2px dashed #3b82f6;
22
+ transition: all 0.3s ease;
23
+ }
24
+
25
+ .dropzone.active {
26
+ border-color: #10b981;
27
+ background-color: rgba(16, 185, 129, 0.05);
28
+ }
29
+
30
+ .progress-bar {
31
+ height: 6px;
32
+ background-color: #1e40af;
33
+ transition: width 0.3s ease;
34
+ }
35
+
36
+ .slider-thumb::-webkit-slider-thumb {
37
+ -webkit-appearance: none;
38
+ appearance: none;
39
+ width: 20px;
40
+ height: 20px;
41
+ border-radius: 50%;
42
+ background: #3b82f6;
43
+ cursor: pointer;
44
+ }
45
+
46
+ .slider-thumb::-moz-range-thumb {
47
+ width: 20px;
48
+ height: 20px;
49
+ border-radius: 50%;
50
+ background: #3b82f6;
51
+ cursor: pointer;
52
+ }
53
+
54
+ .result-image {
55
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
56
+ transition: all 0.3s ease;
57
+ max-height: 300px;
58
+ object-fit: contain;
59
+ }
60
+
61
+ .result-image:hover {
62
+ transform: scale(1.02);
63
+ }
64
+
65
+ .tooltip {
66
+ position: relative;
67
+ }
68
+
69
+ .tooltip-text {
70
+ visibility: hidden;
71
+ width: 120px;
72
+ background-color: #1e293b;
73
+ color: #fff;
74
+ text-align: center;
75
+ border-radius: 6px;
76
+ padding: 5px;
77
+ position: absolute;
78
+ z-index: 1;
79
+ bottom: 125%;
80
+ left: 50%;
81
+ margin-left: -60px;
82
+ opacity: 0;
83
+ transition: opacity 0.3s;
84
+ }
85
+
86
+ .tooltip:hover .tooltip-text {
87
+ visibility: visible;
88
+ opacity: 1;
89
+ }
90
+
91
+ .preview-container {
92
+ position: relative;
93
+ background-color: #1e293b;
94
+ background-image:
95
+ linear-gradient(45deg, #334155 25%, transparent 25%),
96
+ linear-gradient(-45deg, #334155 25%, transparent 25%),
97
+ linear-gradient(45deg, transparent 75%, #334155 75%),
98
+ linear-gradient(-45deg, transparent 75%, #334155 75%);
99
+ background-size: 20px 20px;
100
+ background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
101
+ }
102
+
103
+ .model-btn.active {
104
+ background-color: #3b82f6;
105
+ color: white;
106
+ }
107
+
108
+ /* Modal styles */
109
+ .modal {
110
+ display: none;
111
+ position: fixed;
112
+ top: 0;
113
+ left: 0;
114
+ width: 100%;
115
+ height: 100%;
116
+ background-color: rgba(0, 0, 0, 0.7);
117
+ z-index: 1000;
118
+ overflow-y: auto;
119
+ }
120
+
121
+ .modal-content {
122
+ background-color: #1e293b;
123
+ margin: 5% auto;
124
+ padding: 20px;
125
+ border-radius: 10px;
126
+ width: 90%;
127
+ max-width: 800px;
128
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
129
+ }
130
+
131
+ .close-modal {
132
+ color: #aaa;
133
+ float: right;
134
+ font-size: 28px;
135
+ font-weight: bold;
136
+ cursor: pointer;
137
+ }
138
+
139
+ .close-modal:hover {
140
+ color: #fff;
141
+ }
142
+
143
+ .batch-preview {
144
+ max-height: 300px;
145
+ overflow-y: auto;
146
+ margin-bottom: 20px;
147
+ border: 1px solid #334155;
148
+ border-radius: 5px;
149
+ padding: 10px;
150
+ }
151
+
152
+ .batch-item {
153
+ display: flex;
154
+ align-items: center;
155
+ padding: 8px;
156
+ border-bottom: 1px solid #334155;
157
+ }
158
+
159
+ .batch-item:last-child {
160
+ border-bottom: none;
161
+ }
162
+
163
+ .batch-thumbnail {
164
+ width: 50px;
165
+ height: 50px;
166
+ object-fit: cover;
167
+ margin-right: 10px;
168
+ border-radius: 3px;
169
+ }
170
+
171
+ .batch-info {
172
+ flex-grow: 1;
173
+ }
174
+
175
+ .batch-status {
176
+ margin-left: 10px;
177
+ font-size: 12px;
178
+ padding: 3px 6px;
179
+ border-radius: 3px;
180
+ }
181
+
182
+ .status-pending {
183
+ background-color: #3b82f6;
184
+ }
185
+
186
+ .status-completed {
187
+ background-color: #10b981;
188
+ }
189
+
190
+ .status-error {
191
+ background-color: #ef4444;
192
+ }
193
+
194
+ .batch-actions {
195
+ display: flex;
196
+ justify-content: space-between;
197
+ margin-top: 20px;
198
+ }
199
+
200
+ .preset-option {
201
+ transition: all 0.2s ease;
202
+ cursor: pointer;
203
+ }
204
+
205
+ .preset-option:hover {
206
+ transform: translateY(-2px);
207
+ }
208
+
209
+ .preset-option.selected {
210
+ border: 2px solid #3b82f6;
211
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
212
+ }
213
+ </style>
214
+ </head>
215
+ <body class="min-h-screen">
216
+ <div class="container mx-auto px-4 py-8 max-w-6xl">
217
+ <!-- Header -->
218
+ <header class="flex justify-between items-center mb-10">
219
+ <div class="flex items-center space-x-2">
220
+ <i class="fas fa-expand text-blue-500 text-2xl"></i>
221
+ <h1 class="text-2xl font-bold bg-gradient-to-r from-blue-500 to-emerald-500 bg-clip-text text-transparent">CMS - DICI/SAMU IMAGE PERFECT</h1>
222
+ </div>
223
+ <div class="flex space-x-4">
224
+ <button id="batchBtn" class="px-4 py-2 rounded-full bg-slate-800 hover:bg-slate-700 transition-colors">
225
+ <i class="fas fa-layer-group mr-2"></i>Batch Resize
226
+ </button>
227
+ <button class="px-4 py-2 rounded-full bg-slate-800 hover:bg-slate-700 transition-colors">
228
+ <i class="fas fa-question-circle mr-2"></i>Help
229
+ </button>
230
+ </div>
231
+ </header>
232
+
233
+ <!-- Main Content -->
234
+ <main>
235
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
236
+ <!-- Upload Section -->
237
+ <div class="bg-slate-800 rounded-xl p-6 shadow-lg">
238
+ <h2 class="text-xl font-semibold mb-4">Upload Image</h2>
239
+ <p class="text-slate-400 mb-6">Enhance your images with AI-powered upscaling. Supports JPG, PNG up to 10MB.</p>
240
+
241
+ <div id="dropzone" class="dropzone rounded-xl p-8 text-center cursor-pointer mb-6">
242
+ <div id="uploadContent" class="flex flex-col items-center justify-center space-y-3">
243
+ <i class="fas fa-cloud-upload-alt text-blue-500 text-4xl"></i>
244
+ <p class="font-medium">Drag & drop your image here</p>
245
+ <p class="text-slate-400 text-sm">or click to browse files</p>
246
+ <input type="file" id="fileInput" class="hidden" accept="image/*">
247
+ </div>
248
+ <div id="imagePreview" class="hidden">
249
+ <div class="preview-container rounded-lg overflow-hidden mb-3">
250
+ <img id="previewImage" src="" alt="Preview" class="w-full h-auto max-h-64 mx-auto">
251
+ </div>
252
+ <p id="fileName" class="text-sm font-medium truncate max-w-full"></p>
253
+ <p id="fileSize" class="text-xs text-slate-400"></p>
254
+ </div>
255
+ </div>
256
+
257
+ <div class="flex justify-between items-center mb-6">
258
+ <div>
259
+ <h3 class="font-medium mb-1">Upscale Settings</h3>
260
+ <p class="text-slate-400 text-sm">Adjust quality and scale</p>
261
+ </div>
262
+ <button id="resetBtn" class="px-3 py-1 rounded-md bg-slate-700 hover:bg-slate-600 text-sm transition-colors">
263
+ <i class="fas fa-redo mr-1"></i>Reset
264
+ </button>
265
+ </div>
266
+
267
+ <!-- Settings -->
268
+ <div class="space-y-5">
269
+ <div>
270
+ <div class="flex justify-between mb-2">
271
+ <label class="font-medium">Scale Factor</label>
272
+ <span id="scaleValue" class="text-blue-400">2x</span>
273
+ </div>
274
+ <input type="range" id="scaleSlider" min="1" max="4" step="1" value="2" class="w-full slider-thumb">
275
+ <div class="flex justify-between text-xs text-slate-400 mt-1">
276
+ <span>1x</span>
277
+ <span>2x</span>
278
+ <span>3x</span>
279
+ <span>4x</span>
280
+ </div>
281
+ </div>
282
+
283
+ <div>
284
+ <div class="flex justify-between mb-2">
285
+ <label class="font-medium">AI Model</label>
286
+ <span id="modelValue" class="text-blue-400">Standard</span>
287
+ </div>
288
+ <div class="grid grid-cols-2 gap-2">
289
+ <button id="standardModel" class="py-2 px-1 rounded-md bg-blue-600 hover:bg-blue-700 transition-colors text-sm active">
290
+ Standard
291
+ </button>
292
+ <button id="enhancedModel" class="py-2 px-1 rounded-md bg-slate-700 hover:bg-slate-600 transition-colors text-sm">
293
+ Enhanced
294
+ </button>
295
+ </div>
296
+ </div>
297
+
298
+ <div>
299
+ <div class="flex justify-between mb-2">
300
+ <label class="font-medium">Noise Reduction</label>
301
+ <span id="noiseValue" class="text-blue-400">50%</span>
302
+ </div>
303
+ <input type="range" id="noiseSlider" min="0" max="100" value="50" class="w-full slider-thumb">
304
+ </div>
305
+ </div>
306
+
307
+ <button id="processBtn" class="w-full mt-8 py-3 rounded-xl bg-gradient-to-r from-blue-600 to-emerald-600 hover:from-blue-700 hover:to-emerald-700 font-medium transition-all transform hover:scale-[1.02] disabled:opacity-50 disabled:cursor-not-allowed" disabled>
308
+ <i class="fas fa-magic mr-2"></i>Upscale Image
309
+ </button>
310
+ </div>
311
+
312
+ <!-- Results Section -->
313
+ <div class="bg-slate-800 rounded-xl p-6 shadow-lg">
314
+ <h2 class="text-xl font-semibold mb-4">Results</h2>
315
+ <p class="text-slate-400 mb-6">Your enhanced images will appear here after processing.</p>
316
+
317
+ <div id="resultContainer" class="hidden">
318
+ <div class="flex justify-between items-center mb-4">
319
+ <div>
320
+ <h3 class="font-medium">Comparison</h3>
321
+ <p class="text-slate-400 text-sm">Original vs Upscaled</p>
322
+ </div>
323
+ <div class="flex space-x-2">
324
+ <button id="downloadBtn" class="p-2 rounded-md bg-slate-700 hover:bg-slate-600 transition-colors tooltip">
325
+ <i class="fas fa-download"></i>
326
+ <span class="tooltip-text">Download</span>
327
+ </button>
328
+ <button class="p-2 rounded-md bg-slate-700 hover:bg-slate-600 transition-colors tooltip">
329
+ <i class="fas fa-copy"></i>
330
+ <span class="tooltip-text">Copy</span>
331
+ </button>
332
+ <button class="p-2 rounded-md bg-slate-700 hover:bg-slate-600 transition-colors tooltip">
333
+ <i class="fas fa-share-alt"></i>
334
+ <span class="tooltip-text">Share</span>
335
+ </button>
336
+ </div>
337
+ </div>
338
+
339
+ <div class="grid grid-cols-2 gap-4 mb-6">
340
+ <div>
341
+ <div class="preview-container rounded-lg overflow-hidden mb-2">
342
+ <img id="originalImage" src="" alt="Original" class="w-full h-auto result-image">
343
+ </div>
344
+ <p class="text-center text-sm text-slate-400">Original</p>
345
+ </div>
346
+ <div>
347
+ <div class="preview-container rounded-lg overflow-hidden mb-2 relative">
348
+ <img id="upscaledImage" src="" alt="Upscaled" class="w-full h-auto result-image">
349
+ <div class="absolute top-2 right-2 bg-blue-600 text-white text-xs px-2 py-1 rounded-full">
350
+ <span id="scaleBadge">2x</span>
351
+ </div>
352
+ </div>
353
+ <p class="text-center text-sm text-slate-400">Upscaled</p>
354
+ </div>
355
+ </div>
356
+
357
+ <div class="bg-slate-900 rounded-lg p-4">
358
+ <div class="flex justify-between items-center mb-2">
359
+ <span class="font-medium">Details</span>
360
+ <span class="text-xs text-slate-400">Processing time: <span id="processingTime">1.4</span>s</span>
361
+ </div>
362
+ <div class="grid grid-cols-3 gap-2 text-center text-sm">
363
+ <div class="bg-slate-800 p-2 rounded">
364
+ <div class="text-slate-400">Original Size</div>
365
+ <div id="originalSize" class="font-medium">1.2 MB</div>
366
+ </div>
367
+ <div class="bg-slate-800 p-2 rounded">
368
+ <div class="text-slate-400">New Size</div>
369
+ <div id="newSize" class="font-medium">3.8 MB</div>
370
+ </div>
371
+ <div class="bg-slate-800 p-2 rounded">
372
+ <div class="text-slate-400">Resolution</div>
373
+ <div id="resolution" class="font-medium">1920×1080 → 3840×2160</div>
374
+ </div>
375
+ </div>
376
+ </div>
377
+ </div>
378
+
379
+ <div id="emptyState" class="flex flex-col items-center justify-center py-12">
380
+ <div class="bg-slate-900 rounded-full p-6 mb-4">
381
+ <i class="fas fa-image text-3xl text-slate-600"></i>
382
+ </div>
383
+ <h3 class="font-medium mb-1">No image processed yet</h3>
384
+ <p class="text-slate-400 text-sm">Upload an image and click "Upscale Image"</p>
385
+ </div>
386
+
387
+ <div id="progressContainer" class="hidden">
388
+ <div class="flex flex-col items-center justify-center py-12 space-y-4">
389
+ <div class="relative">
390
+ <div class="w-16 h-16 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
391
+ <i class="fas fa-magic text-blue-500 text-xl absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"></i>
392
+ </div>
393
+ <h3 class="font-medium">Processing your image</h3>
394
+ <p class="text-slate-400 text-sm">This may take a few seconds...</p>
395
+ <div class="w-full bg-slate-700 rounded-full h-1.5">
396
+ <div id="progressBar" class="progress-bar h-1.5 rounded-full" style="width: 0%"></div>
397
+ </div>
398
+ <p id="progressText" class="text-xs text-slate-400">0% completed</p>
399
+ </div>
400
+ </div>
401
+ </div>
402
+ </div>
403
+ </main>
404
+
405
+ <!-- Footer -->
406
+ <footer class="mt-16 text-center text-slate-400 text-sm">
407
+ <p>© 2025 CMS - DICI/SAMU IMAGE PERFECT. All rights reserved.</p>
408
+ </footer>
409
+ </div>
410
+
411
+ <!-- Batch Resize Modal -->
412
+ <div id="batchModal" class="modal">
413
+ <div class="modal-content">
414
+ <span class="close-modal">&times;</span>
415
+ <h2 class="text-xl font-semibold mb-4">Batch Image Resize</h2>
416
+ <p class="text-slate-400 mb-6">Resize multiple images to standard dimensions in one operation.</p>
417
+
418
+ <div class="dropzone rounded-xl p-8 text-center cursor-pointer mb-6" id="batchDropzone">
419
+ <div id="batchUploadContent" class="flex flex-col items-center justify-center space-y-3">
420
+ <i class="fas fa-layer-group text-blue-500 text-4xl"></i>
421
+ <p class="font-medium">Drag & drop your images here</p>
422
+ <p class="text-slate-400 text-sm">or click to browse files (max 20 images)</p>
423
+ <input type="file" id="batchFileInput" class="hidden" accept="image/*" multiple>
424
+ </div>
425
+ </div>
426
+
427
+ <div id="batchPreview" class="batch-preview hidden">
428
+ <!-- Preview items will be added here -->
429
+ </div>
430
+
431
+ <div class="space-y-5">
432
+ <h3 class="font-medium">Resize Presets</h3>
433
+
434
+ <div class="grid grid-cols-2 gap-4">
435
+ <div id="eviarPreset" class="preset-option bg-slate-800 p-4 rounded-lg selected">
436
+ <div class="flex items-center mb-3">
437
+ <input type="radio" name="preset" value="eviar" class="mr-2" checked>
438
+ <label class="font-medium">EVIAR</label>
439
+ </div>
440
+ <p class="text-slate-400 text-sm">32.4cm × 21.6cm @ 300dpi</p>
441
+ <p class="text-slate-400 text-xs">(3827 × 2551 pixels)</p>
442
+ </div>
443
+
444
+ <div id="sitePreset" class="preset-option bg-slate-800 p-4 rounded-lg">
445
+ <div class="flex items-center mb-3">
446
+ <input type="radio" name="preset" value="site" class="mr-2">
447
+ <label class="font-medium">SITE</label>
448
+ </div>
449
+ <p class="text-slate-400 text-sm">1000px × 667px @ 72dpi</p>
450
+ <p class="text-slate-400 text-xs">(Optimized for web)</p>
451
+ </div>
452
+ </div>
453
+
454
+ <div>
455
+ <div class="flex justify-between mb-2">
456
+ <label class="font-medium">JPEG Quality</label>
457
+ <span id="qualityValue" class="text-blue-400">85%</span>
458
+ </div>
459
+ <input type="range" id="qualitySlider" min="50" max="100" value="85" class="w-full slider-thumb">
460
+ </div>
461
+ </div>
462
+
463
+ <div class="batch-actions">
464
+ <button id="cancelBatchBtn" class="px-4 py-2 rounded-md bg-slate-700 hover:bg-slate-600 transition-colors">
465
+ Cancel
466
+ </button>
467
+ <button id="processBatchBtn" class="px-4 py-2 rounded-md bg-gradient-to-r from-blue-600 to-emerald-600 hover:from-blue-700 hover:to-emerald-700 font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed" disabled>
468
+ <i class="fas fa-cogs mr-2"></i>Process Batch
469
+ </button>
470
+ </div>
471
+
472
+ <div id="batchProgressContainer" class="hidden mt-6">
473
+ <h4 class="font-medium mb-2">Processing Progress</h4>
474
+ <div class="w-full bg-slate-700 rounded-full h-2.5 mb-2">
475
+ <div id="batchProgressBar" class="progress-bar h-2.5 rounded-full" style="width: 0%"></div>
476
+ </div>
477
+ <div class="flex justify-between text-sm text-slate-400">
478
+ <span id="batchProgressText">0% (0/0)</span>
479
+ <span id="batchTimeRemaining">Estimated time: calculating...</span>
480
+ </div>
481
+ </div>
482
+
483
+ <div id="batchResults" class="hidden mt-6">
484
+ <h4 class="font-medium mb-2">Results</h4>
485
+ <div class="bg-slate-900 rounded-lg p-4">
486
+ <div class="grid grid-cols-3 gap-2 text-center text-sm">
487
+ <div class="bg-slate-800 p-2 rounded">
488
+ <div class="text-slate-400">Processed</div>
489
+ <div id="processedCount" class="font-medium">0</div>
490
+ </div>
491
+ <div class="bg-slate-800 p-2 rounded">
492
+ <div class="text-slate-400">Success</div>
493
+ <div id="successCount" class="font-medium">0</div>
494
+ </div>
495
+ <div class="bg-slate-800 p-2 rounded">
496
+ <div class="text-slate-400">Errors</div>
497
+ <div id="errorCount" class="font-medium">0</div>
498
+ </div>
499
+ </div>
500
+ </div>
501
+
502
+ <button id="downloadBatchBtn" class="w-full mt-4 py-2 rounded-md bg-gradient-to-r from-blue-600 to-emerald-600 hover:from-blue-700 hover:to-emerald-700 font-medium transition-colors">
503
+ <i class="fas fa-file-archive mr-2"></i>Download All as ZIP
504
+ </button>
505
+ </div>
506
+ </div>
507
+ </div>
508
+
509
+ <script>
510
+ document.addEventListener('DOMContentLoaded', function() {
511
+ // DOM Elements
512
+ const dropzone = document.getElementById('dropzone');
513
+ const fileInput = document.getElementById('fileInput');
514
+ const processBtn = document.getElementById('processBtn');
515
+ const resetBtn = document.getElementById('resetBtn');
516
+ const scaleSlider = document.getElementById('scaleSlider');
517
+ const scaleValue = document.getElementById('scaleValue');
518
+ const noiseSlider = document.getElementById('noiseSlider');
519
+ const noiseValue = document.getElementById('noiseValue');
520
+ const resultContainer = document.getElementById('resultContainer');
521
+ const emptyState = document.getElementById('emptyState');
522
+ const progressContainer = document.getElementById('progressContainer');
523
+ const originalImage = document.getElementById('originalImage');
524
+ const upscaledImage = document.getElementById('upscaledImage');
525
+ const originalSize = document.getElementById('originalSize');
526
+ const newSize = document.getElementById('newSize');
527
+ const resolution = document.getElementById('resolution');
528
+ const processingTime = document.getElementById('processingTime');
529
+ const scaleBadge = document.getElementById('scaleBadge');
530
+ const progressBar = document.getElementById('progressBar');
531
+ const progressText = document.getElementById('progressText');
532
+ const uploadContent = document.getElementById('uploadContent');
533
+ const imagePreview = document.getElementById('imagePreview');
534
+ const previewImage = document.getElementById('previewImage');
535
+ const fileName = document.getElementById('fileName');
536
+ const fileSize = document.getElementById('fileSize');
537
+ const downloadBtn = document.getElementById('downloadBtn');
538
+ const standardModel = document.getElementById('standardModel');
539
+ const enhancedModel = document.getElementById('enhancedModel');
540
+ const modelValue = document.getElementById('modelValue');
541
+
542
+ // Batch resize elements
543
+ const batchBtn = document.getElementById('batchBtn');
544
+ const batchModal = document.getElementById('batchModal');
545
+ const closeModal = document.querySelector('.close-modal');
546
+ const cancelBatchBtn = document.getElementById('cancelBatchBtn');
547
+ const batchDropzone = document.getElementById('batchDropzone');
548
+ const batchFileInput = document.getElementById('batchFileInput');
549
+ const batchUploadContent = document.getElementById('batchUploadContent');
550
+ const batchPreview = document.getElementById('batchPreview');
551
+ const processBatchBtn = document.getElementById('processBatchBtn');
552
+ const batchProgressContainer = document.getElementById('batchProgressContainer');
553
+ const batchProgressBar = document.getElementById('batchProgressBar');
554
+ const batchProgressText = document.getElementById('batchProgressText');
555
+ const batchTimeRemaining = document.getElementById('batchTimeRemaining');
556
+ const batchResults = document.getElementById('batchResults');
557
+ const processedCount = document.getElementById('processedCount');
558
+ const successCount = document.getElementById('successCount');
559
+ const errorCount = document.getElementById('errorCount');
560
+ const downloadBatchBtn = document.getElementById('downloadBatchBtn');
561
+ const qualitySlider = document.getElementById('qualitySlider');
562
+ const qualityValue = document.getElementById('qualityValue');
563
+ const eviarPreset = document.getElementById('eviarPreset');
564
+ const sitePreset = document.getElementById('sitePreset');
565
+
566
+ // Variables
567
+ let selectedFile = null;
568
+ let upscaledImageBlob = null;
569
+ let currentModel = 'standard';
570
+ let batchFiles = [];
571
+ let batchResultsData = [];
572
+ let selectedPreset = 'eviar'; // Default to EVIAR
573
+
574
+ // Event Listeners
575
+ dropzone.addEventListener('click', () => fileInput.click());
576
+
577
+ fileInput.addEventListener('change', (e) => {
578
+ if (e.target.files.length) {
579
+ handleFileSelect(e.target.files[0]);
580
+ }
581
+ });
582
+
583
+ dropzone.addEventListener('dragover', (e) => {
584
+ e.preventDefault();
585
+ dropzone.classList.add('active');
586
+ });
587
+
588
+ ['dragleave', 'dragend'].forEach(type => {
589
+ dropzone.addEventListener(type, () => {
590
+ dropzone.classList.remove('active');
591
+ });
592
+ });
593
+
594
+ dropzone.addEventListener('drop', (e) => {
595
+ e.preventDefault();
596
+ dropzone.classList.remove('active');
597
+
598
+ if (e.dataTransfer.files.length) {
599
+ handleFileSelect(e.dataTransfer.files[0]);
600
+ }
601
+ });
602
+
603
+ scaleSlider.addEventListener('input', () => {
604
+ scaleValue.textContent = `${scaleSlider.value}x`;
605
+ });
606
+
607
+ noiseSlider.addEventListener('input', () => {
608
+ noiseValue.textContent = `${noiseSlider.value}%`;
609
+ });
610
+
611
+ standardModel.addEventListener('click', () => {
612
+ currentModel = 'standard';
613
+ modelValue.textContent = 'Standard';
614
+ standardModel.classList.add('bg-blue-600');
615
+ standardModel.classList.remove('bg-slate-700');
616
+ enhancedModel.classList.add('bg-slate-700');
617
+ enhancedModel.classList.remove('bg-blue-600');
618
+ });
619
+
620
+ enhancedModel.addEventListener('click', () => {
621
+ currentModel = 'enhanced';
622
+ modelValue.textContent = 'Enhanced';
623
+ enhancedModel.classList.add('bg-blue-600');
624
+ enhancedModel.classList.remove('bg-slate-700');
625
+ standardModel.classList.add('bg-slate-700');
626
+ standardModel.classList.remove('bg-blue-600');
627
+ });
628
+
629
+ processBtn.addEventListener('click', processImage);
630
+
631
+ resetBtn.addEventListener('click', resetApp);
632
+
633
+ downloadBtn.addEventListener('click', downloadUpscaledImage);
634
+
635
+ // Batch resize event listeners
636
+ batchBtn.addEventListener('click', () => {
637
+ batchModal.style.display = 'block';
638
+ });
639
+
640
+ closeModal.addEventListener('click', () => {
641
+ batchModal.style.display = 'none';
642
+ });
643
+
644
+ cancelBatchBtn.addEventListener('click', () => {
645
+ batchModal.style.display = 'none';
646
+ resetBatchModal();
647
+ });
648
+
649
+ batchDropzone.addEventListener('click', () => batchFileInput.click());
650
+
651
+ batchFileInput.addEventListener('change', (e) => {
652
+ if (e.target.files.length) {
653
+ handleBatchFiles(e.target.files);
654
+ }
655
+ });
656
+
657
+ batchDropzone.addEventListener('dragover', (e) => {
658
+ e.preventDefault();
659
+ batchDropzone.classList.add('active');
660
+ });
661
+
662
+ ['dragleave', 'dragend'].forEach(type => {
663
+ batchDropzone.addEventListener(type, () => {
664
+ batchDropzone.classList.remove('active');
665
+ });
666
+ });
667
+
668
+ batchDropzone.addEventListener('drop', (e) => {
669
+ e.preventDefault();
670
+ batchDropzone.classList.remove('active');
671
+
672
+ if (e.dataTransfer.files.length) {
673
+ handleBatchFiles(e.dataTransfer.files);
674
+ }
675
+ });
676
+
677
+ qualitySlider.addEventListener('input', () => {
678
+ qualityValue.textContent = `${qualitySlider.value}%`;
679
+ });
680
+
681
+ // Preset selection
682
+ eviarPreset.addEventListener('click', () => {
683
+ selectedPreset = 'eviar';
684
+ eviarPreset.classList.add('selected');
685
+ sitePreset.classList.remove('selected');
686
+ document.querySelector('input[name="preset"][value="eviar"]').checked = true;
687
+ });
688
+
689
+ sitePreset.addEventListener('click', () => {
690
+ selectedPreset = 'site';
691
+ sitePreset.classList.add('selected');
692
+ eviarPreset.classList.remove('selected');
693
+ document.querySelector('input[name="preset"][value="site"]').checked = true;
694
+ });
695
+
696
+ processBatchBtn.addEventListener('click', processBatchImages);
697
+
698
+ downloadBatchBtn.addEventListener('click', downloadBatchResults);
699
+
700
+ // Close modal when clicking outside
701
+ window.addEventListener('click', (e) => {
702
+ if (e.target === batchModal) {
703
+ batchModal.style.display = 'none';
704
+ }
705
+ });
706
+
707
+ // Functions
708
+ function handleFileSelect(file) {
709
+ // Check if file is an image
710
+ if (!file.type.match('image.*')) {
711
+ alert('Please select an image file (JPG, PNG)');
712
+ return;
713
+ }
714
+
715
+ // Check file size (max 10MB)
716
+ if (file.size > 10 * 1024 * 1024) {
717
+ alert('File size exceeds 10MB limit');
718
+ return;
719
+ }
720
+
721
+ selectedFile = file;
722
+
723
+ // Display file preview
724
+ const reader = new FileReader();
725
+ reader.onload = function(e) {
726
+ // Show preview
727
+ uploadContent.classList.add('hidden');
728
+ imagePreview.classList.remove('hidden');
729
+ previewImage.src = e.target.result;
730
+ fileName.textContent = file.name;
731
+ fileSize.textContent = formatFileSize(file.size);
732
+
733
+ // Set original image for comparison
734
+ originalImage.src = e.target.result;
735
+
736
+ // Simulate file info
737
+ originalSize.textContent = formatFileSize(file.size);
738
+ const dimensions = getRandomDimensions();
739
+ resolution.textContent = `${dimensions.original} → ${dimensions.upscaled}`;
740
+
741
+ // Enable process button
742
+ processBtn.disabled = false;
743
+ };
744
+ reader.readAsDataURL(file);
745
+ }
746
+
747
+ function processImage() {
748
+ if (!selectedFile) {
749
+ alert('Please select an image first');
750
+ return;
751
+ }
752
+
753
+ // Show progress
754
+ emptyState.classList.add('hidden');
755
+ resultContainer.classList.add('hidden');
756
+ progressContainer.classList.remove('hidden');
757
+
758
+ // Simulate processing
759
+ let progress = 0;
760
+ const interval = setInterval(() => {
761
+ progress += Math.random() * 10;
762
+ if (progress > 100) progress = 100;
763
+
764
+ progressBar.style.width = `${progress}%`;
765
+ progressText.textContent = `${Math.round(progress)}% completed`;
766
+
767
+ if (progress === 100) {
768
+ clearInterval(interval);
769
+ setTimeout(() => {
770
+ // For demo purposes, we'll create a slightly modified version of the original
771
+ createUpscaledImage(selectedFile).then(blob => {
772
+ upscaledImageBlob = blob;
773
+ showResults();
774
+ });
775
+ }, 500);
776
+ }
777
+ }, 300);
778
+ }
779
+
780
+ function createUpscaledImage(file) {
781
+ return new Promise((resolve) => {
782
+ const reader = new FileReader();
783
+ reader.onload = function(e) {
784
+ const img = new Image();
785
+ img.onload = function() {
786
+ // Create a canvas to "upscale" the image (just for demo)
787
+ const canvas = document.createElement('canvas');
788
+ const scale = parseInt(scaleSlider.value);
789
+ canvas.width = img.width * scale;
790
+ canvas.height = img.height * scale;
791
+
792
+ const ctx = canvas.getContext('2d');
793
+ ctx.imageSmoothingEnabled = true;
794
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
795
+
796
+ // Apply different effects based on selected model
797
+ if (currentModel === 'enhanced') {
798
+ // Enhanced model adds more sharpness
799
+ ctx.globalAlpha = 0.7;
800
+ ctx.filter = 'contrast(1.1) saturate(1.1)';
801
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
802
+ ctx.globalAlpha = 1;
803
+ ctx.filter = 'none';
804
+ }
805
+
806
+ // Apply noise reduction based on slider
807
+ if (noiseSlider.value > 50) {
808
+ ctx.globalAlpha = 0.5;
809
+ ctx.filter = 'blur(0.5px)';
810
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
811
+ ctx.globalAlpha = 1;
812
+ ctx.filter = 'none';
813
+ }
814
+
815
+ // Convert to blob
816
+ canvas.toBlob(blob => {
817
+ resolve(blob);
818
+ }, file.type, 0.9);
819
+ };
820
+ img.src = e.target.result;
821
+ };
822
+ reader.readAsDataURL(file);
823
+ });
824
+ }
825
+
826
+ function showResults() {
827
+ progressContainer.classList.add('hidden');
828
+ resultContainer.classList.remove('hidden');
829
+
830
+ // Create object URL for the upscaled image
831
+ const upscaledUrl = URL.createObjectURL(upscaledImageBlob);
832
+ upscaledImage.src = upscaledUrl;
833
+
834
+ // Update info
835
+ const scale = scaleSlider.value;
836
+ scaleBadge.textContent = `${scale}x`;
837
+
838
+ // Simulate upscaled file size (original * scale factor)
839
+ const originalSizeValue = selectedFile.size;
840
+ const newSizeValue = originalSizeValue * scale * (currentModel === 'enhanced' ? 1.2 : 0.8);
841
+ newSize.textContent = formatFileSize(newSizeValue);
842
+
843
+ // Simulate processing time (1-3 seconds for standard, 2-4 for enhanced)
844
+ const processingTimeValue = currentModel === 'enhanced'
845
+ ? (2 + Math.random() * 2).toFixed(1)
846
+ : (1 + Math.random() * 2).toFixed(1);
847
+ processingTime.textContent = processingTimeValue;
848
+ }
849
+
850
+ function downloadUpscaledImage() {
851
+ if (!upscaledImageBlob) return;
852
+
853
+ const a = document.createElement('a');
854
+ const url = URL.createObjectURL(upscaledImageBlob);
855
+ const fileName = selectedFile.name.replace(/\.[^/.]+$/, '') + '_upscaled' + selectedFile.name.match(/\.[^/.]+$/)[0];
856
+
857
+ a.href = url;
858
+ a.download = fileName;
859
+ document.body.appendChild(a);
860
+ a.click();
861
+
862
+ setTimeout(() => {
863
+ document.body.removeChild(a);
864
+ URL.revokeObjectURL(url);
865
+ }, 0);
866
+ }
867
+
868
+ function resetApp() {
869
+ // Reset file input
870
+ fileInput.value = '';
871
+ selectedFile = null;
872
+ upscaledImageBlob = null;
873
+
874
+ // Reset UI
875
+ originalImage.src = '';
876
+ upscaledImage.src = '';
877
+ emptyState.classList.remove('hidden');
878
+ resultContainer.classList.add('hidden');
879
+ progressContainer.classList.add('hidden');
880
+ uploadContent.classList.remove('hidden');
881
+ imagePreview.classList.add('hidden');
882
+ previewImage.src = '';
883
+
884
+ // Reset sliders
885
+ scaleSlider.value = 2;
886
+ scaleValue.textContent = '2x';
887
+ noiseSlider.value = 50;
888
+ noiseValue.textContent = '50%';
889
+
890
+ // Reset model to standard
891
+ currentModel = 'standard';
892
+ modelValue.textContent = 'Standard';
893
+ standardModel.classList.add('bg-blue-600');
894
+ standardModel.classList.remove('bg-slate-700');
895
+ enhancedModel.classList.add('bg-slate-700');
896
+ enhancedModel.classList.remove('bg-blue-600');
897
+
898
+ // Disable process button
899
+ processBtn.disabled = true;
900
+ }
901
+
902
+ // Batch resize functions
903
+ function handleBatchFiles(files) {
904
+ // Clear previous files
905
+ batchFiles = [];
906
+
907
+ // Convert FileList to array and filter images
908
+ const fileArray = Array.from(files).filter(file =>
909
+ file.type.match('image.*') && file.size <= 10 * 1024 * 1024
910
+ );
911
+
912
+ // Limit to 20 files
913
+ if (fileArray.length > 20) {
914
+ alert('Maximum 20 images allowed. Only the first 20 will be processed.');
915
+ batchFiles = fileArray.slice(0, 20);
916
+ } else {
917
+ batchFiles = fileArray;
918
+ }
919
+
920
+ if (batchFiles.length === 0) {
921
+ alert('No valid image files selected. Please select JPG or PNG files under 10MB.');
922
+ return;
923
+ }
924
+
925
+ // Show preview
926
+ batchUploadContent.classList.add('hidden');
927
+ batchPreview.classList.remove('hidden');
928
+ batchPreview.innerHTML = '';
929
+
930
+ // Add each file to preview
931
+ batchFiles.forEach((file, index) => {
932
+ const reader = new FileReader();
933
+ reader.onload = function(e) {
934
+ const item = document.createElement('div');
935
+ item.className = 'batch-item';
936
+ item.innerHTML = `
937
+ <img src="${e.target.result}" class="batch-thumbnail">
938
+ <div class="batch-info">
939
+ <div class="font-medium truncate">${file.name}</div>
940
+ <div class="text-xs text-slate-400">${formatFileSize(file.size)}</div>
941
+ </div>
942
+ <span class="batch-status status-pending">Pending</span>
943
+ `;
944
+ batchPreview.appendChild(item);
945
+ };
946
+ reader.readAsDataURL(file);
947
+ });
948
+
949
+ // Enable process button
950
+ processBatchBtn.disabled = false;
951
+ }
952
+
953
+ function processBatchImages() {
954
+ if (batchFiles.length === 0) {
955
+ alert('Please select images first');
956
+ return;
957
+ }
958
+
959
+ // Get selected preset and quality
960
+ const quality = parseInt(qualitySlider.value) / 100;
961
+
962
+ // Show progress
963
+ batchProgressContainer.classList.remove('hidden');
964
+ batchResults.classList.add('hidden');
965
+
966
+ // Reset results data
967
+ batchResultsData = [];
968
+
969
+ // Process each image
970
+ let processed = 0;
971
+ let success = 0;
972
+ let errors = 0;
973
+
974
+ // Calculate estimated time (1-3 seconds per image)
975
+ const estimatedTime = batchFiles.length * (2 + Math.random());
976
+ let remainingTime = estimatedTime;
977
+
978
+ const updateTime = setInterval(() => {
979
+ remainingTime -= 0.1;
980
+ if (remainingTime <= 0) remainingTime = 0;
981
+ batchTimeRemaining.textContent = `Estimated time: ${remainingTime.toFixed(1)}s remaining`;
982
+ }, 100);
983
+
984
+ const processNextImage = () => {
985
+ if (processed >= batchFiles.length) {
986
+ clearInterval(updateTime);
987
+
988
+ // Show results
989
+ batchProgressContainer.classList.add('hidden');
990
+ batchResults.classList.remove('hidden');
991
+
992
+ processedCount.textContent = processed;
993
+ successCount.textContent = success;
994
+ errorCount.textContent = errors;
995
+
996
+ return;
997
+ }
998
+
999
+ const file = batchFiles[processed];
1000
+ const item = batchPreview.children[processed];
1001
+
1002
+ // Update progress
1003
+ processed++;
1004
+ const progress = (processed / batchFiles.length) * 100;
1005
+ batchProgressBar.style.width = `${progress}%`;
1006
+ batchProgressText.textContent = `${Math.round(progress)}% (${processed}/${batchFiles.length})`;
1007
+
1008
+ // Process the image
1009
+ resizeImage(file, selectedPreset, quality).then(result => {
1010
+ if (result.status === 'success') {
1011
+ success++;
1012
+ item.querySelector('.batch-status').className = 'batch-status status-completed';
1013
+ item.querySelector('.batch-status').textContent = 'Completed';
1014
+
1015
+ batchResultsData.push({
1016
+ file: file,
1017
+ status: 'success',
1018
+ processedFile: result.blob,
1019
+ fileName: result.fileName
1020
+ });
1021
+ } else {
1022
+ errors++;
1023
+ item.querySelector('.batch-status').className = 'batch-status status-error';
1024
+ item.querySelector('.batch-status').textContent = 'Error';
1025
+
1026
+ batchResultsData.push({
1027
+ file: file,
1028
+ status: 'error',
1029
+ error: result.error
1030
+ });
1031
+ }
1032
+
1033
+ // Process next image
1034
+ setTimeout(processNextImage, 300);
1035
+ }).catch(error => {
1036
+ errors++;
1037
+ item.querySelector('.batch-status').className = 'batch-status status-error';
1038
+ item.querySelector('.batch-status').textContent = 'Error';
1039
+
1040
+ batchResultsData.push({
1041
+ file: file,
1042
+ status: 'error',
1043
+ error: error.message
1044
+ });
1045
+
1046
+ // Process next image
1047
+ setTimeout(processNextImage, 300);
1048
+ });
1049
+ };
1050
+
1051
+ // Start processing
1052
+ processNextImage();
1053
+ }
1054
+
1055
+ function resizeImage(file, preset, quality) {
1056
+ return new Promise((resolve, reject) => {
1057
+ const reader = new FileReader();
1058
+ reader.onload = function(e) {
1059
+ const img = new Image();
1060
+ img.onload = function() {
1061
+ try {
1062
+ // Determine target dimensions based on preset
1063
+ let targetWidth, targetHeight;
1064
+
1065
+ if (preset === 'eviar') {
1066
+ // EVIAR: 3827 × 2551 pixels (32.4cm × 21.6cm @ 300dpi)
1067
+ targetWidth = 3827;
1068
+ targetHeight = 2551;
1069
+ } else {
1070
+ // SITE: 1000 × 667 pixels
1071
+ targetWidth = 1000;
1072
+ targetHeight = 667;
1073
+ }
1074
+
1075
+ // Calculate new dimensions maintaining aspect ratio
1076
+ let newWidth, newHeight;
1077
+ const aspectRatio = img.width / img.height;
1078
+
1079
+ if (img.width / img.height > targetWidth / targetHeight) {
1080
+ // Image is wider than target - scale to width
1081
+ newWidth = targetWidth;
1082
+ newHeight = targetWidth / aspectRatio;
1083
+ } else {
1084
+ // Image is taller than target - scale to height
1085
+ newHeight = targetHeight;
1086
+ newWidth = targetHeight * aspectRatio;
1087
+ }
1088
+
1089
+ // Create canvas
1090
+ const canvas = document.createElement('canvas');
1091
+ canvas.width = newWidth;
1092
+ canvas.height = newHeight;
1093
+
1094
+ const ctx = canvas.getContext('2d');
1095
+
1096
+ // Fill with white background (for transparent PNGs)
1097
+ ctx.fillStyle = 'white';
1098
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
1099
+
1100
+ // Draw image centered
1101
+ const offsetX = (targetWidth - newWidth) / 2;
1102
+ const offsetY = (targetHeight - newHeight) / 2;
1103
+
1104
+ ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
1105
+
1106
+ // Convert to blob
1107
+ canvas.toBlob(blob => {
1108
+ const fileName = file.name.replace(/\.[^/.]+$/, '') +
1109
+ (preset === 'eviar' ? '_eviar' : '_site') +
1110
+ (file.name.match(/\.[^/.]+$/) ? file.name.match(/\.[^/.]+$/)[0] : '.jpg');
1111
+
1112
+ resolve({
1113
+ status: 'success',
1114
+ blob: blob,
1115
+ fileName: fileName
1116
+ });
1117
+ }, 'image/jpeg', quality);
1118
+ } catch (error) {
1119
+ reject(new Error('Image processing failed'));
1120
+ }
1121
+ };
1122
+
1123
+ img.onerror = function() {
1124
+ reject(new Error('Failed to load image'));
1125
+ };
1126
+
1127
+ img.src = e.target.result;
1128
+ };
1129
+
1130
+ reader.onerror = function() {
1131
+ reject(new Error('Failed to read file'));
1132
+ };
1133
+
1134
+ reader.readAsDataURL(file);
1135
+ });
1136
+ }
1137
+
1138
+ function downloadBatchResults() {
1139
+ if (batchResultsData.length === 0 || batchResultsData.filter(item => item.status === 'success').length === 0) {
1140
+ alert('No successfully processed images to download');
1141
+ return;
1142
+ }
1143
+
1144
+ // Create a new JSZip instance
1145
+ const zip = new JSZip();
1146
+ const successful = batchResultsData.filter(item => item.status === 'success');
1147
+
1148
+ // Add each successful image to the zip
1149
+ successful.forEach(item => {
1150
+ zip.file(item.fileName, item.processedFile);
1151
+ });
1152
+
1153
+ // Generate the zip file
1154
+ zip.generateAsync({ type: 'blob' }).then(content => {
1155
+ // Create download link
1156
+ const a = document.createElement('a');
1157
+ const url = URL.createObjectURL(content);
1158
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
1159
+ const zipName = `batch-resize-${timestamp}.zip`;
1160
+
1161
+ a.href = url;
1162
+ a.download = zipName;
1163
+ document.body.appendChild(a);
1164
+ a.click();
1165
+
1166
+ // Clean up
1167
+ setTimeout(() => {
1168
+ document.body.removeChild(a);
1169
+ URL.revokeObjectURL(url);
1170
+ }, 0);
1171
+ });
1172
+ }
1173
+
1174
+ function resetBatchModal() {
1175
+ batchFileInput.value = '';
1176
+ batchFiles = [];
1177
+ batchResultsData = [];
1178
+
1179
+ batchUploadContent.classList.remove('hidden');
1180
+ batchPreview.classList.add('hidden');
1181
+ batchProgressContainer.classList.add('hidden');
1182
+ batchResults.classList.add('hidden');
1183
+
1184
+ processBatchBtn.disabled = true;
1185
+
1186
+ // Reset to default values
1187
+ qualitySlider.value = 85;
1188
+ qualityValue.textContent = '85%';
1189
+ selectedPreset = 'eviar';
1190
+ eviarPreset.classList.add('selected');
1191
+ sitePreset.classList.remove('selected');
1192
+ document.querySelector('input[name="preset"][value="eviar"]').checked = true;
1193
+ }
1194
+
1195
+ // Helper functions
1196
+ function formatFileSize(bytes) {
1197
+ if (bytes < 1024) return bytes + ' bytes';
1198
+ else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
1199
+ else return (bytes / 1048576).toFixed(1) + ' MB';
1200
+ }
1201
+
1202
+ function getRandomDimensions() {
1203
+ const widths = [640, 800, 1024, 1280, 1920];
1204
+ const heights = [480, 600, 768, 720, 1080];
1205
+
1206
+ const randomIndex = Math.floor(Math.random() * widths.length);
1207
+ const originalWidth = widths[randomIndex];
1208
+ const originalHeight = heights[randomIndex];
1209
+
1210
+ const scale = parseInt(scaleSlider.value);
1211
+ const upscaledWidth = originalWidth * scale;
1212
+ const upscaledHeight = originalHeight * scale;
1213
+
1214
+ return {
1215
+ original: `${originalWidth}×${originalHeight}`,
1216
+ upscaled: `${upscaledWidth}×${upscaledHeight}`
1217
+ };
1218
+ }
1219
+ });
1220
+ </script>
1221
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=zakkx2000/photo-imageperfect" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1222
+ </html>