Spaces:
Sleeping
Sleeping
| /** | |
| * 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(); | |