-
-
Notifications
You must be signed in to change notification settings - Fork 127
Expand file tree
/
Copy pathindex.html
More file actions
625 lines (559 loc) · 38.4 KB
/
index.html
File metadata and controls
625 lines (559 loc) · 38.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
<!DOCTYPE html>
<html class="animate-0" lang="en">
<head>
<title>MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Optimize images locally and privately by converting and compressing them offline in your browser. Supports JPG, PNG, WebP, HEIC, AVIF, GIF, SVG.">
<meta name="robots" content="noindex, nofollow">
<script>
function setTheme(themeName) {
localStorage.setItem('theme', themeName);
document.documentElement.classList.toggle('theme-dark', themeName === 'theme-dark');
document.documentElement.classList.toggle('theme-light', themeName === 'theme-light');
setTimeout(() => document.documentElement.classList.remove('animate-0'), 300);
}
function toggleTheme() {
document.documentElement.classList.add('animate-0');
setTheme(localStorage.getItem('theme') === 'theme-dark' ? 'theme-light' : 'theme-dark');
}
(function () {
const theme = localStorage.getItem('theme') || 'theme-dark';
setTheme(theme);
})();
</script>
<link rel="stylesheet" type="text/css" href="./assets/css/fonts.css">
<link rel="stylesheet" type="text/css" href="./assets/css/variables.css">
<link rel="stylesheet" type="text/css" href="./assets/css/style.css">
<link rel="apple-touch-icon" sizes="180x180" href="./assets/images/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="./assets/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="./assets/images/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#191919">
</head>
<body>
<div class="body-tint-bottom"></div>
<nav class="top-nav">
<div class="logo-container">
<img class="logo-image" src="./assets/images/symbol-192x192.png" alt="MAZANOKE logo">
<h1 class="logo-text">MAZANOKE</h1>
</div>
<div class="top-nav-actions">
<div class="theme-switch-container">
<label id="themeSwitch" class="switch switch--theme">
<input type="checkbox" onchange="toggleTheme()" id="themeSwitchThumb">
<span class="switch-thumb">
<span class="switch-icon switch-icon--light">
<svg height="16" stroke-linejoin="round" viewBox="0 0 16 16" width="16" style="color: currentcolor;"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.75 0.75V0H7.25V0.75V2V2.75H8.75V2V0.75ZM11.182 3.75732L11.7123 3.22699L12.0659 2.87344L12.5962 2.34311L13.6569 3.40377L13.1265 3.9341L12.773 4.28765L12.2426 4.81798L11.182 3.75732ZM8 10.5C9.38071 10.5 10.5 9.38071 10.5 8C10.5 6.61929 9.38071 5.5 8 5.5C6.61929 5.5 5.5 6.61929 5.5 8C5.5 9.38071 6.61929 10.5 8 10.5ZM8 12C10.2091 12 12 10.2091 12 8C12 5.79086 10.2091 4 8 4C5.79086 4 4 5.79086 4 8C4 10.2091 5.79086 12 8 12ZM13.25 7.25H14H15.25H16V8.75H15.25H14H13.25V7.25ZM0.75 7.25H0V8.75H0.75H2H2.75V7.25H2H0.75ZM2.87348 12.0659L2.34315 12.5962L3.40381 13.6569L3.93414 13.1265L4.28769 12.773L4.81802 12.2426L3.75736 11.182L3.22703 11.7123L2.87348 12.0659ZM3.75735 4.81798L3.22702 4.28765L2.87347 3.9341L2.34314 3.40377L3.4038 2.34311L3.93413 2.87344L4.28768 3.22699L4.81802 3.75732L3.75735 4.81798ZM12.0659 13.1265L12.5962 13.6569L13.6569 12.5962L13.1265 12.0659L12.773 11.7123L12.2426 11.182L11.182 12.2426L11.7123 12.773L12.0659 13.1265ZM8.75 13.25V14V15.25V16H7.25V15.25V14V13.25H8.75Z" fill="currentColor"></path></svg>
</span>
<span class="switch-icon switch-icon--dark">
<svg height="16" stroke-linejoin="round" viewBox="0 0 16 16" width="16" style="color: currentcolor;"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 8.00005C1.5 5.53089 2.99198 3.40932 5.12349 2.48889C4.88136 3.19858 4.75 3.95936 4.75 4.7501C4.75 8.61609 7.88401 11.7501 11.75 11.7501C11.8995 11.7501 12.048 11.7454 12.1953 11.7361C11.0955 13.1164 9.40047 14.0001 7.5 14.0001C4.18629 14.0001 1.5 11.3138 1.5 8.00005ZM6.41706 0.577759C2.78784 1.1031 0 4.22536 0 8.00005C0 12.1422 3.35786 15.5001 7.5 15.5001C10.5798 15.5001 13.2244 13.6438 14.3792 10.9921L13.4588 9.9797C12.9218 10.155 12.3478 10.2501 11.75 10.2501C8.71243 10.2501 6.25 7.78767 6.25 4.7501C6.25 3.63431 6.58146 2.59823 7.15111 1.73217L6.41706 0.577759ZM13.25 1V1.75V2.75L14.25 2.75H15V4.25H14.25H13.25V5.25V6H11.75V5.25V4.25H10.75L10 4.25V2.75H10.75L11.75 2.75V1.75V1H13.25Z" fill="currentColor"></path></svg> </span>
</span>
</label>
</div>
<button id="installPWAPrompt"
class="button-cta button-secondary minw-auto hidden"
onclick="installPWADialog.showModal()">
<svg height="16" stroke-linejoin="round" viewBox="0 0 16 16" width="16" style="color: currentcolor;"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.75 5.25V4.5H7.25V5.25V9.43934L5.78033 7.96967L5.25 7.43934L4.18934 8.5L4.71967 9.03033L7.46967 11.7803C7.76256 12.0732 8.23744 12.0732 8.53033 11.7803L11.2803 9.03033L11.8107 8.5L10.75 7.43934L10.2197 7.96967L8.75 9.43934V5.25ZM1.5 8C1.5 4.41015 4.41015 1.5 8 1.5C11.5899 1.5 14.5 4.41015 14.5 8C14.5 11.5899 11.5899 14.5 8 14.5C4.41015 14.5 1.5 11.5899 1.5 8ZM8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0Z" fill="currentColor"></path></svg>
<span>Install</span>
</button>
</div>
</nav>
<div class="content">
<div class="drop-zone" id="compressDropZone">
<div class="drop-zone__border-dashed"></div>
<div class="drop-zone__cell-grid"></div>
<div class="drop-zone__vignette-outer"></div>
<div class="drop-zone__vignette-inner"></div>
<div class="drop-zone__shade"></div>
<div class="drop-zone__shade-top"></div>
<div class="drop-zone__sheen"></div>
<div class="drop-zone-content">
<div class="drop-zone-content__border-top-fade"></div>
<div class="drop-zone-content__border"></div>
<div id="dropZoneActions" class="drop-zone-content__actions">
<div class="flex flex-col items-center gap-3xs w-100">
<svg width="48" height="48" style="stroke: currentcolor" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path stroke-linejoin="round" stroke-linecap="round" stroke-width="1.6" d="M3.00005 17.0001C3 16.9355 3 16.8689 3 16.8002V7.2002C3 6.08009 3 5.51962 3.21799 5.0918C3.40973 4.71547 3.71547 4.40973 4.0918 4.21799C4.51962 4 5.08009 4 6.2002 4H17.8002C18.9203 4 19.4801 4 19.9079 4.21799C20.2842 4.40973 20.5905 4.71547 20.7822 5.0918C21 5.5192 21 6.07899 21 7.19691V16.8031C21 17.2881 21 17.6679 20.9822 17.9774M3.00005 17.0001C3.00082 17.9884 3.01337 18.5058 3.21799 18.9074C3.40973 19.2837 3.71547 19.5905 4.0918 19.7822C4.5192 20 5.07899 20 6.19691 20H17.8036C18.9215 20 19.4805 20 19.9079 19.7822C20.2842 19.5905 20.5905 19.2837 20.7822 18.9074C20.9055 18.6654 20.959 18.3813 20.9822 17.9774M3.00005 17.0001L7.76798 11.4375L7.76939 11.436C8.19227 10.9426 8.40406 10.6955 8.65527 10.6064C8.87594 10.5282 9.11686 10.53 9.33643 10.6113C9.58664 10.704 9.79506 10.9539 10.2119 11.4541L12.8831 14.6595C13.269 15.1226 13.463 15.3554 13.6986 15.4489C13.9065 15.5313 14.1357 15.5406 14.3501 15.4773C14.5942 15.4053 14.8091 15.1904 15.2388 14.7607L15.7358 14.2637C16.1733 13.8262 16.3921 13.6076 16.6397 13.5361C16.8571 13.4734 17.0896 13.4869 17.2988 13.5732C17.537 13.6716 17.7302 13.9124 18.1167 14.3955L20.9822 17.9774M20.9822 17.9774L21 17.9996M15 10C14.4477 10 14 9.55228 14 9C14 8.44772 14.4477 8 15 8C15.5523 8 16 8.44772 16 9C16 9.55228 15.5523 10 15 10Z"></path></svg>
<div class="flex flex-col items-center gap-3xs w-100">
<div class="drop-zone-content__actions-title">Drop or paste images</div>
<div class="drop-zone-content__actions-description">
<span>jpg</span>
<span>png</span>
<span>webp</span>
<span>heic</span>
<span>avif</span>
<span>tiff</span>
<span>gif</span>
<span>svg</span>
<span>ico</span>
</div>
</div>
</div>
<button class="button-cta button-secondary">
<svg height="16" stroke-linejoin="round" viewBox="0 0 16 16" width="16" style="color: currentcolor;"><path fill-rule="evenodd" clip-rule="evenodd" d="M14.5 7.5V12.5C14.5 13.0523 14.0523 13.5 13.5 13.5H2.5C1.94772 13.5 1.5 13.0523 1.5 12.5V7.5H14.5ZM14.5 6V4H8.83333C8.29241 4 7.76607 3.82456 7.33333 3.5L6 2.5H1.5V6H14.5ZM0 1H1.5H6.16667C6.38304 1 6.59357 1.07018 6.76667 1.2L8.23333 2.3C8.40643 2.42982 8.61696 2.5 8.83333 2.5H14.5H16V4V12.5C16 13.8807 14.8807 15 13.5 15H2.5C1.11929 15 0 13.8807 0 12.5V2.5V1Z" fill="currentColor"></path></svg>
Browse
</button>
</div>
<div class="progress-container hidden">
<div id="compressProgressQueueCount"></div>
<div id="compressProgressTrack">
<div id="compressProgressBar"></div>
</div>
<div id="compressProgressText" data-progress="0"></div>
<div id="compressAbort" class="mt-sm hidden">
<button class="button-cta button-secondary" onclick="abort(event)">Cancel</button>
</div>
</div>
<input id="compress" type="file" accept=".svg, .gif, .jpg, .jpeg, .png, .webp, .heic, .heif, .avif, .jxl, .ico, .tiff" multiple />
</div>
</div>
<nav class="nav-subpage">
<div class="segmented-control-group h6" id="selectSettingsSubpage" data-elevation="2">
<div class="segmented-control segmented-control--is-selected" onclick="selectSubpage('settings')">
<div class="segmented-control__selected-item">
<!-- Selected control indicator -->
</div>
<div class="segmented-control__text">
<svg height="20" width="20" stroke-linejoin="round" style="color:currentColor" viewBox="0 0 16 16" ><path fill-rule="evenodd" clip-rule="evenodd" d="M10.75 5.5C11.7165 5.5 12.5 4.7165 12.5 3.75C12.5 2.7835 11.7165 2 10.75 2C9.7835 2 9 2.7835 9 3.75C9 4.7165 9.7835 5.5 10.75 5.5ZM10.75 0.75C12.1479 0.75 13.3225 1.70608 13.6555 3H15.25H16V4.5H15.25H13.6555C13.3225 5.79392 12.1479 6.75 10.75 6.75C9.35212 6.75 8.17754 5.79392 7.84451 4.5H0.75H0V3H0.75H7.84451C8.17754 1.70608 9.35212 0.75 10.75 0.75ZM15.25 13H16V11.5H15.25L8.15549 11.5C7.82245 10.2061 6.64788 9.25 5.25 9.25C3.85212 9.25 2.67755 10.2061 2.34451 11.5H0.75H0V13H0.75H2.34451C2.67755 14.2939 3.85212 15.25 5.25 15.25C6.64788 15.25 7.82246 14.2939 8.15549 13L15.25 13ZM7 12.2513C7 12.2509 7 12.2504 7 12.25C7 12.2496 7 12.2491 7 12.2487C6.99929 11.2828 6.21606 10.5 5.25 10.5C4.2835 10.5 3.5 11.2835 3.5 12.25C3.5 13.2165 4.2835 14 5.25 14C6.21606 14 6.99929 13.2172 7 12.2513Z" fill="currentColor"></path></svg>
Settings
</div>
<input type="radio" name="settingsSubpage" value="settings" class="hidden" checked>
</div>
<div class="segmented-control" id="subpageOutput" onclick="selectSubpage('output')" data-count="0">
<div class="segmented-control__text">
<svg width="24" height="24" style="stroke: currentcolor" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.00005 18.0001C3 17.9355 3 17.8689 3 17.8002V6.2002C3 5.08009 3 4.51962 3.21799 4.0918C3.40973 3.71547 3.71547 3.40973 4.0918 3.21799C4.51962 3 5.08009 3 6.2002 3H17.8002C18.9203 3 19.4801 3 19.9079 3.21799C20.2842 3.40973 20.5905 3.71547 20.7822 4.0918C21 4.5192 21 5.07899 21 6.19691V17.8031C21 18.2881 21 18.6679 20.9822 18.9774M3.00005 18.0001C3.00082 18.9884 3.01337 19.5058 3.21799 19.9074C3.40973 20.2837 3.71547 20.5905 4.0918 20.7822C4.5192 21 5.07899 21 6.19691 21H17.8036C18.9215 21 19.4805 21 19.9079 20.7822C20.2842 20.5905 20.5905 20.2837 20.7822 19.9074C20.9055 19.6654 20.959 19.3813 20.9822 18.9774M3.00005 18.0001L7.76798 12.4375L7.76939 12.436C8.19227 11.9426 8.40406 11.6955 8.65527 11.6064C8.87594 11.5282 9.11686 11.53 9.33643 11.6113C9.58664 11.704 9.79506 11.9539 10.2119 12.4541L12.8831 15.6595C13.269 16.1226 13.463 16.3554 13.6986 16.4489C13.9065 16.5313 14.1357 16.5406 14.3501 16.4773C14.5942 16.4053 14.8091 16.1904 15.2388 15.7607L15.7358 15.2637C16.1733 14.8262 16.3921 14.6076 16.6397 14.5361C16.8571 14.4734 17.0896 14.4869 17.2988 14.5732C17.537 14.6716 17.7302 14.9124 18.1167 15.3955L20.9822 18.9774M20.9822 18.9774L21 18.9996M15 9C14.4477 9 14 8.55228 14 8C14 7.44772 14.4477 7 15 7C15.5523 7 16 7.44772 16 8C16 8.55228 15.5523 9 15 9Z" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span>
Images
<span class="badge">
<span id="compressedImageCount" class="badge-text">
0
</span>
</span>
</span>
</div>
<input type="radio" name="settingsSubpage" value="output" class="hidden">
</div>
</div>
</nav>
<section class="image-subpage-container">
<div class="image-subpage">
<div class="image-options-container" data-elevation="2">
<div class="image-subpage-title-container">
<h2 class="h4">Settings</h2>
<div class="image-subpage-title-description">Images are processed locally on your device, ensuring privacy.</div>
</div>
<div id="compressMethodGroup" class="form-group">
<div class="form-group__label-content">
<label class="form-group__label-text">
Optimization method
</label>
</div>
<div class="form-group__input-content">
<div class="button-card-radio-group">
<div class="button-card-radio button-card-radio--is-selected" onclick="setCompressMethod('quality')">
<div class="button-card-radio__content">
<div class="flex gap-sm w-100">
<label class="button-card-radio__label">Set image quality</label>
<div class="button-card-radio__radio-input">
<div class="button-card-radio__radio-icon"></div>
<input class="hidden" type="radio" name="compressMethod" value="quality" checked>
</div>
</div>
<div class="button-card-radio__description">
<div class="button-card-radio__description-text">
Higher values retain more detail, while lower values result in smaller file sizes.
</div>
</div>
</div>
</div>
<div class="button-card-radio" onclick="setCompressMethod('limitWeight')">
<div class="button-card-radio__content">
<div class="flex gap-sm w-100">
<label class="button-card-radio__label">Limit file size</label>
<div class="button-card-radio__radio-input">
<div class="button-card-radio__radio-icon"></div>
<input class="hidden" type="radio" name="compressMethod" value="limitWeight">
</div>
</div>
<div class="button-card-radio__description">
<div class="button-card-radio__description-text">
Try to compress the image to a target file size.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="qualityField" class="form-group hidden">
<div class="form-group__label-content">
<label class="form-group__label-text" for="quality">
<!-- Quality -->
</label>
</div>
<div class="form-group__input-content">
<div class="fade-in-up w-100">
<label class="mb-2xs">Quality</label>
<div class="flex items-center gap-sm flex-wrap">
<div class="form-field-container form-field-container--suffix">
<input type="number" id="quality" name="quality" value="80" min="0" max="100" step="1" oninput="setSlider(this.value, 'qualitySlider')" />
<label for="quality" class="form-field-suffix">
%
</label>
</div>
<div class="slider-track flex-1" id="qualitySlider" onmousedown="startSliderDrag(event, 'quality')">
<div class="slider-fill"></div>
<div class="slider-thumb"></div>
</div>
</div>
</div>
</div>
</div>
<div id="limitWeightField" class="form-group hidden">
<div class="form-group__label-content">
<label class="form-group__label-text" for="maxWeight">
<!-- Target file size -->
</label>
</div>
<div class="form-group__input-content">
<div class="fade-in-up w-100">
<label class="mb-2xs">Target file size</label>
<div class="form-field-container">
<div class="flex flex-wrap gap-xs w-100">
<div class="flex-1" style="min-width: 200px">
<div class="form-field-container form-field-container--suffix w-100">
<input type="number" id="limitWeight" name="limitWeight" value="2" step="0.1" class="w-100" />
<label for="limitWeight" class="form-field-suffix" data-suffix="MB">MB</label>
</div>
</div>
<div class="flex-1" style="min-width: 200px">
<select id="limitWeightUnit" class="w-100">
<option value="MB">Megabytes (MB)</option>
<option value="KB">Kilobytes (KB)</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="form-group mt-lg">
<div class="form-group__label-content">
<label class="form-group__label-text" for="limitDimensions">
Dimensions
</label>
<div class="form-group__description">
</div>
</div>
<div class="form-group__input-content">
<div id="dimensionsMethodGroup" class="button-card-radio-group">
<div class="button-card-radio button-card-radio--is-selected" onclick="setDimensionMethod('original')">
<div class="button-card-radio__content">
<div class="flex gap-sm w-100">
<label class="button-card-radio__label">Keep original dimensions</label>
<div class="button-card-radio__radio-input">
<div class="button-card-radio__radio-icon"></div>
<input class="hidden" type="radio" name="dimensionMethod" value="original" checked>
</div>
</div>
<div class="button-card-radio__description">
<div class="button-card-radio__description-text">
Do not alter the width and height of the image.
</div>
</div>
</div>
</div>
<div class="button-card-radio" onclick="setDimensionMethod('limit')">
<div class="button-card-radio__content">
<div class="flex gap-sm w-100">
<label class="button-card-radio__label">Limit dimensions</label>
<div class="button-card-radio__radio-input">
<div class="button-card-radio__radio-icon"></div>
<input class="hidden" type="radio" name="dimensionMethod" value="limit">
</div>
</div>
<div class="button-card-radio__description">
<div class="button-card-radio__description-text">
Limit the width and height of the image.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="form-group hidden" id="resizeDimensionsField">
<div class="form-group__label-content">
<label class="form-group__label-text" for="limitDimensions">
<!-- Max dimension -->
</label>
</div>
<div class="form-group__input-content">
<div class="fade-in-up w-100">
<label class="mb-2xs">Limit dimensions</label>
<div class="form-field-container form-field-container--suffix">
<input type="number" id="limitDimensions" name="limitDimensions" value="1200" step="50" class="w-100" />
<label for="limitDimensions" class="form-field-suffix">
px
</label>
</div>
</div>
</div>
</div>
<div id="formatMethodGroup" class="form-group mt-lg">
<div class="form-group__label-content">
<label class="form-group__label-text">
Convert to
</label>
</div>
<div class="form-group__input-content">
<div class="button-card-radio-group">
<div class="button-card-radio button-card-radio--is-selected basis-full" onclick="setConvertMethod('default')">
<div class="button-card-radio__content">
<div class="flex gap-sm w-100">
<label class="button-card-radio__label">Default</label>
<div class="button-card-radio__radio-input">
<div class="button-card-radio__radio-icon"></div>
<input class="hidden" type="radio" name="formatSelect" value="default" checked>
</div>
</div>
<div class="button-card-radio__description">
<div class="button-card-radio__description-text">
<div class="flex flex-col gap-3xs">
<div>• JPG, PNG, WebP, ICO → Unchanged.</div>
<div>• HEIC, AVIF, TIFF, GIF, SVG → PNG.</div>
</div>
</div>
</div>
</div>
</div>
<div class="button-card-radio" onclick="setConvertMethod('image/jpeg')">
<div class="button-card-radio__content">
<div class="flex gap-sm w-100">
<label class="button-card-radio__label">JPG</label>
<div class="button-card-radio__radio-input">
<div class="button-card-radio__radio-icon"></div>
<input class="hidden" type="radio" name="formatSelect" value="image/jpeg">
</div>
</div>
<div class="button-card-radio__description">
<div class="button-card-radio__description-text">
Effective at reducing file size of large photos and images, with some quality loss.
</div>
</div>
</div>
</div>
<div class="button-card-radio" onclick="setConvertMethod('image/png')">
<div class="button-card-radio__content">
<div class="flex gap-sm w-100">
<label class="button-card-radio__label">PNG</label>
<div class="button-card-radio__radio-input">
<div class="button-card-radio__radio-icon"></div>
<input class="hidden" type="radio" name="formatSelect" value="image/png">
</div>
</div>
<div class="button-card-radio__description">
<div class="button-card-radio__description-text">
Preserves image sharpness, colors, and transparency, but may result in large files.
</div>
</div>
</div>
</div>
<div class="button-card-radio" onclick="setConvertMethod('image/webp')">
<div class="button-card-radio__content">
<div class="flex gap-sm w-100">
<label class="button-card-radio__label">WebP</label>
<div class="button-card-radio__radio-input">
<div class="button-card-radio__radio-icon"></div>
<input class="hidden" type="radio" name="formatSelect" value="image/webp">
</div>
</div>
<div class="button-card-radio__description">
<div class="button-card-radio__description-text">
Retains good details and transparency, may affect colors slightly at low quality settings.
</div>
</div>
</div>
</div>
<div class="button-card-radio" onclick="setConvertMethod('image/vnd.microsoft.icon')">
<div class="button-card-radio__content">
<div class="flex gap-sm w-100">
<label class="button-card-radio__label">ICO</label>
<div class="button-card-radio__radio-input">
<div class="button-card-radio__radio-icon"></div>
<input class="hidden" type="radio" name="formatSelect" value="image/vnd.microsoft.icon">
</div>
</div>
<div class="button-card-radio__description">
<div class="button-card-radio__description-text">
Mainly used for website favicons and Windows file icons
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="outputDownloadContainer" class="image-output-container" data-count="0" data-elevation="2">
<div class="image-subpage-title-container">
<div class="image-subpage-title-content">
<div class="image-subpage-title__text">
<h2 class="h4">Images</h2>
<div class="image-subpage-title-description">Optimized images ready for review and download.</div>
</div>
<div id="imageOutputActionsContainer" class="image-output__actions-container">
<button id="deleteAllImagesButton" class="button-cta button-secondary" onclick="deleteAllImages()">
<svg height="16" stroke-linejoin="round" viewBox="0 0 16 16" width="16" style="color: currentcolor;"><path fill-rule="evenodd" clip-rule="evenodd" d="M6.75 2.75C6.75 2.05964 7.30964 1.5 8 1.5C8.69036 1.5 9.25 2.05964 9.25 2.75V3H6.75V2.75ZM5.25 3V2.75C5.25 1.23122 6.48122 0 8 0C9.51878 0 10.75 1.23122 10.75 2.75V3H12.9201H14.25H15V4.5H14.25H13.8846L13.1776 13.6917C13.0774 14.9942 11.9913 16 10.6849 16H5.31508C4.00874 16 2.92263 14.9942 2.82244 13.6917L2.11538 4.5H1.75H1V3H1.75H3.07988H5.25ZM4.31802 13.5767L3.61982 4.5H12.3802L11.682 13.5767C11.6419 14.0977 11.2075 14.5 10.6849 14.5H5.31508C4.79254 14.5 4.3581 14.0977 4.31802 13.5767Z" fill="currentColor"></path></svg>
<span>Delete all</span>
</button>
<button id="downloadAllImagesButton" class="button-cta button-primary" onclick="downloadAllImages()" aria-busy="false">
<span class="flex aria-busy:visible">
<svg width="16" height="16" style="fill: currentcolor;" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_z9k8{transform-origin:center;animation:spinner_StKS .75s infinite linear}@keyframes spinner_StKS{100%{transform:rotate(360deg)}}</style><path d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" opacity=".25"/><path d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z" class="spinner_z9k8"/></svg></span>
<span class="flex aria-busy:hidden">
<svg height="16" stroke-linejoin="round" viewBox="0 0 16 16" width="16" style="color: currentcolor;"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.75 1V1.75V8.68934L10.7197 6.71967L11.25 6.18934L12.3107 7.25L11.7803 7.78033L8.70711 10.8536C8.31658 11.2441 7.68342 11.2441 7.29289 10.8536L4.21967 7.78033L3.68934 7.25L4.75 6.18934L5.28033 6.71967L7.25 8.68934V1.75V1H8.75ZM13.5 9.25V13.5H2.5V9.25V8.5H1V9.25V14C1 14.5523 1.44771 15 2 15H14C14.5523 15 15 14.5523 15 14V9.25V8.5H13.5V9.25Z" fill="currentColor"></path></svg>
</span>
<span>Download all</span>
</button>
</div>
</div>
</div>
<div id="imageOutputEmptyState" class="image-output__empty-state is-active">
<div class="flex flex-col justify-center items-center gap-xs pt-3xl fade-in-up">
<svg width="64" height="64" style="stroke: currentcolor" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path stroke-linejoin="round" stroke-linecap="round" stroke-width="1.6" d="M3.00005 17.0001C3 16.9355 3 16.8689 3 16.8002V7.2002C3 6.08009 3 5.51962 3.21799 5.0918C3.40973 4.71547 3.71547 4.40973 4.0918 4.21799C4.51962 4 5.08009 4 6.2002 4H17.8002C18.9203 4 19.4801 4 19.9079 4.21799C20.2842 4.40973 20.5905 4.71547 20.7822 5.0918C21 5.5192 21 6.07899 21 7.19691V16.8031C21 17.2881 21 17.6679 20.9822 17.9774M3.00005 17.0001C3.00082 17.9884 3.01337 18.5058 3.21799 18.9074C3.40973 19.2837 3.71547 19.5905 4.0918 19.7822C4.5192 20 5.07899 20 6.19691 20H17.8036C18.9215 20 19.4805 20 19.9079 19.7822C20.2842 19.5905 20.5905 19.2837 20.7822 18.9074C20.9055 18.6654 20.959 18.3813 20.9822 17.9774M3.00005 17.0001L7.76798 11.4375L7.76939 11.436C8.19227 10.9426 8.40406 10.6955 8.65527 10.6064C8.87594 10.5282 9.11686 10.53 9.33643 10.6113C9.58664 10.704 9.79506 10.9539 10.2119 11.4541L12.8831 14.6595C13.269 15.1226 13.463 15.3554 13.6986 15.4489C13.9065 15.5313 14.1357 15.5406 14.3501 15.4773C14.5942 15.4053 14.8091 15.1904 15.2388 14.7607L15.7358 14.2637C16.1733 13.8262 16.3921 13.6076 16.6397 13.5361C16.8571 13.4734 17.0896 13.4869 17.2988 13.5732C17.537 13.6716 17.7302 13.9124 18.1167 14.3955L20.9822 17.9774M20.9822 17.9774L21 17.9996M15 10C14.4477 10 14 9.55228 14 9C14 8.44772 14.4477 8 15 8C15.5523 8 16 8.44772 16 9C16 9.55228 15.5523 10 15 10Z"></path></svg>
<div class="flex flex-col gap-2xs">
<h3 class="h5">No image optimized yet</h3>
<p>Upload images above to optimize them. Once compressed, they'll appear here.</p>
</div>
</div>
</div>
<div id="outputDownloadContent" class="image-output-content"></div>
</div>
</div>
</section>
</div>
<dialog id="installPWADialog" data-elevation="2">
<h2 class="h4">Install MAZANOKE</h2>
<div class="pl-sm pr-sm mt-md mb-md text-md" style="max-width: 360px;">
<div class="mb-sm">
<div class="flex gap-2xs">
<svg height="24" width="24" stroke-linejoin="round" viewBox="0 0 16 16"style="color: currentcolor;"><path fill-rule="evenodd" clip-rule="evenodd" d="M8 0.154663L8.34601 0.334591L14.596 3.58459L15 3.79466V4.25V11.75V12.2053L14.596 12.4154L8.34601 15.6654L8 15.8453L7.65399 15.6654L1.40399 12.4154L1 12.2053V11.75V4.25V3.79466L1.40399 3.58459L7.65399 0.334591L8 0.154663ZM2.5 11.2947V5.44058L7.25 7.81559V13.7647L2.5 11.2947ZM8.75 13.7647L13.5 11.2947V5.44056L8.75 7.81556V13.7647ZM8 1.84534L12.5766 4.22519L7.99998 6.51352L3.42335 4.2252L8 1.84534Z" fill="currentColor"></path></svg>
<p>An app shortcut is added to your device.</p>
</div>
</div>
<div class="mb-sm">
<div class="flex gap-2xs">
<svg height="24" width="24" stroke-linejoin="round" viewBox="0 0 16 16" style="color: currentcolor;"><path fill-rule="evenodd" clip-rule="evenodd" d="M1 2.5C7.90356 2.5 13.5 8.09644 13.5 15H15C15 7.26801 8.73199 1 1 1V2.5ZM8 15C8 11.134 4.86599 8 1 8V6.5C5.69442 6.5 9.5 10.3056 9.5 15H8ZM2.5 15C2.5 14.1716 1.82843 13.5 1 13.5V12C2.65685 12 4 13.3431 4 15H2.5Z" fill="currentColor"></path></svg>
<p>Use even without an internet connection.</p>
</div>
</div>
<div class="mb-sm">
<div class="flex gap-2xs">
<svg height="24" width="24" stroke-linejoin="round" viewBox="0 0 16 16" style="color: currentcolor;"><path fill-rule="evenodd" clip-rule="evenodd" d="M10 4.5V6H6V4.5C6 3.39543 6.89543 2.5 8 2.5C9.10457 2.5 10 3.39543 10 4.5ZM4.5 6V4.5C4.5 2.567 6.067 1 8 1C9.933 1 11.5 2.567 11.5 4.5V6H12.5H14V7.5V12.5C14 13.8807 12.8807 15 11.5 15H4.5C3.11929 15 2 13.8807 2 12.5V7.5V6H3.5H4.5ZM11.5 7.5H10H6H4.5H3.5V12.5C3.5 13.0523 3.94772 13.5 4.5 13.5H11.5C12.0523 13.5 12.5 13.0523 12.5 12.5V7.5H11.5Z" fill="currentColor"></path></svg>
<p>Whether on the web or app, your images are always processed privately on-device.
</p>
</div>
</div>
</div>
<div class="button-group button-group--col justify-end mt-md">
<button class="button-cta button-secondary minw-auto" onclick="installPWADialog.close()">
Cancel
</button>
<button id="installPWA" class="button-cta button-primary">
<svg height="16" stroke-linejoin="round" viewBox="0 0 16 16" width="16" style="color: currentcolor;"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.75 5.25V4.5H7.25V5.25V9.43934L5.78033 7.96967L5.25 7.43934L4.18934 8.5L4.71967 9.03033L7.46967 11.7803C7.76256 12.0732 8.23744 12.0732 8.53033 11.7803L11.2803 9.03033L11.8107 8.5L10.75 7.43934L10.2197 7.96967L8.75 9.43934V5.25ZM1.5 8C1.5 4.41015 4.41015 1.5 8 1.5C11.5899 1.5 14.5 4.41015 14.5 8C14.5 11.5899 11.5899 14.5 8 14.5C4.41015 14.5 1.5 11.5899 1.5 8ZM8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0Z" fill="currentColor"></path></svg>
<span>Install</span>
</button>
</div>
</dialog>
<div id="updateToast" class="toast hidden" data-elevation="3">
<div class="toast-content">
<div class="toast-title">Update available</div>
<div class="toast-message">Refresh the page to update.</div>
</div>
<div class="toast-actions">
<button class="button-cta button-secondary minw-auto" onclick="updateToast.classList.add('hidden')">Ignore</button>
<button id="updateToastRefreshButton" class="button-cta button-secondary minw-auto">Refresh</button>
</div>
</div>
<footer>
<a class="footer-text" href="https://github.com/civilblur/mazanoke" target="_blank" rel="noopener noreferrer">
<span>created by civilblur</span>
<!-- app-version-placeholder -->
</a>
</footer>
<button id="backToTop">
<svg height="16" stroke-linejoin="round" style="color:currentColor" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.70711 1.39644C8.31659 1.00592 7.68342 1.00592 7.2929 1.39644L2.21968 6.46966L1.68935 6.99999L2.75001 8.06065L3.28034 7.53032L7.25001 3.56065V14.25V15H8.75001V14.25V3.56065L12.7197 7.53032L13.25 8.06065L14.3107 6.99999L13.7803 6.46966L8.70711 1.39644Z" fill="currentColor"></path></svg>
</button>
<script type="text/javascript" src="./assets/vendor/browser-image-compression.js"></script>
<script type="text/javascript" src="./assets/vendor/heic-to.js"></script>
<script type="text/javascript" src="./assets/vendor/libheif.js"></script>
<script type="text/javascript" src="./assets/vendor/ico.js"></script>
<script type="text/javascript" src="./assets/vendor/png2ico.js"></script>
<script type="text/javascript" src="./assets/vendor/utif.js"></script>
<script type="text/javascript" src="./assets/vendor/jszip.js"></script>
<script type="text/javascript" src="./assets/js/global.js"></script>
<script type="text/javascript" src="./assets/js/utilities.js"></script>
<script type="text/javascript" src="./assets/js/helpers.js"></script>
<script type="text/javascript" src="./assets/js/ui.js"></script>
<script type="text/javascript" src="./assets/js/compression.js"></script>
<script type="text/javascript" src="./assets/js/download.js"></script>
<script type="text/javascript" src="./assets/js/events.js"></script>
<script>
if ('serviceWorker' in navigator) {
function promptUpdate(registration) {
updateToast.classList.remove('hidden');
updateToastRefreshButton.addEventListener('click', () => {
if (registration.waiting) {
registration.waiting.postMessage('SKIP_WAITING')
}
})
}
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
const registration = await navigator.serviceWorker.register('/service-worker.js')
if (registration.waiting) {
promptUpdate(registration)
}
registration.addEventListener('updatefound', () => {
if (registration.installing) {
registration.installing.addEventListener('statechange', () => {
if (registration.waiting) {
if (navigator.serviceWorker.controller) {
promptUpdate(registration)
} else {
console.log('Service Worker initialized')
}
}
})
}
})
let refreshing = false;
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (!refreshing) {
window.location.reload();
refreshing = true;
}
})
})
}
}
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
document.getElementById('installPWAPrompt').classList.remove('hidden');
});
document.getElementById('installPWA').addEventListener('click', async () => {
if (deferredPrompt) {
deferredPrompt.prompt();
await deferredPrompt.userChoice;
deferredPrompt = null;
}
});
window.addEventListener('appinstalled', (event) => {
document.getElementById('installPWAPrompt').classList.add('hidden');
installPWADialog.close();
});
</script>
</body>
</html>