-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
501 lines (457 loc) · 23 KB
/
index.html
File metadata and controls
501 lines (457 loc) · 23 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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SafeScan IMG2PDF</title>
<meta
name="description"
content="Convert photos into black-and-white scan PDFs entirely in your browser. No login, no upload, no storage."
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Manrope:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="./styles.css?v=20260326-6" />
</head>
<body>
<div class="page-shell">
<header class="hero">
<div class="hero-main">
<div class="hero-copy">
<p class="eyebrow">GitHub Pages Demo</p>
<h1>Turn photos into clean PDF documents right in your browser.</h1>
<p class="lede">
Add receipts, forms, notes, or whiteboard photos and get a ready-to-download PDF in seconds.
Everything happens on your device, so the process stays simple, private, and easy to understand.
</p>
<ul class="trust-list" aria-label="Privacy guarantees">
<li>
<strong>Your files stay on your device</strong>
<span>Your images are not sent to a server.</span>
</li>
<li>
<strong>Nothing is saved by this site</strong>
<span>When you close the page, your files are gone from the app.</span>
</li>
<li>
<strong>No account needed</strong>
<span>Open the page, add your images, and download the PDF.</span>
</li>
</ul>
</div>
</div>
<div class="hero-rail">
<aside class="identity-card founder-card" aria-label="Owner and founder">
<p class="card-label">Owner/Founder</p>
<div class="identity-row identity-row-founder">
<img
class="identity-avatar"
src="https://github.com/brown9804.png?size=160"
alt="Portrait of Timna Brown"
loading="lazy"
/>
<div class="identity-links identity-links-founder" aria-label="Founder profile links">
<a href="https://github.com/brown9804" target="_blank" rel="noopener noreferrer">@brown9804</a>
<a href="https://www.linkedin.com/in/timna-b-939492161/" target="_blank" rel="noopener noreferrer">LinkedIn</a>
</div>
</div>
</aside>
<aside class="identity-card org-card" aria-label="Organization identity">
<p class="card-label">Organization</p>
<div class="identity-rail">
<img
class="identity-avatar identity-avatar-org"
src="https://github.com/Cloud2BR-MSFTLearningHub.png?size=256"
alt="Cloud2BR Open Source Microsoft Cloud Sandbox - Learning Hub organization icon"
loading="lazy"
/>
<a class="identity-org-link" href="https://github.com/Cloud2BR-MSFTLearningHub" target="_blank" rel="noopener noreferrer">Cloud2BR Open Source Microsoft Cloud Sandbox - Learning Hub</a>
</div>
<p class="identity-note">Cloud2BR Open Source Microsoft Cloud Sandbox - Learning Hub. Community demos, learning assets, and lightweight browser tools.</p>
</aside>
<aside class="hero-card" aria-label="How it works">
<p class="card-label">Local processing</p>
<ol>
<li>Choose one or more images.</li>
<li>Tune the black-and-white scan look.</li>
<li>Download a multi-page PDF.</li>
</ol>
<p class="card-note">All processing happens on-device using canvas and client-side PDF generation.</p>
</aside>
</div>
</header>
<main class="workspace">
<section class="panel intake-panel">
<div
id="dropzone"
class="dropzone"
tabindex="0"
role="button"
aria-describedby="dropzone-help"
>
<input id="file-input" type="file" accept="image/*" multiple hidden />
<div class="dropzone-content">
<p class="dropzone-title">Drop images here</p>
<p class="dropzone-subtitle">or click to browse from your device</p>
<p id="dropzone-help" class="dropzone-help">
JPG, PNG, GIF, BMP, WebP, AVIF, SVG, HEIC, HEIF, and TIFF.
</p>
</div>
</div>
<div class="mode-switch" role="group" aria-label="PDF appearance">
<p class="mode-switch-label">PDF appearance</p>
<label class="toggle-row" for="scan-look-toggle">
<span>Use black-and-white scan</span>
<input id="scan-look-toggle" type="checkbox" checked />
</label>
<p class="mode-switch-note">Turn this off to export the original images without black-and-white scan styling.</p>
</div>
<div class="quick-controls">
<label>
<span>Output paper</span>
<select id="page-size">
<option value="a4">A4</option>
<option value="letter">Letter</option>
</select>
</label>
<label>
<span>Mode</span>
<select id="scan-preset">
<option value="clean">Office scanner</option>
<option value="clean-text" selected>Clean text only</option>
<option value="clean-handwriting">Clean handwriting</option>
<option value="classic">Photocopier</option>
<option value="high-contrast">Receipts and forms</option>
<option value="thermal">Dot matrix / thermal receipt</option>
<option value="fax">Fax / 1-bit mono</option>
</select>
</label>
<label>
<span>Download as</span>
<select id="export-format">
<option value="pdf" selected>PDF</option>
<option value="jpg">JPG page set (.zip)</option>
<option value="png">PNG page set (.zip)</option>
</select>
</label>
<label>
<span>Image fit</span>
<select id="image-fit">
<option value="contain" selected>Fit inside page</option>
<option value="cover">Fill page</option>
</select>
</label>
<label class="quick-toggle">
<span>Auto background cleanup</span>
<input id="auto-cleanup" type="checkbox" checked />
</label>
<label class="quick-toggle">
<span>Export visible pages only</span>
<input id="export-visible-only" type="checkbox" />
</label>
<p id="preset-description" class="control-hint" aria-live="polite">
Office scanner keeps pages light and clean with the least texture.
</p>
<p id="preset-recommendation" class="control-hint preset-recommendation" aria-live="polite"></p>
</div>
<details class="preset-comparison-panel">
<summary aria-labelledby="preset-comparison-title">
<div>
<p class="card-label">Live Compare</p>
<h3 id="preset-comparison-title">Preset comparison</h3>
</div>
</summary>
<div class="preset-comparison-body">
<p id="preset-comparison-note" class="section-note">Upload a page to compare presets side by side.</p>
<div id="preset-comparison-grid" class="preset-comparison-grid"></div>
</div>
</details>
<details class="control-disclosure">
<summary>Scan tuning</summary>
<div class="controls-grid">
<label>
<span>Brightness</span>
<input id="brightness" type="range" min="-40" max="40" value="14" />
</label>
<label>
<span>Contrast</span>
<input id="contrast" type="range" min="60" max="180" value="106" />
</label>
<label>
<span>Grain</span>
<input id="grain" type="range" min="0" max="24" value="1" />
</label>
<label>
<span>Edge vignette</span>
<input id="vignette" type="range" min="0" max="40" value="2" />
</label>
<label>
<span>Scanner age</span>
<input id="scanner-age" type="range" min="0" max="100" value="8" />
</label>
<label>
<span>Thermal fade</span>
<input id="thermal-fade" type="range" min="0" max="100" value="0" />
</label>
<label class="checkbox-control">
<span>OCR-first cleanup</span>
<input id="ocr-first-mode" type="checkbox" />
</label>
</div>
</details>
<details class="control-disclosure">
<summary>Advanced export</summary>
<div class="controls-grid controls-grid-secondary">
<label>
<span>Page margin</span>
<input id="page-margin" type="range" min="0" max="20" value="8" />
</label>
<label>
<span>PDF quality</span>
<select id="pdf-quality">
<option value="compact">Compact</option>
<option value="standard" selected>Standard</option>
<option value="high">High</option>
</select>
</label>
</div>
</details>
<details class="control-disclosure">
<summary>OCR and review</summary>
<div class="controls-grid controls-grid-secondary">
<label>
<span>OCR language</span>
<select id="ocr-language">
<option value="eng" selected>English</option>
<option value="eng+spa">English + Spanish</option>
<option value="eng+fra">English + French</option>
<option value="eng+deu">English + German</option>
<option value="eng+por">English + Portuguese</option>
<option value="eng+ita">English + Italian</option>
<option value="eng+nld">English + Dutch</option>
<option value="eng+pol">English + Polish</option>
<option value="eng+tur">English + Turkish</option>
<option value="eng+rus">English + Russian</option>
<option value="eng+ukr">English + Ukrainian</option>
<option value="eng+ara">English + Arabic</option>
<option value="eng+hin">English + Hindi</option>
<option value="eng+jpn">English + Japanese</option>
<option value="eng+kor">English + Korean</option>
<option value="eng+chi_sim">English + Simplified Chinese</option>
</select>
</label>
<label>
<span>Custom OCR code</span>
<input id="ocr-language-custom" type="text" placeholder="Example: eng+spa or deu" spellcheck="false" />
</label>
<label class="checkbox-control">
<span>Select detected blanks first</span>
<input id="select-detected-blanks" type="checkbox" checked />
</label>
</div>
</details>
<details class="control-disclosure automation-disclosure">
<summary>Saved preferences</summary>
<section class="automation-panel" aria-labelledby="automation-title">
<div class="automation-header">
<div>
<p class="section-label">Automation</p>
<h3 id="automation-title">Saved Preferences</h3>
</div>
<p class="section-note">Keep repetitive review and export settings on this device.</p>
</div>
<div class="automation-grid">
<label class="checkbox-control">
<span>Always use visible-only export</span>
<input id="pref-always-visible-export" type="checkbox" />
</label>
<label class="checkbox-control">
<span>Remember OCR language</span>
<input id="pref-remember-ocr-language" type="checkbox" checked />
</label>
<label class="checkbox-control">
<span>Remember review preset</span>
<input id="pref-remember-review-preset" type="checkbox" checked />
</label>
<label class="checkbox-control">
<span>Auto-select after review preset</span>
<input id="pref-auto-select-review-preset" type="checkbox" />
</label>
</div>
<div class="export-preset-bar" role="toolbar" aria-label="Saved export presets">
<button class="export-preset-button" type="button" data-export-preset="archive-pdf">Archive PDF</button>
<button class="export-preset-button" type="button" data-export-preset="visible-review-zip">Visible review ZIP</button>
<button class="export-preset-button" type="button" data-export-preset="ocr-review-mode">OCR review mode</button>
</div>
</section>
</details>
<div class="actions-row">
<button id="download-btn" class="primary-button" disabled>Download PDF</button>
<select id="page-actions-select" class="action-select" disabled>
<option value="" selected disabled>Page actions…</option>
<option value="apply-scan-selected">Apply scan to selected</option>
<option value="clear-scan-selected">Clear selected scan override</option>
<option value="keep-selected">Keep selected only</option>
<option value="remove-blank">Select blank pages</option>
<option value="remove-selected">Remove selected pages</option>
<option value="clear">Clear all pages</option>
</select>
<select id="ocr-actions-select" class="action-select" disabled>
<option value="" selected disabled>Extract text…</option>
<option value="ocr-all">Extract all text</option>
<option value="ocr-visible">Extract visible text</option>
<option value="ocr-selected">Extract selected text</option>
</select>
</div>
<p id="status" class="status" aria-live="polite">
Add images to start building a local black-and-white scan PDF.
</p>
</section>
<section class="panel preview-panel">
<div class="panel-heading">
<div>
<p class="section-label">Pages</p>
<h2>Preview queue</h2>
</div>
<div class="preview-toolbar">
<label>
<span class="sr-only">Page selection</span>
<select id="page-selection-action">
<option value="" selected disabled>Select pages…</option>
<option value="select-all">Select all</option>
<option value="select-scan-overrides">Select scan overrides</option>
<option value="select-perspective">Select perspective corrected</option>
<option value="clear-selection">Clear selection</option>
</select>
</label>
<label>
<span class="sr-only">Page filter</span>
<select id="page-filter-select">
<option value="all">Show all pages</option>
<option value="selected">Selected only</option>
<option value="blank">Blank candidates</option>
<option value="scan-override">Scan overrides</option>
<option value="perspective">Perspective corrected</option>
</select>
</label>
</div>
</div>
<p id="active-filters-summary" class="active-filters-summary">Showing all pages.</p>
<div id="preview-list" class="preview-list" aria-live="polite"></div>
</section>
</main>
</div>
<template id="preview-template">
<article class="preview-card" draggable="true">
<label class="preview-select">
<input class="select-page-toggle" type="checkbox" />
<span>Select page</span>
</label>
<div class="preview-chips">
<span class="preview-badge blank-candidate-badge hidden" title="This page was auto-detected as likely blank during blank-page review.">Blank candidate</span>
<span class="preview-badge scan-override-badge hidden" title="This page uses page-specific scan settings instead of the global scan controls.">Scan override</span>
<span class="preview-badge perspective-badge hidden" title="Perspective correction is enabled for this page to flatten a photographed document.">Perspective corrected</span>
</div>
<div class="preview-frame">
<canvas></canvas>
</div>
<div class="preview-meta">
<div>
<p class="preview-name"></p>
<p class="preview-dimensions"></p>
</div>
<div class="preview-actions">
<button class="icon-button rotate-left-button" type="button" aria-label="Rotate page left">Rotate Left</button>
<button class="icon-button rotate-right-button" type="button" aria-label="Rotate page right">Rotate Right</button>
<button class="icon-button duplicate-button" type="button" aria-label="Duplicate page">Duplicate</button>
<button class="icon-button adjust-button" type="button">Adjust</button>
<button class="icon-button remove-button" type="button" aria-label="Remove page">Remove</button>
</div>
</div>
</article>
</template>
<div id="adjust-overlay" class="adjust-overlay hidden" aria-hidden="true">
<section class="adjust-panel" role="dialog" aria-modal="true" aria-labelledby="adjust-title">
<div class="adjust-header">
<div>
<p class="section-label">Page Adjust</p>
<h2 id="adjust-title">Straighten and crop</h2>
</div>
<button id="adjust-close" class="icon-button" type="button">Close</button>
</div>
<p class="adjust-copy">Use these controls when a form or receipt is tilted, too wide, or has extra background around the page.</p>
<div class="adjust-layout">
<div class="adjust-preview-grid">
<div class="adjust-preview-frame">
<p class="adjust-preview-label">Source view</p>
<div id="adjust-preview-surface" class="adjust-preview-surface">
<canvas id="adjust-preview-canvas"></canvas>
<div id="adjust-crop-summary" class="adjust-crop-summary hidden" aria-live="polite"></div>
<div id="crop-overlay" class="crop-overlay" aria-hidden="true">
<div id="crop-box" class="crop-box" tabindex="0" aria-label="Move crop area">
<button class="crop-handle crop-handle-top" data-edge="top" type="button" aria-label="Adjust crop top"></button>
<button class="crop-handle crop-handle-right" data-edge="right" type="button" aria-label="Adjust crop right"></button>
<button class="crop-handle crop-handle-bottom" data-edge="bottom" type="button" aria-label="Adjust crop bottom"></button>
<button class="crop-handle crop-handle-left" data-edge="left" type="button" aria-label="Adjust crop left"></button>
</div>
</div>
<div id="corner-overlay" class="corner-overlay hidden" aria-hidden="true">
<button class="corner-handle" data-corner="0" type="button" aria-label="Top left corner"></button>
<button class="corner-handle" data-corner="1" type="button" aria-label="Top right corner"></button>
<button class="corner-handle" data-corner="2" type="button" aria-label="Bottom right corner"></button>
<button class="corner-handle" data-corner="3" type="button" aria-label="Bottom left corner"></button>
</div>
</div>
</div>
<div class="adjust-preview-frame">
<p class="adjust-preview-label">Corrected result</p>
<canvas id="adjust-result-canvas"></canvas>
</div>
</div>
<div class="adjust-controls">
<label class="adjust-toggle">
<span>Perspective correct</span>
<input id="adjust-perspective" type="checkbox" />
</label>
<button id="adjust-auto-detect" class="secondary-button" type="button">Auto detect document</button>
<label>
<span>Straighten</span>
<input id="adjust-rotate" type="range" min="-12" max="12" step="0.5" value="0" />
</label>
<label>
<span>Crop top</span>
<input id="adjust-crop-top" type="range" min="0" max="45" step="1" value="0" />
</label>
<label>
<span>Crop right</span>
<input id="adjust-crop-right" type="range" min="0" max="45" step="1" value="0" />
</label>
<label>
<span>Crop bottom</span>
<input id="adjust-crop-bottom" type="range" min="0" max="45" step="1" value="0" />
</label>
<label>
<span>Crop left</span>
<input id="adjust-crop-left" type="range" min="0" max="45" step="1" value="0" />
</label>
<div class="adjust-actions">
<button id="adjust-reset" class="secondary-button" type="button">Reset</button>
<button id="adjust-apply-selected" class="secondary-button" type="button">Apply to Selected Pages</button>
<button id="adjust-done" class="primary-button" type="button">Done</button>
</div>
</div>
</div>
</section>
</div>
<script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.2/dist/jspdf.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/heic2any@0.0.4/dist/heic2any.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/utif@3.1.0/UTIF.min.js"></script>
<script async src="https://docs.opencv.org/4.x/opencv.js"></script>
<script src="./app.js?v=20260326-6" defer></script>
</body>
</html>