ClauseGuard / extension /sidepanel.js
gaurv007's picture
UI overhaul: Lucide icons (no emojis), expandable clause cards, severity filters, progress bars, copy/PDF actions, shared nav, interactive hover states
bdba541 verified
raw
history blame
5.02 kB
/**
* ClauseGuard — Side Panel (redesigned)
*/
const DESCS = {
"Limitation of liability": "Company limits or excludes liability for losses or damages.",
"Unilateral termination": "They can close your account without reason.",
"Unilateral change": "Terms can change without your consent.",
"Content removal": "Your content can be deleted without notice.",
"Contract by using": "You agree just by visiting or using the site.",
"Choice of law": "Foreign law applies instead of your local protections.",
"Jurisdiction": "Disputes handled in their preferred court.",
"Arbitration": "You waive your right to sue in court.",
};
// SVG icons for severity
const SEV_ICONS = {
HIGH: '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>',
MEDIUM: '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg>',
LOW: '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>',
};
let allClauses = [];
let currentFilter = "all";
async function loadResults() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) return;
const results = await chrome.runtime.sendMessage({ type: "GET_RESULTS", tabId: tab.id });
if (!results || !results.results) {
document.getElementById("empty-state").style.display = "block";
return;
}
document.getElementById("empty-state").style.display = "none";
document.getElementById("score-bar").style.display = "flex";
document.getElementById("filters").style.display = "flex";
// Meta
document.getElementById("meta").textContent = `${results.total_clauses} clauses · ${results.flagged_count} flagged`;
// Grade
const gb = document.getElementById("grade-box");
gb.textContent = results.grade;
gb.className = `grade-box grade-${results.grade.toLowerCase()}`;
// Score
document.getElementById("score-num").textContent = `${results.risk_score} / 100`;
const pf = document.getElementById("progress-fill");
pf.style.width = `${results.risk_score}%`;
pf.style.background = results.risk_score >= 60 ? "#ef4444" : results.risk_score >= 30 ? "#f59e0b" : "#22c55e";
// Counts
const counts = { HIGH: 0, MEDIUM: 0, LOW: 0 };
const flagged = results.results.filter(r => r.categories?.length > 0);
flagged.forEach(r => r.categories.forEach(c => { if (counts[c.severity] !== undefined) counts[c.severity]++; }));
document.getElementById("fc-high").textContent = counts.HIGH;
document.getElementById("fc-med").textContent = counts.MEDIUM;
document.getElementById("fc-low").textContent = counts.LOW;
allClauses = flagged;
renderClauses();
}
function renderClauses() {
const list = document.getElementById("clause-list");
const filtered = currentFilter === "all" ? allClauses : allClauses.filter(c => c.categories.some(cat => cat.severity === currentFilter));
if (filtered.length === 0) {
list.innerHTML = '<div style="text-align:center;padding:24px;color:#a1a1aa;font-size:12px;">No clauses match this filter.</div>';
return;
}
list.innerHTML = filtered.map((clause, i) => {
const maxSev = clause.categories.reduce((m, c) => {
const o = { HIGH: 3, MEDIUM: 2, LOW: 1 };
return (o[c.severity] || 0) > (o[m] || 0) ? c.severity : m;
}, "LOW");
const tagMap = { HIGH: "tag-high", MEDIUM: "tag-medium", LOW: "tag-low" };
const tags = clause.categories.map(c =>
`<span class="tag ${tagMap[c.severity] || "tag-medium"}">${SEV_ICONS[c.severity] || ""} ${c.name}</span>`
).join("");
const descs = clause.categories.map(c =>
`<div class="clause-desc">${DESCS[c.name] || c.name}</div>`
).join("");
const text = clause.text.length > 200 ? clause.text.slice(0, 200) + "…" : clause.text;
return `
<div class="clause-card sev-${maxSev.toLowerCase()}">
<div class="clause-tags">${tags}</div>
<div class="clause-text">${text}</div>
${descs}
</div>
`;
}).join("");
}
// Filters
document.getElementById("filters").addEventListener("click", (e) => {
const btn = e.target.closest(".filter-btn");
if (!btn) return;
document.querySelectorAll(".filter-btn").forEach(b => b.classList.remove("active"));
btn.classList.add("active");
currentFilter = btn.dataset.filter;
renderClauses();
});
// Listen for updates
chrome.storage.onChanged.addListener((changes) => {
for (const key of Object.keys(changes)) {
if (key.startsWith("results_")) { loadResults(); break; }
}
});
loadResults();