diff --git a/unredact/static/app.js b/unredact/static/app.js new file mode 100644 index 0000000..13b17f9 --- /dev/null +++ b/unredact/static/app.js @@ -0,0 +1,84 @@ +const dropZone = document.getElementById("drop-zone"); +const fileInput = document.getElementById("file-input"); +const uploadSection = document.getElementById("upload-section"); +const viewerSection = document.getElementById("viewer-section"); +const docImage = document.getElementById("doc-image"); +const pageInfo = document.getElementById("page-info"); +const prevBtn = document.getElementById("prev-page"); +const nextBtn = document.getElementById("next-page"); +const overlayToggle = document.getElementById("show-overlay"); +const fontInfo = document.getElementById("font-info"); + +let state = { + docId: null, + pageCount: 0, + currentPage: 1, + pageData: {}, +}; + +// Drag and drop +dropZone.addEventListener("click", () => fileInput.click()); +dropZone.addEventListener("dragover", (e) => { + e.preventDefault(); + dropZone.classList.add("dragover"); +}); +dropZone.addEventListener("dragleave", () => dropZone.classList.remove("dragover")); +dropZone.addEventListener("drop", (e) => { + e.preventDefault(); + dropZone.classList.remove("dragover"); + if (e.dataTransfer.files.length) uploadFile(e.dataTransfer.files[0]); +}); +fileInput.addEventListener("change", () => { + if (fileInput.files.length) uploadFile(fileInput.files[0]); +}); + +async function uploadFile(file) { + uploadSection.innerHTML = '

Analyzing document...

'; + + const form = new FormData(); + form.append("file", file); + + const resp = await fetch("/api/upload", { method: "POST", body: form }); + const data = await resp.json(); + + state.docId = data.doc_id; + state.pageCount = data.page_count; + state.currentPage = 1; + + uploadSection.hidden = true; + viewerSection.hidden = false; + + await loadPage(1); +} + +async function loadPage(page) { + state.currentPage = page; + updateControls(); + + const showOverlay = overlayToggle.checked; + const endpoint = showOverlay ? "overlay" : "original"; + docImage.src = `/api/doc/${state.docId}/page/${page}/${endpoint}`; + + // Load page data (font info, lines) + if (!state.pageData[page]) { + const resp = await fetch(`/api/doc/${state.docId}/page/${page}/data`); + state.pageData[page] = await resp.json(); + } + + const pd = state.pageData[page]; + fontInfo.textContent = `${pd.font.name} ~${pd.font.size}px (score: ${pd.font.score.toFixed(1)})`; +} + +function updateControls() { + pageInfo.textContent = `Page ${state.currentPage} / ${state.pageCount}`; + prevBtn.disabled = state.currentPage <= 1; + nextBtn.disabled = state.currentPage >= state.pageCount; +} + +prevBtn.addEventListener("click", () => { + if (state.currentPage > 1) loadPage(state.currentPage - 1); +}); +nextBtn.addEventListener("click", () => { + if (state.currentPage < state.pageCount) loadPage(state.currentPage + 1); +}); +overlayToggle.addEventListener("change", () => loadPage(state.currentPage)); diff --git a/unredact/static/index.html b/unredact/static/index.html new file mode 100644 index 0000000..21ca9eb --- /dev/null +++ b/unredact/static/index.html @@ -0,0 +1,42 @@ + + + + + + Unredact + + + +
+

UNREDACT

+

Epstein Files Redaction Analysis

+
+ +
+
+
+

Drop a PDF here or click to upload

+ +
+
+ + +
+ + + + diff --git a/unredact/static/style.css b/unredact/static/style.css new file mode 100644 index 0000000..461b601 --- /dev/null +++ b/unredact/static/style.css @@ -0,0 +1,102 @@ +* { margin: 0; padding: 0; box-sizing: border-box; } + +body { + font-family: system-ui, -apple-system, sans-serif; + background: #1a1a2e; + color: #e0e0e0; + min-height: 100vh; +} + +header { + padding: 1.5rem 2rem; + background: #16213e; + border-bottom: 2px solid #0f3460; +} + +header h1 { + font-size: 1.5rem; + font-weight: 700; + letter-spacing: 0.2em; + color: #00d474; +} + +.subtitle { + font-size: 0.85rem; + color: #888; + margin-top: 0.25rem; +} + +main { padding: 2rem; } + +#drop-zone { + border: 2px dashed #0f3460; + border-radius: 12px; + padding: 4rem; + text-align: center; + cursor: pointer; + transition: border-color 0.2s, background 0.2s; + max-width: 600px; + margin: 0 auto; +} + +#drop-zone:hover, #drop-zone.dragover { + border-color: #00d474; + background: rgba(0, 212, 116, 0.05); +} + +#drop-zone p { font-size: 1.1rem; color: #aaa; } + +#controls { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1rem; + padding: 0.75rem 1rem; + background: #16213e; + border-radius: 8px; +} + +#controls button { + background: #0f3460; + color: #e0e0e0; + border: none; + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; +} + +#controls button:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +#overlay-toggle { + margin-left: auto; + cursor: pointer; + user-select: none; +} + +#font-info { + font-size: 0.8rem; + color: #00d474; +} + +#doc-container { + background: #111; + border-radius: 8px; + overflow: auto; + max-height: 80vh; + text-align: center; +} + +#doc-image { + max-width: 100%; + height: auto; +} + +.loading { + color: #00d474; + text-align: center; + padding: 2rem; + font-size: 1.2rem; +}