designlang 7.1.0 → 8.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/CHANGELOG.md +49 -0
- package/README.md +46 -4
- package/bin/design-extract.js +28 -2
- package/package.json +1 -1
- package/src/config.js +4 -1
- package/src/crawler.js +376 -6
- package/src/extractors/accessibility.js +44 -1
- package/src/extractors/colors.js +50 -12
- package/src/extractors/interaction-states.js +57 -0
- package/src/extractors/modern-css.js +100 -0
- package/src/extractors/scoring.js +49 -30
- package/src/extractors/token-sources.js +65 -0
- package/src/extractors/wide-gamut.js +47 -0
- package/src/formatters/routes-reconciliation.js +160 -0
- package/src/index.js +29 -0
- package/src/utils/color-gamut.js +82 -0
- package/.github/FUNDING.yml +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -62
- package/.github/ISSUE_TEMPLATE/config.yml +0 -8
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -28
- package/chrome-extension/README.md +0 -41
- package/chrome-extension/icons/favicon.svg +0 -7
- package/chrome-extension/icons/icon-128.png +0 -0
- package/chrome-extension/icons/icon-16.png +0 -0
- package/chrome-extension/icons/icon-32.png +0 -0
- package/chrome-extension/icons/icon-48.png +0 -0
- package/chrome-extension/manifest.json +0 -26
- package/chrome-extension/popup.html +0 -167
- package/chrome-extension/popup.js +0 -59
- package/docs/superpowers/plans/2026-04-18-designlang-v7.md +0 -1121
- package/docs/superpowers/specs/2026-04-18-designlang-v7-design.md +0 -150
- package/docs/superpowers/specs/2026-04-18-website-redesign-design.md +0 -120
- package/docs/superpowers/specs/2026-04-19-designlang-v7-1-design.md +0 -111
- package/tests/cli.test.js +0 -84
- package/tests/cookies.test.js +0 -98
- package/tests/extractors.test.js +0 -792
- package/tests/formatters.test.js +0 -709
- package/tests/mcp.test.js +0 -68
- package/tests/utils.test.js +0 -413
- package/website/.claude/launch.json +0 -11
- package/website/AGENTS.md +0 -5
- package/website/CLAUDE.md +0 -1
- package/website/README.md +0 -36
- package/website/app/api/extract/route.js +0 -245
- package/website/app/components/A11ySlider.js +0 -369
- package/website/app/components/Comparison.js +0 -286
- package/website/app/components/CssHealth.js +0 -243
- package/website/app/components/Extractor.js +0 -184
- package/website/app/components/HeroExtractor.js +0 -455
- package/website/app/components/Marginalia.js +0 -3
- package/website/app/components/McpSection.js +0 -223
- package/website/app/components/PlatformTabs.js +0 -250
- package/website/app/components/RegionsComponents.js +0 -429
- package/website/app/components/Rule.js +0 -13
- package/website/app/components/Specimens.js +0 -237
- package/website/app/components/StructuredData.js +0 -144
- package/website/app/components/TokenBrowser.js +0 -344
- package/website/app/components/token-browser-sample.js +0 -65
- package/website/app/globals.css +0 -505
- package/website/app/icon.svg +0 -7
- package/website/app/layout.js +0 -126
- package/website/app/opengraph-image.js +0 -170
- package/website/app/page.js +0 -352
- package/website/app/robots.js +0 -15
- package/website/app/seo-config.js +0 -82
- package/website/app/sitemap.js +0 -18
- package/website/jsconfig.json +0 -7
- package/website/lib/cache.js +0 -73
- package/website/lib/rate-limit.js +0 -30
- package/website/lib/rate-limit.test.js +0 -55
- package/website/lib/specimens.json +0 -86
- package/website/lib/token-helpers.js +0 -70
- package/website/lib/url-safety.js +0 -103
- package/website/lib/url-safety.test.js +0 -116
- package/website/lib/zip-files.js +0 -15
- package/website/next.config.mjs +0 -15
- package/website/package-lock.json +0 -1353
- package/website/package.json +0 -19
- package/website/public/favicon.svg +0 -7
- package/website/public/logo-specimen.svg +0 -76
- package/website/public/mark.svg +0 -12
- package/website/public/site.webmanifest +0 -13
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
import Marginalia from './Marginalia';
|
|
2
|
-
|
|
3
|
-
const TOOLS = [
|
|
4
|
-
'designlang',
|
|
5
|
-
'v0',
|
|
6
|
-
'Builder.io Visual Copilot',
|
|
7
|
-
'Style Dictionary',
|
|
8
|
-
'Subframe',
|
|
9
|
-
'Project Wallace',
|
|
10
|
-
];
|
|
11
|
-
|
|
12
|
-
// Y = yes, N = no, P = partial
|
|
13
|
-
const ROWS = [
|
|
14
|
-
{
|
|
15
|
-
feature: 'Extracts from a live URL',
|
|
16
|
-
cells: ['Y', 'N', 'Y', 'N', 'N', 'Y'],
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
feature: 'Emits W3C DTCG tokens',
|
|
20
|
-
cells: ['Y', 'N', 'N', 'Y', 'N', 'N'],
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
feature: 'Semantic alias layer',
|
|
24
|
-
cells: ['Y', 'N', 'N', 'Y', 'P', 'N'],
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
feature: 'Multi-platform output (iOS, Android, Flutter)',
|
|
28
|
-
cells: ['Y', 'N', 'N', 'Y', 'N', 'N'],
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
feature: 'MCP server over stdio',
|
|
32
|
-
cells: ['Y', 'N', 'N', 'N', 'N', 'N'],
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
feature: 'CSS health audit',
|
|
36
|
-
cells: ['Y', 'N', 'N', 'N', 'N', 'Y'],
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
feature: 'A11y remediation suggestions',
|
|
40
|
-
cells: ['P', 'N', 'P', 'N', 'N', 'P'],
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
feature: 'Component cluster detection',
|
|
44
|
-
cells: ['P', 'Y', 'Y', 'N', 'Y', 'N'],
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
feature: 'Offline / local-only',
|
|
48
|
-
cells: ['Y', 'N', 'N', 'Y', 'N', 'N'],
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
feature: 'Open source / MIT',
|
|
52
|
-
cells: ['Y', 'N', 'N', 'Y', 'N', 'P'],
|
|
53
|
-
},
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
function Mark({ kind }) {
|
|
57
|
-
if (kind === 'Y') {
|
|
58
|
-
return (
|
|
59
|
-
<svg width="14" height="14" viewBox="0 0 14 14" aria-label="yes" role="img">
|
|
60
|
-
<rect x="2" y="2" width="10" height="10" fill="var(--ink)" transform="rotate(45 7 7)" />
|
|
61
|
-
</svg>
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
if (kind === 'P') {
|
|
65
|
-
return (
|
|
66
|
-
<svg width="16" height="16" viewBox="0 0 16 16" aria-label="partial" role="img">
|
|
67
|
-
<rect
|
|
68
|
-
x="3"
|
|
69
|
-
y="3"
|
|
70
|
-
width="10"
|
|
71
|
-
height="10"
|
|
72
|
-
fill="var(--accent)"
|
|
73
|
-
stroke="var(--ink)"
|
|
74
|
-
strokeWidth="1"
|
|
75
|
-
transform="rotate(45 8 8)"
|
|
76
|
-
/>
|
|
77
|
-
</svg>
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
return (
|
|
81
|
-
<span className="mono" style={{ color: 'var(--ink-3)', fontSize: 14 }} aria-label="no">
|
|
82
|
-
—
|
|
83
|
-
</span>
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export default function Comparison() {
|
|
88
|
-
return (
|
|
89
|
-
<div>
|
|
90
|
-
<div className="with-margin" style={{ marginTop: 'var(--r5)', marginBottom: 'var(--r7)' }}>
|
|
91
|
-
<div>
|
|
92
|
-
<div className="section-label" style={{ marginBottom: 'var(--r5)' }}>
|
|
93
|
-
<span>§08 — Compared</span>
|
|
94
|
-
</div>
|
|
95
|
-
<h2 className="display" style={{ marginBottom: 'var(--r4)' }}>
|
|
96
|
-
Where designlang doesn't win,<br />
|
|
97
|
-
<em style={{ fontStyle: 'italic', color: 'var(--accent)' }}>it says so.</em>
|
|
98
|
-
</h2>
|
|
99
|
-
<p className="prose" style={{ fontSize: 18, lineHeight: 1.5, maxWidth: '62ch' }}>
|
|
100
|
-
We picked five tools doing closely adjacent work. The matrix below is our honest
|
|
101
|
-
assessment on 2026-04-18, written by someone who actually uses all six.
|
|
102
|
-
</p>
|
|
103
|
-
</div>
|
|
104
|
-
<Marginalia>
|
|
105
|
-
<div>legend</div>
|
|
106
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 8 }}>
|
|
107
|
-
<Mark kind="Y" /> <span>supported</span>
|
|
108
|
-
</div>
|
|
109
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 6 }}>
|
|
110
|
-
<Mark kind="P" /> <span>partial</span>
|
|
111
|
-
</div>
|
|
112
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 6 }}>
|
|
113
|
-
<Mark kind="N" /> <span>not supported</span>
|
|
114
|
-
</div>
|
|
115
|
-
<hr style={{ margin: '12px 0', border: 0, borderTop: '1px solid var(--ink-3)' }} />
|
|
116
|
-
<p className="foot">
|
|
117
|
-
Open a PR if this matrix is wrong for your tool. We'll update it or explain
|
|
118
|
-
our read.
|
|
119
|
-
</p>
|
|
120
|
-
</Marginalia>
|
|
121
|
-
</div>
|
|
122
|
-
|
|
123
|
-
<div style={{ overflowX: 'auto', marginBottom: 'var(--r7)' }}>
|
|
124
|
-
<table
|
|
125
|
-
style={{
|
|
126
|
-
width: '100%',
|
|
127
|
-
minWidth: 900,
|
|
128
|
-
borderCollapse: 'collapse',
|
|
129
|
-
tableLayout: 'fixed',
|
|
130
|
-
}}
|
|
131
|
-
>
|
|
132
|
-
<thead>
|
|
133
|
-
<tr>
|
|
134
|
-
<th
|
|
135
|
-
scope="col"
|
|
136
|
-
className="mono"
|
|
137
|
-
style={{
|
|
138
|
-
textAlign: 'left',
|
|
139
|
-
fontSize: 12,
|
|
140
|
-
textTransform: 'uppercase',
|
|
141
|
-
letterSpacing: '0.12em',
|
|
142
|
-
color: 'var(--ink-2)',
|
|
143
|
-
padding: '12px 20px 12px 0',
|
|
144
|
-
borderBottom: '1px solid var(--ink)',
|
|
145
|
-
fontWeight: 400,
|
|
146
|
-
width: '28%',
|
|
147
|
-
}}
|
|
148
|
-
>
|
|
149
|
-
Feature
|
|
150
|
-
</th>
|
|
151
|
-
{TOOLS.map((t, i) => (
|
|
152
|
-
<th
|
|
153
|
-
key={t}
|
|
154
|
-
scope="col"
|
|
155
|
-
className="mono"
|
|
156
|
-
style={{
|
|
157
|
-
textAlign: 'center',
|
|
158
|
-
fontSize: 12,
|
|
159
|
-
textTransform: 'uppercase',
|
|
160
|
-
letterSpacing: '0.08em',
|
|
161
|
-
color: i === 0 ? 'var(--ink)' : 'var(--ink-2)',
|
|
162
|
-
padding: '12px 8px',
|
|
163
|
-
borderBottom: '1px solid var(--ink)',
|
|
164
|
-
fontWeight: 400,
|
|
165
|
-
}}
|
|
166
|
-
>
|
|
167
|
-
{t}
|
|
168
|
-
</th>
|
|
169
|
-
))}
|
|
170
|
-
</tr>
|
|
171
|
-
</thead>
|
|
172
|
-
<tbody>
|
|
173
|
-
{ROWS.map((row, rIdx) => (
|
|
174
|
-
<tr key={row.feature}>
|
|
175
|
-
<th
|
|
176
|
-
scope="row"
|
|
177
|
-
style={{
|
|
178
|
-
textAlign: 'left',
|
|
179
|
-
padding: '14px 20px 14px 0',
|
|
180
|
-
fontWeight: 400,
|
|
181
|
-
fontSize: 15,
|
|
182
|
-
color: 'var(--ink)',
|
|
183
|
-
borderBottom: rIdx === ROWS.length - 1 ? 0 : '1px solid var(--paper-3)',
|
|
184
|
-
}}
|
|
185
|
-
>
|
|
186
|
-
{row.feature}
|
|
187
|
-
</th>
|
|
188
|
-
{row.cells.map((c, i) => (
|
|
189
|
-
<td
|
|
190
|
-
key={i}
|
|
191
|
-
style={{
|
|
192
|
-
textAlign: 'center',
|
|
193
|
-
padding: '14px 8px',
|
|
194
|
-
verticalAlign: 'middle',
|
|
195
|
-
borderBottom: rIdx === ROWS.length - 1 ? 0 : '1px solid var(--paper-3)',
|
|
196
|
-
}}
|
|
197
|
-
>
|
|
198
|
-
<span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
199
|
-
<Mark kind={c} />
|
|
200
|
-
</span>
|
|
201
|
-
</td>
|
|
202
|
-
))}
|
|
203
|
-
</tr>
|
|
204
|
-
))}
|
|
205
|
-
</tbody>
|
|
206
|
-
</table>
|
|
207
|
-
</div>
|
|
208
|
-
|
|
209
|
-
<div className="with-margin">
|
|
210
|
-
<div>
|
|
211
|
-
<h3
|
|
212
|
-
className="mono"
|
|
213
|
-
style={{
|
|
214
|
-
fontSize: 12,
|
|
215
|
-
textTransform: 'uppercase',
|
|
216
|
-
letterSpacing: '0.14em',
|
|
217
|
-
color: 'var(--ink-2)',
|
|
218
|
-
marginBottom: 'var(--r4)',
|
|
219
|
-
}}
|
|
220
|
-
>
|
|
221
|
-
What designlang is not
|
|
222
|
-
</h3>
|
|
223
|
-
<ul
|
|
224
|
-
style={{
|
|
225
|
-
listStyle: 'none',
|
|
226
|
-
padding: 0,
|
|
227
|
-
display: 'grid',
|
|
228
|
-
gap: 'var(--r3)',
|
|
229
|
-
maxWidth: '72ch',
|
|
230
|
-
}}
|
|
231
|
-
>
|
|
232
|
-
<li
|
|
233
|
-
style={{
|
|
234
|
-
borderLeft: '1px solid var(--ink)',
|
|
235
|
-
paddingLeft: 'var(--r4)',
|
|
236
|
-
fontSize: 16,
|
|
237
|
-
lineHeight: 1.5,
|
|
238
|
-
}}
|
|
239
|
-
>
|
|
240
|
-
<em style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic' }}>
|
|
241
|
-
It is not a design-to-code generator.
|
|
242
|
-
</em>{' '}
|
|
243
|
-
It extracts the <strong>system</strong>, not the components as JSX.
|
|
244
|
-
</li>
|
|
245
|
-
<li
|
|
246
|
-
style={{
|
|
247
|
-
borderLeft: '1px solid var(--ink)',
|
|
248
|
-
paddingLeft: 'var(--r4)',
|
|
249
|
-
fontSize: 16,
|
|
250
|
-
lineHeight: 1.5,
|
|
251
|
-
}}
|
|
252
|
-
>
|
|
253
|
-
<em style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic' }}>
|
|
254
|
-
It is not a hosted cloud service.
|
|
255
|
-
</em>{' '}
|
|
256
|
-
The free website extractor is rate-limited; production use should run the CLI or
|
|
257
|
-
MCP server locally.
|
|
258
|
-
</li>
|
|
259
|
-
<li
|
|
260
|
-
style={{
|
|
261
|
-
borderLeft: '1px solid var(--ink)',
|
|
262
|
-
paddingLeft: 'var(--r4)',
|
|
263
|
-
fontSize: 16,
|
|
264
|
-
lineHeight: 1.5,
|
|
265
|
-
}}
|
|
266
|
-
>
|
|
267
|
-
<em style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic' }}>
|
|
268
|
-
It is not a Figma plugin.
|
|
269
|
-
</em>{' '}
|
|
270
|
-
designlang reads the <strong>rendered DOM</strong>, not the Figma file — the
|
|
271
|
-
output is what your users actually see, not what a designer intended.
|
|
272
|
-
</li>
|
|
273
|
-
</ul>
|
|
274
|
-
</div>
|
|
275
|
-
<Marginalia>
|
|
276
|
-
<div>honesty clause</div>
|
|
277
|
-
<p className="foot" style={{ marginTop: 6 }}>
|
|
278
|
-
Two partial cells for designlang on purpose — the a11y remediation engine
|
|
279
|
-
suggests fixes but won't rewrite your markup, and component cluster
|
|
280
|
-
detection is heuristic, not vision-based.
|
|
281
|
-
</p>
|
|
282
|
-
</Marginalia>
|
|
283
|
-
</div>
|
|
284
|
-
</div>
|
|
285
|
-
);
|
|
286
|
-
}
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
// §04 CSS health — mostly static. Static SVG plot, no ResizeObserver needed
|
|
2
|
-
// (responsive via 100% width + aspect-ratio). Numbers are the PR B smoke
|
|
3
|
-
// sample taken from stripe.com: 14 sheets, 89% unused, 764 !important rules,
|
|
4
|
-
// 9,041 duplicate declarations.
|
|
5
|
-
|
|
6
|
-
// Synthetic sample shaped from the PR B smoke distribution: a mostly low
|
|
7
|
-
// specificity body with a rising-ramp tail — the classic "!important wall"
|
|
8
|
-
// silhouette. X = rule order (0..100), Y = collapsed specificity (a*100+b*10+c).
|
|
9
|
-
const POINTS = [
|
|
10
|
-
[2, 11], [4, 10], [6, 20], [8, 12], [10, 21], [12, 11], [14, 22],
|
|
11
|
-
[16, 20], [18, 12], [20, 22], [22, 30], [24, 20], [26, 21], [28, 31],
|
|
12
|
-
[30, 22], [32, 30], [34, 31], [36, 22], [38, 32], [40, 30], [42, 40],
|
|
13
|
-
[44, 32], [46, 41], [48, 42], [50, 51], [52, 42], [54, 50], [56, 61],
|
|
14
|
-
[58, 52], [60, 71], [62, 81], [64, 92], [66, 112], [68, 121], [70, 141],
|
|
15
|
-
[72, 161], [74, 172], [76, 191], [78, 202], [80, 221], [82, 232],
|
|
16
|
-
[84, 252], [86, 272], [88, 291], [90, 311], [92, 331], [94, 352],
|
|
17
|
-
[96, 372], [98, 391], [100, 412],
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
const VENDOR_CHIPS = [
|
|
21
|
-
['-webkit-', '183'],
|
|
22
|
-
['-moz-', '41'],
|
|
23
|
-
['-ms-', '7'],
|
|
24
|
-
['-o-', '2'],
|
|
25
|
-
['duplicates', '9,041'],
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
const STATS = [
|
|
29
|
-
['89%', 'unused css'],
|
|
30
|
-
['764', '!important rules'],
|
|
31
|
-
['9,041', 'duplicate declarations'],
|
|
32
|
-
['14', 'stylesheets analyzed'],
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
import Rule from './Rule';
|
|
36
|
-
import Marginalia from './Marginalia';
|
|
37
|
-
|
|
38
|
-
function SpecificityPlot() {
|
|
39
|
-
// viewBox 100 x 100 (abstract units), axis gutters inside.
|
|
40
|
-
const maxY = 500;
|
|
41
|
-
const padL = 10;
|
|
42
|
-
const padB = 12;
|
|
43
|
-
const padT = 4;
|
|
44
|
-
const padR = 4;
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<svg
|
|
48
|
-
viewBox="0 0 200 125"
|
|
49
|
-
preserveAspectRatio="none"
|
|
50
|
-
role="img"
|
|
51
|
-
aria-label="Specificity scatter plot — rule order vs specificity score"
|
|
52
|
-
style={{
|
|
53
|
-
width: '100%',
|
|
54
|
-
aspectRatio: '16 / 10',
|
|
55
|
-
border: '1px solid var(--ink)',
|
|
56
|
-
background: 'var(--paper)',
|
|
57
|
-
display: 'block',
|
|
58
|
-
}}
|
|
59
|
-
>
|
|
60
|
-
{/* axis lines */}
|
|
61
|
-
<line x1={padL} y1={125 - padB} x2={200 - padR} y2={125 - padB} stroke="var(--ink)" strokeWidth="0.5" />
|
|
62
|
-
<line x1={padL} y1={padT} x2={padL} y2={125 - padB} stroke="var(--ink)" strokeWidth="0.5" />
|
|
63
|
-
|
|
64
|
-
{/* axis labels (mono ~10px visually — SVG units scaled) */}
|
|
65
|
-
<text x={padL} y={124} fontFamily="var(--font-mono)" fontSize="3.2" fill="var(--ink-2)" letterSpacing="0.1">
|
|
66
|
-
rule order →
|
|
67
|
-
</text>
|
|
68
|
-
<text
|
|
69
|
-
x={padL + 1}
|
|
70
|
-
y={padT + 3}
|
|
71
|
-
fontFamily="var(--font-mono)"
|
|
72
|
-
fontSize="3.2"
|
|
73
|
-
fill="var(--ink-2)"
|
|
74
|
-
letterSpacing="0.1"
|
|
75
|
-
>
|
|
76
|
-
↑ specificity
|
|
77
|
-
</text>
|
|
78
|
-
<text x={200 - padR - 14} y={124} fontFamily="var(--font-mono)" fontSize="3" fill="var(--ink-3)">
|
|
79
|
-
n=50
|
|
80
|
-
</text>
|
|
81
|
-
|
|
82
|
-
{/* points */}
|
|
83
|
-
{POINTS.map(([x, y], i) => {
|
|
84
|
-
const px = padL + (x / 100) * (200 - padL - padR);
|
|
85
|
-
const py = 125 - padB - (Math.min(y, maxY) / maxY) * (125 - padB - padT);
|
|
86
|
-
const outlier = y > 200;
|
|
87
|
-
return (
|
|
88
|
-
<rect
|
|
89
|
-
key={i}
|
|
90
|
-
x={px - 1}
|
|
91
|
-
y={py - 1}
|
|
92
|
-
width="2"
|
|
93
|
-
height="2"
|
|
94
|
-
fill={outlier ? 'var(--accent)' : 'var(--ink)'}
|
|
95
|
-
/>
|
|
96
|
-
);
|
|
97
|
-
})}
|
|
98
|
-
</svg>
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export default function CssHealth() {
|
|
103
|
-
return (
|
|
104
|
-
<>
|
|
105
|
-
<Rule number="04" label="CSS health audit" />
|
|
106
|
-
<div className="with-margin" style={{ marginTop: 'var(--r5)' }}>
|
|
107
|
-
<div>
|
|
108
|
-
<div className="eyebrow" style={{ marginBottom: 'var(--r3)' }}>§04 CSS health</div>
|
|
109
|
-
<h2 className="display" style={{ marginBottom: 'var(--r4)' }}>
|
|
110
|
-
The stylesheet is the problem.
|
|
111
|
-
</h2>
|
|
112
|
-
<p className="prose" style={{ fontSize: 18, maxWidth: '62ch', color: 'var(--ink-2)' }}>
|
|
113
|
-
Most sites ship 40–90% unused CSS, long walls of <code>!important</code> escalations,
|
|
114
|
-
and a specificity graph that rises forever. v7.0 surfaces all of it — not as a
|
|
115
|
-
vanity score, but as exact declaration-level evidence.
|
|
116
|
-
</p>
|
|
117
|
-
|
|
118
|
-
<div
|
|
119
|
-
className="grid-12"
|
|
120
|
-
style={{
|
|
121
|
-
marginTop: 'var(--r7)',
|
|
122
|
-
gap: 'var(--col-gap)',
|
|
123
|
-
alignItems: 'start',
|
|
124
|
-
}}
|
|
125
|
-
>
|
|
126
|
-
{/* Big numerals — span 5 */}
|
|
127
|
-
<div
|
|
128
|
-
style={{
|
|
129
|
-
gridColumn: 'span 5',
|
|
130
|
-
display: 'grid',
|
|
131
|
-
gridTemplateColumns: '1fr 1fr',
|
|
132
|
-
gap: 'var(--r5) var(--r4)',
|
|
133
|
-
}}
|
|
134
|
-
>
|
|
135
|
-
{STATS.map(([n, label]) => (
|
|
136
|
-
<div key={label} style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
137
|
-
<div
|
|
138
|
-
className="display"
|
|
139
|
-
style={{
|
|
140
|
-
fontSize: 'clamp(64px, 7vw, 120px)',
|
|
141
|
-
lineHeight: 0.95,
|
|
142
|
-
fontVariantNumeric: 'tabular-nums',
|
|
143
|
-
fontFeatureSettings: "'tnum' 1",
|
|
144
|
-
letterSpacing: '-0.04em',
|
|
145
|
-
}}
|
|
146
|
-
>
|
|
147
|
-
{n}
|
|
148
|
-
</div>
|
|
149
|
-
<div
|
|
150
|
-
className="mono"
|
|
151
|
-
style={{
|
|
152
|
-
fontSize: 11,
|
|
153
|
-
letterSpacing: '0.14em',
|
|
154
|
-
textTransform: 'uppercase',
|
|
155
|
-
color: 'var(--ink-2)',
|
|
156
|
-
}}
|
|
157
|
-
>
|
|
158
|
-
{label}
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
|
-
))}
|
|
162
|
-
</div>
|
|
163
|
-
|
|
164
|
-
{/* Plot — span 7 */}
|
|
165
|
-
<div style={{ gridColumn: 'span 7' }}>
|
|
166
|
-
<div
|
|
167
|
-
className="mono"
|
|
168
|
-
style={{
|
|
169
|
-
fontSize: 11,
|
|
170
|
-
letterSpacing: '0.14em',
|
|
171
|
-
textTransform: 'uppercase',
|
|
172
|
-
color: 'var(--ink-2)',
|
|
173
|
-
marginBottom: 'var(--r3)',
|
|
174
|
-
display: 'flex',
|
|
175
|
-
justifyContent: 'space-between',
|
|
176
|
-
}}
|
|
177
|
-
>
|
|
178
|
-
<span>specificity distribution</span>
|
|
179
|
-
<span style={{ color: 'var(--accent)' }}>outliers: specificity > 200</span>
|
|
180
|
-
</div>
|
|
181
|
-
<SpecificityPlot />
|
|
182
|
-
<div
|
|
183
|
-
className="mono"
|
|
184
|
-
style={{
|
|
185
|
-
fontSize: 11,
|
|
186
|
-
color: 'var(--ink-3)',
|
|
187
|
-
marginTop: 'var(--r3)',
|
|
188
|
-
}}
|
|
189
|
-
>
|
|
190
|
-
collapsed score: a×100 + b×10 + c. rising tail = accumulated !important wall.
|
|
191
|
-
</div>
|
|
192
|
-
</div>
|
|
193
|
-
</div>
|
|
194
|
-
|
|
195
|
-
{/* Caption strip — vendor prefix chips */}
|
|
196
|
-
<div
|
|
197
|
-
style={{
|
|
198
|
-
marginTop: 'var(--r7)',
|
|
199
|
-
display: 'flex',
|
|
200
|
-
flexWrap: 'wrap',
|
|
201
|
-
gap: 'var(--r3)',
|
|
202
|
-
borderTop: 'var(--hair)',
|
|
203
|
-
paddingTop: 'var(--r4)',
|
|
204
|
-
}}
|
|
205
|
-
>
|
|
206
|
-
{VENDOR_CHIPS.map(([k, v]) => (
|
|
207
|
-
<span
|
|
208
|
-
key={k}
|
|
209
|
-
className="mono"
|
|
210
|
-
style={{
|
|
211
|
-
display: 'inline-flex',
|
|
212
|
-
alignItems: 'baseline',
|
|
213
|
-
gap: 8,
|
|
214
|
-
padding: '3px 8px',
|
|
215
|
-
border: '1px solid var(--ink)',
|
|
216
|
-
fontSize: 11,
|
|
217
|
-
letterSpacing: '0.04em',
|
|
218
|
-
}}
|
|
219
|
-
>
|
|
220
|
-
<span style={{ color: 'var(--ink-2)' }}>{k}</span>
|
|
221
|
-
<span style={{ fontVariantNumeric: 'tabular-nums' }}>{v}</span>
|
|
222
|
-
</span>
|
|
223
|
-
))}
|
|
224
|
-
</div>
|
|
225
|
-
</div>
|
|
226
|
-
|
|
227
|
-
<Marginalia>
|
|
228
|
-
<div>additive scoring</div>
|
|
229
|
-
<p style={{ marginTop: 6 }}>
|
|
230
|
-
Tracked additively: existing score fields kept for back-compat; CSS health joins as a new
|
|
231
|
-
dimension.
|
|
232
|
-
</p>
|
|
233
|
-
<hr style={{ margin: '12px 0', border: 0, borderTop: '1px solid var(--ink-3)' }} />
|
|
234
|
-
<div>works on any site</div>
|
|
235
|
-
<p className="foot" style={{ marginTop: 6 }}>
|
|
236
|
-
Flags rising specificity, zombie declarations, and abandoned <code>!important</code> walls —
|
|
237
|
-
with exact selector provenance.
|
|
238
|
-
</p>
|
|
239
|
-
</Marginalia>
|
|
240
|
-
</div>
|
|
241
|
-
</>
|
|
242
|
-
);
|
|
243
|
-
}
|