@systemverification/styling-kit 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +98 -0
- package/README.md +411 -0
- package/bin/sv-style.js +74 -0
- package/docs/BAD_EXAMPLES.md +145 -0
- package/docs/COMPONENTS/BUTTON.md +108 -0
- package/docs/COMPONENTS/CARD.md +68 -0
- package/docs/DESIGN_SYSTEM.md +614 -0
- package/package.json +22 -0
- package/src/assets.js +67 -0
- package/src/bundle.js +67 -0
- package/src/config.js +67 -0
- package/src/doctor.js +113 -0
- package/src/fs-utils.js +15 -0
- package/src/init.js +182 -0
- package/src/login.js +65 -0
- package/src/mcp.js +215 -0
- package/src/path-rewrite.js +48 -0
- package/src/shutdown.js +26 -0
- package/src/update.js +101 -0
- package/templates/AGENTS_SNIPPET.md +40 -0
- package/templates/CLAUDE_SNIPPET.md +14 -0
- package/tokens/tokens-core.css +51 -0
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
# System Verification — Design System
|
|
2
|
+
|
|
3
|
+
This file defines the visual identity for System Verification products. When building UI, **always follow these conventions** — do not invent new colors, fonts, or spacing values.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Brand Colors (CSS Custom Properties)
|
|
8
|
+
|
|
9
|
+
Use these as CSS custom properties. Define them in `:root` and reference via `var(--token-name)`.
|
|
10
|
+
|
|
11
|
+
```css
|
|
12
|
+
:root {
|
|
13
|
+
/* ── Core palette ── */
|
|
14
|
+
--color-dark-blue: #0B1B28; /* Primary background, dark surfaces, input backgrounds */
|
|
15
|
+
--color-light-blue: #435364; /* Secondary backgrounds, dividers, card borders */
|
|
16
|
+
--color-sand: #F2F2EA; /* Primary text color on dark backgrounds */
|
|
17
|
+
--color-yellow: #F7F965; /* Accent / CTA — buttons, links, active states, focus rings */
|
|
18
|
+
--color-white: #ffffff; /* Headings text, high-emphasis text */
|
|
19
|
+
|
|
20
|
+
/* ── Semantic colors ── */
|
|
21
|
+
--color-error: #ef4444; /* Error states, destructive actions, unhealthy badges */
|
|
22
|
+
--color-success: #10b981; /* Running/healthy states, positive badges */
|
|
23
|
+
--color-yellow-hover: #f8faaa; /* Primary button hover — lightened yellow */
|
|
24
|
+
--color-yellow-active: #f5f950;/* Primary button active — saturated yellow */
|
|
25
|
+
--color-error-light: #f87171; /* Danger button text, error badge text — lightened red */
|
|
26
|
+
|
|
27
|
+
/* ── Border radius scale ── */
|
|
28
|
+
--radius-sm: 4px; /* Inputs, code blocks, small elements */
|
|
29
|
+
--radius-md: 8px; /* Cards, form sections, panels */
|
|
30
|
+
--radius-pill: 100px; /* Buttons, badges, tags, pills */
|
|
31
|
+
|
|
32
|
+
/* ── Motion ── */
|
|
33
|
+
--transition: 0.3s ease; /* Default transition for all interactive elements */
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Extended Color Usage
|
|
38
|
+
|
|
39
|
+
These derivative colors are used throughout the UI. Most are `rgba()` variants of the core palette, used contextually. The tokenized ones (marked with `var(--...)`) **must** be referenced via their token — never hardcoded:
|
|
40
|
+
|
|
41
|
+
| Purpose | Value | Base color |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| Card background | `rgba(67, 83, 100, 0.35)` | light-blue |
|
|
44
|
+
| Card/section border | `rgba(67, 83, 100, 0.5)` | light-blue |
|
|
45
|
+
| Header border | `rgba(67, 83, 100, 0.4)` | light-blue |
|
|
46
|
+
| Form label text | `rgba(242, 242, 234, 0.7)` | sand |
|
|
47
|
+
| Secondary/meta text | `rgba(242, 242, 234, 0.5)` | sand |
|
|
48
|
+
| Muted text (placeholders) | `rgba(242, 242, 234, 0.3)` | sand |
|
|
49
|
+
| Subtle borders (inputs) | `rgba(242, 242, 234, 0.15)` | sand |
|
|
50
|
+
| Subtle button hover bg | `rgba(242, 242, 234, 0.1)` | sand |
|
|
51
|
+
| Button hover (yellow) | `var(--color-yellow-hover)` = `#f8faaa` | yellow lightened |
|
|
52
|
+
| Button active (yellow) | `var(--color-yellow-active)` = `#f5f950` | yellow saturated |
|
|
53
|
+
| Yellow badge background | `rgba(247, 249, 101, 0.15)` | yellow |
|
|
54
|
+
| Yellow focus ring shadow | `rgba(247, 249, 101, 0.15)` | yellow |
|
|
55
|
+
| Green badge text | `#34d399` | success lightened |
|
|
56
|
+
| Green badge background | `rgba(16, 185, 129, 0.15)` | success |
|
|
57
|
+
| Red badge text / danger text | `var(--color-error-light)` = `#f87171` | error lightened |
|
|
58
|
+
| Red badge background | `rgba(239, 68, 68, 0.15)` | error |
|
|
59
|
+
| Warning/branch text | `#fb923c` | orange (warning) |
|
|
60
|
+
| Warning background | `rgba(251, 146, 60, 0.1)` | orange |
|
|
61
|
+
|
|
62
|
+
### Design Principle
|
|
63
|
+
|
|
64
|
+
The palette is **dark-mode first**. Background is `--color-dark-blue`, primary text is `--color-sand`, headings are `--color-white`, and `--color-yellow` is the singular accent color used for all interactive/CTA elements. Do not introduce additional accent colors.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Typography
|
|
69
|
+
|
|
70
|
+
### Fonts
|
|
71
|
+
|
|
72
|
+
Two custom fonts, self-hosted as `.woff2` files:
|
|
73
|
+
|
|
74
|
+
| Font | CSS family | Usage | File |
|
|
75
|
+
|---|---|---|---|
|
|
76
|
+
| **BodyFont** | `"BodyFont", sans-serif` | All body text (globally inherited) | `fonts/body-font.woff2`, `fonts/body-font-italic.woff2` |
|
|
77
|
+
| **HeadingFont** | `"HeadingFont", sans-serif` | All headings (`h1`–`h6`) | `fonts/heading-font.woff2` |
|
|
78
|
+
| Monospace | `monospace` | Code blocks, technical values | System default |
|
|
79
|
+
|
|
80
|
+
### @font-face Declarations
|
|
81
|
+
|
|
82
|
+
```css
|
|
83
|
+
@font-face {
|
|
84
|
+
font-family: "BodyFont";
|
|
85
|
+
src: url("/fonts/body-font.woff2") format("woff2");
|
|
86
|
+
font-weight: normal;
|
|
87
|
+
font-style: normal;
|
|
88
|
+
font-display: swap;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@font-face {
|
|
92
|
+
font-family: "BodyFont";
|
|
93
|
+
src: url("/fonts/body-font-italic.woff2") format("woff2");
|
|
94
|
+
font-weight: normal;
|
|
95
|
+
font-style: italic;
|
|
96
|
+
font-display: swap;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@font-face {
|
|
100
|
+
font-family: "HeadingFont";
|
|
101
|
+
src: url("/fonts/heading-font.woff2") format("woff2");
|
|
102
|
+
font-weight: normal;
|
|
103
|
+
font-style: normal;
|
|
104
|
+
font-display: swap;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Font Weights
|
|
109
|
+
|
|
110
|
+
| Weight | Usage |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `300` | Body text default |
|
|
113
|
+
| `400` | Meta text, secondary content |
|
|
114
|
+
| `500` | Labels, links, buttons, nav items |
|
|
115
|
+
| `600` | Headings, badges, emphasized labels |
|
|
116
|
+
|
|
117
|
+
### Font Sizes
|
|
118
|
+
|
|
119
|
+
| Size | Usage |
|
|
120
|
+
|---|---|
|
|
121
|
+
| `1.05rem` | Card titles |
|
|
122
|
+
| `1rem` | Header title, base size |
|
|
123
|
+
| `0.875rem` | Form inputs |
|
|
124
|
+
| `0.85rem` | Section headings, buttons, toasts |
|
|
125
|
+
| `0.8rem` | Form labels, links, user names |
|
|
126
|
+
| `0.78rem` | Meta text, action buttons, banners |
|
|
127
|
+
| `0.75rem` | Status text, small inputs |
|
|
128
|
+
| `0.7rem` | Status badges, health badges |
|
|
129
|
+
| `0.68rem` | Role badges, code text, small UI |
|
|
130
|
+
| `0.65rem` | Tiny labels |
|
|
131
|
+
|
|
132
|
+
### Text Styling Conventions
|
|
133
|
+
|
|
134
|
+
- Headings, labels, badges, and status indicators use `text-transform: uppercase`
|
|
135
|
+
- Letter spacing: `0.02em` for header/CTA text, `0.04em`–`0.05em` for labels and badges
|
|
136
|
+
- Use `-webkit-font-smoothing: antialiased` on `body`
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Logo & Favicon
|
|
141
|
+
|
|
142
|
+
| Asset | File | Format | Notes |
|
|
143
|
+
|---|---|---|---|
|
|
144
|
+
| **Logo** | `logo.svg` | SVG | "System Verification" wordmark in white (`fill: #ffffff`). Display at `height: 28px`. |
|
|
145
|
+
| **Favicon** | `favicon.png` | PNG | "S" symbol in orange/gold tones. ~92 KB. |
|
|
146
|
+
|
|
147
|
+
### Logo Usage
|
|
148
|
+
|
|
149
|
+
```html
|
|
150
|
+
<img src="/logo.svg" alt="System Verification" style="height: 28px;">
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```html
|
|
154
|
+
<link rel="icon" type="image/png" href="/favicon.png">
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The logo SVG uses `fill: #ffffff` — it is designed for dark backgrounds only. If you need it on a light background, you must create a separate dark variant.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Layout Conventions
|
|
162
|
+
|
|
163
|
+
| Property | Value | Context |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| Max content width | `1300px` | Header and main content area |
|
|
166
|
+
| Page padding (horizontal) | `2rem` | Header, main |
|
|
167
|
+
| Page padding (vertical) | `2.5rem` top/bottom | Main area |
|
|
168
|
+
| Card grid | `repeat(auto-fill, minmax(340px, 1fr))` | Instance/item cards |
|
|
169
|
+
| Card gap | `1.25rem` | Between grid items |
|
|
170
|
+
| Card padding | `1.5rem` | Inside cards |
|
|
171
|
+
|
|
172
|
+
### Sticky Header
|
|
173
|
+
|
|
174
|
+
The header uses a frosted-glass effect:
|
|
175
|
+
|
|
176
|
+
```css
|
|
177
|
+
header {
|
|
178
|
+
position: sticky;
|
|
179
|
+
top: 0;
|
|
180
|
+
z-index: 100;
|
|
181
|
+
background: var(--color-dark-blue);
|
|
182
|
+
backdrop-filter: blur(8px);
|
|
183
|
+
border-bottom: 1px solid rgba(67, 83, 100, 0.4);
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Component Specifications
|
|
190
|
+
|
|
191
|
+
### Button
|
|
192
|
+
|
|
193
|
+
**Variants:**
|
|
194
|
+
- **Primary (CTA)**: yellow background for calls-to-action
|
|
195
|
+
- **Secondary/Action**: transparent button for secondary actions
|
|
196
|
+
- **Ghost**: minimal styling for tertiary interactions
|
|
197
|
+
- **Danger**: red-tinted for destructive actions
|
|
198
|
+
|
|
199
|
+
**Structure:**
|
|
200
|
+
- Button element with rounded pill shape (`border-radius: var(--radius-pill)`)
|
|
201
|
+
- Padding: `0.5rem 1rem` (adjust for density)
|
|
202
|
+
- Font: `0.85rem`, `font-weight: 500`
|
|
203
|
+
- Always uppercase via `text-transform: uppercase`
|
|
204
|
+
|
|
205
|
+
**Detailed Variants:**
|
|
206
|
+
|
|
207
|
+
| Variant | Background | Text | Border | Hover State |
|
|
208
|
+
|---|---|---|---|---|
|
|
209
|
+
| Primary | `var(--color-yellow)` | `var(--color-dark-blue)` | none | `var(--color-yellow-hover)` |
|
|
210
|
+
| Secondary | transparent | `var(--color-sand)` | `1px solid rgba(242, 242, 234, 0.2)` | `rgba(242, 242, 234, 0.1)` background |
|
|
211
|
+
| Danger | transparent | `var(--color-error-light)` | `rgba(239, 68, 68, 0.3)` | red-tinted background `rgba(239, 68, 68, 0.1)` |
|
|
212
|
+
|
|
213
|
+
**Required States** (all buttons must include):
|
|
214
|
+
- **Default**: base styling as defined
|
|
215
|
+
- **Hover**: background/text shift per variant
|
|
216
|
+
- **Active**: darker variant of hover state
|
|
217
|
+
- **Focus-visible**: `outline: 2px solid var(--color-yellow)`, `outline-offset: 2px`
|
|
218
|
+
- **Disabled**: `opacity: 0.5`, `cursor: not-allowed`, no pointer events
|
|
219
|
+
|
|
220
|
+
**Code Example (Primary):**
|
|
221
|
+
```css
|
|
222
|
+
button.primary {
|
|
223
|
+
background: var(--color-yellow);
|
|
224
|
+
color: var(--color-dark-blue);
|
|
225
|
+
border: none;
|
|
226
|
+
border-radius: var(--radius-pill);
|
|
227
|
+
padding: 0.5rem 1rem;
|
|
228
|
+
font: 0.85rem;
|
|
229
|
+
font-weight: 500;
|
|
230
|
+
text-transform: uppercase;
|
|
231
|
+
cursor: pointer;
|
|
232
|
+
transition: var(--transition);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
button.primary:hover {
|
|
236
|
+
background: var(--color-yellow-hover);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
button.primary:active {
|
|
240
|
+
background: var(--color-yellow-active);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
button.primary:focus-visible {
|
|
244
|
+
outline: 2px solid var(--color-yellow);
|
|
245
|
+
outline-offset: 2px;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
button.primary:disabled {
|
|
249
|
+
opacity: 0.5;
|
|
250
|
+
cursor: not-allowed;
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
### Card
|
|
257
|
+
|
|
258
|
+
**Structure:**
|
|
259
|
+
- Container with border and translucent background
|
|
260
|
+
- Optional title (uppercase, `1.05rem`, `font-weight: 600`)
|
|
261
|
+
- Content area (flexible layout)
|
|
262
|
+
- Optional footer section
|
|
263
|
+
|
|
264
|
+
**Styling:**
|
|
265
|
+
- `background: rgba(67, 83, 100, 0.35)`
|
|
266
|
+
- `border: 1px solid rgba(67, 83, 100, 0.5)`
|
|
267
|
+
- `border-radius: var(--radius-md)`
|
|
268
|
+
- `padding: 1.5rem`
|
|
269
|
+
|
|
270
|
+
**Required States:**
|
|
271
|
+
- **Default**: base styling
|
|
272
|
+
- **Hover**: `transform: translateY(-2px)`, `box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25)`
|
|
273
|
+
- **Focus** (if interactive): highlight border with accent color
|
|
274
|
+
- **Disabled** (if interactive): reduced opacity, no pointer
|
|
275
|
+
|
|
276
|
+
**Code Example:**
|
|
277
|
+
```css
|
|
278
|
+
.card {
|
|
279
|
+
background: rgba(67, 83, 100, 0.35);
|
|
280
|
+
border: 1px solid rgba(67, 83, 100, 0.5);
|
|
281
|
+
border-radius: var(--radius-md);
|
|
282
|
+
padding: 1.5rem;
|
|
283
|
+
transition: var(--transition);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.card:hover {
|
|
287
|
+
transform: translateY(-2px);
|
|
288
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.card-title {
|
|
292
|
+
font-family: "HeadingFont", sans-serif;
|
|
293
|
+
font-size: 1.05rem;
|
|
294
|
+
font-weight: 600;
|
|
295
|
+
text-transform: uppercase;
|
|
296
|
+
color: var(--color-white);
|
|
297
|
+
margin-bottom: 0.75rem;
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
### Badges / Status Pills
|
|
304
|
+
|
|
305
|
+
- Pill shaped (`border-radius: var(--radius-pill)`)
|
|
306
|
+
- Font size: `0.68rem`–`0.7rem`, `font-weight: 600`
|
|
307
|
+
- Letter spacing: `0.04em` (uppercase)
|
|
308
|
+
- Background: `0.12`–`0.15` alpha tint of status color
|
|
309
|
+
- Text: lighter shade of status color
|
|
310
|
+
|
|
311
|
+
**Status Colors:**
|
|
312
|
+
- **Success**: text `#34d399`, background `rgba(16, 185, 129, 0.15)`
|
|
313
|
+
- **Error**: text `#f87171`, background `rgba(239, 68, 68, 0.15)`
|
|
314
|
+
- **Warning**: text `#fb923c`, background `rgba(251, 146, 60, 0.1)`
|
|
315
|
+
- **Yellow/Accent**: text `var(--color-yellow)`, background `rgba(247, 249, 101, 0.15)`
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
### Inputs
|
|
320
|
+
|
|
321
|
+
- `background: var(--color-dark-blue)`
|
|
322
|
+
- `border: 1px solid rgba(242, 242, 234, 0.15)`
|
|
323
|
+
- `border-radius: var(--radius-sm)`
|
|
324
|
+
- `padding: 0.5rem 0.75rem`
|
|
325
|
+
- `font-size: 0.875rem`
|
|
326
|
+
- `color: var(--color-sand)`
|
|
327
|
+
|
|
328
|
+
**Required States:**
|
|
329
|
+
- **Default**: as above
|
|
330
|
+
- **Focus**: `border-color: var(--color-yellow)` with `box-shadow: 0 0 0 2px rgba(247, 249, 101, 0.15)`
|
|
331
|
+
- **Disabled**: `opacity: 0.5`, `cursor: not-allowed`
|
|
332
|
+
- **Error**: `border-color: rgba(239, 68, 68, 0.5)`
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
### Toasts / Notifications
|
|
337
|
+
|
|
338
|
+
- Floating element with `border-radius: var(--radius-pill)`
|
|
339
|
+
- `box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3)`
|
|
340
|
+
- Padding: `1rem 1.5rem`
|
|
341
|
+
|
|
342
|
+
**Variants:**
|
|
343
|
+
- **Default**: `background: rgba(242, 242, 234, 0.9)`, `color: var(--color-dark-blue)`
|
|
344
|
+
- **Error**: `background: var(--color-error)`, `color: var(--color-white)`
|
|
345
|
+
- **Success**: `background: var(--color-success)`, `color: var(--color-white)`
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Common Styling Anti-Patterns
|
|
350
|
+
|
|
351
|
+
**Before writing any styled code, review these patterns to avoid.**
|
|
352
|
+
|
|
353
|
+
### ❌ Hardcoded Colors
|
|
354
|
+
|
|
355
|
+
```css
|
|
356
|
+
/* WRONG */
|
|
357
|
+
background: #123456;
|
|
358
|
+
color: #f0f0f0;
|
|
359
|
+
border-color: #ff9900;
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
```css
|
|
363
|
+
/* CORRECT */
|
|
364
|
+
background: var(--color-dark-blue);
|
|
365
|
+
color: var(--color-sand);
|
|
366
|
+
border-color: var(--color-yellow);
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Rule:** All colors must use `var(--color-*)` tokens defined in `:root`. Never hardcode hex values.
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
### ❌ Inconsistent or Magic Spacing
|
|
374
|
+
|
|
375
|
+
```css
|
|
376
|
+
/* WRONG */
|
|
377
|
+
margin: 17px;
|
|
378
|
+
padding: 13px 21px;
|
|
379
|
+
gap: 9px;
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
```css
|
|
383
|
+
/* CORRECT */
|
|
384
|
+
margin: 1.5rem; /* Use consistent scale */
|
|
385
|
+
padding: 1rem 1.25rem;
|
|
386
|
+
gap: 0.75rem;
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**Rule:** Use the defined spacing scale. No arbitrary pixel values. Use `rem` units for consistency.
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
### ❌ Random Font Sizes (Not on Scale)
|
|
394
|
+
|
|
395
|
+
```css
|
|
396
|
+
/* WRONG */
|
|
397
|
+
font-size: 15px;
|
|
398
|
+
font-size: 18px;
|
|
399
|
+
font-size: 12px;
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
```css
|
|
403
|
+
/* CORRECT */
|
|
404
|
+
font-size: 0.875rem; /* Form inputs */
|
|
405
|
+
font-size: 1rem; /* Base size */
|
|
406
|
+
font-size: 1.05rem; /* Card titles */
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**Rule:** Use only the defined typography scale. Do not invent new sizes.
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
### ❌ Accent Color Misuse
|
|
414
|
+
|
|
415
|
+
```css
|
|
416
|
+
/* WRONG */
|
|
417
|
+
background: var(--color-yellow); /* Large blocks */
|
|
418
|
+
.section { background: var(--color-yellow); } /* Entire panel */
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
```css
|
|
422
|
+
/* CORRECT */
|
|
423
|
+
button { background: var(--color-yellow); } /* CTAs */
|
|
424
|
+
a { color: var(--color-yellow); } /* Links */
|
|
425
|
+
input:focus { outline-color: var(--color-yellow); } /* Interactive focus */
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
**Rule:** Yellow (`--color-yellow`) is **only for interactive/CTA elements**. Never use it for large background areas or non-interactive content.
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
### ❌ Missing Component States
|
|
433
|
+
|
|
434
|
+
```css
|
|
435
|
+
/* WRONG */
|
|
436
|
+
button {
|
|
437
|
+
background: var(--color-yellow);
|
|
438
|
+
color: var(--color-dark-blue);
|
|
439
|
+
}
|
|
440
|
+
/* No hover, focus, active, or disabled states defined */
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
```css
|
|
444
|
+
/* CORRECT */
|
|
445
|
+
button {
|
|
446
|
+
background: var(--color-yellow);
|
|
447
|
+
color: var(--color-dark-blue);
|
|
448
|
+
transition: var(--transition);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
button:hover {
|
|
452
|
+
background: var(--color-yellow-hover);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
button:focus-visible {
|
|
456
|
+
outline: 2px solid var(--color-yellow);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
button:active {
|
|
460
|
+
background: var(--color-yellow-active);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
button:disabled {
|
|
464
|
+
opacity: 0.5;
|
|
465
|
+
cursor: not-allowed;
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
**Rule:** All interactive components **must** include `default`, `hover`, `focus-visible`, `active`, and `disabled` states.
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
### ❌ Inline Styles
|
|
474
|
+
|
|
475
|
+
```css
|
|
476
|
+
/* WRONG */
|
|
477
|
+
<div style="background: red; margin: 10px;">
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
```css
|
|
481
|
+
/* CORRECT */
|
|
482
|
+
<div class="my-card">
|
|
483
|
+
/* Then in CSS */
|
|
484
|
+
.my-card { background: var(--color-light-blue); margin: 1rem; }
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
**Rule:** All styles belong in external stylesheets. Never use `style=""` attributes.
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
### ❌ Non-Semantic Color Values
|
|
492
|
+
|
|
493
|
+
```css
|
|
494
|
+
/* WRONG */
|
|
495
|
+
.label { color: rgba(200, 200, 200, 0.8); }
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
```css
|
|
499
|
+
/* CORRECT */
|
|
500
|
+
.label { color: rgba(242, 242, 234, 0.7); } /* Derived from sand token */
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
**Rule:** All rgba values must be derived from the core palette tokens documented in this spec.
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## Box Shadows
|
|
508
|
+
|
|
509
|
+
| Shadow | Usage |
|
|
510
|
+
|---|---|
|
|
511
|
+
| `0 0 0 2px rgba(247, 249, 101, 0.15)` | Input focus ring |
|
|
512
|
+
| `0 8px 24px rgba(0, 0, 0, 0.25)` | Card hover lift |
|
|
513
|
+
| `0 4px 16px rgba(0, 0, 0, 0.3)` | Toast / floating elements |
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Animation
|
|
518
|
+
|
|
519
|
+
| Name | Value | Usage |
|
|
520
|
+
|---|---|---|
|
|
521
|
+
| `pulse-badge` | `opacity 1→0.5→1`, `1.5s ease-in-out infinite` | Loading/starting state badges |
|
|
522
|
+
| Default transition | `0.3s ease` (via `var(--transition)`) | All interactive hover/focus states |
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## CSS Reset
|
|
527
|
+
|
|
528
|
+
Always start with this minimal reset:
|
|
529
|
+
|
|
530
|
+
```css
|
|
531
|
+
* {
|
|
532
|
+
box-sizing: border-box;
|
|
533
|
+
margin: 0;
|
|
534
|
+
padding: 0;
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
## Quick Start
|
|
541
|
+
|
|
542
|
+
To apply this design system to a new project:
|
|
543
|
+
|
|
544
|
+
1. Copy the `design-system/` folder into your project (see folder structure below)
|
|
545
|
+
2. Add the `@font-face` declarations and `:root` tokens to your main CSS
|
|
546
|
+
3. Reference `logo.svg` and `favicon.png` in your HTML
|
|
547
|
+
4. Use `var(--color-*)` and `var(--radius-*)` tokens — never hardcode brand colors
|
|
548
|
+
5. Follow the typography, component, and layout patterns above
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
# Additions (Normalized for AI usage)
|
|
554
|
+
|
|
555
|
+
## Additional Semantic Tokens
|
|
556
|
+
|
|
557
|
+
Extend system with these tokens (do NOT remove originals):
|
|
558
|
+
|
|
559
|
+
```css
|
|
560
|
+
:root {
|
|
561
|
+
--text-primary: var(--color-white);
|
|
562
|
+
--text-secondary: rgba(242, 242, 234, 0.7);
|
|
563
|
+
--text-muted: rgba(242, 242, 234, 0.5);
|
|
564
|
+
|
|
565
|
+
--surface-card: rgba(67, 83, 100, 0.35);
|
|
566
|
+
--surface-hover: rgba(67, 83, 100, 0.5);
|
|
567
|
+
|
|
568
|
+
--border-subtle: rgba(67, 83, 100, 0.4);
|
|
569
|
+
--focus-ring: var(--color-yellow);
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
## Typography Scale (Explicit)
|
|
574
|
+
|
|
575
|
+
```css
|
|
576
|
+
--text-display: 48px;
|
|
577
|
+
--text-h1: 32px;
|
|
578
|
+
--text-h2: 24px;
|
|
579
|
+
--text-h3: 18px;
|
|
580
|
+
--text-body: 14px;
|
|
581
|
+
--text-label: 12px;
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
## Component States (Mandatory)
|
|
585
|
+
|
|
586
|
+
All components must define:
|
|
587
|
+
|
|
588
|
+
- default
|
|
589
|
+
- hover
|
|
590
|
+
- active
|
|
591
|
+
- focus-visible
|
|
592
|
+
- disabled
|
|
593
|
+
|
|
594
|
+
Optional but recommended:
|
|
595
|
+
- loading
|
|
596
|
+
- error
|
|
597
|
+
- success
|
|
598
|
+
|
|
599
|
+
## Layout Patterns
|
|
600
|
+
|
|
601
|
+
Derived from systemverification.com:
|
|
602
|
+
|
|
603
|
+
- Hero (large heading + short intro + CTA)
|
|
604
|
+
- Card grid (3–4 columns desktop)
|
|
605
|
+
- Logo wall (low contrast)
|
|
606
|
+
- Section blocks with clear vertical rhythm
|
|
607
|
+
|
|
608
|
+
## Key Constraint
|
|
609
|
+
|
|
610
|
+
This design system is **authoritative**.
|
|
611
|
+
|
|
612
|
+
AI must:
|
|
613
|
+
- extend it
|
|
614
|
+
- not override it
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@systemverification/styling-kit",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "System Verification design system CLI and MCP validation server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=18"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"sv-style": "bin/sv-style.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "node --test src/config.test.js src/bundle.test.js src/assets.test.js src/init.test.js src/update.test.js src/shutdown.test.js src/mcp.test.js",
|
|
14
|
+
"build:test-bundle": "node scripts/build-test-bundle.js"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@azure/identity": "^4.10.0",
|
|
18
|
+
"@azure/storage-blob": "^12.27.0",
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
20
|
+
"adm-zip": "^0.5.16"
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/assets.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { DefaultAzureCredential } from '@azure/identity';
|
|
2
|
+
import { BlobClient } from '@azure/storage-blob';
|
|
3
|
+
|
|
4
|
+
const BUNDLE_BLOB_PATH = 'sv-style/assets/latest/asset-bundle.zip';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Download the private asset bundle zip from Azure Blob storage.
|
|
8
|
+
*
|
|
9
|
+
* @param {{ accountName: string, containerName: string }} blobConfig
|
|
10
|
+
* @returns {Promise<Buffer>}
|
|
11
|
+
* @throws with a `blobErrorType` property identifying the failure category
|
|
12
|
+
*/
|
|
13
|
+
export async function downloadBundle(blobConfig) {
|
|
14
|
+
const { accountName, containerName } = blobConfig;
|
|
15
|
+
const url = `https://${accountName}.blob.core.windows.net/${containerName}/${BUNDLE_BLOB_PATH}`;
|
|
16
|
+
|
|
17
|
+
const credential = new DefaultAzureCredential();
|
|
18
|
+
const client = new BlobClient(url, credential);
|
|
19
|
+
|
|
20
|
+
let response;
|
|
21
|
+
try {
|
|
22
|
+
response = await client.download();
|
|
23
|
+
} catch (err) {
|
|
24
|
+
const classified = new Error(userMessage(err));
|
|
25
|
+
classified.blobErrorType = classifyBlobError(err);
|
|
26
|
+
classified.cause = err;
|
|
27
|
+
throw classified;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const chunks = [];
|
|
31
|
+
for await (const chunk of response.readableStreamBody) {
|
|
32
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
33
|
+
}
|
|
34
|
+
return Buffer.concat(chunks);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Classify an Azure Blob error into a coarse category.
|
|
39
|
+
*
|
|
40
|
+
* @param {Error & { statusCode?: number, code?: string }} err
|
|
41
|
+
* @returns {'not-logged-in' | 'unauthorized' | 'not-found' | 'network-error' | 'transient'}
|
|
42
|
+
*/
|
|
43
|
+
export function classifyBlobError(err) {
|
|
44
|
+
const status = err.statusCode ?? err?.response?.status;
|
|
45
|
+
if (status === 401) return 'not-logged-in';
|
|
46
|
+
if (status === 403) return 'unauthorized';
|
|
47
|
+
if (status === 404) return 'not-found';
|
|
48
|
+
if (err.code === 'ENOTFOUND' || err.code === 'ECONNREFUSED' || err.code === 'EAI_AGAIN') {
|
|
49
|
+
return 'network-error';
|
|
50
|
+
}
|
|
51
|
+
return 'transient';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function userMessage(err) {
|
|
55
|
+
switch (classifyBlobError(err)) {
|
|
56
|
+
case 'not-logged-in':
|
|
57
|
+
return 'Not authenticated. Run `az login` (or sign in via VS Code / PowerShell) and try again.';
|
|
58
|
+
case 'unauthorized':
|
|
59
|
+
return 'Access denied. Your account does not have read access to the asset bundle. Contact your administrator.';
|
|
60
|
+
case 'not-found':
|
|
61
|
+
return 'Asset bundle not found in Azure Blob. Check blob.accountName and blob.containerName in sv-style.json.';
|
|
62
|
+
case 'network-error':
|
|
63
|
+
return 'Network error reaching Azure Blob. Check your internet connection and try again.';
|
|
64
|
+
default:
|
|
65
|
+
return `Azure Blob error: ${err.message || err}`;
|
|
66
|
+
}
|
|
67
|
+
}
|