gaurv007 commited on
Commit
bdba541
·
verified ·
1 Parent(s): abe38ff

UI overhaul: Lucide icons (no emojis), expandable clause cards, severity filters, progress bars, copy/PDF actions, shared nav, interactive hover states

Browse files
extension/popup.html CHANGED
@@ -6,183 +6,109 @@
6
  <title>ClauseGuard</title>
7
  <style>
8
  * { margin: 0; padding: 0; box-sizing: border-box; }
9
- body {
10
- width: 360px;
11
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
12
- background: #fafafa;
13
- color: #1f2937;
14
- }
15
- .header {
16
- background: linear-gradient(135deg, #1e1b4b, #312e81);
17
- color: white;
18
- padding: 20px;
19
- text-align: center;
20
- }
21
- .header h1 { font-size: 20px; font-weight: 700; }
22
- .header p { font-size: 12px; opacity: 0.8; margin-top: 4px; }
23
 
24
- .status { padding: 16px 20px; }
25
- .status-card {
26
- background: white;
27
- border: 1px solid #e5e7eb;
28
- border-radius: 12px;
29
- padding: 16px;
30
- text-align: center;
31
- }
32
- .risk-score {
33
- font-size: 48px;
34
- font-weight: 800;
35
- line-height: 1;
36
- }
37
- .risk-label { font-size: 12px; color: #6b7280; margin-top: 4px; }
38
- .grade {
39
- display: inline-flex;
40
- align-items: center;
41
- gap: 6px;
42
- margin-top: 10px;
43
- padding: 6px 14px;
44
- border-radius: 999px;
45
- font-weight: 600;
46
- font-size: 14px;
47
- }
48
- .grade-f { background: #fef2f2; color: #dc2626; }
49
- .grade-d { background: #fff7ed; color: #ea580c; }
50
- .grade-c { background: #fefce8; color: #ca8a04; }
51
- .grade-b { background: #f0fdf4; color: #16a34a; }
52
- .grade-a { background: #f0fdf4; color: #16a34a; }
53
 
54
- .counts {
55
- display: grid;
56
- grid-template-columns: repeat(3, 1fr);
57
- gap: 8px;
58
- margin-top: 12px;
59
- }
60
- .count-box {
61
- padding: 8px;
62
- border-radius: 8px;
63
- text-align: center;
64
- }
65
- .count-box .num { font-size: 20px; font-weight: 700; }
66
- .count-box .label { font-size: 10px; font-weight: 500; }
67
- .count-high { background: #fef2f2; color: #dc2626; }
68
- .count-med { background: #fff7ed; color: #ea580c; }
69
- .count-low { background: #fefce8; color: #ca8a04; }
70
 
71
- .actions { padding: 0 20px 16px; }
72
- .btn {
73
- display: block;
74
- width: 100%;
75
- padding: 12px;
76
- border: none;
77
- border-radius: 10px;
78
- font-size: 14px;
79
- font-weight: 600;
80
- cursor: pointer;
81
- margin-bottom: 8px;
82
- transition: opacity 0.15s;
83
- }
84
- .btn:hover { opacity: 0.9; }
85
- .btn-primary { background: #4f46e5; color: white; }
86
- .btn-secondary { background: #f3f4f6; color: #374151; }
87
- .btn-danger { background: #fef2f2; color: #dc2626; }
88
 
89
- .usage {
90
- padding: 12px 20px;
91
- border-top: 1px solid #e5e7eb;
92
- font-size: 12px;
93
- color: #6b7280;
94
- display: flex;
95
- justify-content: space-between;
96
- align-items: center;
97
- }
98
- .usage-bar {
99
- width: 100px;
100
- height: 4px;
101
- background: #e5e7eb;
102
- border-radius: 99px;
103
- overflow: hidden;
104
- }
105
- .usage-bar-fill {
106
- height: 100%;
107
- background: #4f46e5;
108
- border-radius: 99px;
109
- transition: width 0.3s;
110
- }
111
 
112
- .empty-state {
113
- padding: 40px 20px;
114
- text-align: center;
115
- color: #9ca3af;
116
- }
117
- .empty-state .icon { font-size: 40px; }
118
- .empty-state p { margin-top: 8px; font-size: 13px; }
119
 
120
- .footer {
121
- padding: 12px 20px;
122
- border-top: 1px solid #e5e7eb;
123
- text-align: center;
124
- }
125
- .footer a {
126
- color: #6366f1;
127
- text-decoration: none;
128
- font-size: 12px;
129
- font-weight: 500;
130
- }
131
- .footer a:hover { text-decoration: underline; }
132
  </style>
133
  </head>
134
  <body>
135
  <div class="header">
136
- <h1>🛡️ ClauseGuard</h1>
137
- <p>AI Fine Print Scanner</p>
138
  </div>
139
 
140
  <div id="results-view" style="display:none;">
141
- <div class="status">
142
- <div class="status-card">
143
- <div class="risk-score" id="risk-score">—</div>
144
- <div class="risk-label">RISK SCORE</div>
145
- <div class="grade" id="grade-badge"></div>
146
- <div class="counts">
147
- <div class="count-box count-high">
148
- <div class="num" id="count-high">0</div>
149
- <div class="label">🔴 HIGH</div>
150
- </div>
151
- <div class="count-box count-med">
152
- <div class="num" id="count-med">0</div>
153
- <div class="label">🟠 MEDIUM</div>
154
- </div>
155
- <div class="count-box count-low">
156
- <div class="num" id="count-low">0</div>
157
- <div class="label">🟡 LOW</div>
158
- </div>
159
- </div>
160
  </div>
 
 
 
 
 
 
161
  </div>
162
  <div class="actions">
163
- <button class="btn btn-primary" id="btn-details">📋 View Full Details</button>
164
- <button class="btn btn-secondary" id="btn-rescan">🔄 Re-scan Page</button>
 
 
 
 
 
 
165
  </div>
166
  </div>
167
 
168
  <div id="empty-view">
169
- <div class="empty-state">
170
- <div class="icon">🔍</div>
171
- <p>No scan results for this page yet.</p>
172
  </div>
173
  <div class="actions">
174
- <button class="btn btn-primary" id="btn-scan">🔍 Scan This Page</button>
 
 
 
175
  </div>
176
  </div>
177
 
178
  <div class="usage">
179
- <span id="usage-text">Free: 0/10 scans</span>
180
- <div class="usage-bar"><div class="usage-bar-fill" id="usage-fill" style="width:0%"></div></div>
181
  </div>
182
 
183
  <div class="footer">
184
- <a href="https://clauseguard.com" target="_blank">clauseguard.com</a> ·
185
- <a href="https://app.clauseguard.com/pricing" target="_blank">Upgrade to Pro</a>
186
  </div>
187
 
188
  <script src="popup.js"></script>
 
6
  <title>ClauseGuard</title>
7
  <style>
8
  * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { width: 340px; font-family: system-ui, -apple-system, sans-serif; background: #fff; color: #18181b; font-size: 13px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ .header { padding: 16px 16px 12px; border-bottom: 1px solid #f4f4f5; display: flex; align-items: center; gap: 8px; }
12
+ .header svg { width: 18px; height: 18px; color: #18181b; }
13
+ .header h1 { font-size: 15px; font-weight: 600; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ .score-card { padding: 16px; }
16
+ .score-row { display: flex; align-items: baseline; justify-content: space-between; }
17
+ .score-num { font-size: 36px; font-weight: 600; letter-spacing: -1px; }
18
+ .score-label { font-size: 12px; color: #a1a1aa; margin-left: 4px; }
19
+ .grade { font-size: 12px; font-weight: 600; padding: 3px 10px; border-radius: 6px; border: 1px solid; }
20
+ .grade-f, .grade-d { background: #fef2f2; color: #b91c1c; border-color: #fecaca; }
21
+ .grade-c { background: #fffbeb; color: #a16207; border-color: #fde68a; }
22
+ .grade-b, .grade-a { background: #f0fdf4; color: #15803d; border-color: #bbf7d0; }
 
 
 
 
 
 
 
 
23
 
24
+ .bar-wrap { margin-top: 10px; height: 4px; background: #f4f4f5; border-radius: 99px; overflow: hidden; }
25
+ .bar-fill { height: 100%; border-radius: 99px; transition: width 0.6s ease; }
26
+ .bar-red { background: #ef4444; }
27
+ .bar-amber { background: #f59e0b; }
28
+ .bar-green { background: #22c55e; }
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
+ .counts { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px; padding: 0 16px 12px; }
31
+ .count-box { text-align: center; padding: 8px 4px; border-radius: 8px; border: 1px solid #f4f4f5; }
32
+ .count-num { font-size: 18px; font-weight: 600; }
33
+ .count-label { font-size: 10px; margin-top: 2px; display: flex; align-items: center; justify-content: center; gap: 4px; }
34
+ .dot { width: 6px; height: 6px; border-radius: 50%; }
35
+ .dot-red { background: #ef4444; }
36
+ .dot-amber { background: #f59e0b; }
37
+ .dot-blue { background: #3b82f6; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
+ .actions { padding: 0 16px 12px; display: flex; flex-direction: column; gap: 6px; }
40
+ .btn { display: flex; align-items: center; justify-content: center; gap: 6px; padding: 10px; border: none; border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.15s; }
41
+ .btn svg { width: 15px; height: 15px; }
42
+ .btn-primary { background: #18181b; color: #fff; }
43
+ .btn-primary:hover { background: #27272a; }
44
+ .btn-secondary { background: #f4f4f5; color: #3f3f46; }
45
+ .btn-secondary:hover { background: #e4e4e7; }
46
 
47
+ .usage { padding: 10px 16px; border-top: 1px solid #f4f4f5; display: flex; align-items: center; justify-content: space-between; }
48
+ .usage-text { font-size: 11px; color: #a1a1aa; }
49
+ .usage-bar { width: 80px; height: 3px; background: #f4f4f5; border-radius: 99px; overflow: hidden; }
50
+ .usage-fill { height: 100%; background: #18181b; border-radius: 99px; transition: width 0.3s; }
51
+
52
+ .empty { padding: 40px 16px; text-align: center; }
53
+ .empty svg { width: 32px; height: 32px; color: #d4d4d8; margin: 0 auto 8px; }
54
+ .empty p { color: #a1a1aa; font-size: 13px; }
55
+
56
+ .footer { padding: 8px 16px; border-top: 1px solid #f4f4f5; text-align: center; }
57
+ .footer a { color: #a1a1aa; text-decoration: none; font-size: 11px; }
58
+ .footer a:hover { color: #52525b; }
59
  </style>
60
  </head>
61
  <body>
62
  <div class="header">
63
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/></svg>
64
+ <h1>ClauseGuard</h1>
65
  </div>
66
 
67
  <div id="results-view" style="display:none;">
68
+ <div class="score-card">
69
+ <div class="score-row">
70
+ <div><span class="score-num" id="risk-score">—</span><span class="score-label">/100 risk</span></div>
71
+ <span class="grade" id="grade-badge"></span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  </div>
73
+ <div class="bar-wrap"><div class="bar-fill" id="bar-fill" style="width:0%"></div></div>
74
+ </div>
75
+ <div class="counts">
76
+ <div class="count-box"><div class="count-num" id="c-high">0</div><div class="count-label"><span class="dot dot-red"></span>High</div></div>
77
+ <div class="count-box"><div class="count-num" id="c-med">0</div><div class="count-label"><span class="dot dot-amber"></span>Medium</div></div>
78
+ <div class="count-box"><div class="count-num" id="c-low">0</div><div class="count-label"><span class="dot dot-blue"></span>Low</div></div>
79
  </div>
80
  <div class="actions">
81
+ <button class="btn btn-primary" id="btn-details">
82
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
83
+ View full details
84
+ </button>
85
+ <button class="btn btn-secondary" id="btn-rescan">
86
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/></svg>
87
+ Re-scan page
88
+ </button>
89
  </div>
90
  </div>
91
 
92
  <div id="empty-view">
93
+ <div class="empty">
94
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><path d="M7 12h10"/></svg>
95
+ <p>No scan results yet.</p>
96
  </div>
97
  <div class="actions">
98
+ <button class="btn btn-primary" id="btn-scan">
99
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><path d="M7 12h10"/></svg>
100
+ Scan this page
101
+ </button>
102
  </div>
103
  </div>
104
 
105
  <div class="usage">
106
+ <span class="usage-text" id="usage-text">Free: 0/10 scans</span>
107
+ <div class="usage-bar"><div class="usage-fill" id="usage-fill" style="width:0%"></div></div>
108
  </div>
109
 
110
  <div class="footer">
111
+ <a href="https://clauseguard.com" target="_blank">clauseguard.com</a>
 
112
  </div>
113
 
114
  <script src="popup.js"></script>
extension/sidepanel.html CHANGED
@@ -3,149 +3,84 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>ClauseGuard — Analysis</title>
7
  <style>
8
  * { margin: 0; padding: 0; box-sizing: border-box; }
9
- body {
10
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
11
- background: #f8fafc;
12
- color: #1e293b;
13
- font-size: 14px;
14
- line-height: 1.5;
15
- }
16
 
17
- .header {
18
- background: linear-gradient(135deg, #1e1b4b, #312e81);
19
- color: white;
20
- padding: 20px;
21
- position: sticky;
22
- top: 0;
23
- z-index: 10;
24
- }
25
- .header h1 { font-size: 18px; font-weight: 700; }
26
- .header-meta { font-size: 12px; opacity: 0.7; margin-top: 4px; }
27
 
28
- .summary {
29
- display: grid;
30
- grid-template-columns: 1fr 1fr 1fr;
31
- gap: 8px;
32
- padding: 16px;
33
- }
34
- .summary-box {
35
- padding: 12px;
36
- border-radius: 10px;
37
- text-align: center;
38
- }
39
- .summary-box .num { font-size: 24px; font-weight: 700; }
40
- .summary-box .label { font-size: 11px; font-weight: 500; margin-top: 2px; }
41
- .sum-high { background: #fef2f2; color: #dc2626; }
42
- .sum-med { background: #fff7ed; color: #ea580c; }
43
- .sum-low { background: #fefce8; color: #ca8a04; }
44
 
45
- .clause-list { padding: 0 16px 16px; }
46
- .clause-card {
47
- background: white;
48
- border: 1px solid #e2e8f0;
49
- border-radius: 12px;
50
- padding: 14px;
51
- margin-bottom: 10px;
52
- transition: box-shadow 0.2s;
53
- }
54
- .clause-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
55
- .clause-card.severity-high { border-left: 4px solid #ef4444; }
56
- .clause-card.severity-medium { border-left: 4px solid #f59e0b; }
57
- .clause-card.severity-low { border-left: 4px solid #3b82f6; }
58
 
59
- .clause-num { font-size: 11px; color: #94a3b8; font-weight: 600; text-transform: uppercase; }
60
- .clause-text {
61
- font-size: 13px;
62
- color: #334155;
63
- margin: 6px 0;
64
- font-style: italic;
65
- line-height: 1.6;
66
- }
67
- .clause-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
68
- .tag {
69
- font-size: 11px;
70
- font-weight: 600;
71
- padding: 3px 10px;
72
- border-radius: 999px;
73
- }
74
- .tag-high { background: #fecaca; color: #991b1b; }
75
- .tag-medium { background: #fed7aa; color: #9a3412; }
76
- .tag-low { background: #dbeafe; color: #1e40af; }
77
 
78
- .clause-desc {
79
- font-size: 12px;
80
- color: #64748b;
81
- margin-top: 6px;
82
- padding: 8px 10px;
83
- background: #f8fafc;
84
- border-radius: 8px;
85
- border-left: 3px solid #cbd5e1;
86
- }
87
 
88
- .empty {
89
- text-align: center;
90
- padding: 60px 20px;
91
- color: #94a3b8;
92
- }
93
- .empty .icon { font-size: 48px; margin-bottom: 12px; }
94
 
95
- .filter-bar {
96
- display: flex;
97
- gap: 6px;
98
- padding: 12px 16px;
99
- overflow-x: auto;
100
- }
101
- .filter-btn {
102
- padding: 6px 12px;
103
- border: 1px solid #e2e8f0;
104
- border-radius: 999px;
105
- background: white;
106
- font-size: 12px;
107
- cursor: pointer;
108
- white-space: nowrap;
109
- font-weight: 500;
110
- transition: all 0.15s;
111
- }
112
- .filter-btn:hover { background: #f1f5f9; }
113
- .filter-btn.active { background: #4f46e5; color: white; border-color: #4f46e5; }
114
  </style>
115
  </head>
116
  <body>
117
  <div class="header">
118
- <h1>🛡️ ClauseGuard Analysis</h1>
119
- <div class="header-meta" id="meta"></div>
 
120
  </div>
121
 
122
- <div class="summary" id="summary" style="display:none;">
123
- <div class="summary-box sum-high">
124
- <div class="num" id="s-high">0</div>
125
- <div class="label">🔴 High Risk</div>
126
- </div>
127
- <div class="summary-box sum-med">
128
- <div class="num" id="s-med">0</div>
129
- <div class="label">🟠 Medium</div>
130
- </div>
131
- <div class="summary-box sum-low">
132
- <div class="num" id="s-low">0</div>
133
- <div class="label">🟡 Low</div>
134
  </div>
135
  </div>
136
 
137
- <div class="filter-bar" id="filters" style="display:none;">
138
  <button class="filter-btn active" data-filter="all">All</button>
139
- <button class="filter-btn" data-filter="HIGH">🔴 High</button>
140
- <button class="filter-btn" data-filter="MEDIUM">🟠 Medium</button>
141
- <button class="filter-btn" data-filter="LOW">🟡 Low</button>
142
  </div>
143
 
144
  <div class="clause-list" id="clause-list"></div>
145
 
146
  <div class="empty" id="empty-state">
147
- <div class="icon">🔍</div>
148
- <p>Scan a page to see clause analysis here.</p>
149
  </div>
150
 
151
  <script src="sidepanel.js"></script>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ClauseGuard</title>
7
  <style>
8
  * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { font-family: system-ui, -apple-system, sans-serif; background: #fff; color: #18181b; font-size: 13px; }
 
 
 
 
 
 
10
 
11
+ .header { padding: 14px 16px; border-bottom: 1px solid #f4f4f5; display: flex; align-items: center; gap: 8px; position: sticky; top: 0; background: #fff; z-index: 10; }
12
+ .header svg { width: 16px; height: 16px; }
13
+ .header-title { font-size: 14px; font-weight: 600; }
14
+ .header-meta { font-size: 11px; color: #a1a1aa; margin-left: auto; }
 
 
 
 
 
 
15
 
16
+ .score-bar { padding: 12px 16px; border-bottom: 1px solid #f4f4f5; display: flex; align-items: center; gap: 12px; }
17
+ .grade-box { width: 44px; height: 44px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: 700; border: 1px solid; }
18
+ .grade-f, .grade-d { background: #fef2f2; color: #b91c1c; border-color: #fecaca; }
19
+ .grade-c { background: #fffbeb; color: #a16207; border-color: #fde68a; }
20
+ .grade-b, .grade-a { background: #f0fdf4; color: #15803d; border-color: #bbf7d0; }
21
+ .score-info { flex: 1; }
22
+ .score-top { display: flex; justify-content: space-between; font-size: 11px; color: #a1a1aa; margin-bottom: 4px; }
23
+ .progress { height: 5px; background: #f4f4f5; border-radius: 99px; overflow: hidden; }
24
+ .progress-fill { height: 100%; border-radius: 99px; transition: width 0.5s ease; }
 
 
 
 
 
 
 
25
 
26
+ .filters { display: flex; gap: 4px; padding: 10px 16px; border-bottom: 1px solid #f4f4f5; overflow-x: auto; }
27
+ .filter-btn { padding: 5px 10px; border: 1px solid #e4e4e7; border-radius: 6px; background: #fff; font-size: 12px; cursor: pointer; white-space: nowrap; font-weight: 500; color: #71717a; transition: all 0.15s; display: flex; align-items: center; gap: 5px; }
28
+ .filter-btn:hover { background: #f4f4f5; }
29
+ .filter-btn.active { background: #18181b; color: #fff; border-color: #18181b; }
30
+ .filter-count { font-size: 10px; opacity: 0.6; }
31
+ .dot { width: 6px; height: 6px; border-radius: 50%; }
32
+ .dot-red { background: #ef4444; }
33
+ .dot-amber { background: #f59e0b; }
34
+ .dot-blue { background: #3b82f6; }
 
 
 
 
35
 
36
+ .clause-list { padding: 8px; }
37
+ .clause-card { border: 1px solid #e4e4e7; border-radius: 10px; padding: 12px; margin-bottom: 6px; transition: all 0.15s; cursor: default; }
38
+ .clause-card:hover { border-color: #d4d4d8; box-shadow: 0 1px 3px rgba(0,0,0,0.04); }
39
+ .clause-card.sev-high { border-left: 3px solid #ef4444; }
40
+ .clause-card.sev-medium { border-left: 3px solid #f59e0b; }
41
+ .clause-card.sev-low { border-left: 3px solid #3b82f6; }
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
+ .clause-tags { display: flex; flex-wrap: wrap; gap: 4px; }
44
+ .tag { font-size: 10px; font-weight: 600; padding: 2px 8px; border-radius: 4px; border: 1px solid; display: inline-flex; align-items: center; gap: 3px; }
45
+ .tag svg { width: 10px; height: 10px; }
46
+ .tag-high { background: #fef2f2; color: #b91c1c; border-color: #fecaca; }
47
+ .tag-medium { background: #fffbeb; color: #a16207; border-color: #fde68a; }
48
+ .tag-low { background: #eff6ff; color: #1d4ed8; border-color: #bfdbfe; }
 
 
 
49
 
50
+ .clause-text { font-size: 12px; color: #3f3f46; margin-top: 8px; line-height: 1.6; }
51
+ .clause-desc { font-size: 11px; color: #71717a; margin-top: 6px; padding: 6px 8px; background: #fafafa; border-radius: 6px; line-height: 1.5; }
 
 
 
 
52
 
53
+ .empty { text-align: center; padding: 48px 16px; color: #a1a1aa; }
54
+ .empty svg { width: 36px; height: 36px; color: #d4d4d8; margin: 0 auto 10px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  </style>
56
  </head>
57
  <body>
58
  <div class="header">
59
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/></svg>
60
+ <span class="header-title">ClauseGuard</span>
61
+ <span class="header-meta" id="meta"></span>
62
  </div>
63
 
64
+ <div class="score-bar" id="score-bar" style="display:none;">
65
+ <div class="grade-box" id="grade-box">—</div>
66
+ <div class="score-info">
67
+ <div class="score-top"><span>Risk Score</span><span id="score-num">0 / 100</span></div>
68
+ <div class="progress"><div class="progress-fill" id="progress-fill" style="width:0%"></div></div>
 
 
 
 
 
 
 
69
  </div>
70
  </div>
71
 
72
+ <div class="filters" id="filters" style="display:none;">
73
  <button class="filter-btn active" data-filter="all">All</button>
74
+ <button class="filter-btn" data-filter="HIGH"><span class="dot dot-red"></span>High <span class="filter-count" id="fc-high">0</span></button>
75
+ <button class="filter-btn" data-filter="MEDIUM"><span class="dot dot-amber"></span>Medium <span class="filter-count" id="fc-med">0</span></button>
76
+ <button class="filter-btn" data-filter="LOW"><span class="dot dot-blue"></span>Low <span class="filter-count" id="fc-low">0</span></button>
77
  </div>
78
 
79
  <div class="clause-list" id="clause-list"></div>
80
 
81
  <div class="empty" id="empty-state">
82
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><path d="M7 12h10"/></svg>
83
+ <p>Scan a page to see the analysis.</p>
84
  </div>
85
 
86
  <script src="sidepanel.js"></script>
extension/sidepanel.js CHANGED
@@ -1,17 +1,23 @@
1
  /**
2
- * ClauseGuard — Side Panel Script
3
- * Displays detailed clause analysis with filtering
4
  */
5
 
6
- const CATEGORY_DESCRIPTIONS = {
7
- "Limitation of liability": "Company limits or excludes liability for losses, data breaches, or service failures.",
8
- "Unilateral termination": "Company can terminate your account at any time without reason.",
9
- "Unilateral change": "Company can change terms at any time without your consent.",
10
- "Content removal": "Company can delete your content without notice or justification.",
11
- "Contract by using": "You're bound to the contract simply by using the service — a dark pattern.",
12
- "Choice of law": "Governing law may differ from your country — reducing your legal protections.",
13
- "Jurisdiction": "Disputes must be resolved in a jurisdiction that may disadvantage you.",
14
- "Arbitration": "Forces disputes to arbitration instead of court — you waive your right to sue.",
 
 
 
 
 
 
 
15
  };
16
 
17
  let allClauses = [];
@@ -28,87 +34,87 @@ async function loadResults() {
28
  }
29
 
30
  document.getElementById("empty-state").style.display = "none";
31
- document.getElementById("summary").style.display = "grid";
32
  document.getElementById("filters").style.display = "flex";
33
 
34
- document.getElementById("meta").textContent =
35
- `${results.total_clauses} clauses scanned · ${results.flagged_count} flagged · Risk: ${results.risk_score}/100`;
 
 
 
 
 
 
 
 
 
 
 
36
 
37
- // Count severities
38
  const counts = { HIGH: 0, MEDIUM: 0, LOW: 0 };
39
  const flagged = results.results.filter(r => r.categories?.length > 0);
40
- flagged.forEach(r => r.categories.forEach(c => counts[c.severity]++));
41
-
42
- document.getElementById("s-high").textContent = counts.HIGH;
43
- document.getElementById("s-med").textContent = counts.MEDIUM;
44
- document.getElementById("s-low").textContent = counts.LOW;
45
 
46
  allClauses = flagged;
47
- renderClauses(flagged);
48
  }
49
 
50
- function renderClauses(clauses) {
51
  const list = document.getElementById("clause-list");
52
- list.innerHTML = "";
53
-
54
- const filtered = currentFilter === "all"
55
- ? clauses
56
- : clauses.filter(c => c.categories.some(cat => cat.severity === currentFilter));
57
 
58
  if (filtered.length === 0) {
59
- list.innerHTML = '<div style="text-align:center; padding:30px; color:#94a3b8;">No clauses match this filter.</div>';
60
  return;
61
  }
62
 
63
- filtered.forEach((clause, i) => {
64
- const maxSev = getMaxSeverity(clause.categories);
65
- const card = document.createElement("div");
66
- card.className = `clause-card severity-${maxSev.toLowerCase()}`;
67
- card.innerHTML = `
68
- <div class="clause-num">Clause #${i + 1}</div>
69
- <div class="clause-text">"${truncate(clause.text, 250)}"</div>
70
- <div class="clause-tags">
71
- ${clause.categories.map(c => `
72
- <span class="tag tag-${c.severity.toLowerCase()}">${c.name}</span>
73
- `).join("")}
74
- </div>
75
- ${clause.categories.map(c => `
76
- <div class="clause-desc">
77
- <strong>${c.name}:</strong> ${CATEGORY_DESCRIPTIONS[c.name] || c.name}
78
- </div>
79
- `).join("")}
80
- `;
81
- list.appendChild(card);
82
- });
83
- }
84
 
85
- function getMaxSeverity(categories) {
86
- const order = { HIGH: 3, MEDIUM: 2, LOW: 1 };
87
- return categories.reduce((max, c) => order[c.severity] > order[max] ? c.severity : max, "LOW");
88
- }
 
 
 
 
 
89
 
90
- function truncate(text, maxLen) {
91
- return text.length > maxLen ? text.substring(0, maxLen) + "…" : text;
 
 
 
 
 
 
 
 
92
  }
93
 
94
- // ─── Filter buttons ───
95
  document.getElementById("filters").addEventListener("click", (e) => {
96
- if (!e.target.matches(".filter-btn")) return;
 
97
  document.querySelectorAll(".filter-btn").forEach(b => b.classList.remove("active"));
98
- e.target.classList.add("active");
99
- currentFilter = e.target.dataset.filter;
100
- renderClauses(allClauses);
101
  });
102
 
103
- // ─── Listen for updates from background ───
104
  chrome.storage.onChanged.addListener((changes) => {
105
  for (const key of Object.keys(changes)) {
106
- if (key.startsWith("results_")) {
107
- loadResults();
108
- break;
109
- }
110
  }
111
  });
112
 
113
- // ─── Init ───
114
  loadResults();
 
1
  /**
2
+ * ClauseGuard — Side Panel (redesigned)
 
3
  */
4
 
5
+ const DESCS = {
6
+ "Limitation of liability": "Company limits or excludes liability for losses or damages.",
7
+ "Unilateral termination": "They can close your account without reason.",
8
+ "Unilateral change": "Terms can change without your consent.",
9
+ "Content removal": "Your content can be deleted without notice.",
10
+ "Contract by using": "You agree just by visiting or using the site.",
11
+ "Choice of law": "Foreign law applies instead of your local protections.",
12
+ "Jurisdiction": "Disputes handled in their preferred court.",
13
+ "Arbitration": "You waive your right to sue in court.",
14
+ };
15
+
16
+ // SVG icons for severity
17
+ const SEV_ICONS = {
18
+ 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>',
19
+ 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>',
20
+ 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>',
21
  };
22
 
23
  let allClauses = [];
 
34
  }
35
 
36
  document.getElementById("empty-state").style.display = "none";
37
+ document.getElementById("score-bar").style.display = "flex";
38
  document.getElementById("filters").style.display = "flex";
39
 
40
+ // Meta
41
+ document.getElementById("meta").textContent = `${results.total_clauses} clauses · ${results.flagged_count} flagged`;
42
+
43
+ // Grade
44
+ const gb = document.getElementById("grade-box");
45
+ gb.textContent = results.grade;
46
+ gb.className = `grade-box grade-${results.grade.toLowerCase()}`;
47
+
48
+ // Score
49
+ document.getElementById("score-num").textContent = `${results.risk_score} / 100`;
50
+ const pf = document.getElementById("progress-fill");
51
+ pf.style.width = `${results.risk_score}%`;
52
+ pf.style.background = results.risk_score >= 60 ? "#ef4444" : results.risk_score >= 30 ? "#f59e0b" : "#22c55e";
53
 
54
+ // Counts
55
  const counts = { HIGH: 0, MEDIUM: 0, LOW: 0 };
56
  const flagged = results.results.filter(r => r.categories?.length > 0);
57
+ flagged.forEach(r => r.categories.forEach(c => { if (counts[c.severity] !== undefined) counts[c.severity]++; }));
58
+ document.getElementById("fc-high").textContent = counts.HIGH;
59
+ document.getElementById("fc-med").textContent = counts.MEDIUM;
60
+ document.getElementById("fc-low").textContent = counts.LOW;
 
61
 
62
  allClauses = flagged;
63
+ renderClauses();
64
  }
65
 
66
+ function renderClauses() {
67
  const list = document.getElementById("clause-list");
68
+ const filtered = currentFilter === "all" ? allClauses : allClauses.filter(c => c.categories.some(cat => cat.severity === currentFilter));
 
 
 
 
69
 
70
  if (filtered.length === 0) {
71
+ list.innerHTML = '<div style="text-align:center;padding:24px;color:#a1a1aa;font-size:12px;">No clauses match this filter.</div>';
72
  return;
73
  }
74
 
75
+ list.innerHTML = filtered.map((clause, i) => {
76
+ const maxSev = clause.categories.reduce((m, c) => {
77
+ const o = { HIGH: 3, MEDIUM: 2, LOW: 1 };
78
+ return (o[c.severity] || 0) > (o[m] || 0) ? c.severity : m;
79
+ }, "LOW");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
+ const tagMap = { HIGH: "tag-high", MEDIUM: "tag-medium", LOW: "tag-low" };
82
+
83
+ const tags = clause.categories.map(c =>
84
+ `<span class="tag ${tagMap[c.severity] || "tag-medium"}">${SEV_ICONS[c.severity] || ""} ${c.name}</span>`
85
+ ).join("");
86
+
87
+ const descs = clause.categories.map(c =>
88
+ `<div class="clause-desc">${DESCS[c.name] || c.name}</div>`
89
+ ).join("");
90
 
91
+ const text = clause.text.length > 200 ? clause.text.slice(0, 200) + "…" : clause.text;
92
+
93
+ return `
94
+ <div class="clause-card sev-${maxSev.toLowerCase()}">
95
+ <div class="clause-tags">${tags}</div>
96
+ <div class="clause-text">${text}</div>
97
+ ${descs}
98
+ </div>
99
+ `;
100
+ }).join("");
101
  }
102
 
103
+ // Filters
104
  document.getElementById("filters").addEventListener("click", (e) => {
105
+ const btn = e.target.closest(".filter-btn");
106
+ if (!btn) return;
107
  document.querySelectorAll(".filter-btn").forEach(b => b.classList.remove("active"));
108
+ btn.classList.add("active");
109
+ currentFilter = btn.dataset.filter;
110
+ renderClauses();
111
  });
112
 
113
+ // Listen for updates
114
  chrome.storage.onChanged.addListener((changes) => {
115
  for (const key of Object.keys(changes)) {
116
+ if (key.startsWith("results_")) { loadResults(); break; }
 
 
 
117
  }
118
  });
119
 
 
120
  loadResults();
web/app/dashboard-pages/analyze/page.tsx CHANGED
@@ -1,13 +1,35 @@
1
  "use client";
2
 
3
  import { useState } from "react";
 
 
 
 
 
4
 
5
- interface ClauseCategory { name: string; severity: string; confidence: number; }
6
- interface ClauseResult { text: string; categories: ClauseCategory[]; }
7
- interface AnalysisResult {
8
- risk_score: number; grade: string; total_clauses: number;
9
- flagged_count: number; results: ClauseResult[]; model: string; latency_ms: number;
10
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  const EXAMPLE = `By using the Spotify Service, you agree to be bound by these Terms of Use.
13
 
@@ -17,7 +39,7 @@ In no event will Spotify be liable for any indirect, incidental, special, conseq
17
 
18
  Spotify reserves the right to remove or disable access to any User Content for any reason, without prior notice.
19
 
20
- Spotify may terminate your account or suspend your access to all or part of the Service at any time, with or without cause, effective immediately.
21
 
22
  These Terms will be governed by and construed in accordance with the laws of the State of New York.
23
 
@@ -25,105 +47,212 @@ Any dispute shall be finally settled by arbitration in New York County.`;
25
 
26
  export default function AnalyzePage() {
27
  const [text, setText] = useState("");
28
- const [results, setResults] = useState<AnalysisResult | null>(null);
29
  const [loading, setLoading] = useState(false);
30
  const [error, setError] = useState("");
 
 
 
31
 
32
  async function handleAnalyze() {
33
  if (!text || text.trim().length < 50) { setError("Enter at least 50 characters."); return; }
34
- setLoading(true); setError(""); setResults(null);
35
  try {
36
- const res = await fetch("/api/analyze", {
37
- method: "POST", headers: { "Content-Type": "application/json" },
38
- body: JSON.stringify({ text }),
39
- });
40
- if (!res.ok) { const err = await res.json(); throw new Error(err.error || "Failed"); }
41
  setResults(await res.json());
42
- } catch (err: any) { setError(err.message); }
43
  finally { setLoading(false); }
44
  }
45
 
46
- const sevStyle: Record<string, string> = {
47
- HIGH: "text-red-700 bg-red-50 border-red-200",
48
- MEDIUM: "text-amber-700 bg-amber-50 border-amber-200",
49
- LOW: "text-blue-700 bg-blue-50 border-blue-200",
50
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  return (
53
  <div className="min-h-screen bg-white">
54
- <div className="max-w-5xl mx-auto px-6 py-12">
55
  <div className="mb-8">
56
- <h1 className="text-2xl font-semibold tracking-tight">Scan a document</h1>
57
- <p className="mt-1 text-sm text-zinc-500">Paste any Terms of Service, contract, or lease below.</p>
 
 
 
58
  </div>
59
 
60
- <div className="grid lg:grid-cols-2 gap-8">
61
- <div>
62
- <textarea
63
- value={text} onChange={(e) => setText(e.target.value)}
64
- placeholder="Paste your text here..."
65
- className="w-full h-80 p-4 border border-zinc-200 rounded-lg text-sm font-mono leading-relaxed resize-none focus:outline-none focus:border-zinc-400 placeholder:text-zinc-300"
66
- />
67
  <div className="mt-3 flex gap-2">
68
  <button onClick={handleAnalyze} disabled={loading}
69
- className="flex-1 bg-zinc-900 text-white py-2.5 rounded-md text-sm font-medium hover:bg-zinc-800 disabled:opacity-40">
70
- {loading ? "Scanning..." : "Scan"}
71
  </button>
72
  <button onClick={() => setText(EXAMPLE)}
73
- className="px-4 border border-zinc-200 rounded-md text-sm text-zinc-500 hover:bg-zinc-50">
74
  Example
75
  </button>
76
  </div>
77
- {error && <p className="mt-2 text-sm text-red-600">{error}</p>}
78
  </div>
79
 
80
- <div>
 
81
  {results ? (
82
- <div>
83
- {/* Score */}
84
- <div className="border border-zinc-200 rounded-lg p-5 mb-4">
85
- <div className="flex items-baseline justify-between">
86
  <div>
87
- <span className="text-3xl font-semibold">{results.risk_score}</span>
88
- <span className="text-sm text-zinc-400 ml-1">/100 risk</span>
 
 
 
 
 
 
 
89
  </div>
90
- <span className={`text-sm font-medium px-2 py-0.5 rounded ${
91
- results.grade === "F" || results.grade === "D" ? "bg-red-50 text-red-700" :
92
- results.grade === "C" ? "bg-amber-50 text-amber-700" :
93
- "bg-emerald-50 text-emerald-700"
94
- }`}>
95
- Grade {results.grade}
 
 
 
 
 
 
 
 
 
 
96
  </span>
97
  </div>
98
- <p className="mt-2 text-xs text-zinc-400">
99
- {results.total_clauses} clauses scanned · {results.flagged_count} flagged · {results.latency_ms}ms
100
- </p>
101
  </div>
102
 
103
- {/* Clauses */}
104
- <div className="space-y-2 max-h-96 overflow-y-auto">
105
- {results.results.filter(r => r.categories.length > 0).map((clause, i) => (
106
- <div key={i} className="border border-zinc-200 rounded-lg p-4">
107
- <p className="text-sm text-zinc-700 leading-relaxed line-clamp-2">{clause.text}</p>
108
- <div className="flex flex-wrap gap-1.5 mt-2">
109
- {clause.categories.map((cat, j) => (
110
- <span key={j} className={`text-xs font-medium px-2 py-0.5 rounded border ${sevStyle[cat.severity] || sevStyle.MEDIUM}`}>
111
- {cat.name}
112
- </span>
113
- ))}
114
- </div>
115
- </div>
116
- ))}
117
- {results.flagged_count === 0 && (
118
- <div className="border border-zinc-200 rounded-lg p-8 text-center">
119
- <p className="text-sm text-zinc-500">No unfair clauses found. Looks fair.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  </div>
121
- )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  </div>
123
  </div>
124
  ) : (
125
- <div className="border border-dashed border-zinc-200 rounded-lg h-80 flex items-center justify-center">
126
- <p className="text-sm text-zinc-300">Results will appear here</p>
 
127
  </div>
128
  )}
129
  </div>
@@ -132,3 +261,7 @@ export default function AnalyzePage() {
132
  </div>
133
  );
134
  }
 
 
 
 
 
1
  "use client";
2
 
3
  import { useState } from "react";
4
+ import {
5
+ ScanText, ScanLine, TriangleAlert, CircleAlert, CircleCheck, Info,
6
+ Download, FileDown, ChevronDown, ChevronUp, Highlighter, Copy, Check,
7
+ ShieldCheck, ShieldAlert, Scale, Gavel, Ban, Globe, Eye, Stamp, FileX
8
+ } from "lucide-react";
9
 
10
+ interface Cat { name: string; severity: string; description?: string; confidence?: number; }
11
+ interface Clause { text: string; categories: Cat[]; }
12
+ interface Result { risk_score: number; grade: string; total_clauses: number; flagged_count: number; results: Clause[]; model: string; latency_ms: number; }
13
+
14
+ const SEV_CONFIG: Record<string, { icon: any; label: string; text: string; bg: string; border: string; dot: string }> = {
15
+ HIGH: { icon: TriangleAlert, label: "High", text: "text-red-600", bg: "bg-red-50", border: "border-red-200", dot: "bg-red-500" },
16
+ MEDIUM: { icon: CircleAlert, label: "Medium", text: "text-amber-600", bg: "bg-amber-50", border: "border-amber-200", dot: "bg-amber-500" },
17
+ LOW: { icon: Info, label: "Low", text: "text-blue-600", bg: "bg-blue-50", border: "border-blue-200", dot: "bg-blue-500" },
18
+ };
19
+
20
+ const GRADE_STYLE: Record<string, string> = {
21
+ A: "bg-emerald-50 text-emerald-700 border-emerald-200",
22
+ B: "bg-emerald-50 text-emerald-700 border-emerald-200",
23
+ C: "bg-amber-50 text-amber-700 border-amber-200",
24
+ D: "bg-red-50 text-red-700 border-red-200",
25
+ F: "bg-red-50 text-red-700 border-red-200",
26
+ };
27
+
28
+ const CATEGORY_ICONS: Record<string, any> = {
29
+ "Arbitration": Scale, "Limitation of liability": ShieldAlert, "Unilateral termination": Ban,
30
+ "Unilateral change": FileX, "Content removal": Eye, "Jurisdiction": Globe,
31
+ "Choice of law": Gavel, "Contract by using": Stamp,
32
+ };
33
 
34
  const EXAMPLE = `By using the Spotify Service, you agree to be bound by these Terms of Use.
35
 
 
39
 
40
  Spotify reserves the right to remove or disable access to any User Content for any reason, without prior notice.
41
 
42
+ Spotify may terminate your account or suspend your access at any time, with or without cause, with or without notice, effective immediately.
43
 
44
  These Terms will be governed by and construed in accordance with the laws of the State of New York.
45
 
 
47
 
48
  export default function AnalyzePage() {
49
  const [text, setText] = useState("");
50
+ const [results, setResults] = useState<Result | null>(null);
51
  const [loading, setLoading] = useState(false);
52
  const [error, setError] = useState("");
53
+ const [expandedIdx, setExpandedIdx] = useState<number | null>(null);
54
+ const [filter, setFilter] = useState<string>("all");
55
+ const [copied, setCopied] = useState(false);
56
 
57
  async function handleAnalyze() {
58
  if (!text || text.trim().length < 50) { setError("Enter at least 50 characters."); return; }
59
+ setLoading(true); setError(""); setResults(null); setExpandedIdx(null);
60
  try {
61
+ const res = await fetch("/api/analyze", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text }) });
62
+ if (!res.ok) throw new Error((await res.json()).error || "Failed");
 
 
 
63
  setResults(await res.json());
64
+ } catch (e: any) { setError(e.message); }
65
  finally { setLoading(false); }
66
  }
67
 
68
+ async function handleDownloadPDF() {
69
+ if (!results) return;
70
+ try {
71
+ const res = await fetch("/api/pdf/report", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(results) });
72
+ const blob = await res.blob();
73
+ const url = URL.createObjectURL(blob);
74
+ const a = document.createElement("a"); a.href = url; a.download = "clauseguard-report.pdf"; a.click();
75
+ URL.revokeObjectURL(url);
76
+ } catch {}
77
+ }
78
+
79
+ function handleCopy() {
80
+ if (!results) return;
81
+ const summary = `ClauseGuard Report\nRisk: ${results.risk_score}/100 (Grade ${results.grade})\n${results.flagged_count} of ${results.total_clauses} clauses flagged\n\n` +
82
+ results.results.filter(r => r.categories.length > 0).map((r, i) =>
83
+ `${i+1}. [${r.categories.map(c => c.name).join(", ")}] ${r.text.slice(0, 100)}...`
84
+ ).join("\n");
85
+ navigator.clipboard.writeText(summary);
86
+ setCopied(true); setTimeout(() => setCopied(false), 2000);
87
+ }
88
+
89
+ const flagged = results?.results.filter(r => r.categories.length > 0) || [];
90
+ const filtered = filter === "all" ? flagged : flagged.filter(r => r.categories.some(c => c.severity === filter));
91
+
92
+ const sevCounts = { HIGH: 0, MEDIUM: 0, LOW: 0 };
93
+ flagged.forEach(r => r.categories.forEach(c => { if (sevCounts[c.severity as keyof typeof sevCounts] !== undefined) sevCounts[c.severity as keyof typeof sevCounts]++; }));
94
 
95
  return (
96
  <div className="min-h-screen bg-white">
97
+ <div className="max-w-6xl mx-auto px-5 py-10">
98
  <div className="mb-8">
99
+ <h1 className="text-2xl font-semibold tracking-tight flex items-center gap-2">
100
+ <ScanText className="w-6 h-6 text-zinc-400" />
101
+ Scan a document
102
+ </h1>
103
+ <p className="mt-1 text-sm text-zinc-500">Paste Terms of Service, a contract, or a lease agreement.</p>
104
  </div>
105
 
106
+ <div className="grid lg:grid-cols-5 gap-6">
107
+ {/* Input — 2 cols */}
108
+ <div className="lg:col-span-2">
109
+ <textarea value={text} onChange={(e) => setText(e.target.value)}
110
+ placeholder="Paste your document text here..."
111
+ className="w-full h-[420px] p-4 border border-zinc-200 rounded-xl text-sm leading-relaxed resize-none focus:outline-none focus:ring-2 focus:ring-zinc-900/10 focus:border-zinc-300 placeholder:text-zinc-300 font-mono" />
 
112
  <div className="mt-3 flex gap-2">
113
  <button onClick={handleAnalyze} disabled={loading}
114
+ className="flex-1 inline-flex items-center justify-center gap-2 bg-zinc-900 text-white py-2.5 rounded-lg text-sm font-medium hover:bg-zinc-800 disabled:opacity-40 transition-colors">
115
+ {loading ? <><ScanLine className="w-4 h-4 animate-pulse" /> Scanning...</> : <><ScanText className="w-4 h-4" /> Scan</>}
116
  </button>
117
  <button onClick={() => setText(EXAMPLE)}
118
+ className="px-4 border border-zinc-200 rounded-lg text-sm text-zinc-500 hover:bg-zinc-50 transition-colors">
119
  Example
120
  </button>
121
  </div>
122
+ {error && <p className="mt-2 text-sm text-red-600 flex items-center gap-1.5"><TriangleAlert className="w-3.5 h-3.5" />{error}</p>}
123
  </div>
124
 
125
+ {/* Results — 3 cols */}
126
+ <div className="lg:col-span-3">
127
  {results ? (
128
+ <div className="space-y-4">
129
+ {/* Score card */}
130
+ <div className="border border-zinc-200 rounded-xl p-5">
131
+ <div className="flex items-start justify-between">
132
  <div>
133
+ <div className="flex items-baseline gap-2">
134
+ <span className="text-4xl font-semibold tracking-tight">{results.risk_score}</span>
135
+ <span className="text-sm text-zinc-400">/100 risk</span>
136
+ </div>
137
+ <div className="mt-2 h-1.5 w-48 bg-zinc-100 rounded-full overflow-hidden">
138
+ <div className={`h-full rounded-full transition-all duration-700 ${
139
+ results.risk_score >= 60 ? "bg-red-500" : results.risk_score >= 30 ? "bg-amber-400" : "bg-emerald-500"
140
+ }`} style={{ width: `${results.risk_score}%` }} />
141
+ </div>
142
  </div>
143
+ <div className="flex items-center gap-2">
144
+ <span className={`text-sm font-semibold px-3 py-1 rounded-lg border ${GRADE_STYLE[results.grade] || GRADE_STYLE.C}`}>
145
+ Grade {results.grade}
146
+ </span>
147
+ </div>
148
+ </div>
149
+ <div className="mt-4 flex items-center gap-4 text-xs text-zinc-400">
150
+ <span>{results.total_clauses} clauses</span>
151
+ <span className="w-px h-3 bg-zinc-200" />
152
+ <span>{results.flagged_count} flagged</span>
153
+ <span className="w-px h-3 bg-zinc-200" />
154
+ <span>{results.latency_ms}ms</span>
155
+ <span className="w-px h-3 bg-zinc-200" />
156
+ <span className="flex items-center gap-1">
157
+ {results.model === "ml" ? <Sparkles className="w-3 h-3" /> : null}
158
+ {results.model === "ml" ? "Legal-BERT" : "Pattern matching"}
159
  </span>
160
  </div>
 
 
 
161
  </div>
162
 
163
+ {/* Actions bar */}
164
+ <div className="flex items-center justify-between">
165
+ <div className="flex gap-1">
166
+ {[
167
+ { key: "all", label: "All", count: flagged.length },
168
+ { key: "HIGH", label: "High", count: sevCounts.HIGH },
169
+ { key: "MEDIUM", label: "Medium", count: sevCounts.MEDIUM },
170
+ { key: "LOW", label: "Low", count: sevCounts.LOW },
171
+ ].map((f) => (
172
+ <button key={f.key} onClick={() => setFilter(f.key)}
173
+ className={`px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${
174
+ filter === f.key ? "bg-zinc-900 text-white" : "text-zinc-500 hover:bg-zinc-100"
175
+ }`}>
176
+ {f.label} {f.count > 0 && <span className="ml-1 opacity-60">{f.count}</span>}
177
+ </button>
178
+ ))}
179
+ </div>
180
+ <div className="flex gap-1.5">
181
+ <button onClick={handleCopy} className="p-2 rounded-md hover:bg-zinc-100 text-zinc-400 hover:text-zinc-600 transition-colors" title="Copy summary">
182
+ {copied ? <Check className="w-4 h-4 text-emerald-500" /> : <Copy className="w-4 h-4" />}
183
+ </button>
184
+ <button onClick={handleDownloadPDF} className="p-2 rounded-md hover:bg-zinc-100 text-zinc-400 hover:text-zinc-600 transition-colors" title="Download PDF">
185
+ <FileDown className="w-4 h-4" />
186
+ </button>
187
+ </div>
188
+ </div>
189
+
190
+ {/* Clause list */}
191
+ <div className="space-y-2 max-h-[400px] overflow-y-auto pr-1">
192
+ {filtered.length === 0 ? (
193
+ <div className="border border-dashed border-zinc-200 rounded-xl p-10 text-center">
194
+ <CircleCheck className="w-8 h-8 text-emerald-400 mx-auto mb-2" />
195
+ <p className="text-sm text-zinc-500">{filter === "all" ? "No unfair clauses found." : "No clauses at this severity level."}</p>
196
  </div>
197
+ ) : filtered.map((clause, i) => {
198
+ const maxSev = clause.categories.reduce((m, c) => {
199
+ const order: Record<string, number> = { HIGH: 3, MEDIUM: 2, LOW: 1 };
200
+ return (order[c.severity] || 0) > (order[m] || 0) ? c.severity : m;
201
+ }, "LOW");
202
+ const conf = SEV_CONFIG[maxSev] || SEV_CONFIG.MEDIUM;
203
+ const isExpanded = expandedIdx === i;
204
+ const CatIcon = CATEGORY_ICONS[clause.categories[0]?.name] || TriangleAlert;
205
+
206
+ return (
207
+ <div key={i}
208
+ className={`border rounded-xl overflow-hidden transition-all ${conf.border} ${isExpanded ? "shadow-sm" : ""}`}>
209
+ <button onClick={() => setExpandedIdx(isExpanded ? null : i)}
210
+ className="w-full text-left p-4 flex items-start gap-3 hover:bg-zinc-50/50 transition-colors">
211
+ <div className={`w-8 h-8 rounded-lg flex items-center justify-center shrink-0 ${conf.bg}`}>
212
+ <CatIcon className={`w-4 h-4 ${conf.text}`} />
213
+ </div>
214
+ <div className="flex-1 min-w-0">
215
+ <div className="flex items-center gap-2 flex-wrap">
216
+ {clause.categories.map((cat, j) => {
217
+ const s = SEV_CONFIG[cat.severity] || SEV_CONFIG.MEDIUM;
218
+ return (
219
+ <span key={j} className={`text-[11px] font-medium px-2 py-0.5 rounded border ${s.bg} ${s.text} ${s.border}`}>
220
+ {cat.name}
221
+ {cat.confidence ? ` ${Math.round(cat.confidence * 100)}%` : ""}
222
+ </span>
223
+ );
224
+ })}
225
+ </div>
226
+ <p className="mt-1.5 text-sm text-zinc-600 leading-relaxed line-clamp-2">{clause.text}</p>
227
+ </div>
228
+ <div className="shrink-0 mt-1">
229
+ {isExpanded ? <ChevronUp className="w-4 h-4 text-zinc-400" /> : <ChevronDown className="w-4 h-4 text-zinc-400" />}
230
+ </div>
231
+ </button>
232
+ {isExpanded && (
233
+ <div className="px-4 pb-4 pt-0 border-t border-zinc-100">
234
+ <p className="text-sm text-zinc-700 leading-relaxed mt-3 font-mono bg-zinc-50 rounded-lg p-3">
235
+ {clause.text}
236
+ </p>
237
+ {clause.categories.map((cat, j) => (
238
+ <div key={j} className="mt-3 flex items-start gap-2">
239
+ <TriangleAlert className={`w-3.5 h-3.5 mt-0.5 shrink-0 ${(SEV_CONFIG[cat.severity] || SEV_CONFIG.MEDIUM).text}`} />
240
+ <p className="text-[13px] text-zinc-500 leading-relaxed">
241
+ <span className="font-medium text-zinc-700">{cat.name}:</span> {cat.description || "This clause may be unfair under consumer protection law."}
242
+ </p>
243
+ </div>
244
+ ))}
245
+ </div>
246
+ )}
247
+ </div>
248
+ );
249
+ })}
250
  </div>
251
  </div>
252
  ) : (
253
+ <div className="border border-dashed border-zinc-200 rounded-xl h-[420px] flex flex-col items-center justify-center">
254
+ <ScanText className="w-10 h-10 text-zinc-200 mb-3" />
255
+ <p className="text-sm text-zinc-300">Paste text and scan to see results</p>
256
  </div>
257
  )}
258
  </div>
 
261
  </div>
262
  );
263
  }
264
+
265
+ function Sparkles({ className }: { className?: string }) {
266
+ return <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/></svg>;
267
+ }
web/app/layout.tsx CHANGED
@@ -1,29 +1,27 @@
1
  import type { Metadata } from "next";
 
2
  import "./globals.css";
3
 
4
  export const metadata: Metadata = {
5
  title: "ClauseGuard — AI Fine Print Scanner",
6
- description:
7
- "Stop signing away your rights. ClauseGuard uses AI to scan Terms of Service, contracts, and legal documents for unfair clauses instantly.",
8
- keywords: ["terms of service", "contract scanner", "legal AI", "unfair clauses", "fine print"],
9
  openGraph: {
10
- title: "ClauseGuard — AI Fine Print Scanner",
11
- description: "Stop signing away your rights. AI scans contracts for unfair clauses instantly.",
12
  url: "https://clauseguard.com",
13
  siteName: "ClauseGuard",
14
  type: "website",
15
  },
16
- twitter: {
17
- card: "summary_large_image",
18
- title: "ClauseGuard — AI Fine Print Scanner",
19
- description: "Stop signing away your rights.",
20
- },
21
  };
22
 
23
  export default function RootLayout({ children }: { children: React.ReactNode }) {
24
  return (
25
  <html lang="en">
26
- <body className="antialiased">{children}</body>
 
 
 
27
  </html>
28
  );
29
  }
 
1
  import type { Metadata } from "next";
2
+ import { Nav } from "@/components/nav";
3
  import "./globals.css";
4
 
5
  export const metadata: Metadata = {
6
  title: "ClauseGuard — AI Fine Print Scanner",
7
+ description: "Scans Terms of Service, contracts, and leases for unfair clauses. Get a risk score before you click accept.",
8
+ keywords: ["terms of service scanner", "contract analyzer", "unfair clauses", "legal AI"],
 
9
  openGraph: {
10
+ title: "ClauseGuard",
11
+ description: "Know what you are agreeing to.",
12
  url: "https://clauseguard.com",
13
  siteName: "ClauseGuard",
14
  type: "website",
15
  },
 
 
 
 
 
16
  };
17
 
18
  export default function RootLayout({ children }: { children: React.ReactNode }) {
19
  return (
20
  <html lang="en">
21
+ <body className="antialiased text-zinc-900 bg-white">
22
+ <Nav />
23
+ {children}
24
+ </body>
25
  </html>
26
  );
27
  }
web/app/page.tsx CHANGED
@@ -1,84 +1,100 @@
1
  import Link from "next/link";
 
 
 
 
 
2
 
3
- const CLAUSE_TYPES = [
4
- { name: "Arbitration", desc: "Waives your right to sue in court" },
5
- { name: "Liability limits", desc: "Company avoids responsibility for damages" },
6
- { name: "Unilateral termination", desc: "They can close your account without reason" },
7
- { name: "Unilateral change", desc: "Terms can change without your consent" },
8
- { name: "Content removal", desc: "Your content deleted without notice" },
9
- { name: "Jurisdiction", desc: "Disputes handled in their preferred court" },
10
- { name: "Choice of law", desc: "Foreign law overrides your local protections" },
11
- { name: "Contract by using", desc: "You agree just by visiting the site" },
 
 
 
 
 
 
12
  ];
13
 
14
  const PRICING = [
15
  {
16
- name: "Free", price: "$0", period: "", highlight: false,
17
- features: ["10 scans per month", "All 8 clause types", "Risk score and grade", "Chrome extension"],
18
- cta: "Get started",
19
  },
20
  {
21
- name: "Pro", price: "$12", period: "/mo", highlight: true,
22
- features: ["Unlimited scans", "Upload contracts and leases", "Plain-English explanations", "Scan history and dashboard", "PDF report export", "Email support"],
23
- cta: "Start free trial",
24
  },
25
  {
26
- name: "Team", price: "$49", period: "/mo", highlight: false,
27
- features: ["Everything in Pro", "5 seats", "10,000 API calls", "Shared dashboard", "Slack support"],
28
- cta: "Talk to us",
29
  },
30
  ];
31
 
 
 
 
 
 
 
32
  export default function Home() {
33
  return (
34
  <main className="min-h-screen bg-white text-zinc-900">
35
- {/* Nav */}
36
- <nav className="border-b border-zinc-100">
37
- <div className="max-w-5xl mx-auto px-6 h-14 flex items-center justify-between">
38
- <span className="font-semibold tracking-tight">ClauseGuard</span>
39
- <div className="hidden sm:flex items-center gap-6 text-sm text-zinc-500">
40
- <a href="#features" className="hover:text-zinc-900">Features</a>
41
- <a href="#pricing" className="hover:text-zinc-900">Pricing</a>
42
- <Link href="/auth/login" className="hover:text-zinc-900">Log in</Link>
43
- <Link href="/auth/signup" className="bg-zinc-900 text-white px-3.5 py-1.5 rounded-md text-sm hover:bg-zinc-800">Get started</Link>
44
- </div>
45
- </div>
46
- </nav>
47
-
48
  {/* Hero */}
49
- <section className="max-w-5xl mx-auto px-6 pt-24 pb-20">
50
  <div className="max-w-2xl">
51
- <p className="text-sm text-zinc-500 mb-4">Free Chrome extension</p>
52
- <h1 className="text-4xl sm:text-5xl font-semibold tracking-tight leading-tight">
53
- Know what you are agreeing to
 
 
 
54
  </h1>
55
- <p className="mt-5 text-lg text-zinc-500 leading-relaxed">
56
  ClauseGuard scans Terms of Service, contracts, and leases for unfair clauses.
57
- You get a clear breakdown before you click accept.
58
  </p>
59
  <div className="mt-8 flex flex-wrap gap-3">
60
- <a href="#" className="bg-zinc-900 text-white px-5 py-2.5 rounded-md font-medium text-sm hover:bg-zinc-800">
 
61
  Add to Chrome
62
  </a>
63
- <Link href="/dashboard-pages/analyze" className="border border-zinc-200 px-5 py-2.5 rounded-md font-medium text-sm hover:border-zinc-300 hover:bg-zinc-50">
64
- Try the web scanner
 
 
65
  </Link>
66
  </div>
 
67
  </div>
68
  </section>
69
 
70
  {/* What it detects */}
71
  <section id="features" className="border-t border-zinc-100">
72
- <div className="max-w-5xl mx-auto px-6 py-20">
 
 
 
 
73
  <h2 className="text-2xl font-semibold tracking-tight">Eight types of unfair clauses</h2>
74
- <p className="mt-2 text-zinc-500 max-w-lg">
75
- Based on the CLAUDETTE academic taxonomy used by EU consumer protection researchers.
76
  </p>
77
- <div className="mt-10 grid sm:grid-cols-2 lg:grid-cols-4 gap-px bg-zinc-100 border border-zinc-100 rounded-lg overflow-hidden">
78
- {CLAUSE_TYPES.map((c) => (
79
- <div key={c.name} className="bg-white p-5">
80
- <p className="font-medium text-sm">{c.name}</p>
81
- <p className="mt-1 text-sm text-zinc-500">{c.desc}</p>
 
 
 
 
82
  </div>
83
  ))}
84
  </div>
@@ -86,19 +102,25 @@ export default function Home() {
86
  </section>
87
 
88
  {/* How it works */}
89
- <section className="border-t border-zinc-100">
90
- <div className="max-w-5xl mx-auto px-6 py-20">
91
- <h2 className="text-2xl font-semibold tracking-tight">How it works</h2>
92
- <div className="mt-10 grid sm:grid-cols-3 gap-10">
93
- {[
94
- { step: "1", title: "Install", desc: "Add the Chrome extension. Takes two seconds." },
95
- { step: "2", title: "Browse", desc: "Visit any terms page, contract, or lease agreement." },
96
- { step: "3", title: "Read the flags", desc: "Unfair clauses are highlighted with severity and explanation." },
97
- ].map((s) => (
98
- <div key={s.step}>
99
- <span className="inline-flex items-center justify-center w-7 h-7 rounded-full bg-zinc-100 text-xs font-semibold text-zinc-600">{s.step}</span>
100
- <h3 className="mt-3 font-medium">{s.title}</h3>
101
- <p className="mt-1 text-sm text-zinc-500 leading-relaxed">{s.desc}</p>
 
 
 
 
 
 
102
  </div>
103
  ))}
104
  </div>
@@ -107,25 +129,36 @@ export default function Home() {
107
 
108
  {/* Pricing */}
109
  <section id="pricing" className="border-t border-zinc-100">
110
- <div className="max-w-5xl mx-auto px-6 py-20">
111
  <h2 className="text-2xl font-semibold tracking-tight">Pricing</h2>
112
- <p className="mt-2 text-zinc-500">Free forever. Upgrade if you need more.</p>
113
- <div className="mt-10 grid sm:grid-cols-3 gap-6">
 
114
  {PRICING.map((plan) => (
115
- <div key={plan.name} className={`rounded-lg p-6 ${plan.highlight ? "border-2 border-zinc-900" : "border border-zinc-200"}`}>
116
- <p className="text-sm font-medium text-zinc-500">{plan.name}</p>
117
- <p className="mt-2">
118
- <span className="text-3xl font-semibold">{plan.price}</span>
 
 
 
 
 
119
  <span className="text-sm text-zinc-400">{plan.period}</span>
120
  </p>
121
- <ul className="mt-5 space-y-2">
122
  {plan.features.map((f) => (
123
- <li key={f} className="text-sm text-zinc-600 flex items-start gap-2">
124
- <span className="text-zinc-400 mt-0.5">—</span> {f}
 
125
  </li>
126
  ))}
127
  </ul>
128
- <button className={`mt-6 w-full py-2 rounded-md text-sm font-medium ${plan.highlight ? "bg-zinc-900 text-white hover:bg-zinc-800" : "border border-zinc-200 hover:bg-zinc-50"}`}>
 
 
 
 
129
  {plan.cta}
130
  </button>
131
  </div>
@@ -134,11 +167,31 @@ export default function Home() {
134
  </div>
135
  </section>
136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  {/* Footer */}
138
  <footer className="border-t border-zinc-100">
139
- <div className="max-w-5xl mx-auto px-6 py-8 flex flex-col sm:flex-row justify-between items-center gap-4">
140
- <span className="text-sm text-zinc-400">ClauseGuard — not legal advice</span>
141
- <div className="flex gap-5 text-sm text-zinc-400">
 
 
 
142
  <a href="/privacy" className="hover:text-zinc-600">Privacy</a>
143
  <a href="/terms" className="hover:text-zinc-600">Terms</a>
144
  <a href="mailto:hello@clauseguard.com" className="hover:text-zinc-600">Contact</a>
 
1
  import Link from "next/link";
2
+ import {
3
+ ShieldCheck, ShieldAlert, Scale, Gavel, ScrollText, Handshake,
4
+ ScanText, FileCheck, TriangleAlert, ArrowRight, Zap, Eye, Download,
5
+ ChevronRight, Sparkles, Lock, Globe, Ban, FileX, Stamp
6
+ } from "lucide-react";
7
 
8
+ const CLAUSES = [
9
+ { icon: Scale, name: "Arbitration", desc: "Waives your right to sue in court", severity: "high" },
10
+ { icon: ShieldAlert, name: "Liability limits", desc: "Company avoids responsibility for damages", severity: "high" },
11
+ { icon: Ban, name: "Unilateral termination", desc: "They can close your account without reason", severity: "high" },
12
+ { icon: FileX, name: "Unilateral change", desc: "Terms can change without your consent", severity: "medium" },
13
+ { icon: Eye, name: "Content removal", desc: "Your content deleted without notice", severity: "medium" },
14
+ { icon: Globe, name: "Jurisdiction", desc: "Disputes handled in their preferred court", severity: "medium" },
15
+ { icon: Gavel, name: "Choice of law", desc: "Foreign law overrides your local protections", severity: "medium" },
16
+ { icon: Stamp, name: "Contract by using", desc: "You agree just by visiting the site", severity: "low" },
17
+ ];
18
+
19
+ const STEPS = [
20
+ { icon: Download, title: "Install", desc: "Add the Chrome extension. Two clicks, no signup required." },
21
+ { icon: ScanText, title: "Browse normally", desc: "Visit any terms page. ClauseGuard scans it in the background." },
22
+ { icon: TriangleAlert, title: "See the flags", desc: "Unfair clauses get highlighted. Open the sidebar for the full breakdown." },
23
  ];
24
 
25
  const PRICING = [
26
  {
27
+ name: "Free", price: "$0", period: "", highlight: false, cta: "Get started",
28
+ features: ["10 scans per month", "All 8 clause types", "Risk score and grade", "Chrome extension", "Local fallback"],
 
29
  },
30
  {
31
+ name: "Pro", price: "$12", period: "/mo", highlight: true, cta: "Start free trial",
32
+ features: ["Unlimited scans", "Upload contracts and leases", "AI clause explanations", "Scan history", "PDF report export", "Email scan reports", "Priority support"],
 
33
  },
34
  {
35
+ name: "Team", price: "$49", period: "/mo", highlight: false, cta: "Talk to us",
36
+ features: ["Everything in Pro", "5 team seats", "10,000 API calls", "Shared dashboard", "Slack support", "Custom clause rules"],
 
37
  },
38
  ];
39
 
40
+ const sevColor: Record<string, string> = {
41
+ high: "text-red-500 bg-red-50",
42
+ medium: "text-amber-500 bg-amber-50",
43
+ low: "text-blue-500 bg-blue-50",
44
+ };
45
+
46
  export default function Home() {
47
  return (
48
  <main className="min-h-screen bg-white text-zinc-900">
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  {/* Hero */}
50
+ <section className="max-w-6xl mx-auto px-5 pt-24 pb-20">
51
  <div className="max-w-2xl">
52
+ <div className="inline-flex items-center gap-2 px-3 py-1 rounded-full border border-zinc-200 text-[13px] text-zinc-500 mb-6">
53
+ <Sparkles className="w-3.5 h-3.5 text-zinc-400" />
54
+ Trained on 9,414 legal clauses
55
+ </div>
56
+ <h1 className="text-[42px] sm:text-5xl font-semibold tracking-tight leading-[1.1]">
57
+ Know what you are<br />agreeing to
58
  </h1>
59
+ <p className="mt-5 text-[17px] text-zinc-500 leading-relaxed max-w-lg">
60
  ClauseGuard scans Terms of Service, contracts, and leases for unfair clauses.
61
+ Get a clear risk score before you click accept.
62
  </p>
63
  <div className="mt-8 flex flex-wrap gap-3">
64
+ <a href="#" className="inline-flex items-center gap-2 bg-zinc-900 text-white px-5 py-2.5 rounded-lg text-sm font-medium hover:bg-zinc-800 transition-colors">
65
+ <Download className="w-4 h-4" />
66
  Add to Chrome
67
  </a>
68
+ <Link href="/dashboard-pages/analyze"
69
+ className="inline-flex items-center gap-2 border border-zinc-200 px-5 py-2.5 rounded-lg text-sm font-medium hover:bg-zinc-50 transition-colors">
70
+ Try the scanner
71
+ <ArrowRight className="w-4 h-4" />
72
  </Link>
73
  </div>
74
+ <p className="mt-4 text-xs text-zinc-400">No account needed for free tier</p>
75
  </div>
76
  </section>
77
 
78
  {/* What it detects */}
79
  <section id="features" className="border-t border-zinc-100">
80
+ <div className="max-w-6xl mx-auto px-5 py-20">
81
+ <div className="flex items-center gap-2 mb-2">
82
+ <ShieldCheck className="w-4 h-4 text-zinc-400" />
83
+ <p className="text-[13px] font-medium text-zinc-400 uppercase tracking-wider">Detection</p>
84
+ </div>
85
  <h2 className="text-2xl font-semibold tracking-tight">Eight types of unfair clauses</h2>
86
+ <p className="mt-2 text-zinc-500 text-[15px] max-w-lg">
87
+ Based on the CLAUDETTE taxonomy — the same framework used by EU consumer protection researchers.
88
  </p>
89
+
90
+ <div className="mt-10 grid sm:grid-cols-2 lg:grid-cols-4 gap-3">
91
+ {CLAUSES.map((c) => (
92
+ <div key={c.name} className="group border border-zinc-100 rounded-xl p-4 hover:border-zinc-200 hover:shadow-sm transition-all cursor-default">
93
+ <div className={`w-8 h-8 rounded-lg flex items-center justify-center ${sevColor[c.severity]}`}>
94
+ <c.icon className="w-4 h-4" />
95
+ </div>
96
+ <p className="mt-3 text-sm font-medium">{c.name}</p>
97
+ <p className="mt-1 text-[13px] text-zinc-500 leading-relaxed">{c.desc}</p>
98
  </div>
99
  ))}
100
  </div>
 
102
  </section>
103
 
104
  {/* How it works */}
105
+ <section className="border-t border-zinc-100 bg-zinc-50/50">
106
+ <div className="max-w-6xl mx-auto px-5 py-20">
107
+ <div className="flex items-center gap-2 mb-2">
108
+ <Zap className="w-4 h-4 text-zinc-400" />
109
+ <p className="text-[13px] font-medium text-zinc-400 uppercase tracking-wider">How it works</p>
110
+ </div>
111
+ <h2 className="text-2xl font-semibold tracking-tight">Three steps, under two seconds</h2>
112
+
113
+ <div className="mt-10 grid sm:grid-cols-3 gap-8">
114
+ {STEPS.map((s, i) => (
115
+ <div key={s.title} className="relative">
116
+ <div className="w-10 h-10 rounded-xl bg-white border border-zinc-200 flex items-center justify-center shadow-sm">
117
+ <s.icon className="w-5 h-5 text-zinc-600" />
118
+ </div>
119
+ <h3 className="mt-4 text-[15px] font-medium">{s.title}</h3>
120
+ <p className="mt-1.5 text-[13px] text-zinc-500 leading-relaxed">{s.desc}</p>
121
+ {i < 2 && (
122
+ <ChevronRight className="hidden sm:block absolute top-4 -right-5 w-4 h-4 text-zinc-300" />
123
+ )}
124
  </div>
125
  ))}
126
  </div>
 
129
 
130
  {/* Pricing */}
131
  <section id="pricing" className="border-t border-zinc-100">
132
+ <div className="max-w-6xl mx-auto px-5 py-20">
133
  <h2 className="text-2xl font-semibold tracking-tight">Pricing</h2>
134
+ <p className="mt-2 text-zinc-500 text-[15px]">Free forever. Upgrade when you need more.</p>
135
+
136
+ <div className="mt-10 grid sm:grid-cols-3 gap-5 max-w-4xl">
137
  {PRICING.map((plan) => (
138
+ <div key={plan.name}
139
+ className={`rounded-xl p-6 transition-shadow ${
140
+ plan.highlight
141
+ ? "border-2 border-zinc-900 shadow-sm"
142
+ : "border border-zinc-200"
143
+ }`}>
144
+ <p className="text-[13px] font-medium text-zinc-400">{plan.name}</p>
145
+ <p className="mt-2 flex items-baseline gap-1">
146
+ <span className="text-3xl font-semibold tracking-tight">{plan.price}</span>
147
  <span className="text-sm text-zinc-400">{plan.period}</span>
148
  </p>
149
+ <ul className="mt-5 space-y-2.5">
150
  {plan.features.map((f) => (
151
+ <li key={f} className="flex items-start gap-2.5 text-[13px] text-zinc-600">
152
+ <FileCheck className="w-3.5 h-3.5 text-zinc-300 mt-0.5 shrink-0" />
153
+ {f}
154
  </li>
155
  ))}
156
  </ul>
157
+ <button className={`mt-6 w-full py-2.5 rounded-lg text-[13px] font-medium transition-colors ${
158
+ plan.highlight
159
+ ? "bg-zinc-900 text-white hover:bg-zinc-800"
160
+ : "border border-zinc-200 text-zinc-700 hover:bg-zinc-50"
161
+ }`}>
162
  {plan.cta}
163
  </button>
164
  </div>
 
167
  </div>
168
  </section>
169
 
170
+ {/* CTA */}
171
+ <section className="border-t border-zinc-100 bg-zinc-50/50">
172
+ <div className="max-w-6xl mx-auto px-5 py-16 text-center">
173
+ <Lock className="w-6 h-6 text-zinc-300 mx-auto mb-4" />
174
+ <h2 className="text-2xl font-semibold tracking-tight">Read the fine print without reading it</h2>
175
+ <p className="mt-2 text-[15px] text-zinc-500 max-w-md mx-auto">
176
+ Join thousands protecting themselves before clicking accept.
177
+ </p>
178
+ <div className="mt-6">
179
+ <a href="#" className="inline-flex items-center gap-2 bg-zinc-900 text-white px-6 py-3 rounded-lg text-sm font-medium hover:bg-zinc-800 transition-colors">
180
+ <Download className="w-4 h-4" />
181
+ Add to Chrome — free
182
+ </a>
183
+ </div>
184
+ </div>
185
+ </section>
186
+
187
  {/* Footer */}
188
  <footer className="border-t border-zinc-100">
189
+ <div className="max-w-6xl mx-auto px-5 py-8 flex flex-col sm:flex-row justify-between items-center gap-4">
190
+ <div className="flex items-center gap-2">
191
+ <ShieldCheck className="w-4 h-4 text-zinc-300" />
192
+ <span className="text-[13px] text-zinc-400">ClauseGuard — not legal advice</span>
193
+ </div>
194
+ <div className="flex gap-5 text-[13px] text-zinc-400">
195
  <a href="/privacy" className="hover:text-zinc-600">Privacy</a>
196
  <a href="/terms" className="hover:text-zinc-600">Terms</a>
197
  <a href="mailto:hello@clauseguard.com" className="hover:text-zinc-600">Contact</a>
web/components/nav.tsx ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
5
+ import { ShieldCheck, Menu, X } from "lucide-react";
6
+ import { useState } from "react";
7
+
8
+ const links = [
9
+ { href: "/#features", label: "Features" },
10
+ { href: "/#pricing", label: "Pricing" },
11
+ { href: "/dashboard-pages/analyze", label: "Scanner" },
12
+ ];
13
+
14
+ export function Nav() {
15
+ const [open, setOpen] = useState(false);
16
+ const pathname = usePathname();
17
+ const isDashboard = pathname?.startsWith("/dashboard");
18
+
19
+ return (
20
+ <nav className="sticky top-0 z-50 bg-white/80 backdrop-blur-md border-b border-zinc-100">
21
+ <div className="max-w-6xl mx-auto px-5 h-14 flex items-center justify-between">
22
+ <Link href="/" className="flex items-center gap-2">
23
+ <ShieldCheck className="w-5 h-5 text-zinc-900" strokeWidth={2.2} />
24
+ <span className="font-semibold text-[15px] tracking-tight text-zinc-900">ClauseGuard</span>
25
+ </Link>
26
+
27
+ {/* Desktop */}
28
+ <div className="hidden md:flex items-center gap-1">
29
+ {links.map((l) => (
30
+ <a key={l.href} href={l.href}
31
+ className="px-3 py-1.5 text-[13px] text-zinc-500 hover:text-zinc-900 rounded-md hover:bg-zinc-50 transition-colors">
32
+ {l.label}
33
+ </a>
34
+ ))}
35
+ <div className="w-px h-4 bg-zinc-200 mx-2" />
36
+ {isDashboard ? (
37
+ <Link href="/dashboard-pages/settings"
38
+ className="px-3 py-1.5 text-[13px] text-zinc-500 hover:text-zinc-900 rounded-md hover:bg-zinc-50">
39
+ Settings
40
+ </Link>
41
+ ) : (
42
+ <Link href="/auth/login"
43
+ className="px-3 py-1.5 text-[13px] text-zinc-500 hover:text-zinc-900 rounded-md hover:bg-zinc-50">
44
+ Log in
45
+ </Link>
46
+ )}
47
+ <Link href={isDashboard ? "/dashboard-pages/analyze" : "/auth/signup"}
48
+ className="ml-1 px-3.5 py-1.5 text-[13px] font-medium text-white bg-zinc-900 rounded-md hover:bg-zinc-800 transition-colors">
49
+ {isDashboard ? "New scan" : "Get started"}
50
+ </Link>
51
+ </div>
52
+
53
+ {/* Mobile toggle */}
54
+ <button className="md:hidden p-1.5 rounded-md hover:bg-zinc-100" onClick={() => setOpen(!open)}>
55
+ {open ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
56
+ </button>
57
+ </div>
58
+
59
+ {/* Mobile menu */}
60
+ {open && (
61
+ <div className="md:hidden border-t border-zinc-100 bg-white px-5 py-3 space-y-1">
62
+ {links.map((l) => (
63
+ <a key={l.href} href={l.href} onClick={() => setOpen(false)}
64
+ className="block px-3 py-2 text-sm text-zinc-600 rounded-md hover:bg-zinc-50">
65
+ {l.label}
66
+ </a>
67
+ ))}
68
+ <Link href="/auth/login" onClick={() => setOpen(false)}
69
+ className="block px-3 py-2 text-sm text-zinc-600 rounded-md hover:bg-zinc-50">Log in</Link>
70
+ </div>
71
+ )}
72
+ </nav>
73
+ );
74
+ }