// Constants & Configuration const MODEL_COLORS = { "XGBoost": "#f59e0b", "LightGBM": "#10b981", "CatBoost": "#6366f1", "TabPFN": "#3b82f6", "SAP RPT-1 OSS": "#ec4899", "Voting Ensemble": "#fbbf24", "Stacking Ensemble": "#a78bfa", }; const MODEL_EMOJIS = { "XGBoost": "๐ŸŸก", "LightGBM": "๐ŸŸข", "CatBoost": "๐ŸŸฃ", "TabPFN": "๐ŸŸฆ", "SAP RPT-1 OSS": "๐Ÿฉท", "Voting Ensemble": "๐Ÿ†", "Stacking Ensemble": "โœจ", }; const ENSEMBLE_NAMES = ["Voting Ensemble", "Stacking Ensemble"]; // DOM Elements const dropZone = document.getElementById("drop-zone"); const fileInput = document.getElementById("file-input"); const uploadError = document.getElementById("upload-error"); const uploadSection = document.getElementById("upload-section"); const previewSection = document.getElementById("preview-section"); const previewMeta = document.getElementById("preview-meta"); const targetSelect = document.getElementById("target-select"); const previewTable = document.getElementById("preview-table"); const changeFileBtn = document.getElementById("change-file-btn"); const runBtn = document.getElementById("run-btn"); const loadingSection = document.getElementById("loading-section"); const resultsSection = document.getElementById("results-section"); const resetBtn = document.getElementById("reset-btn"); const exportCsvBtn = document.getElementById("export-csv-btn"); const exportJsonBtn = document.getElementById("export-json-btn"); const resumeSection = document.getElementById("resume-section"); const resumeFilename = document.getElementById("resume-filename"); const resumeClearBtn = document.getElementById("resume-clear-btn"); const resumeGoBtn = document.getElementById("resume-go-btn"); let currentFile = null; let chartInstances = []; // Drag & Drop Handling if (dropZone) { dropZone.addEventListener("click", () => fileInput.click()); dropZone.addEventListener("keydown", e => { if (e.key === "Enter" || e.key === " ") fileInput.click(); }); dropZone.addEventListener("dragover", e => { e.preventDefault(); dropZone.classList.add("drag-over"); }); dropZone.addEventListener("dragleave", () => dropZone.classList.remove("drag-over")); dropZone.addEventListener("drop", e => { e.preventDefault(); dropZone.classList.remove("drag-over"); const f = e.dataTransfer.files[0]; if (f) handleFile(f); }); } if (fileInput) { fileInput.addEventListener("change", () => { if (fileInput.files[0]) handleFile(fileInput.files[0]); }); } if (changeFileBtn) changeFileBtn.addEventListener("click", resetToUpload); if (resetBtn) resetBtn.addEventListener("click", resetToUpload); if (exportCsvBtn) exportCsvBtn.addEventListener("click", () => { const data = JSON.parse(sessionStorage.getItem("lastResults")); if (data) exportToCSV(data); }); if (exportJsonBtn) exportJsonBtn.addEventListener("click", () => { const data = JSON.parse(sessionStorage.getItem("lastResults")); if (data) exportToJSON(data); }); if (resumeClearBtn) resumeClearBtn.addEventListener("click", () => { sessionStorage.removeItem("lastResults"); sessionStorage.removeItem("lastFileName"); window.location.reload(); }); if (resumeGoBtn) resumeGoBtn.addEventListener("click", () => { window.location.href = "/static/arena.html"; }); // File selection and preview initialization async function handleFile(file) { uploadError.hidden = true; if (!file.name.endsWith(".csv")) { showError("Please upload a .csv file."); return; } const MAX_MB = 5; if (file.size > MAX_MB * 1024 * 1024) { showError(`File is too large (${(file.size / 1048576).toFixed(1)} MB). Maximum is ${MAX_MB} MB.`); return; } currentFile = file; const fd = new FormData(); fd.append("file", file); try { const res = await fetch("/preview", { method: "POST", body: fd }); if (!res.ok) { const err = await res.json(); showError(err.detail || "Failed to read CSV."); return; } const data = await res.json(); renderPreview(data, file); } catch (e) { showError("Network error: " + e.message); } } function renderPreview(data, file) { // Meta badges previewMeta.innerHTML = ` ๐Ÿ“„ ${file.name} ${data.n_rows.toLocaleString()} rows ${data.n_cols} columns `; // Target column selector targetSelect.innerHTML = ""; data.columns.forEach(col => { const opt = document.createElement("option"); opt.value = col; opt.textContent = col; if (col === data.default_target) opt.selected = true; targetSelect.appendChild(opt); }); // Preview table const cols = data.columns; let thead = "" + cols.map(c => `${esc(c)}`).join("") + ""; let tbody = "" + data.preview.map(row => "" + cols.map(c => `${esc(String(row[c] ?? ""))}`).join("") + "" ).join("") + ""; previewTable.innerHTML = thead + tbody; // Highlight target column on select change targetSelect.addEventListener("change", () => highlightTarget(targetSelect.value, cols)); uploadSection.hidden = true; previewSection.hidden = false; } function highlightTarget(targetCol, cols) { previewTable.querySelectorAll("th, td").forEach(el => el.classList.remove("target-col")); const idx = cols.indexOf(targetCol); if (idx < 0) return; previewTable.querySelectorAll("tr").forEach(row => { const cells = row.querySelectorAll("th, td"); if (cells[idx]) cells[idx].classList.add("target-col"); }); } // Execute benchmarking suite if (runBtn) { runBtn.addEventListener("click", async () => { if (!currentFile) return; previewSection.hidden = true; loadingSection.hidden = false; // Animate loader steps const steps = ["step-xgb", "step-lgb", "step-cat", "step-tabpfn", "step-sap", "step-vote", "step-stack"]; const delays = [0, 150, 300, 450, 600, 750, 900]; let stepIdx = 0; const stepTimer = setInterval(() => { if (stepIdx > 0) { document.getElementById(steps[stepIdx - 1])?.classList.remove("active"); document.getElementById(steps[stepIdx - 1])?.classList.add("done"); } if (stepIdx < steps.length) { document.getElementById(steps[stepIdx])?.classList.add("active"); stepIdx++; } else { clearInterval(stepTimer); } }, 1400); const fd = new FormData(); fd.append("file", currentFile); fd.append("target_col", targetSelect.value); try { const res = await fetch("/benchmark", { method: "POST", body: fd }); if (!res.ok) { const err = await res.json(); clearInterval(stepTimer); loadingSection.hidden = true; previewSection.hidden = false; showError(err.detail || "Benchmarking failed."); return; } const data = await res.json(); clearInterval(stepTimer); loadingSection.hidden = true; sessionStorage.setItem("lastResults", JSON.stringify(data)); sessionStorage.setItem("lastFileName", currentFile.name); window.location.href = "/static/arena.html"; } catch (e) { clearInterval(stepTimer); loadingSection.hidden = true; previewSection.hidden = false; showError("Network error: " + e.message); } }); } // Visualization of benchmarking results function renderResults(data) { const { dataset_info, task, results, recommendation, n_folds } = data; const isCLF = task === "classification"; const primaryKey = isCLF ? "roc_auc" : "r2"; const primaryLabel = isCLF ? "ROC-AUC" : "Rยฒ"; const fileName = sessionStorage.getItem("lastFileName") || "Dataset"; // โ”€โ”€ Info bar const taskBadge = isCLF ? `๐Ÿท Classification` : `๐Ÿ“ˆ Regression`; document.getElementById("info-bar").innerHTML = ` ๐Ÿ“„ ${esc(fileName)} ${taskBadge} ${dataset_info.n_samples.toLocaleString()} samples ${dataset_info.n_features} features Target: ${esc(dataset_info.target_col)} ${isCLF ? `${dataset_info.n_classes} classes` : ""} ${n_folds}-Fold CV `; // โ”€โ”€ KPI cards const kpiGrid = document.getElementById("kpi-grid"); kpiGrid.innerHTML = ""; const validModels = Object.entries(results).filter(([, v]) => !v.error); const bestEntry = validModels.reduce((best, [name, v]) => (v.mean[primaryKey] || 0) > (best[1].mean[primaryKey] || 0) ? [name, v] : best , validModels[0]); const kpis = [ { label: "Best Model", value: bestEntry[0], sub: `${primaryLabel}: ${fmt(bestEntry[1].mean[primaryKey])}`, color: MODEL_COLORS[bestEntry[0]], }, { label: `Best ${primaryLabel}`, value: fmt(bestEntry[1].mean[primaryKey]), sub: `ยฑ ${fmt(bestEntry[1].std[primaryKey])} std`, color: "#818cf8", }, { label: "Models Evaluated", value: validModels.length, sub: `${n_folds}-fold cross-validation`, color: "#10b981", }, { label: "Dataset Size", value: dataset_info.n_samples.toLocaleString(), sub: `${dataset_info.n_features} features ยท ${isCLF ? dataset_info.n_classes + " classes" : "regression"}`, color: "#f59e0b", }, ]; kpis.forEach(k => { const card = document.createElement("div"); card.className = "kpi-card"; card.style.setProperty("--accent-bar", k.color); card.innerHTML = `
${k.label}
${esc(String(k.value))}
${k.sub}
`; kpiGrid.appendChild(card); }); // โ”€โ”€ Legend const legendEl = document.getElementById("legend"); legendEl.innerHTML = Object.entries(MODEL_COLORS).map(([name, color]) => `
${name}
` ).join(""); // โ”€โ”€ Charts chartInstances.forEach(c => c.destroy()); chartInstances = []; const chartsGrid = document.getElementById("charts-grid"); chartsGrid.innerHTML = ""; const metricsToChart = isCLF ? [["roc_auc", "ROC-AUC"], ["accuracy", "Accuracy"], ["f1_macro", "F1-Macro"]] : [["r2", "Rยฒ"], ["mae", "MAE"], ["rmse", "RMSE"]]; metricsToChart.forEach(([key, label]) => { const modelNames = Object.keys(results).filter(n => !results[n].error && results[n].mean[key] != null); if (!modelNames.length) return; const vals = modelNames.map(n => roundN(results[n].mean[key], 4)); const errs = modelNames.map(n => roundN(results[n].std[key] || 0, 4)); const bgs = modelNames.map(n => (MODEL_COLORS[n] || "#888") + "cc"); const bords = modelNames.map(n => MODEL_COLORS[n] || "#888"); const isErrorMetric = ["mae", "rmse", "log_loss"].includes(key.toLowerCase()); const highQual = isErrorMetric ? "poor" : "excellent"; const lowQual = isErrorMetric ? "excellent" : "poor"; const card = document.createElement("div"); card.className = "chart-card"; const canvasId = `chart-${key}`; card.innerHTML = `

${label}

${label} (mean ยฑ std over ${n_folds} folds)
High ${label} = ${highQual}
Low ${label} = ${lowQual}
`; chartsGrid.appendChild(card); const minVal = Math.min(...vals); const maxVal = Math.max(...vals); const pad = Math.max((maxVal - minVal) * 0.15, 0.02); const inst = new Chart(document.getElementById(canvasId), { type: "bar", data: { labels: modelNames, datasets: [{ label, data: vals, backgroundColor: bgs, borderColor: bords, borderWidth: 2, borderRadius: 8, }], }, options: { responsive: true, plugins: { legend: { display: false }, tooltip: { callbacks: { label: ctx => `${label}: ${ctx.parsed.y.toFixed(4)} ยฑ ${errs[ctx.dataIndex].toFixed(4)}`, }, }, }, scales: { y: { min: Math.max(key === "roc_auc" || key === "accuracy" ? 0 : -Infinity, minVal - pad), max: key === "roc_auc" || key === "accuracy" ? Math.min(1, maxVal + pad) : maxVal + pad, grid: { color: "rgba(100, 116, 139, 0.1)" }, ticks: { color: "rgba(100, 116, 139, 0.8)", font: { size: 11 } }, }, x: { grid: { display: false }, ticks: { color: "rgba(100, 116, 139, 0.8)", font: { size: 12 } }, }, }, }, }); chartInstances.push(inst); }); // โ”€โ”€ Full table const thead = document.getElementById("results-thead"); const tbody = document.getElementById("results-tbody"); const allMetrics = isCLF ? ["accuracy", "f1_macro", "roc_auc", "log_loss", "fit_time"] : ["r2", "mae", "rmse", "fit_time"]; const metricLabels = isCLF ? ["Accuracy", "F1-Macro", "ROC-AUC", "Log Loss", "Fit Time"] : ["Rยฒ", "MAE", "RMSE", "Fit Time"]; thead.innerHTML = "Model" + metricLabels.map(l => `${l}`).join("") + ""; tbody.innerHTML = Object.entries(results).map(([name, d]) => { if (d.error) { const errText = d.error.startsWith("Error:") ? d.error : `Error: ${d.error}`; return `${name}${esc(errText)}`; } const cells = allMetrics.map(k => { const v = d.mean[k]; if (v == null) return `โ€”`; const isTime = k === "fit_time"; if (isTime) return `${v.toFixed(3)}s`; const cls = scoreClass(v, k, task); return `${v.toFixed(4)} ยฑ${(d.std[k]||0).toFixed(3)}`; }).join(""); return `${name}${cells}`; }).join(""); // โ”€โ”€ Recommendations const recGrid = document.getElementById("recommendation-grid"); recGrid.innerHTML = ""; const recs = recommendation.recommendations || {}; const recDefs = [ { key: "best_overall", label: "๐Ÿ† Best Overall", winner: true }, { key: "production", label: "๐Ÿš€ Production Ready", winner: false }, { key: "best_accuracy", label: "๐ŸŽฏ Highest Accuracy", winner: false }, { key: "best_speed", label: "โšก Fastest Training", winner: false }, { key: "best_consistency", label: "๐Ÿ›ก Most Consistent", winner: false }, ]; recDefs.forEach(({ key, label, winner }) => { const rec = recs[key]; if (!rec) return; const color = MODEL_COLORS[rec.model] || "#888"; const score = rec.score != null ? `
${recommendation.primary_metric}: ${typeof rec.score === "number" ? rec.score.toFixed(4) : rec.score}
` : ""; const card = document.createElement("div"); card.className = `rec-card ${key}${winner ? " winner" : ""}`; card.innerHTML = `
${label}
${winner ? '๐Ÿ†' : ""} ${rec.model}
${score}

