Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>PDF Compressor</title> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <style> | |
| :root { | |
| --bg: #121212; | |
| --card: #1e1e1e; | |
| --text: #e0e0e0; | |
| --accent: #0d6efd; | |
| --accent-hover: #3b82f6; | |
| --success: #28a745; | |
| --error: #dc3545; | |
| } | |
| body { | |
| font-family: "Segoe UI", Arial, sans-serif; | |
| background: var(--bg); | |
| margin: 0; | |
| padding: 15px; | |
| color: var(--text); | |
| } | |
| .container { | |
| max-width: 600px; | |
| margin: auto; | |
| background: var(--card); | |
| padding: 20px; | |
| border-radius: 14px; | |
| box-shadow: 0 6px 20px rgba(0,0,0,0.6); | |
| } | |
| h2 { | |
| text-align: center; | |
| margin-bottom: 20px; | |
| color: var(--text); | |
| } | |
| /* Drop Area */ | |
| .drop-area { | |
| display: block; | |
| width: 100%; | |
| border: 2px dashed var(--accent); | |
| border-radius: 12px; | |
| padding: 15px 20px; | |
| text-align: center; | |
| color: #bbb; | |
| transition: background 0.3s, border 0.3s, box-shadow 0.3s; | |
| cursor: pointer; | |
| font-size: 15px; | |
| box-sizing: border-box; | |
| } | |
| .drop-area:hover { | |
| border-color: var(--accent-hover); | |
| box-shadow: 0 0 12px rgba(13,110,253,0.4); | |
| } | |
| .drop-area.dragover { | |
| background: rgba(13, 110, 253, 0.1); | |
| border-color: var(--accent-hover); | |
| box-shadow: 0 0 18px rgba(13,110,253,0.6); | |
| } | |
| input[type="file"] { | |
| display: none; | |
| } | |
| /* Preview */ | |
| .preview { | |
| margin-top: 15px; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| text-align: center; | |
| } | |
| .preview canvas { | |
| width: 130px; | |
| border: 1px solid #333; | |
| border-radius: 8px; | |
| margin-bottom: 10px; | |
| } | |
| .file-info { | |
| font-size: 14px; | |
| color: #aaa; | |
| } | |
| /* Form elements */ | |
| select, input[type="number"], button { | |
| margin-top: 12px; | |
| padding: 14px; | |
| width: 100%; | |
| border-radius: 10px; | |
| border: 1px solid #333; | |
| font-size: 15px; | |
| background: #2a2a2a; | |
| color: var(--text); | |
| box-sizing: border-box; | |
| } | |
| select:focus, input:focus { | |
| outline: none; | |
| border-color: var(--accent); | |
| } | |
| button { | |
| background: var(--accent); | |
| font-weight: 600; | |
| cursor: pointer; | |
| border: none; | |
| transition: background 0.3s, transform 0.2s; | |
| } | |
| button:hover { | |
| background: var(--accent-hover); | |
| transform: translateY(-1px); | |
| } | |
| /* Status & Download */ | |
| #status { | |
| text-align: center; | |
| margin-top: 15px; | |
| font-weight: 500; | |
| font-size: 15px; | |
| } | |
| #status.success { color: var(--success); } | |
| #status.error { color: var(--error); } | |
| #downloadLink { | |
| display: none; | |
| text-align: center; | |
| margin: 12px auto 0 auto; | |
| text-decoration: none; | |
| color: white; | |
| background: var(--success); | |
| padding: 12px 20px; | |
| border-radius: 10px; | |
| font-weight: 600; | |
| transition: background 0.3s; | |
| width: fit-content; | |
| } | |
| #downloadLink:hover { background: #218838; } | |
| /* Mobile tweaks */ | |
| @media (max-width: 600px) { | |
| .container { | |
| padding: 15px; | |
| } | |
| h2 { font-size: 20px; } | |
| button, select, input[type="number"] { | |
| font-size: 14px; | |
| padding: 12px; | |
| } | |
| .preview canvas { | |
| width: 100px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h2>📄 PDF Compressor</h2> | |
| <label for="fileInput" class="drop-area" id="dropArea"> | |
| <p>🚀 Drag & Drop your PDF here<br>or <strong>Tap to Select</strong></p> | |
| </label> | |
| <input type="file" id="fileInput" name="pdf" accept="application/pdf" required> | |
| <div class="preview" id="preview"></div> | |
| <form style="display: none;" id="uploadForm" enctype="multipart/form-data"> | |
| <label>Compression Type:</label> | |
| <select name="preset" id="preset"> | |
| <option value="low">Low (10%) Compression</option> | |
| <option value="medium">Medium (20%) Compression</option> | |
| <option value="high">High (%70) Compression</option> | |
| <option value="custom" selected>Custom Size</option> | |
| </select> | |
| <div id="customSizeDiv" style="display:block;"> | |
| <label>Target Size (KB):</label> | |
| <input type="number" name="target_kb" placeholder="e.g. 500 (for 500KB)"> | |
| </div> | |
| <button type="submit">Compress</button> | |
| </form> | |
| <p id="status"></p> | |
| <a id="downloadLink" href="#">✔️Save Compressed PDF</a> | |
| </div> | |
| <!-- PDF.js --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.min.js"></script> | |
| <script> | |
| const fileInput = document.getElementById("fileInput"); | |
| const preview = document.getElementById("preview"); | |
| let sizeKB_temp; | |
| dropArea.addEventListener("dragover", (e) => { | |
| e.preventDefault(); | |
| dropArea.classList.add("dragover"); | |
| }); | |
| dropArea.addEventListener("dragleave", () => dropArea.classList.remove("dragover")); | |
| dropArea.addEventListener("drop", (e) => { | |
| e.preventDefault(); | |
| dropArea.classList.remove("dragover"); | |
| if (e.dataTransfer.files.length) { | |
| fileInput.files = e.dataTransfer.files; | |
| showPreview(fileInput.files[0]); | |
| } | |
| }); | |
| fileInput.addEventListener("change", () => { | |
| if (fileInput.files.length) { | |
| const file = fileInput.files[0]; // ✅ correct way to get the file | |
| const ext = file.name.split(".").pop().toLowerCase(); | |
| if (ext !== "pdf" || file.type !== "application/pdf") { | |
| alert("❌ Only PDF files are allowed!"); | |
| fileInput.value = ""; | |
| return; | |
| } | |
| const sizeKB = Math.round(file.size / 1024); | |
| document.querySelector("#preset option[value='low']").textContent = `Low (~ ${Math.round(sizeKB-(sizeKB * 0.1))}KB)`; | |
| document.querySelector("#preset option[value='medium']").textContent = `Medium (~ ${Math.round(sizeKB-(sizeKB * 0.3))}KB)`; | |
| document.querySelector("#preset option[value='high']").textContent = `High (~ ${Math.round(sizeKB-(sizeKB * 0.6))}KB)`; | |
| document.getElementById("uploadForm").style.display = "block"; | |
| showPreview(fileInput.files[0]); | |
| } | |
| }); | |
| // Preview PDF | |
| async function showPreview(file) { | |
| preview.innerHTML = ""; | |
| if (file && file.type === "application/pdf") { | |
| let sizeKB = (file.size / 1024).toFixed(2); | |
| sizeKB_temp = sizeKB; | |
| const fileInfo = document.createElement("p"); | |
| fileInfo.className = "file-info"; | |
| fileInfo.innerText = `📂 ${file.name} — ${sizeKB} KB`; | |
| preview.appendChild(fileInfo); | |
| const fileReader = new FileReader(); | |
| fileReader.onload = async function() { | |
| const typedarray = new Uint8Array(this.result); | |
| const pdf = await pdfjsLib.getDocument(typedarray).promise; | |
| const page = await pdf.getPage(1); | |
| const viewport = page.getViewport({ scale: 0.25 }); | |
| const canvas = document.createElement("canvas"); | |
| const context = canvas.getContext("2d"); | |
| canvas.height = viewport.height; | |
| canvas.width = viewport.width; | |
| await page.render({ canvasContext: context, viewport }).promise; | |
| preview.insertBefore(canvas, fileInfo); | |
| }; | |
| fileReader.readAsArrayBuffer(file); | |
| } | |
| } | |
| // Custom size toggle | |
| document.getElementById("preset").addEventListener("change", function() { | |
| document.getElementById("customSizeDiv").style.display = | |
| this.value === "custom" ? "block" : "none"; | |
| }); | |
| // Form submit | |
| document.getElementById("uploadForm").addEventListener("submit", async function(e) { | |
| e.preventDefault(); | |
| const statusEl = document.getElementById("status"); | |
| statusEl.innerText = "⏳ Compressing..."; | |
| statusEl.className = ""; | |
| document.getElementById("downloadLink").style.display = "none"; | |
| let formData = new FormData(); | |
| if (fileInput.files.length) { | |
| formData.append("pdf", fileInput.files[0]); | |
| } | |
| const preset_value = document.getElementById("preset").value; | |
| let targetInput = document.querySelector("input[name='target_kb']"); | |
| switch(preset_value){ | |
| case "low": | |
| var temp = (sizeKB_temp - (sizeKB_temp * 0.1)); | |
| targetInput.value = Math.ceil(temp); | |
| break; | |
| case "medium": | |
| var temp = (sizeKB_temp - (sizeKB_temp * 0.3)); | |
| targetInput.value = Math.ceil(temp); | |
| break; | |
| case "high": | |
| var temp = (sizeKB_temp - (sizeKB_temp * 0.6)); | |
| targetInput.value = Math.ceil(temp); | |
| break; | |
| } | |
| formData.append("preset", preset_value); | |
| if (targetInput && targetInput.value) { | |
| formData.append("target_kb", targetInput.value); | |
| console.log(targetInput.value) | |
| } | |
| let res = await fetch("/compress", { method: "POST", body: formData }); | |
| let data = await res.json(); | |
| if (data.success) { | |
| statusEl.innerText = "✅ Compression successful!"; | |
| statusEl.className = "success"; | |
| let link = document.getElementById("downloadLink"); | |
| link.href = "/download/" + data.filename; | |
| link.style.display = "block"; | |
| } else { | |
| statusEl.innerText = "❌ Compression failed!"; | |
| statusEl.className = "error"; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |