drdata commited on
Commit
2e80eb8
·
verified ·
1 Parent(s): 071a8af

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +5 -3
  2. index.html +550 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Image Compressor
3
- emoji: 📉
4
  colorFrom: blue
5
  colorTo: purple
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: image-compressor
3
+ emoji: 🐳
4
  colorFrom: blue
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,550 @@
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>Online Image Compressor</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
+ <style>
10
+ .drop-zone {
11
+ border: 2px dashed #cbd5e0;
12
+ transition: all 0.3s ease;
13
+ }
14
+ .drop-zone.active {
15
+ border-color: #4299e1;
16
+ background-color: #ebf8ff;
17
+ }
18
+ .slider-thumb::-webkit-slider-thumb {
19
+ -webkit-appearance: none;
20
+ width: 20px;
21
+ height: 20px;
22
+ background: #4299e1;
23
+ border-radius: 50%;
24
+ cursor: pointer;
25
+ }
26
+ .slider-thumb::-moz-range-thumb {
27
+ width: 20px;
28
+ height: 20px;
29
+ background: #4299e1;
30
+ border-radius: 50%;
31
+ cursor: pointer;
32
+ }
33
+ .image-preview {
34
+ max-height: 300px;
35
+ object-fit: contain;
36
+ }
37
+ .file-info {
38
+ background-color: #f7fafc;
39
+ border-radius: 0.375rem;
40
+ }
41
+ </style>
42
+ </head>
43
+ <body class="bg-gray-50 min-h-screen flex flex-col">
44
+ <!-- Header -->
45
+ <header class="bg-white shadow-sm py-4">
46
+ <div class="container mx-auto px-4">
47
+ <h1 class="text-2xl font-bold text-center text-blue-600">Online Image Compressor</h1>
48
+ </div>
49
+ </header>
50
+
51
+ <!-- Main Content -->
52
+ <main class="flex-grow container mx-auto px-4 py-8">
53
+ <div class="max-w-4xl mx-auto bg-white rounded-lg shadow-md overflow-hidden">
54
+ <!-- Upload Section -->
55
+ <div class="p-6 border-b">
56
+ <div id="dropZone" class="drop-zone rounded-lg p-12 text-center cursor-pointer">
57
+ <div class="flex flex-col items-center justify-center space-y-4">
58
+ <i class="fas fa-cloud-upload-alt text-4xl text-blue-400"></i>
59
+ <h2 class="text-xl font-semibold text-gray-700">Drag & Drop or Click to Upload Images</h2>
60
+ <p class="text-gray-500">Supports JPEG, PNG, WEBP (Max 10MB)</p>
61
+ <input type="file" id="fileInput" class="hidden" accept="image/*" multiple>
62
+ <button id="selectFilesBtn" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-6 rounded-lg transition duration-200">
63
+ Select Files
64
+ </button>
65
+ </div>
66
+ </div>
67
+ <div id="fileList" class="mt-4 space-y-2 hidden"></div>
68
+ </div>
69
+
70
+ <!-- Settings Section -->
71
+ <div class="p-6 border-b">
72
+ <h3 class="text-lg font-medium text-gray-800 mb-4">Compression Settings</h3>
73
+
74
+ <div class="space-y-6">
75
+ <!-- Compression Level -->
76
+ <div>
77
+ <label for="compressionLevel" class="block text-sm font-medium text-gray-700 mb-1">
78
+ Compression Level: <span id="compressionValue">70</span>%
79
+ </label>
80
+ <input type="range" id="compressionLevel" min="0" max="100" value="70"
81
+ class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer slider-thumb">
82
+ </div>
83
+
84
+ <!-- Maintain Aspect Ratio -->
85
+ <div class="flex items-center">
86
+ <input type="checkbox" id="maintainAspect" checked class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
87
+ <label for="maintainAspect" class="ml-2 block text-sm text-gray-700">
88
+ Maintain Aspect Ratio
89
+ </label>
90
+ </div>
91
+
92
+ <!-- Custom Dimensions -->
93
+ <div class="grid grid-cols-2 gap-4">
94
+ <div>
95
+ <label for="widthInput" class="block text-sm font-medium text-gray-700 mb-1">Width (px)</label>
96
+ <input type="number" id="widthInput" placeholder="Original" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
97
+ </div>
98
+ <div>
99
+ <label for="heightInput" class="block text-sm font-medium text-gray-700 mb-1">Height (px)</label>
100
+ <input type="number" id="heightInput" placeholder="Original" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
101
+ </div>
102
+ </div>
103
+
104
+ <!-- Output Format -->
105
+ <div>
106
+ <label class="block text-sm font-medium text-gray-700 mb-1">Output Format</label>
107
+ <div class="flex space-x-4">
108
+ <label class="inline-flex items-center">
109
+ <input type="radio" name="outputFormat" value="jpeg" checked class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300">
110
+ <span class="ml-2 text-sm text-gray-700">JPEG</span>
111
+ </label>
112
+ <label class="inline-flex items-center">
113
+ <input type="radio" name="outputFormat" value="png" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300">
114
+ <span class="ml-2 text-sm text-gray-700">PNG</span>
115
+ </label>
116
+ <label class="inline-flex items-center">
117
+ <input type="radio" name="outputFormat" value="webp" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300">
118
+ <span class="ml-2 text-sm text-gray-700">WEBP</span>
119
+ </label>
120
+ </div>
121
+ </div>
122
+ </div>
123
+
124
+ <div class="mt-6 flex justify-center">
125
+ <button id="compressBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-8 rounded-lg transition duration-200 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
126
+ Compress Images
127
+ </button>
128
+ </div>
129
+ </div>
130
+
131
+ <!-- Results Section -->
132
+ <div id="resultsSection" class="p-6 hidden">
133
+ <h3 class="text-lg font-medium text-gray-800 mb-4">Results</h3>
134
+
135
+ <div id="resultsContainer" class="space-y-6">
136
+ <!-- Results will be added here dynamically -->
137
+ </div>
138
+
139
+ <div class="mt-6 flex justify-center">
140
+ <button id="downloadAllBtn" class="bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-8 rounded-lg transition duration-200 hidden">
141
+ <i class="fas fa-download mr-2"></i> Download All
142
+ </button>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </main>
147
+
148
+ <!-- Footer -->
149
+ <footer class="bg-white border-t py-6">
150
+ <div class="container mx-auto px-4">
151
+ <div class="flex flex-col md:flex-row justify-between items-center">
152
+ <div class="text-center md:text-left mb-4 md:mb-0">
153
+ <p class="text-sm text-gray-500">All processing is done locally in your browser. No images are uploaded to the server.</p>
154
+ </div>
155
+ <div class="flex space-x-4">
156
+ <a href="#" class="text-sm text-gray-600 hover:text-blue-600">Privacy Policy</a>
157
+ <a href="#" class="text-sm text-gray-600 hover:text-blue-600">Terms of Service</a>
158
+ <a href="#" class="text-sm text-gray-600 hover:text-blue-600">Contact Us</a>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ </footer>
163
+
164
+ <script>
165
+ document.addEventListener('DOMContentLoaded', function() {
166
+ // DOM Elements
167
+ const dropZone = document.getElementById('dropZone');
168
+ const fileInput = document.getElementById('fileInput');
169
+ const selectFilesBtn = document.getElementById('selectFilesBtn');
170
+ const fileList = document.getElementById('fileList');
171
+ const compressionLevel = document.getElementById('compressionLevel');
172
+ const compressionValue = document.getElementById('compressionValue');
173
+ const maintainAspect = document.getElementById('maintainAspect');
174
+ const widthInput = document.getElementById('widthInput');
175
+ const heightInput = document.getElementById('heightInput');
176
+ const compressBtn = document.getElementById('compressBtn');
177
+ const resultsSection = document.getElementById('resultsSection');
178
+ const resultsContainer = document.getElementById('resultsContainer');
179
+ const downloadAllBtn = document.getElementById('downloadAllBtn');
180
+
181
+ // Variables
182
+ let files = [];
183
+ let compressedFiles = [];
184
+
185
+ // Event Listeners
186
+ selectFilesBtn.addEventListener('click', () => fileInput.click());
187
+ fileInput.addEventListener('change', handleFileSelect);
188
+ dropZone.addEventListener('dragover', handleDragOver);
189
+ dropZone.addEventListener('dragleave', handleDragLeave);
190
+ dropZone.addEventListener('drop', handleDrop);
191
+ compressionLevel.addEventListener('input', updateCompressionValue);
192
+ compressBtn.addEventListener('click', compressImages);
193
+ downloadAllBtn.addEventListener('click', downloadAllFiles);
194
+
195
+ // Maintain aspect ratio when width or height changes
196
+ let originalAspectRatio = null;
197
+ widthInput.addEventListener('change', () => {
198
+ if (maintainAspect.checked && originalAspectRatio && widthInput.value) {
199
+ heightInput.value = Math.round(widthInput.value / originalAspectRatio);
200
+ }
201
+ });
202
+
203
+ heightInput.addEventListener('change', () => {
204
+ if (maintainAspect.checked && originalAspectRatio && heightInput.value) {
205
+ widthInput.value = Math.round(heightInput.value * originalAspectRatio);
206
+ }
207
+ });
208
+
209
+ // Functions
210
+ function handleFileSelect(e) {
211
+ files = Array.from(e.target.files);
212
+ if (files.length > 0) {
213
+ displayFileList();
214
+ }
215
+ }
216
+
217
+ function handleDragOver(e) {
218
+ e.preventDefault();
219
+ dropZone.classList.add('active');
220
+ }
221
+
222
+ function handleDragLeave() {
223
+ dropZone.classList.remove('active');
224
+ }
225
+
226
+ function handleDrop(e) {
227
+ e.preventDefault();
228
+ dropZone.classList.remove('active');
229
+
230
+ files = Array.from(e.dataTransfer.files);
231
+ if (files.length > 0) {
232
+ displayFileList();
233
+ }
234
+ }
235
+
236
+ function updateCompressionValue() {
237
+ compressionValue.textContent = compressionLevel.value;
238
+ }
239
+
240
+ function displayFileList() {
241
+ fileList.innerHTML = '';
242
+ fileList.classList.remove('hidden');
243
+
244
+ files.forEach((file, index) => {
245
+ const fileItem = document.createElement('div');
246
+ fileItem.className = 'flex items-center p-3 bg-gray-50 rounded-lg';
247
+
248
+ const fileIcon = document.createElement('i');
249
+ fileIcon.className = 'fas fa-image text-blue-400 mr-3';
250
+
251
+ const fileInfo = document.createElement('div');
252
+ fileInfo.className = 'flex-grow';
253
+
254
+ const fileName = document.createElement('div');
255
+ fileName.className = 'text-sm font-medium text-gray-800 truncate';
256
+ fileName.textContent = file.name;
257
+
258
+ const fileSize = document.createElement('div');
259
+ fileSize.className = 'text-xs text-gray-500';
260
+ fileSize.textContent = formatFileSize(file.size);
261
+
262
+ fileInfo.appendChild(fileName);
263
+ fileInfo.appendChild(fileSize);
264
+
265
+ const removeBtn = document.createElement('button');
266
+ removeBtn.className = 'text-red-500 hover:text-red-700 ml-2';
267
+ removeBtn.innerHTML = '<i class="fas fa-times"></i>';
268
+ removeBtn.addEventListener('click', () => removeFile(index));
269
+
270
+ fileItem.appendChild(fileIcon);
271
+ fileItem.appendChild(fileInfo);
272
+ fileItem.appendChild(removeBtn);
273
+
274
+ fileList.appendChild(fileItem);
275
+ });
276
+
277
+ compressBtn.disabled = false;
278
+ }
279
+
280
+ function removeFile(index) {
281
+ files.splice(index, 1);
282
+ if (files.length > 0) {
283
+ displayFileList();
284
+ } else {
285
+ fileList.classList.add('hidden');
286
+ compressBtn.disabled = true;
287
+ }
288
+ }
289
+
290
+ function formatFileSize(bytes) {
291
+ if (bytes === 0) return '0 Bytes';
292
+ const k = 1024;
293
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
294
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
295
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
296
+ }
297
+
298
+ async function compressImages() {
299
+ if (files.length === 0) return;
300
+
301
+ compressBtn.disabled = true;
302
+ compressBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Compressing...';
303
+
304
+ // Clear previous results
305
+ resultsContainer.innerHTML = '';
306
+ compressedFiles = [];
307
+
308
+ // Get settings
309
+ const quality = compressionLevel.value / 100;
310
+ const outputFormat = document.querySelector('input[name="outputFormat"]:checked').value;
311
+ const width = widthInput.value ? parseInt(widthInput.value) : null;
312
+ const height = heightInput.value ? parseInt(heightInput.value) : null;
313
+
314
+ // Process each file
315
+ for (let i = 0; i < files.length; i++) {
316
+ const file = files[i];
317
+
318
+ try {
319
+ const result = await processImage(file, quality, outputFormat, width, height);
320
+ compressedFiles.push(result);
321
+ displayResult(result, i);
322
+ } catch (error) {
323
+ console.error('Error processing image:', error);
324
+ displayError(file.name, error.message);
325
+ }
326
+ }
327
+
328
+ resultsSection.classList.remove('hidden');
329
+ compressBtn.disabled = false;
330
+ compressBtn.textContent = 'Compress Images';
331
+
332
+ if (compressedFiles.length > 1) {
333
+ downloadAllBtn.classList.remove('hidden');
334
+ } else {
335
+ downloadAllBtn.classList.add('hidden');
336
+ }
337
+ }
338
+
339
+ async function processImage(file, quality, format, targetWidth, targetHeight) {
340
+ return new Promise((resolve, reject) => {
341
+ const reader = new FileReader();
342
+
343
+ reader.onload = function(e) {
344
+ const img = new Image();
345
+ img.onload = function() {
346
+ // Calculate dimensions
347
+ let width = img.width;
348
+ let height = img.height;
349
+
350
+ // Store original aspect ratio
351
+ originalAspectRatio = width / height;
352
+
353
+ if (targetWidth || targetHeight) {
354
+ if (targetWidth && targetHeight) {
355
+ width = targetWidth;
356
+ height = targetHeight;
357
+ } else if (targetWidth) {
358
+ height = Math.round(targetWidth / (img.width / img.height));
359
+ width = targetWidth;
360
+ } else if (targetHeight) {
361
+ width = Math.round(targetHeight * (img.width / img.height));
362
+ height = targetHeight;
363
+ }
364
+ }
365
+
366
+ // Create canvas
367
+ const canvas = document.createElement('canvas');
368
+ canvas.width = width;
369
+ canvas.height = height;
370
+ const ctx = canvas.getContext('2d');
371
+
372
+ // Draw image on canvas
373
+ ctx.drawImage(img, 0, 0, width, height);
374
+
375
+ // Compress image
376
+ let mimeType;
377
+ switch (format) {
378
+ case 'jpeg':
379
+ mimeType = 'image/jpeg';
380
+ break;
381
+ case 'png':
382
+ mimeType = 'image/png';
383
+ break;
384
+ case 'webp':
385
+ mimeType = 'image/webp';
386
+ break;
387
+ default:
388
+ mimeType = 'image/jpeg';
389
+ }
390
+
391
+ canvas.toBlob((blob) => {
392
+ if (!blob) {
393
+ reject(new Error('Failed to compress image'));
394
+ return;
395
+ }
396
+
397
+ const compressedFile = new File([blob], `compressed_${file.name}`, {
398
+ type: mimeType,
399
+ lastModified: Date.now()
400
+ });
401
+
402
+ resolve({
403
+ original: file,
404
+ compressed: compressedFile,
405
+ originalImageData: e.target.result,
406
+ compressedImageData: URL.createObjectURL(blob),
407
+ width: width,
408
+ height: height
409
+ });
410
+ }, mimeType, quality);
411
+ };
412
+
413
+ img.onerror = function() {
414
+ reject(new Error('Failed to load image'));
415
+ };
416
+
417
+ img.src = e.target.result;
418
+ };
419
+
420
+ reader.onerror = function() {
421
+ reject(new Error('Failed to read file'));
422
+ };
423
+
424
+ reader.readAsDataURL(file);
425
+ });
426
+ }
427
+
428
+ function displayResult(result, index) {
429
+ const resultItem = document.createElement('div');
430
+ resultItem.className = 'border rounded-lg overflow-hidden';
431
+
432
+ const header = document.createElement('div');
433
+ header.className = 'bg-gray-50 px-4 py-2 border-b flex justify-between items-center';
434
+
435
+ const fileName = document.createElement('div');
436
+ fileName.className = 'text-sm font-medium text-gray-800 truncate';
437
+ fileName.textContent = result.original.name;
438
+
439
+ const fileSize = document.createElement('div');
440
+ fileSize.className = 'text-xs text-gray-500';
441
+ fileSize.textContent = `${formatFileSize(result.original.size)} → ${formatFileSize(result.compressed.size)} (${Math.round((1 - result.compressed.size / result.original.size) * 100)}% smaller)`;
442
+
443
+ header.appendChild(fileName);
444
+ header.appendChild(fileSize);
445
+
446
+ const content = document.createElement('div');
447
+ content.className = 'grid grid-cols-1 md:grid-cols-2 gap-4 p-4';
448
+
449
+ // Original Image
450
+ const originalCol = document.createElement('div');
451
+ originalCol.className = 'flex flex-col items-center';
452
+
453
+ const originalLabel = document.createElement('div');
454
+ originalLabel.className = 'text-sm font-medium text-gray-700 mb-2';
455
+ originalLabel.textContent = 'Original';
456
+
457
+ const originalImg = document.createElement('img');
458
+ originalImg.src = result.originalImageData;
459
+ originalImg.className = 'image-preview max-w-full h-auto rounded border';
460
+ originalImg.alt = 'Original image';
461
+
462
+ const originalInfo = document.createElement('div');
463
+ originalInfo.className = 'file-info text-xs text-gray-600 mt-2 px-3 py-2 text-center';
464
+ originalInfo.textContent = `${result.original.width}×${result.original.height}px • ${formatFileSize(result.original.size)}`;
465
+
466
+ originalCol.appendChild(originalLabel);
467
+ originalCol.appendChild(originalImg);
468
+ originalCol.appendChild(originalInfo);
469
+
470
+ // Compressed Image
471
+ const compressedCol = document.createElement('div');
472
+ compressedCol.className = 'flex flex-col items-center';
473
+
474
+ const compressedLabel = document.createElement('div');
475
+ compressedLabel.className = 'text-sm font-medium text-gray-700 mb-2';
476
+ compressedLabel.textContent = 'Compressed';
477
+
478
+ const compressedImg = document.createElement('img');
479
+ compressedImg.src = result.compressedImageData;
480
+ compressedImg.className = 'image-preview max-w-full h-auto rounded border';
481
+ compressedImg.alt = 'Compressed image';
482
+
483
+ const compressedInfo = document.createElement('div');
484
+ compressedInfo.className = 'file-info text-xs text-gray-600 mt-2 px-3 py-2 text-center';
485
+ compressedInfo.textContent = `${result.width}×${result.height}px • ${formatFileSize(result.compressed.size)}`;
486
+
487
+ compressedCol.appendChild(compressedLabel);
488
+ compressedCol.appendChild(compressedImg);
489
+ compressedCol.appendChild(compressedInfo);
490
+
491
+ // Download Button
492
+ const downloadBtn = document.createElement('button');
493
+ downloadBtn.className = 'bg-blue-500 hover:bg-blue-600 text-white text-sm font-medium py-1 px-4 rounded transition duration-200 mt-2';
494
+ downloadBtn.innerHTML = '<i class="fas fa-download mr-2"></i> Download';
495
+ downloadBtn.addEventListener('click', () => downloadFile(result.compressed));
496
+
497
+ compressedCol.appendChild(downloadBtn);
498
+
499
+ content.appendChild(originalCol);
500
+ content.appendChild(compressedCol);
501
+
502
+ resultItem.appendChild(header);
503
+ resultItem.appendChild(content);
504
+
505
+ resultsContainer.appendChild(resultItem);
506
+ }
507
+
508
+ function displayError(fileName, errorMessage) {
509
+ const errorItem = document.createElement('div');
510
+ errorItem.className = 'border rounded-lg overflow-hidden bg-red-50';
511
+
512
+ const header = document.createElement('div');
513
+ header.className = 'bg-red-100 px-4 py-2 border-b border-red-200 flex justify-between items-center';
514
+
515
+ const fileNameEl = document.createElement('div');
516
+ fileNameEl.className = 'text-sm font-medium text-red-800 truncate';
517
+ fileNameEl.textContent = fileName;
518
+
519
+ header.appendChild(fileNameEl);
520
+
521
+ const content = document.createElement('div');
522
+ content.className = 'p-4 text-red-700 text-sm';
523
+ content.textContent = `Error: ${errorMessage}`;
524
+
525
+ errorItem.appendChild(header);
526
+ errorItem.appendChild(content);
527
+
528
+ resultsContainer.appendChild(errorItem);
529
+ }
530
+
531
+ function downloadFile(file) {
532
+ const url = URL.createObjectURL(file);
533
+ const a = document.createElement('a');
534
+ a.href = url;
535
+ a.download = file.name;
536
+ document.body.appendChild(a);
537
+ a.click();
538
+ document.body.removeChild(a);
539
+ URL.revokeObjectURL(url);
540
+ }
541
+
542
+ function downloadAllFiles() {
543
+ compressedFiles.forEach(file => {
544
+ downloadFile(file.compressed);
545
+ });
546
+ }
547
+ });
548
+ </script>
549
+ <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=drdata/image-compressor" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
550
+ </html>