PageSourceSearch

https://imgmin.pro/

html imgmin.pro collected 2026-10-02 01:07:54 UTC 42,485 bytes, 695 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5    <meta charset="UTF-8">
6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
7    <meta name="yandex-verification" content="7b9d2a6b6a23c52f" />
8    <meta name="baidu-site-verification" content="codeva-5mO3i5ot2y" />
9    <title>Free Image Compressor — Private, Fast & No Upload | ImgMin Pro</title>
10    <meta name="description"
11        content="Compress JPG, PNG and WebP images directly in your browser. No upload, no account, batch processing and target file size controls. Free with ImgMin Pro.">
12    <meta name="keywords"
13        content="free image compressor, online image compressor, compress image without uploading, browser-based image compressor, bulk image compressor, ImgMin Pro">
14
15    <!-- Google tag (gtag.js) -->
16    
16<script>
17        (function () {
18            function loadGtag() {
19                var s = document.createElement('script');
20                s.async = true;
21                s.src = 'https://www.googletagmanager.com/gtag/js?id=G-4ZERSTHF27';
22                document.head.appendChild(s);
23            }
24            if (document.readyState === 'complete') {
25                loadGtag();
26            } else {
27                window.addEventListener('load', loadGtag);
28            }
29        })();
30    </script>
30
31    
31<script>
32        
vendor: 155 bytes, lines 32-36
32window.dataLayer = window.dataLayer || [];
33        function gtag() { dataLayer.push(arguments); }
34        gtag('js', new Date());
35
36        gtag('config', '
36G-4ZERSTHF27
vendor: 13 bytes, lines 36-38
36');
37
38        
38// Extension acquisition loop: track visits driven by Chrome extension
39        (function () {
40            try {
41                var p = new URLSearchParams(location.search);
42                if (p.get('source') === 'ext') {
43                    gtag('event', 'ext_visit', {
44                        medium: p.get('utm_medium') || 'unknown',
45                        path: location.pathname,
46                    });
47                }
48            } catch (e) { /* noop */ }
49        })();
50    </script>
50
51
52    <!-- Favicon -->
53    <link rel="icon" type="image/png" sizes="48x48" href="./favicon-48x48.png">
54    <link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png">
55    <link rel="icon" type="image/png" sizes="16x16" href="./favicon-16x16.png">
56    <link rel="apple-touch-icon" sizes="180x180" href="./apple-touch-icon.png">
57    <link rel="manifest" href="./manifest.json">
58    <meta name="theme-color" content="#6C5CE7">
59
60    <!-- Canonical URL -->
61    <link rel="canonical" href="https://imgmin.pro/">
62
63    <!-- Hreflang for multi-language support -->
64    <link rel="alternate" hreflang="zh-CN" href="https://imgmin.pro/zh/">
65    <link rel="alternate" hreflang="zh-Hant" href="https://imgmin.pro/zh-hant/">
66    <link rel="alternate" hreflang="en-US" href="https://imgmin.pro/">
67    <link rel="alternate" hreflang="x-default" href="https://imgmin.pro/">
68
69    <!-- Open Graph / Facebook -->
70    <meta property="og:type" content="website">
71    <meta property="og:url" content="https://imgmin.pro/">
72    <meta property="og:title" content="Free Image Compressor — No Upload Required | ImgMin Pro">
73    <meta property="og:description" content="Compress JPG, PNG & WebP in your browser — no upload, no server, 100% private. Free image compressor.">
74    <meta property="og:image" content="https://imgmin.pro/android-chrome-512x512.png">
75    <meta property="og:site_name" content="ImgMin Pro">
76
77    <!-- Open Graph locale -->
78    <meta property="og:locale" content="en_US">
79    <meta property="og:locale:alternate" content="en_US">
80    <meta property="og:locale:alternate" content="fr_FR">
81    <meta property="og:locale:alternate" content="de_DE">
82    <meta property="og:locale:alternate" content="es_ES">
83    <meta property="og:locale:alternate" content="ja_JP">
84
85    <!-- Twitter -->
86    <meta name="twitter:card" content="summary_large_image">
87    <meta name="twitter:site" content="@ai_pmer">
88    <meta name="twitter:creator" content="@ai_pmer">
89    <meta name="twitter:title" content="Free Image Compressor — No Upload Required | ImgMin Pro">
90    <meta name="twitter:description" content="Compress JPG, PNG & WebP in your browser — no upload, no server, 100% private.">
91    <meta name="twitter:image" content="https://imgmin.pro/android-chrome-512x512.png">
92
93    <!-- Preconnect hints -->
94    <link rel="preconnect" href="https://www.googletagmanager.com">
95    <link rel="preconnect" href="https://cdnjs.cloudflare.com">
96
97    <!-- JSON-LD Structured Data for SEO -->
98    
98<script type="application/ld+json">
99    {
100        "@context": "https://schema.org",
101        "@type": "WebApplication",
102        "name": "ImgMin Pro",
103        "url": "https://imgmin.pro",
104        "description": "Private image optimization toolkit that runs entirely in your browser. Compress, convert and resize JPG, PNG and WebP images with no upload.",
105        "applicationCategory": "UtilitiesApplication",
106        "operatingSystem": "Any",
107        "offers": {
108            "@type": "Offer",
109            "price": "0",
110            "priceCurrency": "USD"
111        },
112        "featureList": ["JPG compression", "PNG compression", "WebP compression", "Batch processing", "Client-side only", "No data upload"],
113        "browserRequirements": "Requires JavaScript. Works in all modern browsers.",
114        "creator": {
115            "@type": "Organization",
116            "name": "ImgMin Pro"
117        },
118        "inLanguage": ["en-US", "zh-CN", "zh-Hant"]
119    }
120    </script>
120
121
122    
122<script type="application/ld+json">
123    {
124        "@context": "https://schema.org",
125        "@type": "Organization",
126        "name": "ImgMin Pro",
127        "url": "https://imgmin.pro/",
128        "logo": "https://imgmin.pro/android-chrome-512x512.png",
129        "description": "Privacy-first online image compression tool. All processing happens client-side — images never leave the browser.",
130        "sameAs": [
131            "https://x.com/ai_pmer",
132            "https://chromewebstore.google.com/detail/imgmin-%E2%80%93-image-compressor/helcbldoipinhoncicmimlekeocdmanl"
133        ]
134    }
135    </script>
135
136
137    <!-- FAQ Structured Data for Rich Snippets -->
138    
138<script type="application/ld+json">
139    {
140        "@context": "https://schema.org",
141        "@type": "FAQPage",
142        "mainEntity": [
143            {
144                "@type": "Question",
145                "name": "How does ImgMin Pro reduce image size while preserving visual quality?",
146                "acceptedAnswer": {
147                    "@type": "Answer",
148                    "text": "ImgMin Pro uses the browser's native Canvas API to intelligently compress images. It analyzes each image and applies optimal compression settings based on your chosen mode (Balanced, Best Quality, or Smallest Size). All processing happens directly in your browser — no server upload required."
149                }
150            },
151            {
152                "@type": "Question",
153                "name": "Is it safe to compress images with ImgMin Pro?",
154                "acceptedAnswer": {
155                    "@type": "Answer",
156                    "text": "Yes, ImgMin Pro is completely safe. Unlike most online compression tools, ImgMin Pro processes images entirely on your device using client-side JavaScript. Your images never leave your browser and are never uploaded to any server."
157                }
158            },
159            {
160                "@type": "Question",
161                "name": "What image formats does ImgMin Pro support?",
162                "acceptedAnswer": {
163                    "@type": "Answer",
164                    "text": "ImgMin Pro supports JPG/JPEG, PNG, and WebP formats, and converts iPhone HEIC photos locally. You can compress up to 20 images at once using the batch upload feature, and download them individually or as a ZIP file."
165                }
166            },
167            {
168                "@type": "Question",
169                "name": "Can I use ImgMin Pro offline?",
170                "acceptedAnswer": {
171                    "@type": "Answer",
172                    "text": "Yes, ImgMin Pro is a Progressive Web App (PWA) that works offline. Since all image compression happens in your browser without server communication, you can compress images even without an internet connection after your first visit."
173                }
174            }
175        ]
176    }
177    </script>
177
178
179    <link rel="stylesheet" href="styles.css?v=20260929a">
180    <link rel="stylesheet" href="/site-header.css?v=20260924c">
181    <!-- JSZip for multi-file download -->
182    
182<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js" integrity="sha384-+mbV2IY1Zk/X1p/nWllGySJSUN8uMs+gUAN10Or95UBH0fpj6GfKgPmgC5EXieXG" crossorigin="anonymous"></script>
182
183</head>
184
185<body>
186    <noscript>
187        <div style="text-align:center;padding:3rem;font-family:sans-serif;">
188            <h1>ImgMin Pro — Free Image Compressor</h1>
189            <p>ImgMin Pro is a free, privacy-first online image compression tool. Compress JPG, PNG, and WebP images directly in your browser — no upload required. All processing happens client-side for maximum privacy and security.</p>
190            <p>Please enable JavaScript to use ImgMin Pro.</p>
191        </div>
192    </noscript>
193    <nav class="navbar">
194        <a href="/" class="logo" aria-label="ImgMin">
195            <img src="/logo-nav.png" alt="ImgMin" width="514" height="120" class="logo-img-file">
196        </a>
197        <div class="nav-right">
198            <!-- Entry hidden 2026-09-16: bg-removal UX/effect not yet good enough for a
199                 default-on nav link. Page and all logic stay live at /remove-background/
200                 and /zh/remove-background/; remove `hidden` here to relaunch. -->
201            <a href="/remove-background/" id="navRemoveBg" hidden style="color:#555;text-decoration:none;font-size:.9rem;font-weight:500;margin-right:.75rem">Remove BG</a>
202            <a href="/blog/" data-i18n="footer.blog" style="color:#555;text-decoration:none;font-size:.9rem;font-weight:500;margin-right:.75rem">Blog</a>
203            <select id="langSelector" class="lang-select">
204                <option value="en-US">English</option>
205                <option value="zh-CN">简体中文</option>
206                <option value="zh-Hant">繁體中文</option>
207            </select>
208        </div>
209    </nav>
210
211    <div class="container">
212        <header>
213            <h1 data-i18n="hero.title">Compress Images Online — No Upload Required</h1>
214            <p data-i18n="hero.subtitle">Compress JPG, PNG &amp; WebP locally while maintaining visual quality. Your images never leave your device.</p>
215        </header>
216
217        <a id="extBanner" class="ext-banner" href="https://chromewebstore.google.com/detail/imgmin-%E2%80%93-image-compressor/helcbldoipinhoncicmimlekeocdmanl?utm_source=imgmin.pro&utm_medium=banner&utm_campaign=ext_launch&utm_content=home" target="_blank" rel="noopener" onclick="if(window.gtag)gtag('event','ext_install_click',{location:'home_banner'})">
218            <span class="ext-banner-tag" data-i18n="ext.banner.tag">NEW</span>
219            <span class="ext-banner-text" data-i18n="ext.banner.text">ImgMin for Chrome — right-click any image to compress.</span>
220            <span class="ext-banner-cta" data-i18n="ext.banner.cta">Add to Chrome →</span>
221        </a>
222
223        <main>
224            <!-- IMGMIN_TOOL:BEGIN -->
225<div class="upload-area" id="dropZone" role="button" tabindex="0" aria-label="Upload images">
226                <input type="file" id="fileInput" accept="image/*" hidden multiple>
227                <div class="upload-content">
228                    <svg class="upload-icon" viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="currentColor" stroke-width="1.5">
229                        <path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" stroke-linecap="round" stroke-linejoin="round"/>
230                        <path d="M12 12v9" stroke-linecap="round" stroke-linejoin="round"/>
231                        <path d="m16 16-4-4-4 4" stroke-linecap="round" stroke-linejoin="round"/>
232                    </svg>
233                    <p data-i18n="upload.text">Click, drag or paste images here</p>
234                    <button class="upload-btn" data-i18n="upload.btn">Select Images</button>
235                    <p class="upload-hint" data-i18n="batch.upload.limit"
236                        style="font-size: 0.8rem; opacity: 0.6; margin-top: 0.5rem;">Maximum 20 images at a time</p>
237                </div>
238            </div>
239
240            <div id="resizeChip" class="resize-chip" style="display:none;">
241                <span id="resizeChipText"></span>
242                <button id="resizeChipDismiss" class="resize-chip-dismiss" aria-label="Dismiss">&times;</button>
243            </div>
244
245            <section class="task-toolbar" aria-label="Processing controls">
246                <div class="compression-mode quick-modes" role="group" aria-label="Compression strategy">
247                    <button class="mode-btn active" data-mode="balanced" data-i18n="mode.balanced">Recommended</button>
248                    <button class="mode-btn" data-mode="quality" data-i18n="mode.quality">Clearer</button>
249                    <button class="mode-btn" data-mode="size" data-i18n="mode.size">Smaller file</button>
250                </div>
251            </section>
252
253            <div id="fileList" class="file-list" style="display: none;">
254                <!-- JS will populate file items here -->
255            </div>
256
257            <div id="batchActions" class="batch-actions" style="display: none;">
258                <span id="batchStatusText" class="batch-status" aria-live="polite"></span>
259                <button id="clearAllBtn" class="batch-clear-btn" type="button" data-i18n="batch.clear">Clear All</button>
260                <button id="downloadAllBtn" class="primary-btn" data-i18n="batch.download">Download All</button>
261                <button id="shareRuleBtn" class="batch-clear-btn" type="button" hidden data-i18n="share.rule">Share settings</button>
262            </div>
263
264            <!-- Comparison Modal (triggered by eye icon) -->
265            <div id="comparisonModal" class="comparison-modal" role="dialog" aria-modal="true" aria-label="Image comparison" style="display: none;">
266                <div class="comparison-modal-content">
267                    <button class="modal-close-btn" id="closeComparisonModal">&times;</button>
268
269                    <!-- Mobile-only compact summary bar (hidden on desktop) -->
270                    <div class="mobile-modal-summary" id="mobileModalSummary" style="display:none;">
271                        <div class="mobile-summary-row">
272                            <span class="mobile-summary-label" data-i18n="file.size">Size: </span>
273                            <span id="mobileOrigSize" class="mobile-orig-size"></span>
274                            <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 8h10M9 4l4 4-4 4"/></svg>
275                            <span id="mobileCompSize" class="mobile-comp-size"></span>
276                            <span id="mobileSavePct" class="mobile-save-pct"></span>
277                        </div>
278                        <div class="mobile-summary-resolution">
279                            <span data-i18n="file.resolution">Resolution: </span>
280                            <strong id="mobileResolution"></strong>
281                        </div>
282                    </div>
283
284                    <div class="mobile-preview-tabs" role="tablist" aria-label="Image comparison">
285                        <button type="button" id="mobileOriginalTab" class="mobile-preview-tab" role="tab" aria-selected="false" data-i18n="preview.original">Original</button>
286                        <button type="button" id="mobileCompressedTab" class="mobile-preview-tab active" role="tab" aria-selected="true" data-i18n="preview.compressed">Compressed</button>
287                    </div>
288
289                    <div class="preview-container">
290                        <div class="preview-box preview-box-original">
291                            <h2 data-i18n="preview.original">Original</h2>
292                            <div class="image-wrapper" id="originalWrapper">
293                                <img id="originalImage" alt="Original">
294                            </div>
295                            <div class="file-info">
296                                <div class="file-info-row">
297                                    <span data-i18n="file.size">Size: </span>
298                                    <span id="originalSize">0 KB</span>
299                                </div>
300                                <div class="file-info-row">
301                                    <span data-i18n="file.resolution">Resolution: </span>
302                                    <span id="originalResolution">0 × 0</span>
303                                </div>
304                            </div>
305                        </div>
306
307                        <div class="preview-box preview-box-compressed">
308                            <h2 data-i18n="preview.compressed">Compressed</h2>
309                            <div class="image-wrapper" id="compressedWrapper">
310                                <img id="compressedImage" alt="Compressed">
311                            </div>
312                            <div class="crop-controls" id="cropControls" hidden>
313                                <p data-i18n="crop.instruction">Drag the image to adjust the crop</p>
314                                <div class="crop-actions">
315                                    <button type="button" class="crop-cancel-btn" id="cropCancel" data-i18n="crop.cancel">Cancel</button>
316                                    <button type="button" class="crop-apply-btn" id="cropApply" data-i18n="crop.apply">Apply crop</button>
317                                </div>
318                            </div>
319                            <div class="file-info">
320                                <div class="file-info-row">
321                                    <span data-i18n="file.size">Size: </span>
322                                    <span id="compressedSize">0 KB</span>
323                                </div>
324                                <div class="file-info-row">
325                                    <span data-i18n="file.resolution">Resolution: </span>
326                                    <span id="compressedResolution">0 × 0</span>
327                                </div>
328                            </div>
329                        </div>
330                    </div>
331
332                    <div id="statsReport" class="stats-report" style="display: none;">
333                        <div class="stats-icon">🚀</div>
334                        <div class="stats-content">
335                            <div class="stats-title" data-i18n="stats.title">Compression Analysis</div>
336                            <div class="stats-text" id="statsText"></div>
337                        </div>
338                    </div>
339
340                    <!-- Mobile-only download button inside modal -->
341                    <button class="modal-mobile-download-btn" id="modalMobileDownloadBtn" data-i18n="download.single" style="display:none;">Download Image</button>
342                </div>
343            </div>
344
345            <div class="controls">
346                <div class="options-wrapper">
347                    <details class="advanced-settings">
348                        <summary class="advanced-summary">
349                            <span id="advancedSummaryText" data-i18n="settings.advanced">Advanced Settings</span>
350                            <svg class="arrow-icon" viewBox="0 0 24 24" width="20" height="20">
351                                <path d="M7 10l5 5 5-5z" fill="currentColor" />
352                            </svg>
353                        </summary>
354                        <div class="advanced-content">
355                            <section class="output-controls" aria-label="Output settings">
356                                <div class="target-shortcuts-head">
357                                    <strong data-i18n="output.targetHeading">Target size</strong>
358                                    <span data-i18n="output.targetHint">Choose a common limit or enter a custom value</span>
359                                </div>
360                                <div class="target-shortcuts" role="group" aria-label="Target size shortcuts">
361                                    <button type="button" class="target-shortcut" data-target-kb="50"><strong>50 KB</strong><small data-i18n="output.target50Hint">Avatars / IDs</small></button>
362                                    <button type="button" class="target-shortcut" data-target-kb="100"><strong>100 KB</strong><small data-i18n="output.target100Hint">Web / products</small></button>
363                                    <button type="button" class="target-shortcut" data-target-kb="200"><strong>200 KB</strong><small data-i18n="output.target200Hint">Clearer images</small></button>
364                                </div>
365                                <div class="output-field">
366                                    <label for="targetKB" data-i18n="output.targetCustom">Custom limit (KB, optional)</label>
367                                    <input id="targetKB" type="number" min="1" max="100000" step="1" inputmode="numeric" aria-describedby="outputHint">
368                                </div>
369                                <div class="output-field">
370                                    <label for="outputFormat" data-i18n="output.format">Output format</label>
371                                    <select id="outputFormat">
372                                        <option value="auto" data-i18n="output.auto">Keep original format</option>
373                                        <option value="jpeg">JPEG</option><option value="png">PNG</option><option value="webp">WebP</option>
374                                    </select>
375                                </div>
376                                <div class="output-field" id="jpegBackgroundControl" hidden>
377                                    <label for="jpegBackground" data-i18n="output.background">JPEG transparency background</label>
378                                    <input id="jpegBackground" type="color" value="#ffffff">
379                                </div>
380                                <p id="outputHint" class="output-hint" data-i18n="output.hint">Leave blank for normal compression. 1 KB = 1000 bytes. With a target, JPEG/WebP quality follows the selected strategy. PNG keeps transparency and may exceed the target.</p>
381                                <div class="local-rule-actions">
382                                    <button id="saveRuleBtn" type="button" data-i18n="rule.save">Save these settings</button>
383                                    <button id="reuseRuleBtn" type="button" hidden data-i18n="rule.reuse">Reuse saved settings</button>
384                                </div>
385                            </section>
386
387                            <div class="advanced-options">
388                            </div>
389
390                            <div class="resize-control">
391                                <div class="resize-header">
392                                    <h4 data-i18n="resize.title">Resize</h4>
393                                    <label class="toggle-switch">
394                                        <input type="checkbox" id="resizeEnable" checked>
395                                        <span class="toggle-slider"></span>
396                                    </label>
397                                </div>
398                                <div class="resize-options" id="resizeOptions" style="display:flex;">
399                                    <div class="resize-preset-row">
400                                        <label data-i18n="resize.preset.label">Platform preset</label>
401                                        <select id="resizePreset"></select>
402                                    </div>
403                                    <div class="resize-dims-row">
404                                        <div class="resize-dim-input">
405                                            <label for="resizeWidth" data-i18n="resize.width">Width (px)</label>
406                                            <input type="number" id="resizeWidth" min="1" max="10000" placeholder="px">
407                                        </div>
408                                        <span class="resize-dims-sep">×</span>
409                                        <div class="resize-dim-input">
410                                            <label for="resizeHeight" data-i18n="resize.height">Height (px)</label>
411                                            <input type="number" id="resizeHeight" min="1" max="10000" placeholder="px">
412                                        </div>
413                                    </div>
414                                </div>
415                            </div>
416
417                            <!-- Rename Section -->
418                            <div class="rename-control">
419                                <div class="rename-header">
420                                    <span class="resize-title" data-i18n="rename.title">Batch Rename</span>
421                                    <label class="toggle-switch">
422                                        <input type="checkbox" id="renameEnable">
423                                        <span class="toggle-slider"></span>
424                                    </label>
425                                </div>
426                                <div class="rename-options" id="renameOptions" style="display:none;">
427                                    <div class="rename-row">
428                                        <span class="rename-pair">
429                                            <label class="rename-label" data-i18n="rename.prefix">Prefix</label>
430                                            <input type="text" id="renamePrefix" class="rename-input" placeholder="" maxlength="40">
431                                        </span>
432                                        <span class="rename-pair">
433                                            <label class="rename-label" data-i18n="rename.suffix">Suffix</label>
434                                            <input type="text" id="renameSuffix" class="rename-input" placeholder="" maxlength="40">
435                                        </span>
436                                    </div>
437                                    <div class="rename-row rename-check-row">
438                                        <label class="rename-check-label">
439                                            <input type="checkbox" id="renameKeepOriginal" checked>
440                                            <span data-i18n="rename.keep_original">Keep original name</span>
441                                        </label>
442                                        <label class="rename-check-label">
443                                            <input type="checkbox" id="renameSeq">
444                                            <span data-i18n="rename.sequence">Add number</span>
445                                        </label>
446                                        <span id="renameSeqOptions" style="display:none; align-items:center; gap:.4rem; flex-wrap:wrap;">
447                                            <label class="rename-label" data-i18n="rename.seq_start">Start</label>
448                                            <input type="number" id="renameSeqStart" class="rename-input rename-input-sm" value="1" min="0" max="9999">
449                                            <label class="rename-label" data-i18n="rename.seq_padding">Digits</label>
450                                            <input type="number" id="renameSeqPad" class="rename-input rename-input-sm" value="2" min="1" max="4">
451                                        </span>
452                                    </div>
453                                    <div class="rename-preview" id="renamePreview" style="display:none;">
454                                        <span class="rename-preview-label" data-i18n="rename.preview_label">Preview:</span>
455                                        <span id="renamePreviewText" class="rename-preview-text"></span>
456                                    </div>
457                                </div>
458                            </div>
459
460                            <!-- Watermark Section (hidden until ready to launch) -->
461                            <div class="watermark-control" style="display:none;">
462                                <div class="rename-header">
463                                    <label class="toggle-switch">
464                                        <input type="checkbox" id="watermarkEnable">
465                                        <span class="toggle-slider"></span>
466                                    </label>
467                                    <span class="resize-title" data-i18n="watermark.title">Watermark</span>
468                                </div>
469                                <div class="watermark-options" id="watermarkOptions" style="display:none;">
470                                    <div class="wm-row">
471                                        <label class="rename-label" data-i18n="watermark.text.label">Text</label>
472                                        <input type="text" id="watermarkText" class="rename-input wm-text-input" data-i18n-placeholder="watermark.text.placeholder" placeholder="© All rights reserved" maxlength="60">
473                                    </div>
474                                    <div class="wm-row wm-logo-row">
475                                        <label class="rename-label" data-i18n="watermark.image.label">Logo</label>
476                                        <label class="wm-upload-btn" for="watermarkImageInput">
477                                            <svg viewBox="0 0 20 20" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 3v10M5 8l5-5 5 5"/><path d="M3 15h14"/></svg>
478                                            <span id="watermarkImageLabel" data-i18n="watermark.image.upload">Upload logo</span>
479                                        </label>
480                                        <input type="file" id="watermarkImageInput" accept="image/*" style="display:none;">
481                                        <button id="watermarkImageClear" class="wm-clear-btn" style="display:none;" title="Remove logo">×</button>
482                                    </div>
483                                    <div class="wm-row">
484                                        <label class="rename-label" data-i18n="watermark.opacity">Opacity</label>
485                                        <input type="range" id="watermarkOpacity" min="10" max="100" value="50" class="wm-opacity-slider">
486                                        <span id="watermarkOpacityVal" class="rename-label">50%</span>
487                                    </div>
488                                    <div class="wm-row wm-pos-row">
489                                        <label class="rename-label" data-i18n="watermark.position">Position</label>
490                                        <div class="wm-grid" id="watermarkGrid">
491                                            <button data-pos="top-left" class="wm-cell"></button>
492                                            <button data-pos="top-center" class="wm-cell"></button>
493                                            <button data-pos="top-right" class="wm-cell"></button>
494                                            <button data-pos="center-left" class="wm-cell"></button>
495                                            <button data-pos="center" class="wm-cell"></button>
496                                            <button data-pos="center-right" class="wm-cell"></button>
497                                            <button data-pos="bottom-left" class="wm-cell"></button>
498                                            <button data-pos="bottom-center" class="wm-cell"></button>
499                                            <button data-pos="bottom-right" class="wm-cell active"></button>
500                                        </div>
501                                    </div>
502                                </div>
503                            </div>
504
505                        </div>
506                    </details>
507                </div>
508            </div>
509
510
511    <div class="modal" id="imageModal">
512        <div class="modal-content">
513            <button class="modal-close" aria-label="Close">×</button>
514            <img id="modalImage" alt="Preview">
515        </div>
516    </div>
517            <!-- IMGMIN_TOOL:END -->
518            <section id="globalStats" class="global-stats">
519                <h2 class="stats-section-title" data-i18n="stats.session">Your local totals</h2>
520                <div class="stats-grid">
521                    <div class="stat-item">
522                        <div class="stat-icon">
523                            <svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor"
524                                stroke-width="2">
525                                <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
526                                <circle cx="8.5" cy="8.5" r="1.5"></circle>
527                                <polyline points="21 15 16 10 5 21"></polyline>
528                            </svg>
529                        </div>
530                        <div class="stat-info">
531                            <div class="stat-value"><span id="statImages">0</span><span class="stat-unit" data-i18n="unit.images">images</span></div>
532                            <div class="stat-label" data-i18n="stats.images">Images Processed</div>
533                        </div>
534                    </div>
535                    <div class="stat-item">
536                        <div class="stat-icon">
537                            <svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor"
538                                stroke-width="2">
539                                <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
540                                <polyline points="17 21 17 13 7 13 7 21"></polyline>
541                                <polyline points="7 3 7 8 15 8"></polyline>
542                            </svg>
543                        </div>
544                        <div class="stat-info">
545                            <div class="stat-value"><span id="statSaved">0</span><span class="stat-unit">M</span></div>
546                            <div class="stat-label" data-i18n="stats.saved">Space Saved</div>
547                        </div>
548                    </div>
549                </div>
550                <p class="stats-estimate" data-i18n="stats.estimate">Only successful tasks in this browser are counted. Clearing browser storage resets these totals.</p>
551            </section>
552
553            <!-- BUILD1: Privacy Verification Bar — below globalStats -->
554            <div class="privacy-verify-bar" id="privacyVerifyBar">
555                <div class="pvb-left">
556                    <span class="pvb-dot"></span>
557                    <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" style="flex-shrink:0;opacity:0.7"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="M9 12l2 2 4-4"/></svg>
558                    <span class="pvb-text" data-i18n-html="pvb.text">Your images <strong>never leave your browser</strong> — zero upload, verified.</span>
559                </div>
560                <button class="pvb-toggle" id="pvbToggle" aria-expanded="false" aria-controls="pvbGuide" data-i18n="pvb.toggle">How to verify ›</button>
561            </div>
562            <div class="pvb-guide" id="pvbGuide" hidden>
563                <p><span data-i18n-html="pvb.guide">Open <strong>Chrome DevTools</strong> (F12) → Network tab → filter Img → compress a photo. You will see 0 image upload requests.</span> <a href="/privacy-test/" data-i18n="pvb.guide_link">Try offline demo →</a></p>
564            </div>
565            <!-- END BUILD1 -->
566
567        </main>
568
569    <footer class="footer">
570        <div class="footer-contact">
571            <a href="https://x.com/ai_pmer" target="_blank" rel="noopener noreferrer" class="social-link"
572                data-i18n-title="footer.follow_us">
573                <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
574                    <path
575                        d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z">
576                    </path>
577                </svg>
578            </a>
579        </div>
580
581        <div class="footer-links">
582            <a href="/blog/" data-i18n="footer.blog">Blog</a>
583            <a href="/compress-jpg/" data-i18n="footer.compress_jpg">Compress JPG</a>
584            <a href="/compress-png/" data-i18n="footer.compress_png">Compress PNG</a>
585            <a href="/compress-webp/" data-i18n="footer.compress_webp">Compress WebP</a>
586            <a href="/compress-without-uploading/" data-i18n="footer.no_upload">No-Upload Compressor</a>
587            <a href="./privacy.html" data-i18n="footer.privacy">Privacy</a>
588            <a href="https://pmer.cn" target="_blank" rel="nofollow noopener">More tools by the creator</a>
589        </div>
590
591        <div class="trust-badges">
592            <div class="trust-badge">
593                <svg viewBox="0 0 24 24" class="trust-icon" fill="none" stroke="currentColor" stroke-width="2">
594                    <path d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
595                    <path d="M9 12l2 2 4-4" />
596                </svg>
597                <span data-i18n="trust.local">Local Processing</span>
598            </div>
599            <div class="trust-badge">
600                <svg viewBox="0 0 24 24" class="trust-icon" fill="none" stroke="currentColor" stroke-width="2">
601                    <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
602                    <path d="M9 12l2 2 4-4" />
603                </svg>
604                <span data-i18n="trust.no_upload">Zero Data Upload</span>
605            </div>
606
607        </div>
608
609        <div class="footer-info">
610            <p data-i18n="footer.copyright">© 2026 ImgMin Pro. All rights reserved.</p>
611        </div>
612        <div class="gdpr-notice">
613            <svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" fill="none" stroke-width="2">
614                <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
615            </svg>
616            <span data-i18n="gdpr.text">Client-side processing. No server upload.</span>
617        </div>
618    </footer>
619    </div>
620
621    <!-- BUILD1: pvb toggle — uses i18n for button label, runs after i18n.js -->
622    
622<script>
623    (function(){
624        function initPvb() {
625            var btn = document.getElementById('pvbToggle');
626            var guide = document.getElementById('pvbGuide');
627            if (!btn || !guide) return;
628            btn.addEventListener('click', function(){
629                var open = btn.getAttribute('aria-expanded') === 'true';
630                btn.setAttribute('aria-expanded', String(!open));
631                guide.hidden = open;
632                // Use i18n if available, fallback to English
633                var i18n = window.imgminI18n;
634                btn.textContent = open
635                    ? (i18n ? i18n.t('pvb.toggle') : 'How to verify ›')
636                    : (i18n ? i18n.t('pvb.toggle_close') : 'Hide ✕');
637            });
638        }
639        // Run after DOM + i18n ready
640        if (document.readyState === 'loading') {
641            document.addEventListener('DOMContentLoaded', initPvb);
642        } else {
643            initPvb();
644        }
645    })();
646    </script>
646
647    <!-- END BUILD1 -->
648    
648<script>window.IMGMIN_I18N_LOCK = true;</script>
648
649    
649<script src="/i18n-zh-hant.js?v=20260924b"></script>
649
650    
650<script src="i18n.js?v=20260924c"></script>
650
651    
651<script>
652        // Placed after i18n.js deliberately: document.documentElement.lang is
653        // only correct once I18n.init() has run — on the root "/" page the
654        // language is detected at runtime (cached preference or browser
655        // language), so checking this any earlier would always see the
656        // page's static "en" attribute regardless of what actually renders.
657        // This is baked into all six generated language homepages as-is
658        // (build-i18n.js only rewrites text inside data-i18n attributes,
659        // never 
659<script> content); it only takes effect where the visitor's
660        // language resolves to Chinese, since /zh/remove-background/ is the
661        // only translated variant so far.
662        (function () {
663            var navRemoveBg = document.getElementById('navRemoveBg');
664            if (!navRemoveBg) return;
665            function syncNavRemoveBg() {
666                var isZh = (document.documentElement.lang || '').toLowerCase().indexOf('zh') === 0;
667                navRemoveBg.href = isZh ? '/zh/remove-background/' : '/remove-background/';
668                navRemoveBg.textContent = isZh ? '去背景' : 'Remove BG';
669            }
670            syncNavRemoveBg();
671            // The langSelector dropdown switches language client-side, without
672            // a navigation, so this has to re-run on that event too — not just
673            // once at load.
674            window.addEventListener('imgmin:languagechange', syncNavRemoveBg);
675        })();
676    </script>
676
677    
677<script src="target-encoder.js?v=20260910a"></script>
677
678    
678<script src="analytics.js?v=20260924b"></script>
678
679    
679<script src="script.js?v=20260929a"></script>
679
680    
680<script src="snow.js"></script>
680
681    
681<script>
682    if ('serviceWorker' in navigator) {
683        window.addEventListener('load', () => {
684            navigator.serviceWorker.register('/sw.js').catch(() => {
685                // Offline caching is progressive enhancement; image processing still works online.
686            });
687        });
688    }
689    </script>
689
690    <button id="floatingDownload" data-i18n="batch.download" aria-label="Download all compressed images" aria-hidden="true" tabindex="-1">Download All</button>
691
692    <!-- Cloudflare Web Analytics -->
vendor: 99 bytes, line 692
692<script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "
69220ba486752104a028f3a399fe7cf82c7
vendor: 13 bytes, line 692
692"}'></script>
692<!-- End Cloudflare Web Analytics -->
693</body>
694
695</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.