File size: 29,988 Bytes
73af74c 034e081 73af74c 1adf57b 73af74c 1adf57b 73af74c 034e081 73af74c 034e081 1adf57b 73af74c 1adf57b 73af74c 1adf57b 034e081 73af74c 034e081 1adf57b c3213ca 1adf57b 73af74c 1adf57b 73af74c 1adf57b 73af74c 1adf57b 73af74c 034e081 73af74c c3213ca 73af74c 034e081 73af74c | 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 | <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#10152b">
<title>Protect PDF — PDFix</title>
<meta name="description" content="Protect PDF files with password encryption and configurable permissions in your browser.">
<link rel="preconnect" href="https://cdnjs.cloudflare.com">
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf-lib/1.17.1/pdf-lib.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@pdfsmaller/pdf-encrypt@1.2.0/dist/pdf-encrypt.umd.js"></script>
<style>
:root{--bg:#f5f7fc;--surface:#fff;--ink:#171c35;--muted:#7c849f;--line:#e6eaf3;--primary:#6558ed;--primary2:#877bff;--mint:#22b89a;--danger:#e45469;--shadow:0 18px 55px rgba(33,43,89,.08);--radius:22px}
*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 12% 0%,#eceaff 0,transparent 30%),radial-gradient(circle at 90% 10%,#e5faf6 0,transparent 25%),var(--bg);font-family:"DM Sans",sans-serif;color:var(--ink);min-height:100vh}button,input,select{font:inherit}button{cursor:pointer}.shell{max-width:1220px;margin:auto;padding:24px 26px 60px}.topbar{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-bottom:44px}.brand{display:flex;align-items:center;gap:12px;text-decoration:none;color:inherit}.brandmark{width:44px;height:44px;border-radius:15px;background:linear-gradient(135deg,#786bff,#5145d7);display:grid;place-items:center;color:white;box-shadow:0 8px 20px #6658ed44}.brandmark svg{width:24px}.brandname{font:800 22px Manrope,sans-serif;letter-spacing:-.8px}.brandname span{color:var(--primary)}.secure-pill{display:flex;align-items:center;gap:8px;background:#fff;border:1px solid var(--line);padding:10px 14px;border-radius:99px;color:#56607b;font-size:12px;font-weight:700}.secure-pill i{width:8px;height:8px;border-radius:50%;background:var(--mint);box-shadow:0 0 0 4px #22b89a1b}.hero{text-align:center;margin:0 auto 34px;max-width:760px}.eyebrow{display:inline-flex;align-items:center;gap:8px;color:var(--primary);font-size:11px;font-weight:800;letter-spacing:1.5px;text-transform:uppercase;background:#eeecff;border:1px solid #e0dcff;border-radius:99px;padding:8px 12px}.hero h1{font:800 clamp(34px,5vw,55px)/1.08 Manrope,sans-serif;letter-spacing:-2.5px;margin:18px 0 14px}.hero h1 em{font-style:normal;background:linear-gradient(90deg,#6558ed,#a16aff);-webkit-background-clip:text;background-clip:text;color:transparent}.hero p{margin:0 auto;max-width:600px;color:var(--muted);font-size:15px;line-height:1.75}.workspace{display:grid;grid-template-columns:minmax(0,1.45fr) minmax(320px,.85fr);gap:22px;align-items:start}.panel{background:var(--surface);border:1px solid #e8ebf4;border-radius:var(--radius);box-shadow:var(--shadow);overflow:hidden}.panel-head{padding:23px 25px 18px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;gap:14px}.panel-title{font:800 16px Manrope,sans-serif;letter-spacing:-.3px}.panel-sub{font-size:12px;color:var(--muted);margin-top:5px}.step{font-size:11px;font-weight:800;color:var(--primary);background:#f0efff;padding:8px 11px;border-radius:10px;white-space:nowrap}.panel-body{padding:24px}.dropzone{border:1.5px dashed #c9c9ee;border-radius:18px;background:linear-gradient(135deg,#fafaff,#f5fffc);min-height:240px;display:flex;align-items:center;justify-content:center;text-align:center;padding:26px;transition:.2s}.dropzone.drag{border-color:var(--primary);background:#f0efff;transform:scale(.995)}.drop-inner{max-width:380px}.upload-icon{width:58px;height:58px;margin:0 auto 15px;display:grid;place-items:center;border-radius:18px;color:var(--primary);background:#ebe9ff}.upload-icon svg{width:28px}.dropzone h3{margin:0 0 8px;font:800 16px Manrope,sans-serif}.dropzone p{margin:0 0 17px;color:var(--muted);font-size:12px;line-height:1.65}.btn{border:0;border-radius:12px;padding:12px 17px;font-weight:800;font-size:12px;transition:transform .15s,box-shadow .15s}.btn:hover{transform:translateY(-1px)}.btn-primary{color:#fff;background:linear-gradient(135deg,#7467ff,#5749dd);box-shadow:0 9px 20px #6558ed30}.btn-light{background:#f3f4fa;color:#535c79;border:1px solid var(--line)}.file-card{display:none;align-items:center;gap:14px;padding:15px;border:1px solid var(--line);border-radius:15px;background:#fbfcff;margin-top:16px}.file-icon{width:44px;height:48px;display:grid;place-items:center;border-radius:11px;background:#fff0f1;color:#e45265;flex:none}.file-icon svg{width:22px}.file-info{min-width:0;flex:1}.file-name{font-weight:800;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.file-meta{color:var(--muted);font-size:11px;margin-top:5px}.remove-btn{border:0;background:#f5f6fb;color:#8a91a9;width:32px;height:32px;border-radius:10px;font-size:19px}.section-label{font:800 12px Manrope,sans-serif;margin:24px 0 13px;display:flex;align-items:center;justify-content:space-between;gap:12px}.section-label small{font:500 11px "DM Sans";color:var(--muted)}.field{margin-bottom:15px}.field label{display:flex;justify-content:space-between;gap:10px;font-size:12px;font-weight:700;margin-bottom:8px;color:#424a68}.field input[type=password],.field input[type=text],.field input[type=number],.field select{width:100%;padding:13px 14px;border:1px solid #e1e5f0;border-radius:11px;outline:none;color:var(--ink);background:#fff;transition:.2s}.field input:focus,.field select:focus{border-color:#8d82ff;box-shadow:0 0 0 3px #6558ed12}.input-wrap{position:relative}.input-wrap input{padding-right:48px!important}.eye{position:absolute;right:7px;top:7px;border:0;background:#f4f5fb;border-radius:8px;width:34px;height:34px;color:#69718d}.strength{display:flex;gap:5px;margin-top:9px}.strength span{height:4px;border-radius:5px;background:#e7eaf3;flex:1}.strength span.on.weak{background:#e45b6d}.strength span.on.medium{background:#efb84c}.strength span.on.strong{background:#20b99a}.strength-copy{font-size:10px;color:var(--muted);margin-top:6px}.checkline{display:flex;align-items:flex-start;gap:10px;padding:12px 13px;border:1px solid var(--line);border-radius:12px;margin:10px 0;background:#fcfcff}.checkline input{accent-color:var(--primary);margin-top:2px}.checkline strong{display:block;font-size:12px}.checkline small{display:block;color:var(--muted);font-size:11px;line-height:1.5;margin-top:4px}.permissions{display:grid;grid-template-columns:1fr 1fr;gap:10px}.permission{display:flex;align-items:flex-start;gap:9px;padding:12px;border:1px solid var(--line);border-radius:12px;background:#fff}.permission input{accent-color:var(--primary);margin-top:2px}.permission strong{font-size:11px;display:block}.permission small{display:block;font-size:10px;color:var(--muted);margin-top:4px;line-height:1.4}.info-note{font-size:11px;color:#6c7290;line-height:1.65;background:#f5f6ff;border:1px solid #e8e8ff;border-radius:12px;padding:12px 14px;margin-top:14px}.info-note b{color:#4f49b8}.side-panel .panel-body{padding:22px}.preview-card{border:1px solid var(--line);border-radius:17px;background:linear-gradient(150deg,#fbfbff,#f5f7ff);padding:22px;text-align:center}.lock-visual{width:82px;height:88px;margin:4px auto 16px;position:relative;display:grid;place-items:center}.lock-shackle{position:absolute;top:0;width:40px;height:42px;border:6px solid #786bff;border-bottom:0;border-radius:22px 22px 0 0}.lock-body{position:absolute;bottom:0;width:67px;height:52px;border-radius:14px;background:linear-gradient(135deg,#7d70ff,#5345d8);box-shadow:0 10px 18px #6558ed35;display:grid;place-items:center;color:white}.lock-body svg{width:24px}.preview-card h3{font:800 15px Manrope,sans-serif;margin:0 0 8px}.preview-card p{color:var(--muted);font-size:11px;line-height:1.65;margin:0}.summary{margin-top:17px}.summary-row{display:flex;justify-content:space-between;gap:15px;padding:12px 0;border-bottom:1px solid #edf0f6;font-size:11px}.summary-row:last-child{border-bottom:0}.summary-row span{color:var(--muted)}.summary-row strong{text-align:right;overflow-wrap:anywhere}.secure-status{display:flex;align-items:center;gap:10px;padding:12px;border-radius:12px;background:#eafaf5;color:#16896f;font-size:11px;font-weight:800;margin-top:16px}.secure-status svg{width:18px;flex:none}.actions{margin-top:19px}.actions .btn{width:100%;padding:15px}.actions .btn:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none}.actions .subtext{text-align:center;color:var(--muted);font-size:10px;line-height:1.6;margin-top:10px}.progress-wrap{display:none;margin-top:16px}.progress-head{display:flex;justify-content:space-between;font-size:11px;color:#59617e;margin-bottom:8px}.progress{height:6px;background:#eceefa;border-radius:99px;overflow:hidden}.progress span{display:block;width:0;height:100%;background:linear-gradient(90deg,#6558ed,#24bea1);transition:width .2s}.result{display:none;margin-top:15px;padding:14px;border:1px solid #cdeee3;background:#f1fcf8;border-radius:13px}.result strong{font-size:12px;color:#16866e}.result p{font-size:11px;color:#5c7180;line-height:1.6;margin:6px 0 12px}.result .btn{width:100%}.trust-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:23px}.trust{display:flex;align-items:center;gap:12px;padding:17px;background:#ffffffad;border:1px solid #e8ebf4;border-radius:16px}.trust-icon{width:38px;height:38px;border-radius:12px;display:grid;place-items:center;background:#efedff;color:var(--primary);flex:none}.trust-icon svg{width:19px}.trust strong{display:block;font-size:11px}.trust span{display:block;font-size:10px;color:var(--muted);margin-top:4px;line-height:1.4}.footer{text-align:center;color:#9299b0;font-size:10px;margin-top:35px}.toast{position:fixed;left:50%;bottom:25px;transform:translate(-50%,20px);opacity:0;pointer-events:none;z-index:10;background:#202542;color:#fff;padding:13px 18px;border-radius:12px;box-shadow:0 15px 35px #171c3533;font-size:12px;transition:.25s;max-width:calc(100vw - 30px)}.toast.show{opacity:1;transform:translate(-50%,0)}.smallprint{font-size:10px;color:var(--muted);line-height:1.65;margin-top:10px}
@media(max-width:900px){.workspace{grid-template-columns:1fr}.side-panel{order:2}.trust-grid{grid-template-columns:1fr 1fr}}@media(max-width:560px){.shell{padding:16px 13px 40px}.topbar{margin-bottom:30px}.secure-pill{font-size:0;padding:11px}.secure-pill i{margin:0}.hero h1{letter-spacing:-1.5px}.panel-head{padding:19px}.panel-body{padding:17px}.permissions{grid-template-columns:1fr}.trust-grid{grid-template-columns:1fr}.dropzone{min-height:220px}.panel{border-radius:18px}}
</style>
</head>
<body>
<div class="shell">
<header class="topbar">
<a class="brand" href="index.html" aria-label="PDFix home">
<span class="brandmark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 3h7l5 5v13H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z"/><path d="M14 3v6h5M9 14h6M9 17h6"/></svg></span>
<span class="brandname">PDF<span>ix</span></span>
</a>
<div class="secure-pill"><i></i> LOCAL BROWSER PROCESSING</div>
</header>
<section class="hero">
<span class="eyebrow">✦ PDF SECURITY STUDIO</span>
<h1>Protect your PDF.<br><em>Keep it private.</em></h1>
<p>Add an opening password and set document permissions before sharing. Real AES-256 PDF encryption runs locally in your browser using WebAssembly; your document is not uploaded to a PDFix server.</p>
</section>
<main class="workspace">
<section class="panel">
<div class="panel-head"><div><div class="panel-title">Secure your document</div><div class="panel-sub">Upload a PDF, choose a password, and export a protected copy.</div></div><span class="step">STEP 01 / 03</span></div>
<div class="panel-body">
<div class="dropzone" id="dropzone">
<div class="drop-inner">
<div class="upload-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M7 3h7l5 5v13H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z"/><path d="M14 3v5h5M12 17V10m-3 3 3-3 3 3"/></svg></div>
<h3>Drop your PDF right here</h3>
<p>or select a file from your device. For best results, use an unencrypted PDF.</p>
<button class="btn btn-primary" id="browseBtn" type="button">Choose PDF file</button>
<input id="fileInput" type="file" accept="application/pdf,.pdf" hidden>
</div>
</div>
<div class="file-card" id="fileCard">
<div class="file-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M7 3h7l5 5v13H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z"/><path d="M14 3v5h5M9 14h6M9 17h6"/></svg></div>
<div class="file-info"><div class="file-name" id="fileName">document.pdf</div><div class="file-meta" id="fileMeta">PDF · 0 MB</div></div>
<button class="remove-btn" id="removeFile" type="button" title="Remove file">×</button>
</div>
<div class="section-label">01 — Opening password <small>Required</small></div>
<div class="field">
<label for="password">Set a password to open this PDF</label>
<div class="input-wrap"><input id="password" type="password" autocomplete="new-password" placeholder="Use a long, unique password"><button type="button" class="eye" data-toggle="password" aria-label="Show password">◉</button></div>
<div class="strength" id="strength"><span></span><span></span><span></span><span></span></div>
<div class="strength-copy" id="strengthCopy">Use at least 12 characters, including a mix of character types.</div>
</div>
<div class="field">
<label for="confirmPassword">Confirm password</label>
<div class="input-wrap"><input id="confirmPassword" type="password" autocomplete="new-password" placeholder="Enter the same password again"><button type="button" class="eye" data-toggle="confirmPassword" aria-label="Show password">◉</button></div>
</div>
<label class="checkline"><input id="showPassword" type="checkbox"><span><strong>Show password while typing</strong><small>Turn this off before sharing your screen or device.</small></span></label>
<div class="section-label">02 — Document permissions <small>Optional</small></div>
<p class="smallprint" style="margin-top:-5px;margin-bottom:12px">These settings are written as PDF permission flags. Support varies across PDF viewers; they are not a substitute for an opening password.</p>
<div class="permissions">
<label class="permission"><input type="checkbox" id="allowPrint" checked><span><strong>Allow printing</strong><small>Permit printing in compatible viewers</small></span></label>
<label class="permission"><input type="checkbox" id="allowCopy" checked><span><strong>Allow copying</strong><small>Permit text and content extraction</small></span></label>
<label class="permission"><input type="checkbox" id="allowModify" checked><span><strong>Allow editing</strong><small>Permit document changes</small></span></label>
<label class="permission"><input type="checkbox" id="allowAnnotate" checked><span><strong>Allow annotations</strong><small>Permit comments and form interactions</small></span></label>
</div>
<div class="field" style="margin-top:18px">
<label for="printQuality">Printing quality</label>
<select id="printQuality"><option value="high">High resolution</option><option value="low">Low resolution</option></select>
</div>
<div class="section-label">03 — Export settings <small>Optional</small></div>
<div class="field"><label for="outputName">Output file name</label><input id="outputName" type="text" value="protected-document.pdf" maxlength="120"></div>
<label class="checkline"><input id="downloadAfter" type="checkbox" checked><span><strong>Download automatically when ready</strong><small>Save the protected copy to your device.</small></span></label>
<div class="info-note"><b>Security note:</b> Use a unique password and share it through a separate channel. If you forget the password, PDFix cannot recover it. The tool verifies that the output is marked encrypted, but always test the downloaded PDF in a trusted reader before relying on it for sensitive documents.</div>
</div>
</section>
<aside class="panel side-panel">
<div class="panel-head"><div><div class="panel-title">Security overview</div><div class="panel-sub">Review your protection settings.</div></div><span class="step">LIVE</span></div>
<div class="panel-body">
<div class="preview-card">
<div class="lock-visual"><div class="lock-shackle"></div><div class="lock-body"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="10" width="14" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3M12 14v3"/></svg></div></div>
<h3 id="previewTitle">Protection is ready to configure</h3>
<p id="previewCopy">Add an opening password to make access to your PDF private.</p>
</div>
<div class="summary">
<div class="summary-row"><span>Selected file</span><strong id="sumFile">Not selected</strong></div>
<div class="summary-row"><span>Opening password</span><strong id="sumPassword">Not set</strong></div>
<div class="summary-row"><span>Printing</span><strong id="sumPrint">Allowed</strong></div>
<div class="summary-row"><span>Copying</span><strong id="sumCopy">Allowed</strong></div>
<div class="summary-row"><span>Editing</span><strong id="sumEdit">Allowed</strong></div>
<div class="summary-row"><span>Annotations</span><strong id="sumAnnotate">Allowed</strong></div>
<div class="summary-row"><span>Output</span><strong id="sumOutput">protected-document.pdf</strong></div>
</div>
<div class="secure-status"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-11V5l-8-3-8 3v6c0 7 8 11 8 11Z"/><path d="m9 12 2 2 4-4"/></svg><span id="statusText">Your document stays on this device during processing.</span></div>
<div class="actions"><button class="btn btn-primary" id="protectBtn" type="button" disabled>🔒 Protect & download PDF</button><div class="subtext">The original file is not modified. A new protected copy will be created.</div></div>
<div class="progress-wrap" id="progressWrap"><div class="progress-head"><span id="progressText">Preparing secure PDF…</span><strong id="progressPct">0%</strong></div><div class="progress"><span id="progressBar"></span></div></div>
<div class="result" id="result"><strong>✓ Protected PDF is ready</strong><p id="resultText">Your file has been processed.</p><button class="btn btn-primary" id="downloadBtn" type="button">Download protected PDF</button></div>
</div>
</aside>
</main>
<section class="trust-grid">
<div class="trust"><div class="trust-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="10" width="16" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></div><div><strong>Password protection</strong><span>Add a password required to open the output PDF.</span></div></div>
<div class="trust"><div class="trust-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 5h16M4 12h16M4 19h16"/><path d="m8 2-4 3 4 3"/></svg></div><div><strong>Permission controls</strong><span>Set printing, copying, editing and annotation flags.</span></div></div>
<div class="trust"><div class="trust-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-11V5l-8-3-8 3v6c0 7 8 11 8 11Z"/><path d="m9 12 2 2 4-4"/></svg></div><div><strong>Browser-side workflow</strong><span>Your PDF is handled locally by this page's JavaScript.</span></div></div>
</section>
<footer class="footer">PDFix · Practical PDF tools, designed for your workflow.</footer>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script>
(() => {
const $ = id => document.getElementById(id);
let selectedFile = null, outputBlob = null, outputUrl = null, toastTimer = null;
const fields = ['password','confirmPassword','outputName','allowPrint','allowCopy','allowModify','allowAnnotate','printQuality','downloadAfter'];
function toast(message){ $('toast').textContent=message; $('toast').classList.add('show'); clearTimeout(toastTimer); toastTimer=setTimeout(()=>$('toast').classList.remove('show'),3000); }
function bytes(n){ if(n<1024) return n+' B'; if(n<1024*1024) return (n/1024).toFixed(1)+' KB'; return (n/1024/1024).toFixed(2)+' MB'; }
function safeName(name){ let v=(name||'protected-document.pdf').trim().replace(/[\\/:*?"<>|]/g,'-').replace(/\.pdf$/i,''); return (v||'protected-document')+'.pdf'; }
function setProgress(p,msg){ $('progressWrap').style.display='block'; $('progressBar').style.width=p+'%'; $('progressPct').textContent=p+'%'; $('progressText').textContent=msg; }
function setFile(file){
if(!file) return;
if(!/\.pdf$/i.test(file.name) && file.type!=='application/pdf'){toast('Please choose a PDF file.');return;}
if(file.size===0){toast('This file is empty. Choose another PDF.');return;}
selectedFile=file; outputBlob=null;
$('dropzone').style.display='none'; $('fileCard').style.display='flex';
$('fileName').textContent=file.name; $('fileMeta').textContent='PDF · '+bytes(file.size);
$('sumFile').textContent=file.name; $('protectBtn').disabled=false; $('result').style.display='none'; $('progressWrap').style.display='none';
$('statusText').textContent='Ready to protect locally in this browser.';
updateSummary();
}
function clearFile(){
selectedFile=null;outputBlob=null;if(outputUrl){URL.revokeObjectURL(outputUrl);outputUrl=null;}
$('fileInput').value='';$('dropzone').style.display='flex';$('fileCard').style.display='none';$('sumFile').textContent='Not selected';$('protectBtn').disabled=true;$('result').style.display='none';$('progressWrap').style.display='none';updateSummary();
}
function passwordScore(p){
if(!p) return 0; let score=0;
if(p.length>=8)score++; if(p.length>=12)score++; if(/[a-z]/.test(p)&&/[A-Z]/.test(p))score++; if(/\d/.test(p))score++; if(/[^A-Za-z0-9]/.test(p))score++;
return Math.min(4,score);
}
function updateStrength(){
const p=$('password').value, score=passwordScore(p), segments=[...$('strength').children];
segments.forEach((s,i)=>{s.className='';if(i<score)s.classList.add('on',score<=1?'weak':score<=2?'medium':'strong');});
$('strengthCopy').textContent=!p?'Use at least 12 characters, including a mix of character types.':score<=1?'Weak password — add length and variety.':score<=2?'Fair password — consider a longer passphrase.':score===3?'Good password — longer is better.':'Strong password pattern. Avoid reusing this password.';
$('sumPassword').textContent=p?(score>=3?'Set · good pattern':'Set · improve strength'):'Not set';
$('previewTitle').textContent=p?'Password protection configured':'Protection is ready to configure';
$('previewCopy').textContent=p?'An opening password will be applied to the exported PDF.':'Add an opening password to make access to your PDF private.';
updateSummary();
}
function updateSummary(){
$('sumPrint').textContent=$('allowPrint').checked?'Allowed':'Restricted';
$('sumCopy').textContent=$('allowCopy').checked?'Allowed':'Restricted';
$('sumEdit').textContent=$('allowModify').checked?'Allowed':'Restricted';
$('sumAnnotate').textContent=$('allowAnnotate').checked?'Allowed':'Restricted';
$('sumOutput').textContent=safeName($('outputName').value);
$('protectBtn').disabled=!(selectedFile && window.PDFEncrypt && typeof window.PDFEncrypt.encryptPDF === 'function');
}
$('browseBtn').addEventListener('click',()=>$('fileInput').click());
$('fileInput').addEventListener('change',e=>setFile(e.target.files[0]));
$('removeFile').addEventListener('click',clearFile);
const dz=$('dropzone');
dz.addEventListener('dragover',e=>{e.preventDefault();dz.classList.add('drag');});
dz.addEventListener('dragleave',()=>dz.classList.remove('drag'));
dz.addEventListener('drop',e=>{e.preventDefault();dz.classList.remove('drag');setFile(e.dataTransfer.files[0]);});
document.querySelectorAll('[data-toggle]').forEach(btn=>btn.addEventListener('click',()=>{const input=$(btn.dataset.toggle);input.type=input.type==='password'?'text':'password';btn.textContent=input.type==='password'?'◉':'◎';}));
$('showPassword').addEventListener('change',e=>{const t=e.target.checked?'text':'password';$('password').type=t;$('confirmPassword').type=t;});
$('password').addEventListener('input',updateStrength);
fields.slice(2).forEach(id=>$(id).addEventListener('input',updateSummary));
['allowPrint','allowCopy','allowModify','allowAnnotate','printQuality','outputName'].forEach(id=>$(id).addEventListener('change',updateSummary));
$('confirmPassword').addEventListener('input',()=>{if($('confirmPassword').value && $('confirmPassword').value!==$('password').value){$('confirmPassword').setCustomValidity('Passwords do not match.');}else $('confirmPassword').setCustomValidity('');});
async function protect(){
if(!selectedFile){toast('Choose a PDF first.');return;}
const password=$('password').value, confirm=$('confirmPassword').value;
if(password.length<8){toast('Use a password of at least 8 characters.');$('password').focus();return;}
if(password!==confirm){toast('Passwords do not match.');$('confirmPassword').focus();return;}
if(!window.PDFEncrypt || typeof window.PDFEncrypt.encryptPDF !== 'function'){
toast('The encryption library did not load. Check your internet connection, then refresh this page.');
$('statusText').textContent='Encryption library unavailable. Check internet and refresh.';
return;
}
$('protectBtn').disabled=true;$('result').style.display='none';setProgress(8,'Reading your PDF…');
try{
setProgress(22,'Loading PDF encryption engine…');
const inputBytes = new Uint8Array(await selectedFile.arrayBuffer());
setProgress(42,'Applying AES-256 password encryption…');
const ownerPassword = Array.from(crypto.getRandomValues(new Uint8Array(24)), b => b.toString(16).padStart(2,'0')).join('');
const print = !$('allowPrint').checked ? 'none' : ($('printQuality').value === 'low' ? 'low' : 'full');
const modify = $('allowModify').checked ? 'all' : ($('allowAnnotate').checked ? 'annotate' : 'none');
const encryptedBytes = await window.PDFEncrypt.encryptPDF(inputBytes, password, {
algorithm: 'AES-256',
ownerPassword,
allowPrinting: $('allowPrint').checked,
allowHighQualityPrint: $('allowPrint').checked && $('printQuality').value === 'high',
allowCopying: $('allowCopy').checked,
allowModifying: $('allowModify').checked,
allowAnnotating: $('allowAnnotate').checked,
allowFillingForms: $('allowAnnotate').checked,
allowExtraction: $('allowCopy').checked,
allowAssembly: $('allowModify').checked
});
setProgress(82,'Checking encrypted output…');
if(!encryptedBytes || encryptedBytes.length<10) throw new Error('The encryption engine returned an empty output file.');
// The qpdf WebAssembly lock operation returns the encrypted PDF bytes.
// Do not block download on a separate metadata-inspection call: that check
// can fail independently even when encryption itself succeeded.
outputBlob=new Blob([encryptedBytes],{type:'application/pdf'});
if(outputUrl)URL.revokeObjectURL(outputUrl);
outputUrl=URL.createObjectURL(outputBlob);
setProgress(100,'AES-256 protected PDF ready');
$('result').style.display='block';
$('resultText').textContent=`${safeName($('outputName').value)} · ${bytes(outputBlob.size)}. AES-256 encryption applied. Open the downloaded file in a PDF reader to confirm it prompts for your password.`;
$('statusText').textContent='AES-256 encrypted copy generated in this browser.';
$('protectBtn').disabled=false;
if($('downloadAfter').checked) download();
toast('Password-protected PDF is ready.');
}catch(err){
console.error(err);
$('statusText').textContent='Could not complete protection.';
toast(err.message||'Unable to protect this PDF. Try another file.');
$('progressWrap').style.display='none';
}finally{$('protectBtn').disabled=!(selectedFile && window.PDFEncrypt && typeof window.PDFEncrypt.encryptPDF === 'function');}
}
function download(){
if(!outputBlob){toast('Protect the PDF first.');return;}
// Create a fresh object URL at click time for more reliable downloads across browsers.
const url=URL.createObjectURL(outputBlob);
const a=document.createElement('a');
a.href=url;
a.download=safeName($('outputName').value);
a.style.display='none';
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(()=>URL.revokeObjectURL(url),60000);
}
$('protectBtn').addEventListener('click',protect);
$('downloadBtn').addEventListener('click',download);
$('outputName').addEventListener('input',updateSummary);
updateSummary();
$('statusText').textContent = (window.PDFEncrypt && typeof window.PDFEncrypt.encryptPDF === 'function')
? 'AES-256 encryption engine ready. Your PDF stays on this device.'
: 'Encryption library unavailable. Check internet and refresh.';
if (!(window.PDFEncrypt && typeof window.PDFEncrypt.encryptPDF === 'function')) {
$('protectBtn').disabled = true;
}
})();
</script>
</body>
</html> |