// Developer: Saksham Pathak (github.com/parthmax2) let classes = []; let selectedFile = null; const dropzone = document.getElementById("dropzone"); const dropzoneEmpty = document.getElementById("dropzoneEmpty"); const dropzonePreview = document.getElementById("dropzonePreview"); const previewImage = document.getElementById("previewImage"); const previewFileName = document.getElementById("previewFileName"); const previewFileInfo = document.getElementById("previewFileInfo"); const changeImageBtn = document.getElementById("changeImageBtn"); const imageInput = document.getElementById("imageInput"); const resizeCb = document.getElementById("resizeCb"); const slidersEl = document.getElementById("sliders"); const legendEl = document.getElementById("legend"); const runBtn = document.getElementById("runBtn"); const scanOverlay = document.getElementById("scanOverlay"); const step1 = document.querySelector('.step[data-step="1"]'); const step2 = document.querySelector('.step[data-step="2"]'); const line1 = document.getElementById("line1"); const stepEyebrow = document.getElementById("stepEyebrow"); const toastStack = document.getElementById("toastStack"); function showToast(message, type = "info") { const el = document.createElement("div"); el.className = `toast ${type}`; el.textContent = message; toastStack.appendChild(el); setTimeout(() => el.remove(), 4000); } function formatBytes(bytes) { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } function readImageDimensions(file) { return new Promise((resolve) => { const img = new Image(); img.onload = () => { resolve({ width: img.naturalWidth, height: img.naturalHeight }); URL.revokeObjectURL(img.src); }; img.src = URL.createObjectURL(file); }); } async function init() { const res = await fetch("/api/classes"); const data = await res.json(); classes = data.classes; renderSliders(); renderLegend(); } function renderSliders() { slidersEl.innerHTML = classes.map((c) => `
${c.default_threshold}
`).join(""); classes.forEach((c) => { const input = document.getElementById(`slider-${c.id}`); const val = document.getElementById(`slider-${c.id}-val`); input.addEventListener("input", () => { val.textContent = input.value; }); }); } function renderLegend() { legendEl.innerHTML = classes.map((c) => `
${c.name}
`).join(""); } function getThresholds() { const thresholds = {}; classes.forEach((c) => { thresholds[c.id] = parseFloat(document.getElementById(`slider-${c.id}`).value); }); return thresholds; } // ── upload / dropzone ────────────────────────────────────── async function setSelectedFile(file) { if (!file || !file.type.startsWith("image/")) { showToast("Please choose a valid image file.", "error"); return; } selectedFile = file; previewImage.src = URL.createObjectURL(file); previewFileName.textContent = file.name; previewFileInfo.textContent = formatBytes(file.size); dropzoneEmpty.hidden = true; dropzonePreview.hidden = false; runBtn.disabled = false; const { width, height } = await readImageDimensions(file); previewFileInfo.textContent = `${width} × ${height} px · ${formatBytes(file.size)}`; } dropzone.addEventListener("click", (e) => { if (e.target.closest(".dropzone-preview")) return; imageInput.click(); }); dropzone.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); imageInput.click(); } }); imageInput.addEventListener("change", () => setSelectedFile(imageInput.files[0])); changeImageBtn.addEventListener("click", (e) => { e.stopPropagation(); imageInput.click(); }); ["dragenter", "dragover"].forEach((evt) => dropzone.addEventListener(evt, (e) => { e.preventDefault(); dropzone.classList.add("dragover"); }) ); ["dragleave", "drop"].forEach((evt) => dropzone.addEventListener(evt, (e) => { e.preventDefault(); dropzone.classList.remove("dragover"); }) ); dropzone.addEventListener("drop", (e) => { const file = e.dataTransfer.files[0]; if (file) setSelectedFile(file); }); // ── run detection ────────────────────────────────────────── runBtn.addEventListener("click", async () => { if (!selectedFile) { showToast("Please choose an image first.", "error"); return; } runBtn.disabled = true; runBtn.textContent = "Analyzing…"; scanOverlay.classList.add("active"); step1.classList.add("completed"); step1.classList.remove("active"); step2.classList.add("active"); line1.classList.add("filled"); stepEyebrow.innerHTML = ` Step 2 of 3`; try { const formData = new FormData(); formData.append("image", selectedFile); formData.append("resize", resizeCb.checked); formData.append("thresholds", JSON.stringify(getThresholds())); const res = await fetch("/api/detect", { method: "POST", body: formData }); if (!res.ok) throw new Error(`Request failed: ${res.status}`); const data = await res.json(); sessionStorage.setItem("cardentiq_result", JSON.stringify(data)); window.location.href = "report.html"; } catch (err) { showToast(`Detection failed: ${err.message}`, "error"); scanOverlay.classList.remove("active"); step1.classList.remove("completed"); step1.classList.add("active"); step2.classList.remove("active"); line1.classList.remove("filled"); stepEyebrow.innerHTML = ` Step 1 of 3`; runBtn.disabled = false; runBtn.textContent = "Analyze Image"; } }); init();