File size: 5,985 Bytes
9548e93
dd28267
 
 
 
9548e93
 
bdba541
 
 
 
 
 
 
 
 
 
 
dd28267
 
 
bdba541
 
dd28267
bdba541
 
 
9548e93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bdba541
9548e93
 
bdba541
 
 
 
 
 
 
 
 
 
 
 
 
9548e93
dd28267
 
9548e93
dd28267
 
 
 
 
 
 
 
 
 
 
bdba541
 
 
9548e93
 
bdba541
9548e93
 
bdba541
9548e93
bdba541
9548e93
 
bdba541
9548e93
 
 
bdba541
 
dd28267
bdba541
9548e93
dd28267
bdba541
 
 
 
 
 
 
 
9548e93
bdba541
 
 
 
 
 
 
 
 
 
9548e93
 
bdba541
9548e93
bdba541
 
9548e93
bdba541
 
 
9548e93
 
bdba541
9548e93
 
bdba541
9548e93
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/**
 * ClauseGuard — Side Panel v4.3
 *
 * FIXED v4.3: Added CRITICAL severity support (filter, cards, icons, descriptions).
 * FIXED v4.3: Severity ordering now uses numeric mapping consistently.
 */

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.",
};

// Severity numeric ordering (higher = more severe)
const SEV_ORDER = { CRITICAL: 4, HIGH: 3, MEDIUM: 2, LOW: 1 };

// SVG icons for severity
const SEV_ICONS = {
  CRITICAL: '<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>',
  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";

  // FIX v4.3: Count CRITICAL severity too
  const counts = { CRITICAL: 0, 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]++;
    else counts.MEDIUM++; // Default unknown to MEDIUM
  }));

  // Show CRITICAL count in the filter if any exist
  const fcCrit = document.getElementById("fc-crit");
  const critFilter = document.getElementById("filter-critical");
  if (fcCrit) fcCrit.textContent = counts.CRITICAL;
  if (critFilter) critFilter.style.display = counts.CRITICAL > 0 ? "flex" : "none";

  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) => {
      return (SEV_ORDER[c.severity] || 0) > (SEV_ORDER[m] || 0) ? c.severity : m;
    }, "LOW");

    const tagMap = { CRITICAL: "tag-critical", 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();