Skip to content

Commit 8d84ac0

Browse files
Lykhoydaclaude
andcommitted
feat(extension): options sidebar layout + teal vector logo
Options page: - Sidebar layout with sticky nav (fills viewport, no max-width card) - Vertical tab navigation with PREFERENCES label - GeneralTab: info-card callout + grouped bordered list (opt-list/opt-row) - Dynamic page title+subtitle per tab via PAGE_META - aria-labelledby on toggle switches (Codex review finding) - All new CSS classes: opt-layout, opt-side, opt-tab, opt-main, opt-group, etc. Logo: - Red scan-based raster (30KB) → teal vector SVG (500B) - Same nested-hexagon silhouette (#1a9b8c fill, white strokes) - extract-icon.mjs updated to handle pure vector SVG via sharp density - Icons regenerated at 16/32/48/128px Reviewed by Gemini + Codex: 0 bugs found, 1 a11y fix applied. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2e1f950 commit 8d84ac0

4 files changed

Lines changed: 350 additions & 95 deletions

File tree

packages/extension/options.html

Lines changed: 212 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,218 @@
7575
::-webkit-scrollbar-track { background: transparent; }
7676
::-webkit-scrollbar-thumb { background: var(--text-dim); border-radius: 3px; }
7777

78-
/* ── Container ── */
78+
/* ── Sidebar layout — fills viewport ── */
79+
.opt-layout {
80+
display: grid;
81+
grid-template-columns: 260px 1fr;
82+
min-height: 100vh;
83+
}
84+
85+
.opt-side {
86+
border-right: 1px solid var(--border-default);
87+
padding: 32px 20px;
88+
display: flex;
89+
flex-direction: column;
90+
gap: 2px;
91+
background: var(--bg-void);
92+
position: sticky;
93+
top: 0;
94+
height: 100vh;
95+
overflow-y: auto;
96+
}
97+
98+
.opt-brand {
99+
display: flex;
100+
align-items: center;
101+
gap: 12px;
102+
padding: 0 12px 24px;
103+
margin-bottom: 12px;
104+
border-bottom: 1px solid var(--border-default);
105+
}
106+
107+
.opt-brand-mark {
108+
width: 32px;
109+
height: 32px;
110+
border-radius: 8px;
111+
}
112+
113+
.opt-brand-text {
114+
font-family: var(--font-mono);
115+
font-size: 16px;
116+
font-weight: 700;
117+
letter-spacing: 0.04em;
118+
color: var(--text-bright);
119+
}
120+
121+
.opt-nav-label {
122+
font-family: var(--font-mono);
123+
font-size: 9px;
124+
font-weight: 600;
125+
text-transform: uppercase;
126+
letter-spacing: 0.1em;
127+
color: var(--text-dim);
128+
padding: 12px 12px 8px;
129+
}
130+
131+
.opt-nav {
132+
display: flex;
133+
flex-direction: column;
134+
gap: 2px;
135+
}
136+
137+
.opt-tab {
138+
display: flex;
139+
align-items: center;
140+
gap: 12px;
141+
padding: 10px 12px;
142+
border-radius: 6px;
143+
font-family: var(--font-ui);
144+
font-size: 13px;
145+
font-weight: 500;
146+
color: var(--text-muted);
147+
background: transparent;
148+
border: none;
149+
cursor: pointer;
150+
transition: all 0.2s;
151+
text-align: left;
152+
width: 100%;
153+
}
154+
155+
.opt-tab:hover {
156+
background: var(--bg-surface);
157+
color: var(--text-primary);
158+
}
159+
160+
.opt-tab.active {
161+
background: var(--phosphor-faint);
162+
border: 1px solid rgba(0, 229, 153, 0.12);
163+
color: var(--text-bright);
164+
}
165+
166+
.opt-tab .material-symbols-outlined {
167+
font-size: 18px;
168+
color: var(--text-muted);
169+
}
170+
171+
.opt-tab.active .material-symbols-outlined {
172+
color: var(--phosphor-dim);
173+
}
174+
175+
.opt-main {
176+
padding: 48px 56px 64px;
177+
overflow-y: auto;
178+
max-width: 860px;
179+
}
180+
181+
.opt-header {
182+
margin-bottom: 36px;
183+
padding-bottom: 24px;
184+
border-bottom: 1px solid var(--border-default);
185+
}
186+
187+
.opt-title {
188+
font-family: var(--font-ui);
189+
font-size: 24px;
190+
font-weight: 600;
191+
letter-spacing: -0.01em;
192+
color: var(--text-bright);
193+
margin-bottom: 6px;
194+
}
195+
196+
.opt-sub {
197+
font-size: 14px;
198+
color: var(--text-muted);
199+
}
200+
201+
.opt-group {
202+
margin-bottom: 28px;
203+
}
204+
205+
.opt-group-title {
206+
font-family: var(--font-mono);
207+
font-size: 10px;
208+
font-weight: 600;
209+
text-transform: uppercase;
210+
letter-spacing: 0.1em;
211+
color: var(--text-dim);
212+
margin-bottom: 12px;
213+
padding-left: 2px;
214+
}
215+
216+
.opt-list {
217+
background: var(--bg-surface);
218+
border: 1px solid var(--border-default);
219+
border-radius: var(--radius-md);
220+
overflow: hidden;
221+
}
222+
223+
.opt-row {
224+
padding: 16px 20px;
225+
display: flex;
226+
align-items: center;
227+
justify-content: space-between;
228+
gap: 20px;
229+
border-bottom: 1px solid var(--border-subtle);
230+
transition: background 0.15s;
231+
}
232+
233+
.opt-row:last-child { border-bottom: none; }
234+
.opt-row:hover { background: var(--bg-surface-hover); }
235+
236+
.opt-row-text { flex: 1; min-width: 0; }
237+
238+
.opt-row-title {
239+
font-size: 14px;
240+
font-weight: 500;
241+
color: var(--text-bright);
242+
margin-bottom: 3px;
243+
}
244+
245+
.opt-row-desc {
246+
font-size: 12px;
247+
color: var(--text-dim);
248+
line-height: 1.5;
249+
}
250+
251+
.info-card {
252+
background: var(--bg-surface);
253+
border: 1px solid var(--border-default);
254+
border-radius: var(--radius-md);
255+
padding: 18px 20px;
256+
display: flex;
257+
gap: 14px;
258+
margin-bottom: 28px;
259+
}
260+
261+
.info-card-icon {
262+
width: 36px;
263+
height: 36px;
264+
border-radius: var(--radius-md);
265+
background: var(--phosphor-faint);
266+
color: var(--phosphor-dim);
267+
display: flex;
268+
align-items: center;
269+
justify-content: center;
270+
flex-shrink: 0;
271+
border: 1px solid rgba(0, 229, 153, 0.08);
272+
}
273+
274+
.info-card-icon .material-symbols-outlined { font-size: 19px; }
275+
276+
.info-card-title {
277+
font-size: 14px;
278+
font-weight: 600;
279+
color: var(--text-bright);
280+
margin-bottom: 3px;
281+
}
282+
283+
.info-card-desc {
284+
font-size: 12px;
285+
color: var(--text-muted);
286+
line-height: 1.5;
287+
}
288+
289+
/* ── Legacy container (kept for backward compat) ── */
79290
.container {
80291
max-width: 780px;
81292
margin: 0 auto;

packages/extension/src/components/options/GeneralTab.tsx

Lines changed: 61 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,51 +10,70 @@ interface Props {
1010

1111
export function GeneralTab({ settings: s, onToggleMediumToast, onToggleAutoRecord }: Props) {
1212
return (
13-
<div class="section">
14-
<h2 class="section-title">
15-
<MaterialIcon name="notifications" />
16-
Notifications
17-
</h2>
18-
<div class="toggle-group">
19-
<div>
20-
<div class="toggle-label">Show Medium Risk Toast</div>
21-
<div class="toggle-description">Display a notification for medium-risk contracts</div>
13+
<>
14+
<div class="info-card">
15+
<div class="info-card-icon">
16+
<MaterialIcon name="security" />
2217
</div>
23-
<div
24-
class={`toggle${s.value.showMediumRiskToast ? ' active' : ''}`}
25-
id="toggle-medium-toast"
26-
role="switch"
27-
aria-checked={s.value.showMediumRiskToast}
28-
tabIndex={0}
29-
onClick={onToggleMediumToast}
30-
onKeyDown={(e) => {
31-
if (e.key === 'Enter' || e.key === ' ') {
32-
e.preventDefault();
33-
onToggleMediumToast();
34-
}
35-
}}
36-
/>
37-
</div>
38-
<div class="toggle-group">
3918
<div>
40-
<div class="toggle-label">Auto-record Scans</div>
41-
<div class="toggle-description">Save scan history for later review</div>
19+
<div class="info-card-title">3-layer defense is active</div>
20+
<div class="info-card-desc">Safe filter → Threat API → Local bytecode analysis.</div>
4221
</div>
43-
<div
44-
class={`toggle${s.value.autoRecordScans ? ' active' : ''}`}
45-
id="toggle-auto-record"
46-
role="switch"
47-
aria-checked={s.value.autoRecordScans}
48-
tabIndex={0}
49-
onClick={onToggleAutoRecord}
50-
onKeyDown={(e) => {
51-
if (e.key === 'Enter' || e.key === ' ') {
52-
e.preventDefault();
53-
onToggleAutoRecord();
54-
}
55-
}}
56-
/>
5722
</div>
58-
</div>
23+
24+
<section class="opt-group">
25+
<h2 class="opt-group-title">Detection</h2>
26+
<div class="opt-list">
27+
<div class="opt-row">
28+
<div class="opt-row-text">
29+
<div class="opt-row-title" id="label-medium-toast">
30+
Show medium-risk warnings
31+
</div>
32+
<div class="opt-row-desc">
33+
Display non-blocking toasts for medium severity threats.
34+
</div>
35+
</div>
36+
<div
37+
class={`toggle${s.value.showMediumRiskToast ? ' active' : ''}`}
38+
id="toggle-medium-toast"
39+
role="switch"
40+
aria-checked={s.value.showMediumRiskToast}
41+
aria-labelledby="label-medium-toast"
42+
tabIndex={0}
43+
onClick={onToggleMediumToast}
44+
onKeyDown={(e) => {
45+
if (e.key === 'Enter' || e.key === ' ') {
46+
e.preventDefault();
47+
onToggleMediumToast();
48+
}
49+
}}
50+
/>
51+
</div>
52+
<div class="opt-row">
53+
<div class="opt-row-text">
54+
<div class="opt-row-title" id="label-auto-record">
55+
Auto-record scans
56+
</div>
57+
<div class="opt-row-desc">Keep local history of analyzed contracts for review.</div>
58+
</div>
59+
<div
60+
class={`toggle${s.value.autoRecordScans ? ' active' : ''}`}
61+
id="toggle-auto-record"
62+
role="switch"
63+
aria-checked={s.value.autoRecordScans}
64+
aria-labelledby="label-auto-record"
65+
tabIndex={0}
66+
onClick={onToggleAutoRecord}
67+
onKeyDown={(e) => {
68+
if (e.key === 'Enter' || e.key === ' ') {
69+
e.preventDefault();
70+
onToggleAutoRecord();
71+
}
72+
}}
73+
/>
74+
</div>
75+
</div>
76+
</section>
77+
</>
5978
);
6079
}

0 commit comments

Comments
 (0)