${esc(rec.reason)}

`; recGrid.appendChild(card); }); // โ”€โ”€ Ensemble Analysis section renderEnsembleSection(data.ensemble_info || {}, results, recommendation, task); // โ”€โ”€ Interactive Playground renderPlayground(data.dataset_info, recommendation.recommendations?.best_overall, task); // โ”€โ”€ Statistical Rigor renderStatisticalSection(data.stats || {}); resultsSection.hidden = false; resultsSection.scrollIntoView({ behavior: "smooth", block: "start" }); } function renderStatisticalSection(stats) { const tbody = document.getElementById("rigor-tbody"); const badge = document.getElementById("friedman-badge"); if (!tbody || !stats.ranking) return; const isSig = stats.significant; badge.className = `p-value-badge ${isSig ? 'significant' : 'not-significant'}`; badge.textContent = isSig ? `Significant (p=${stats.friedman_p})` : `Not Significant (p=${stats.friedman_p})`; tbody.innerHTML = stats.ranking.map(r => { const stability = r.win_rate; return ` ${r.avg_rank <= 1.5 ? '๐Ÿ†' : ''} ${r.model} ${r.avg_rank}
${stability}% ${stability > 50 ? 'Dominant' : (stability > 20 ? 'Competitive' : 'Volatile')} `; }).join(""); } // โ”€โ”€ Playground Logic โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function renderPlayground(datasetInfo, bestOverall, task) { const form = document.getElementById("playground-form"); const valueEl = document.getElementById("prediction-value"); const subEl = document.getElementById("prediction-sub"); const probEl = document.getElementById("probability-bars"); if (!form || !bestOverall) return; form.innerHTML = ""; const features = datasetInfo.columns || []; const preview = datasetInfo.preview ? datasetInfo.preview[0] : {}; features.forEach(f => { const div = document.createElement("div"); div.className = "playground-field"; const types = datasetInfo.feature_types || {}; const isNumeric = types[f] === "numeric"; const sampleVal = preview[f]; const val = sampleVal != null ? sampleVal : (isNumeric ? 0 : ""); const placeholder = isNumeric ? "Enter value..." : "Enter text..."; div.innerHTML = ` `; form.appendChild(div); }); const updatePrediction = async () => { const inputs = form.querySelectorAll("input"); const data = {}; inputs.forEach(i => { const v = i.value; data[i.dataset.feature] = isNaN(parseFloat(v)) ? v : parseFloat(v); }); valueEl.style.opacity = "0.5"; try { const resp = await fetch("/predict", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }); const res = await resp.json(); valueEl.style.opacity = "1"; if (res.error) { valueEl.textContent = "Error"; subEl.textContent = res.error; return; } if (task === "classification") { valueEl.textContent = res.prediction || "โ€”"; subEl.textContent = `Most likely class (via ${bestOverall.model})`; if (res.probabilities && res.labels) { probEl.innerHTML = res.probabilities.map((p, i) => `
${res.labels[i] || 'Class '+i}${(p*100).toFixed(1)}%
`).join(""); } } else { const val = Number(res.prediction); valueEl.textContent = isNaN(val) ? "โ€”" : val.toFixed(4); subEl.textContent = `Regression output (via ${bestOverall.model})`; probEl.innerHTML = ""; } } catch (e) { valueEl.style.opacity = "1"; valueEl.textContent = "Error"; subEl.textContent = "Service unavailable"; } }; form.addEventListener("input", debounce(updatePrediction, 300)); updatePrediction(); // Initial prediction } function debounce(fn, ms) { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => fn.apply(this, args), ms); }; } // โ”€โ”€ Ensemble Analysis renderer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function renderEnsembleSection(ensembleInfo, results, recommendation, task) { const grid = document.getElementById("ensemble-grid"); const title = document.getElementById("ensemble-section-title"); grid.innerHTML = ""; const entries = Object.entries(ensembleInfo).filter(([name]) => results[name] && !results[name].error); if (!entries.length) { title.hidden = true; grid.hidden = true; return; } title.hidden = false; grid.hidden = false; const primaryKey = task === "classification" ? "roc_auc" : "r2"; const primaryLabel = task === "classification" ? "ROC-AUC" : "Rยฒ"; // Find the best individual model score (excluding ensembles) for gain % const indivScores = Object.entries(results) .filter(([n, v]) => !ENSEMBLE_NAMES.includes(n) && !v.error && v.mean[primaryKey] != null) .map(([, v]) => v.mean[primaryKey]); const bestIndivScore = indivScores.length ? Math.max(...indivScores) : 0; entries.forEach(([name, info]) => { const cv = results[name]; const score = cv.mean[primaryKey] ?? 0; const std = cv.std[primaryKey] ?? 0; const ft = cv.mean.fit_time ?? 0; const color = MODEL_COLORS[name] || "#888"; const gain = bestIndivScore > 0 ? ((score - bestIndivScore) / bestIndivScore * 100) : 0; const gainStr = gain >= 0 ? `โ–ฒ +${gain.toFixed(2)}% vs best individual` : `โ–ผ ${gain.toFixed(2)}% vs best individual`; const componentPills = (info.components || []).map(c => `${c}` ).join(""); const metaTag = info.meta_learner ? `
Meta-learner: ${esc(info.meta_learner)}
` : ""; const card = document.createElement("div"); card.className = "ens-card"; card.style.setProperty("--ens-color", color); card.innerHTML = `
${MODEL_EMOJIS[name] || "๐Ÿงฉ"} ${name} ${info.type === "voting" ? "Soft Voting" : "Stacking"}
${score.toFixed(4)} ${primaryLabel} ยฑ ${std.toFixed(3)}
${gainStr}
${metaTag}
${esc(info.description || "")}
Component Models
${componentPills}
`; grid.appendChild(card); }); } // โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function resetToUpload() { currentFile = null; if (fileInput) fileInput.value = ""; if (uploadError) uploadError.hidden = true; if (previewSection) previewSection.hidden = true; if (loadingSection) loadingSection.hidden = true; if (resultsSection) resultsSection.hidden = true; if (uploadSection) uploadSection.hidden = false; chartInstances.forEach(c => c.destroy()); chartInstances = []; sessionStorage.removeItem("lastResults"); sessionStorage.removeItem("lastFileName"); if (window.location.pathname.includes("arena.html")) { window.location.href = "/static/uploader.html"; } else { window.scrollTo({ top: 0, behavior: "smooth" }); } } function showError(msg) { if (!uploadError) return; uploadError.textContent = msg; uploadError.hidden = false; window.scrollTo({ top: 0, behavior: "smooth" }); } function exportToCSV(data) { const results = data.results; const models = Object.keys(results); if (models.length === 0) return; const metricKeys = new Set(); models.forEach(m => { if (results[m].mean) { Object.keys(results[m].mean).forEach(k => metricKeys.add(k)); } }); const metrics = Array.from(metricKeys).sort(); let csv = "Model," + metrics.map(m => m + " (mean)").join(",") + "\n"; models.forEach(m => { if (results[m].error) { const errText = results[m].error.startsWith("Error:") ? results[m].error : `Error: ${results[m].error}`; csv += `${m.replace(/,/g, "")},${errText.replace(/,/g, " ")}\n`; return; } let row = [m.replace(/,/g, "")]; metrics.forEach(met => { let val = results[m].mean ? results[m].mean[met] : ""; row.push(val !== undefined && val !== null ? val : ""); }); csv += row.join(",") + "\n"; }); downloadFile(csv, "benchmark_results.csv", "text/csv"); } function exportToJSON(data) { const json = JSON.stringify(data, null, 2); downloadFile(json, "benchmark_results.json", "application/json"); } function downloadFile(content, fileName, contentType) { const blob = new Blob([content], { type: contentType }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = fileName; a.click(); setTimeout(() => URL.revokeObjectURL(url), 100); } function fmt(v) { if (v == null || isNaN(v)) return "โ€”"; return Number(v).toFixed(4); } function roundN(v, n) { return Math.round(v * Math.pow(10, n)) / Math.pow(10, n); } function esc(str) { return String(str) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function scoreClass(v, metric, task) { if (metric === "fit_time") return ""; const higherBetter = !["mae", "rmse", "mse", "log_loss"].includes(metric); if (!higherBetter) { if (v < 0.1) return "col-excellent"; if (v < 0.3) return "col-good"; if (v < 0.5) return "col-fair"; return "col-poor"; } if (metric === "roc_auc" || metric === "accuracy") { if (v >= 0.95) return "col-excellent"; if (v >= 0.88) return "col-good"; if (v >= 0.75) return "col-fair"; return "col-poor"; } if (metric === "r2") { if (v >= 0.75) return "col-excellent"; if (v >= 0.5) return "col-good"; if (v >= 0.25) return "col-fair"; return "col-poor"; } if (v >= 0.85) return "col-excellent"; if (v >= 0.70) return "col-good"; if (v >= 0.55) return "col-fair"; return "col-poor"; } // โ”€โ”€ Restore state on load โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ window.addEventListener("DOMContentLoaded", () => { checkResumeState(); }); // โ”€โ”€ Handle Back Button (BFCache) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ window.addEventListener("pageshow", function(e) { checkResumeState(); }); // Theme Toggle Logic const themeToggle = document.getElementById("theme-toggle"); const themeIconDark = document.getElementById("theme-icon-dark"); const themeIconLight = document.getElementById("theme-icon-light"); function setTheme(theme) { document.documentElement.setAttribute("data-theme", theme); localStorage.setItem("theme", theme); if (theme === "light") { if (themeIconDark) themeIconDark.style.display = "block"; if (themeIconLight) themeIconLight.style.display = "none"; } else { if (themeIconDark) themeIconDark.style.display = "none"; if (themeIconLight) themeIconLight.style.display = "block"; } } if (themeToggle) { themeToggle.addEventListener("click", () => { const current = document.documentElement.getAttribute("data-theme") || "dark"; setTheme(current === "dark" ? "light" : "dark"); }); } // Initial theme load const savedTheme = localStorage.getItem("theme") || "dark"; setTheme(savedTheme); function checkResumeState() { const savedResults = sessionStorage.getItem("lastResults"); const savedFile = sessionStorage.getItem("lastFileName"); const isUploader = window.location.pathname.includes("uploader.html") || window.location.pathname === "/"; const isArena = window.location.pathname.includes("arena.html"); // Handle MBench logo link privilege const navLogo = document.getElementById("nav-logo"); if (navLogo) { // Privilege: Only uploader page in fresh mode (no results) can go to landing if (isUploader && !savedResults) { navLogo.classList.add("active-link"); navLogo.style.pointerEvents = "auto"; } else { navLogo.classList.remove("active-link"); navLogo.style.pointerEvents = "none"; } } if (savedResults && savedFile) { if (isUploader) { // Always show resume card if data exists, until cleared if (uploadSection) uploadSection.hidden = true; if (previewSection) previewSection.hidden = true; if (loadingSection) loadingSection.hidden = true; if (resumeSection) { resumeSection.hidden = false; resumeFilename.textContent = savedFile; } } else if (isArena) { // Auto-render on results page if data exists try { const data = JSON.parse(savedResults); renderResults(data); } catch (e) { window.location.href = "/static/uploader.html"; } } } else { // No saved data: reset to default if (isUploader) { if (resumeSection) resumeSection.hidden = true; if (uploadSection) uploadSection.hidden = false; } else if (isArena) { window.location.href = "/static/uploader.html"; } } }