Talha912 commited on
Commit
7ed9da4
·
verified ·
1 Parent(s): 034e081

Create organize.html

Browse files
Files changed (1) hide show
  1. organize.html +106 -0
organize.html ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="theme-color" content="#11152d">
7
+ <title>Organize PDF — PDFix</title>
8
+ <meta name="description" content="Reorder, rotate, delete, duplicate, and extract PDF pages in your browser with PDFix Organize PDF.">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <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">
12
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf-lib/1.17.1/pdf-lib.min.js"></script>
13
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
14
+ <style>
15
+ :root{--bg:#f5f7fc;--ink:#171c35;--muted:#7d859e;--line:#e5e9f2;--primary:#6558ed;--primary2:#5145d7;--mint:#20b99a;--red:#e4566b;--surface:#fff;--shadow:0 18px 55px rgba(33,43,89,.08)}
16
+ *{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 8% 0%,#eceaff 0,transparent 28%),radial-gradient(circle at 96% 8%,#e3faf4 0,transparent 24%),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:1400px;margin:auto;padding:23px 25px 55px}.topbar{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-bottom:34px}.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:11px;font-weight:800}.secure-pill i{width:8px;height:8px;border-radius:50%;background:var(--mint);box-shadow:0 0 0 4px #21b99a1b}.hero{display:flex;align-items:flex-end;justify-content:space-between;gap:25px;margin-bottom:27px}.eyebrow{display:inline-flex;align-items:center;gap:8px;color:var(--primary);font-size:10px;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(32px,4vw,48px)/1.08 Manrope,sans-serif;letter-spacing:-2px;margin:15px 0 10px}.hero h1 em{font-style:normal;background:linear-gradient(100deg,#6558ed,#20b99a);-webkit-background-clip:text;background-clip:text;color:transparent}.hero p{color:var(--muted);font-size:14px;line-height:1.7;margin:0;max-width:620px}.hero-note{background:#fff;border:1px solid var(--line);border-radius:16px;padding:14px 17px;display:flex;align-items:center;gap:12px;max-width:235px;box-shadow:0 8px 28px #22264d08}.hero-note .icon{width:38px;height:38px;display:grid;place-items:center;border-radius:12px;background:#e8faf5;color:#149d82;font-size:18px}.hero-note strong{font-size:12px;display:block}.hero-note small{font-size:10px;color:var(--muted);display:block;margin-top:4px}.workspace{display:grid;grid-template-columns:minmax(0,1fr) 285px;gap:20px;align-items:start}.mainpanel,.sidepanel{background:rgba(255,255,255,.91);border:1px solid #e6e9f2;border-radius:23px;box-shadow:var(--shadow);overflow:hidden}.panel-head{display:flex;align-items:center;justify-content:space-between;gap:15px;padding:19px 22px;border-bottom:1px solid var(--line)}.fileinfo{display:flex;align-items:center;gap:12px;min-width:0}.pdficon{width:43px;height:47px;border-radius:11px;background:#fff0f2;border:1px solid #ffdde2;color:#dc4d65;display:grid;place-items:center;font-size:11px;font-weight:800;flex-shrink:0}.fileinfo strong{display:block;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:390px}.fileinfo small{display:block;color:var(--muted);font-size:11px;margin-top:5px}.head-actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end}.btn{border:1px solid var(--line);background:#fff;color:#4c5570;border-radius:11px;padding:10px 12px;font-size:11px;font-weight:800;display:inline-flex;align-items:center;justify-content:center;gap:7px;transition:.18s}.btn:hover{border-color:#c9c3ff;color:var(--primary);transform:translateY(-1px)}.btn:disabled{opacity:.45;cursor:not-allowed;transform:none}.btn-primary{background:linear-gradient(135deg,#7165f5,#574be0);border:0;color:#fff;box-shadow:0 8px 17px #6558ed30}.btn-primary:hover{color:#fff;box-shadow:0 10px 23px #6558ed44}.btn-danger{color:#d84e65;background:#fff7f8}.toolbar{padding:14px 21px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.toolgroup{display:flex;align-items:center;gap:7px;flex-wrap:wrap}.tool-label{font-size:10px;color:var(--muted);font-weight:800;margin-right:3px}.selectall{display:flex;align-items:center;gap:8px;font-size:11px;font-weight:700;color:#5c6580}.selectall input,.page-select{accent-color:var(--primary)}.sort-select{border:1px solid var(--line);border-radius:10px;padding:9px 11px;background:#fff;color:#4c5570;font-size:11px;font-weight:700;outline:none}.dropzone{margin:20px;border:1.5px dashed #c9c8f7;border-radius:18px;background:linear-gradient(135deg,#f8f7ff,#f4fffc);padding:42px 20px;text-align:center;transition:.2s}.dropzone.drag{border-color:var(--primary);background:#eeecff}.drop-icon{width:60px;height:60px;border-radius:19px;margin:0 auto 16px;display:grid;place-items:center;background:#e9e7ff;color:var(--primary);font-size:27px}.dropzone h2{font:800 19px Manrope,sans-serif;margin:0 0 8px}.dropzone p{font-size:12px;color:var(--muted);margin:0 0 19px}.dropzone .btn{padding:12px 18px}.dropzone .hint{font-size:10px;margin-top:14px;color:#9aa1b5}.page-area{padding:22px;min-height:300px}.page-topline{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:17px}.page-topline strong{font-size:13px}.page-topline span{font-size:11px;color:var(--muted)}.pages{display:grid;grid-template-columns:repeat(auto-fill,minmax(145px,1fr));gap:17px}.page-card{position:relative;border:1px solid #e3e7f0;background:#fbfcff;border-radius:15px;padding:12px 11px 10px;min-width:0;transition:border .18s,box-shadow .18s,transform .18s;cursor:grab}.page-card:hover{border-color:#c5c0ff;box-shadow:0 10px 25px #343c6910;transform:translateY(-2px)}.page-card.selected{border:1.5px solid var(--primary);background:#f7f6ff;box-shadow:0 0 0 3px #6558ed12}.page-card.dragging{opacity:.42;transform:scale(.97)}.page-card.over{outline:2px dashed var(--primary);outline-offset:2px}.thumb-wrap{height:175px;display:flex;align-items:center;justify-content:center;overflow:hidden;background:#edf0f7;border-radius:8px;margin-bottom:10px;position:relative}.thumb-wrap canvas{max-width:100%;max-height:100%;height:auto;width:auto;object-fit:contain;box-shadow:0 3px 12px #20294a16;background:white}.page-check{position:absolute;top:8px;left:8px;width:19px;height:19px;border:1px solid #d7dbea;background:#fff;border-radius:6px;display:grid;place-items:center;z-index:2}.page-card.selected .page-check{background:var(--primary);border-color:var(--primary);color:#fff}.page-check svg{width:12px;height:12px}.page-num{font-size:10px;font-weight:800;color:#737c97}.page-card-foot{display:flex;align-items:center;justify-content:space-between;gap:5px}.mini-actions{display:flex;gap:4px}.mini{width:27px;height:27px;border:1px solid #e5e8f1;background:#fff;color:#67708a;border-radius:8px;display:grid;place-items:center;font-size:14px}.mini:hover{color:var(--primary);border-color:#c9c3ff}.mini.remove:hover{color:var(--red);border-color:#f5bdc6}.empty{display:none}.sidepanel{position:sticky;top:15px}.side-head{padding:21px 20px 16px;border-bottom:1px solid var(--line)}.side-head h3{font:800 16px Manrope,sans-serif;margin:0 0 6px}.side-head p{font-size:11px;color:var(--muted);margin:0;line-height:1.6}.side-body{padding:17px 20px}.setting{padding:13px 0;border-bottom:1px solid #eef0f6}.setting:first-child{padding-top:0}.setting:last-child{border-bottom:0}.setting label.title{display:block;font-size:11px;font-weight:800;margin-bottom:7px}.setting small{font-size:10px;color:var(--muted);line-height:1.5;display:block}.field{width:100%;border:1px solid var(--line);border-radius:10px;padding:10px 11px;font-size:11px;color:#424b67;outline:none;background:#fff}.field:focus{border-color:#a9a1ff;box-shadow:0 0 0 3px #6558ed12}.checkrow{display:flex;align-items:center;gap:8px;font-size:11px;color:#58617b;margin-top:10px}.checkrow input{accent-color:var(--primary)}.summary{background:#f6f7fc;border:1px solid #eaedf5;border-radius:13px;padding:13px;margin-top:14px}.summary-row{display:flex;align-items:center;justify-content:space-between;font-size:11px;padding:5px 0;color:#707994}.summary-row strong{color:#343b59}.summary-row.total{border-top:1px solid #e3e7f1;margin-top:5px;padding-top:10px;font-weight:800;color:#343b59}.export-btn{width:100%;padding:14px;margin-top:15px}.status{display:none;margin-top:12px;padding:11px 12px;border-radius:10px;background:#f0efff;color:#5a4fe0;font-size:11px;line-height:1.5}.status.show{display:block}.progress{height:5px;background:#eceef5;border-radius:99px;overflow:hidden;margin-top:10px;display:none}.progress.show{display:block}.progress div{height:100%;width:0;background:linear-gradient(90deg,#6558ed,#20b99a);transition:width .2s}.footnote{padding:0 20px 20px;color:#959cb0;font-size:10px;line-height:1.6}.toast{position:fixed;bottom:22px;left:50%;transform:translate(-50%,20px);opacity:0;pointer-events:none;background:#171c35;color:#fff;padding:12px 18px;border-radius:12px;font-size:12px;box-shadow:0 12px 35px #10152d35;transition:.25s;z-index:20;max-width:90vw}.toast.show{opacity:1;transform:translate(-50%,0)}.hidden{display:none!important}.loading-shimmer{height:175px;width:100%;background:linear-gradient(90deg,#eceff7 25%,#f8f9fc 40%,#eceff7 65%);background-size:300% 100%;animation:shimmer 1.2s infinite}@keyframes shimmer{to{background-position:-150% 0}}.foot{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;color:#9aa1b4;font-size:10px;margin-top:22px;padding:0 4px}
17
+ @media(max-width:1000px){.workspace{grid-template-columns:1fr}.sidepanel{position:static}.side-body{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 20px}.export-area{grid-column:1/-1}.hero-note{display:none}}
18
+ @media(max-width:640px){.shell{padding:16px 12px 35px}.topbar{margin-bottom:27px}.secure-pill{font-size:0;padding:10px}.secure-pill i{margin:0}.hero{display:block}.hero h1{letter-spacing:-1.3px}.panel-head{align-items:flex-start;padding:15px;flex-direction:column}.head-actions{justify-content:flex-start}.toolbar{padding:13px 15px}.page-area{padding:15px}.pages{grid-template-columns:repeat(2,minmax(0,1fr));gap:11px}.thumb-wrap{height:165px}.side-body{display:block}.fileinfo strong{max-width:280px}.dropzone{margin:13px;padding:33px 15px}}
19
+ </style>
20
+ </head>
21
+ <body>
22
+ <div class="shell">
23
+ <header class="topbar">
24
+ <a class="brand" href="index.html" aria-label="PDFix home"><div 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 3v6h5M8 14h8M8 17h6"/></svg></div><div class="brandname">PDF<span>ix</span></div></a>
25
+ <div class="secure-pill"><i></i> PRIVATE · IN-BROWSER PROCESSING</div>
26
+ </header>
27
+ <section class="hero">
28
+ <div><div class="eyebrow">✦ PDF WORKSPACE / ORGANIZE</div><h1>Every page, <em>in its place.</em></h1><p>Reorder pages with drag and drop, rotate the ones that need it, remove unwanted pages, or export just the pages you select. Your PDF stays on your device.</p></div>
29
+ <div class="hero-note"><div class="icon">↔</div><div><strong>Full page control</strong><small>Reorder · Rotate · Remove · Extract</small></div></div>
30
+ </section>
31
+ <main class="workspace">
32
+ <section class="mainpanel">
33
+ <div class="panel-head">
34
+ <div class="fileinfo"><div class="pdficon">PDF</div><div><strong id="filename">No PDF selected</strong><small id="filemeta">Choose a PDF to begin organizing</small></div></div>
35
+ <div class="head-actions"><button class="btn" id="replaceBtn" disabled>+ Replace PDF</button><button class="btn" id="resetBtn" disabled>↺ Reset changes</button></div>
36
+ </div>
37
+ <div class="toolbar">
38
+ <div class="toolgroup"><span class="tool-label">SELECTION</span><label class="selectall"><input type="checkbox" id="selectAll" disabled> Select all</label><button class="btn" id="selectNone" disabled>Clear</button></div>
39
+ <div class="toolgroup"><button class="btn" id="rotateSelected" disabled>↻ Rotate selected</button><button class="btn btn-danger" id="deleteSelected" disabled>⌫ Delete selected</button></div>
40
+ </div>
41
+ <div class="dropzone" id="dropzone">
42
+ <div class="drop-icon">⇧</div><h2>Drop your PDF here</h2><p>Or choose a file from your device to start organizing pages</p><button class="btn btn-primary" id="chooseBtn">Choose PDF file <span>→</span></button><div class="hint">PDF files only · Processing happens locally in your browser</div>
43
+ </div>
44
+ <div class="page-area hidden" id="pageArea">
45
+ <div class="page-topline"><strong>Page overview</strong><span id="pageCount">0 pages</span></div>
46
+ <div class="pages" id="pages"></div>
47
+ </div>
48
+ </section>
49
+ <aside class="sidepanel">
50
+ <div class="side-head"><h3>Export settings</h3><p>Choose what to save from your organized document.</p></div>
51
+ <div class="side-body">
52
+ <div class="setting"><label class="title" for="exportMode">Export mode</label><select class="field" id="exportMode"><option value="all">Save organized PDF</option><option value="selected">Extract selected pages only</option></select><small style="margin-top:7px">Selected-page export keeps the order shown in the page overview.</small></div>
53
+ <div class="setting"><label class="title" for="outputName">Output filename</label><input class="field" id="outputName" value="organized-document.pdf" maxlength="120"><small style="margin-top:7px">The .pdf extension will be added if needed.</small></div>
54
+ <div class="setting"><label class="title">Page actions</label><small>Use the page card controls to rotate or remove a page. Drag any card to change its position. Click a card or its checkbox to select it.</small></div>
55
+ <div class="summary"><div class="summary-row"><span>Original pages</span><strong id="sumOriginal">—</strong></div><div class="summary-row"><span>Current pages</span><strong id="sumCurrent">—</strong></div><div class="summary-row"><span>Selected pages</span><strong id="sumSelected">0</strong></div><div class="summary-row total"><span>Export will contain</span><strong id="sumExport">—</strong></div></div>
56
+ <div class="export-area"><button class="btn btn-primary export-btn" id="exportBtn" disabled>↓ Export PDF</button><div class="progress" id="progress"><div id="progressBar"></div></div><div class="status" id="status" role="status"></div></div>
57
+ </div>
58
+ <div class="footnote">Tip: Select specific pages to extract them into a new PDF. Page edits are applied when you export.</div>
59
+ </aside>
60
+ </main>
61
+ <div class="foot"><span>© PDFix · Simple tools for your PDFs</span><span>Your file is processed in this browser tab; it is not uploaded by this page.</span></div>
62
+ </div>
63
+ <input type="file" id="fileInput" accept="application/pdf,.pdf" hidden>
64
+ <div class="toast" id="toast"></div>
65
+ <script>
66
+ (() => {
67
+ 'use strict';
68
+ if (window.pdfjsLib) pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
69
+ const $ = id => document.getElementById(id);
70
+ const state = {file:null, bytes:null, pages:[], originalCount:0, originalOrder:[], selected:new Set(), busy:false};
71
+ const icons = {
72
+ check:'<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><path d="m3 8 3 3 7-7"/></svg>',
73
+ rotate:'↻', delete:'×'
74
+ };
75
+ function toast(message){const el=$('toast');el.textContent=message;el.classList.add('show');clearTimeout(toast.timer);toast.timer=setTimeout(()=>el.classList.remove('show'),2600)}
76
+ function setStatus(message, progress){const s=$('status');s.textContent=message;s.classList.add('show');if(typeof progress==='number'){$('progress').classList.add('show');$('progressBar').style.width=progress+'%'}else{$('progress').classList.remove('show')}}
77
+ function setBusy(b){state.busy=b;$('exportBtn').disabled=b||!state.pages.length;$('exportBtn').textContent=b?'Preparing PDF…':'↓ Export PDF';$('chooseBtn').disabled=b;$('replaceBtn').disabled=b||!state.file}
78
+ function enabled(){const has=state.pages.length>0;['replaceBtn','resetBtn','selectAll','selectNone','rotateSelected','deleteSelected','exportBtn'].forEach(id=>$(id).disabled=!has||state.busy);$('dropzone').classList.toggle('hidden',has);$('pageArea').classList.toggle('hidden',!has);$('selectAll').checked=has&&state.selected.size===state.pages.length;updateSummary()}
79
+ function updateSummary(){const selected=state.selected.size;const mode=$('exportMode').value;const n=mode==='selected'?selected:state.pages.length;$('sumOriginal').textContent=state.originalCount||'—';$('sumCurrent').textContent=state.pages.length||'—';$('sumSelected').textContent=selected;$('sumExport').textContent=n?`${n} page${n===1?'':'s'}`:'—';$('pageCount').textContent=`${state.pages.length} page${state.pages.length===1?'':'s'} · ${selected} selected`;$('selectAll').checked=state.pages.length>0&&selected===state.pages.length}
80
+ function safeName(name){let n=(name||'organized-document.pdf').trim().replace(/[\\/:*?"<>|]/g,'-');if(!n)n='organized-document.pdf';return n.toLowerCase().endsWith('.pdf')?n:n+'.pdf'}
81
+ function renderCards(){const root=$('pages');root.innerHTML='';state.pages.forEach((p,index)=>{const card=document.createElement('article');card.className='page-card'+(state.selected.has(p.id)?' selected':'');card.draggable=true;card.dataset.id=p.id;card.innerHTML=`<button class="page-check" aria-label="Select page ${index+1}">${state.selected.has(p.id)?icons.check:''}</button><div class="thumb-wrap"><div class="loading-shimmer"></div></div><div class="page-card-foot"><span class="page-num">PAGE ${index+1}</span><div class="mini-actions"><button class="mini rotate" title="Rotate page" aria-label="Rotate page">↻</button><button class="mini remove" title="Delete page" aria-label="Delete page">×</button></div></div>`;
82
+ card.addEventListener('click',e=>{if(e.target.closest('.rotate')){p.rotation=(p.rotation+90)%360;renderCards();return}if(e.target.closest('.remove')){removePage(p.id);return}if(e.target.closest('.page-check')||e.target===card||e.target.closest('.thumb-wrap')||e.target.closest('.page-num')){toggleSelect(p.id)}});
83
+ card.addEventListener('dragstart',e=>{card.classList.add('dragging');e.dataTransfer.setData('text/plain',String(p.id));e.dataTransfer.effectAllowed='move'});
84
+ card.addEventListener('dragend',()=>{card.classList.remove('dragging');root.querySelectorAll('.over').forEach(x=>x.classList.remove('over'))});
85
+ card.addEventListener('dragover',e=>{e.preventDefault();card.classList.add('over');e.dataTransfer.dropEffect='move'});
86
+ card.addEventListener('dragleave',()=>card.classList.remove('over'));
87
+ card.addEventListener('drop',e=>{e.preventDefault();card.classList.remove('over');const from=state.pages.findIndex(x=>String(x.id)===e.dataTransfer.getData('text/plain'));const to=state.pages.findIndex(x=>x.id===p.id);if(from<0||to<0||from===to)return;const moved=state.pages.splice(from,1)[0];state.pages.splice(to,0,moved);renderCards();updateSummary()});
88
+ root.appendChild(card);renderThumb(p,card.querySelector('.thumb-wrap'))});
89
+ enabled()}
90
+ async function renderThumb(page,wrap){try{if(!window.pdfjsLib)throw Error('PDF preview library unavailable');const doc=await pdfjsLib.getDocument({data:state.bytes.slice(0)}).promise;const pageObj=await doc.getPage(page.originalIndex+1);const viewport=pageObj.getViewport({scale:0.48,rotation:page.rotation});const canvas=document.createElement('canvas');const ctx=canvas.getContext('2d');canvas.width=Math.ceil(viewport.width);canvas.height=Math.ceil(viewport.height);canvas.style.maxWidth='100%';canvas.style.maxHeight='100%';await pageObj.render({canvasContext:ctx,viewport}).promise;wrap.innerHTML='';wrap.appendChild(canvas);await doc.destroy()}catch(e){wrap.innerHTML='<div style="padding:14px;text-align:center;color:#9aa1b5;font-size:11px">Preview unavailable<br>Page can still be exported</div>'}}
91
+ function toggleSelect(id){if(state.selected.has(id))state.selected.delete(id);else state.selected.add(id);renderCards();updateSummary()}
92
+ function removePage(id){if(state.pages.length<=1){toast('A PDF must contain at least one page.');return}state.pages=state.pages.filter(p=>p.id!==id);state.selected.delete(id);renderCards();toast('Page removed from export.')}
93
+ function selectMany(on){state.selected=on?new Set(state.pages.map(p=>p.id)):new Set();renderCards();updateSummary()}
94
+ async function loadFile(file){if(!file)return;if(file.type!=='application/pdf'&&!file.name.toLowerCase().endsWith('.pdf')){toast('Please choose a PDF file.');return}if(file.size>250*1024*1024){toast('This file is larger than 250 MB. Try a smaller PDF.');return}
95
+ try{setStatus('Reading PDF…',8);state.file=file;state.bytes=new Uint8Array(await file.arrayBuffer());const pdfDoc=await PDFLib.PDFDocument.load(state.bytes,{ignoreEncryption:false});const count=pdfDoc.getPageCount();if(!count)throw Error('This PDF has no pages.');state.originalCount=count;state.pages=Array.from({length:count},(_,i)=>({id:i+1,originalIndex:i,rotation:((pdfDoc.getPage(i).getRotation().angle%360)+360)%360}));state.originalOrder=state.pages.map(p=>({...p}));state.selected=new Set();$('filename').textContent=file.name;$('filemeta').textContent=`${count} pages · ${formatBytes(file.size)} · Ready to organize`;$('outputName').value=safeName(file.name.replace(/\.pdf$/i,'')+'-organized.pdf');$('status').classList.remove('show');$('progress').classList.remove('show');renderCards();setStatus(`Loaded ${count} pages. Drag cards to reorder them.`,100);setTimeout(()=>$('progress').classList.remove('show'),1000);toast('PDF loaded successfully.')}catch(err){console.error(err);state.file=null;state.bytes=null;state.pages=[];enabled();setStatus(err.message.includes('encrypted')?'This PDF is password-protected. Unlock it first, then try again.':'Could not read this PDF. It may be damaged or password-protected.');toast('Could not open this PDF.')}}
96
+ function formatBytes(n){if(n<1024)return n+' B';if(n<1048576)return (n/1024).toFixed(1)+' KB';return (n/1048576).toFixed(1)+' MB'}
97
+ async function exportPdf(){if(state.busy||!state.pages.length)return;const mode=$('exportMode').value;let chosen=mode==='selected'?state.pages.filter(p=>state.selected.has(p.id)):state.pages.slice();if(!chosen.length){toast('Select at least one page to extract.');return}
98
+ try{setBusy(true);setStatus('Building your organized PDF…',15);const source=await PDFLib.PDFDocument.load(state.bytes);const out=await PDFLib.PDFDocument.create();for(let i=0;i<chosen.length;i++){const p=chosen[i];const [copied]=await out.copyPages(source,[p.originalIndex]);copied.setRotation(PDFLib.degrees(p.rotation));out.addPage(copied);setStatus(`Preparing page ${i+1} of ${chosen.length}…`,15+Math.round((i+1)/chosen.length*75))}const bytes=await out.save({useObjectStreams:true});setStatus('Preparing download…',96);const blob=new Blob([bytes],{type:'application/pdf'});const url=URL.createObjectURL(blob);const a=document.createElement('a');a.href=url;a.download=safeName($('outputName').value);document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),60000);setStatus(`Done! Your PDF with ${chosen.length} page${chosen.length===1?'':'s'} has been downloaded.`,100);toast('Your organized PDF is ready.')}catch(err){console.error(err);setStatus('Export failed: '+(err.message||'Unexpected error')+'. Try reloading the PDF and exporting again.');toast('Could not export the PDF.')}finally{setBusy(false);enabled()}}
99
+ function reset(){if(!state.originalOrder.length)return;state.pages=state.originalOrder.map(p=>({...p}));state.selected=new Set();renderCards();toast('Original page order restored.')}
100
+ $('chooseBtn').addEventListener('click',()=>$('fileInput').click());$('replaceBtn').addEventListener('click',()=>$('fileInput').click());$('fileInput').addEventListener('change',e=>{if(e.target.files[0])loadFile(e.target.files[0]);e.target.value=''});$('selectAll').addEventListener('change',e=>selectMany(e.target.checked));$('selectNone').addEventListener('click',()=>selectMany(false));$('rotateSelected').addEventListener('click',()=>{if(!state.selected.size){toast('Select pages to rotate first.');return}state.pages.forEach(p=>{if(state.selected.has(p.id))p.rotation=(p.rotation+90)%360});renderCards();toast('Selected pages rotated.')});$('deleteSelected').addEventListener('click',()=>{if(!state.selected.size){toast('Select pages to delete first.');return}if(state.selected.size>=state.pages.length){toast('Keep at least one page in the PDF.');return}const n=state.selected.size;state.pages=state.pages.filter(p=>!state.selected.has(p.id));state.selected.clear();renderCards();toast(`${n} page${n===1?'':'s'} removed.`)});$('resetBtn').addEventListener('click',reset);$('exportBtn').addEventListener('click',exportPdf);$('exportMode').addEventListener('change',updateSummary);
101
+ const dz=$('dropzone');['dragenter','dragover'].forEach(ev=>dz.addEventListener(ev,e=>{e.preventDefault();dz.classList.add('drag')}));['dragleave','drop'].forEach(ev=>dz.addEventListener(ev,e=>{e.preventDefault();dz.classList.remove('drag')}));dz.addEventListener('drop',e=>{const f=e.dataTransfer.files[0];if(f)loadFile(f)});document.addEventListener('dragover',e=>e.preventDefault());document.addEventListener('drop',e=>{if(e.target!==dz&&!dz.contains(e.target))e.preventDefault()});
102
+ enabled();
103
+ })();
104
+ </script>
105
+ </body>
106
+ </html>