designlang 7.2.0 → 9.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 +69 -0
- package/README.md +154 -13
- package/bin/design-extract.js +94 -1
- package/package.json +9 -3
- package/src/config.js +2 -0
- package/src/crawler.js +55 -6
- package/src/drift.js +137 -0
- package/src/extractors/accessibility.js +44 -1
- package/src/extractors/colors.js +50 -12
- package/src/extractors/component-anatomy.js +123 -0
- package/src/extractors/motion.js +184 -0
- package/src/extractors/scoring.js +49 -30
- package/src/extractors/voice.js +96 -0
- package/src/formatters/markdown.js +88 -0
- package/src/formatters/motion-tokens.js +22 -0
- package/src/index.js +14 -0
- package/src/lint.js +198 -0
- package/src/visual-diff.js +116 -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/.github/og-preview.png +0 -0
- package/.github/workflows/manavarya-bot.yml +0 -17
- 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/interaction-states.test.js +0 -62
- package/tests/mcp.test.js +0 -68
- package/tests/modern-css.test.js +0 -104
- package/tests/routes-reconciliation.test.js +0 -120
- package/tests/utils.test.js +0 -413
- package/tests/wide-gamut.test.js +0 -90
- 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 -399
- 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,223 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useEffect, useRef, useState } from 'react';
|
|
4
|
-
import Marginalia from './Marginalia';
|
|
5
|
-
|
|
6
|
-
const CURSOR_RULE = `---
|
|
7
|
-
description: Design system extracted from https://stripe.com — use these tokens, do not invent new ones.
|
|
8
|
-
globs: **/*.{ts,tsx,jsx,js,css,scss,html,vue,svelte,swift,kt,dart,php}
|
|
9
|
-
alwaysApply: true
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# Design system reference
|
|
13
|
-
|
|
14
|
-
Source: https://stripe.com
|
|
15
|
-
Extracted by designlang v7.0.0 on 2026-04-18T12:00:00Z
|
|
16
|
-
|
|
17
|
-
## Semantic tokens (use these)
|
|
18
|
-
- color.action.primary: #533afd
|
|
19
|
-
- color.surface.default: #ffffff
|
|
20
|
-
- color.text.body: #0a2540
|
|
21
|
-
- radius.control: 8px
|
|
22
|
-
- typography.body.fontFamily: sohne-var, Helvetica Neue, Arial, sans-serif
|
|
23
|
-
|
|
24
|
-
## How to use
|
|
25
|
-
- Prefer \`semantic.*\` tokens over \`primitive.*\`.
|
|
26
|
-
- Never invent new tokens or hex values; reuse the ones above.
|
|
27
|
-
- When a value is missing, pick the closest existing semantic token and flag the gap.
|
|
28
|
-
- Reference tokens by their dotted path (e.g. \`semantic.color.action.primary\`).
|
|
29
|
-
`;
|
|
30
|
-
|
|
31
|
-
// The transcript. Each step is either typed (user input) or printed (server output).
|
|
32
|
-
const TRANSCRIPT = [
|
|
33
|
-
{ kind: 'typed', text: '$ designlang mcp --output-dir ./design-extract-output' },
|
|
34
|
-
{ kind: 'print', text: '{"jsonrpc":"2.0","id":1,"result":{"serverInfo":{"name":"designlang","version":"7.0.0"}}}' },
|
|
35
|
-
{ kind: 'typed', text: '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search_tokens","arguments":{"query":"action.primary"}}}' },
|
|
36
|
-
{ kind: 'print', text: '{"jsonrpc":"2.0","id":2,"result":{"matches":[{"path":"semantic.color.action.primary","$type":"color","$value":"#533afd"}]}}' },
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
function Terminal({ reduced }) {
|
|
40
|
-
const [lines, setLines] = useState(() =>
|
|
41
|
-
reduced ? TRANSCRIPT.map((t) => ({ ...t, rendered: t.text })) : []
|
|
42
|
-
);
|
|
43
|
-
const containerRef = useRef(null);
|
|
44
|
-
const [inView, setInView] = useState(false);
|
|
45
|
-
|
|
46
|
-
// IntersectionObserver to pause when offscreen.
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
if (reduced) return;
|
|
49
|
-
if (!containerRef.current) return;
|
|
50
|
-
const el = containerRef.current;
|
|
51
|
-
const io = new IntersectionObserver(
|
|
52
|
-
([entry]) => setInView(entry.isIntersecting),
|
|
53
|
-
{ threshold: 0.25 }
|
|
54
|
-
);
|
|
55
|
-
io.observe(el);
|
|
56
|
-
return () => io.disconnect();
|
|
57
|
-
}, [reduced]);
|
|
58
|
-
|
|
59
|
-
// Typewriter runner.
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (reduced) return;
|
|
62
|
-
if (!inView) return;
|
|
63
|
-
const controller = new AbortController();
|
|
64
|
-
const signal = controller.signal;
|
|
65
|
-
|
|
66
|
-
async function run() {
|
|
67
|
-
while (!signal.aborted) {
|
|
68
|
-
setLines([]);
|
|
69
|
-
for (let i = 0; i < TRANSCRIPT.length; i++) {
|
|
70
|
-
if (signal.aborted) return;
|
|
71
|
-
const step = TRANSCRIPT[i];
|
|
72
|
-
if (step.kind === 'typed') {
|
|
73
|
-
// ~55 cps ≈ 18ms per char
|
|
74
|
-
for (let j = 0; j <= step.text.length; j++) {
|
|
75
|
-
if (signal.aborted) return;
|
|
76
|
-
await wait(18, signal);
|
|
77
|
-
setLines((prev) => {
|
|
78
|
-
const next = [...prev];
|
|
79
|
-
next[i] = { ...step, rendered: step.text.slice(0, j) };
|
|
80
|
-
return next;
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
await wait(280, signal);
|
|
84
|
-
} else {
|
|
85
|
-
await wait(220, signal);
|
|
86
|
-
if (signal.aborted) return;
|
|
87
|
-
setLines((prev) => {
|
|
88
|
-
const next = [...prev];
|
|
89
|
-
next[i] = { ...step, rendered: step.text };
|
|
90
|
-
return next;
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
await wait(2000, signal);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
run().catch(() => {});
|
|
99
|
-
return () => controller.abort();
|
|
100
|
-
}, [inView, reduced]);
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
<div
|
|
104
|
-
ref={containerRef}
|
|
105
|
-
aria-label="designlang MCP terminal transcript"
|
|
106
|
-
style={{
|
|
107
|
-
background: 'var(--ink)',
|
|
108
|
-
color: 'var(--paper)',
|
|
109
|
-
border: 'var(--hair)',
|
|
110
|
-
fontFamily: 'var(--font-mono)',
|
|
111
|
-
fontSize: 12,
|
|
112
|
-
lineHeight: 1.55,
|
|
113
|
-
padding: 'var(--r4) var(--r5)',
|
|
114
|
-
minHeight: 320,
|
|
115
|
-
overflow: 'auto',
|
|
116
|
-
}}
|
|
117
|
-
>
|
|
118
|
-
<div style={{ color: 'var(--ink-3)', marginBottom: 8 }}>
|
|
119
|
-
designlang-mcp · stdio
|
|
120
|
-
</div>
|
|
121
|
-
{TRANSCRIPT.map((step, i) => {
|
|
122
|
-
const line = lines[i];
|
|
123
|
-
const rendered = line?.rendered ?? '';
|
|
124
|
-
const done = rendered.length === step.text.length;
|
|
125
|
-
return (
|
|
126
|
-
<pre
|
|
127
|
-
key={i}
|
|
128
|
-
style={{
|
|
129
|
-
margin: 0,
|
|
130
|
-
whiteSpace: 'pre-wrap',
|
|
131
|
-
wordBreak: 'break-all',
|
|
132
|
-
color: step.kind === 'typed' ? 'var(--paper)' : 'var(--ink-3)',
|
|
133
|
-
}}
|
|
134
|
-
>
|
|
135
|
-
{step.kind === 'print' && rendered ? <span style={{ color: 'var(--accent)' }}>» </span> : null}
|
|
136
|
-
{rendered}
|
|
137
|
-
{!done && !reduced ? <span style={{ opacity: 0.7 }}>▍</span> : null}
|
|
138
|
-
</pre>
|
|
139
|
-
);
|
|
140
|
-
})}
|
|
141
|
-
</div>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function wait(ms, signal) {
|
|
146
|
-
return new Promise((resolve, reject) => {
|
|
147
|
-
const t = setTimeout(resolve, ms);
|
|
148
|
-
signal.addEventListener('abort', () => {
|
|
149
|
-
clearTimeout(t);
|
|
150
|
-
reject(new Error('aborted'));
|
|
151
|
-
}, { once: true });
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export default function McpSection() {
|
|
156
|
-
const [reduced, setReduced] = useState(false);
|
|
157
|
-
useEffect(() => {
|
|
158
|
-
const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
159
|
-
const update = () => setReduced(mq.matches);
|
|
160
|
-
update();
|
|
161
|
-
mq.addEventListener?.('change', update);
|
|
162
|
-
return () => mq.removeEventListener?.('change', update);
|
|
163
|
-
}, []);
|
|
164
|
-
|
|
165
|
-
return (
|
|
166
|
-
<div>
|
|
167
|
-
<div style={{ padding: 'var(--r5) 0 var(--r6)' }}>
|
|
168
|
-
<h2 className="display">Your editor reads this.</h2>
|
|
169
|
-
<p className="prose" style={{ marginTop: 'var(--r4)', fontSize: 18, maxWidth: '52ch' }}>
|
|
170
|
-
The MCP server exposes five resources and five tools over stdio, speaking
|
|
171
|
-
JSON-RPC to Claude Code, Cursor, and Windsurf. See <a href="#install">§09</a> for install.
|
|
172
|
-
</p>
|
|
173
|
-
</div>
|
|
174
|
-
|
|
175
|
-
<div
|
|
176
|
-
style={{
|
|
177
|
-
display: 'grid',
|
|
178
|
-
gridTemplateColumns: 'minmax(0, 7fr) minmax(0, 5fr)',
|
|
179
|
-
columnGap: 0,
|
|
180
|
-
alignItems: 'stretch',
|
|
181
|
-
}}
|
|
182
|
-
>
|
|
183
|
-
{/* Left: rule file */}
|
|
184
|
-
<div
|
|
185
|
-
style={{
|
|
186
|
-
background: 'var(--paper-2)',
|
|
187
|
-
border: 'var(--hair)',
|
|
188
|
-
borderRight: 0,
|
|
189
|
-
padding: 'var(--r4) var(--r5)',
|
|
190
|
-
fontFamily: 'var(--font-mono)',
|
|
191
|
-
fontSize: 12,
|
|
192
|
-
lineHeight: 1.55,
|
|
193
|
-
overflow: 'auto',
|
|
194
|
-
}}
|
|
195
|
-
>
|
|
196
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 'var(--r3)' }}>
|
|
197
|
-
<span style={{ color: 'var(--ink-3)' }}>.cursor/rules/designlang.mdc</span>
|
|
198
|
-
<span className="chip" style={{ color: 'var(--accent)', borderColor: 'var(--accent)' }}>
|
|
199
|
-
alwaysApply: true
|
|
200
|
-
</span>
|
|
201
|
-
</div>
|
|
202
|
-
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word', color: 'var(--ink)' }}>
|
|
203
|
-
{CURSOR_RULE}
|
|
204
|
-
</pre>
|
|
205
|
-
</div>
|
|
206
|
-
|
|
207
|
-
{/* Right: terminal */}
|
|
208
|
-
<Terminal reduced={reduced} />
|
|
209
|
-
</div>
|
|
210
|
-
|
|
211
|
-
<div style={{ marginTop: 'var(--r6)' }}>
|
|
212
|
-
<Marginalia>
|
|
213
|
-
<div>install</div>
|
|
214
|
-
<div>
|
|
215
|
-
<code>{'{ "mcpServers": { "designlang": { "command": "npx", "args": ["designlang","mcp"] } } }'}</code>
|
|
216
|
-
</div>
|
|
217
|
-
<hr style={{ margin: '12px 0', border: 0, borderTop: '1px solid var(--ink-3)' }} />
|
|
218
|
-
<div>Zero-config for any MCP-aware agent.</div>
|
|
219
|
-
</Marginalia>
|
|
220
|
-
</div>
|
|
221
|
-
</div>
|
|
222
|
-
);
|
|
223
|
-
}
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useRef, useState } from 'react';
|
|
4
|
-
|
|
5
|
-
const TABS = [
|
|
6
|
-
{
|
|
7
|
-
id: 'web',
|
|
8
|
-
label: 'Web',
|
|
9
|
-
filename: 'tokens.css',
|
|
10
|
-
code: `:root {
|
|
11
|
-
--color-action-primary: #533afd;
|
|
12
|
-
--radius-control: 8px;
|
|
13
|
-
}`,
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
id: 'ios',
|
|
17
|
-
label: 'iOS',
|
|
18
|
-
filename: 'DesignTokens.swift',
|
|
19
|
-
code: `import SwiftUI
|
|
20
|
-
|
|
21
|
-
extension Color {
|
|
22
|
-
static let actionPrimary = Color(hex: 0x533AFD)
|
|
23
|
-
}`,
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
id: 'android',
|
|
27
|
-
label: 'Android',
|
|
28
|
-
filename: 'DesignTokens.kt',
|
|
29
|
-
code: `import androidx.compose.ui.graphics.Color
|
|
30
|
-
|
|
31
|
-
val ActionPrimary = Color(0xFF533AFD)
|
|
32
|
-
|
|
33
|
-
// res/values/colors.xml
|
|
34
|
-
// <color name="action_primary">#FF533AFD</color>`,
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
id: 'flutter',
|
|
38
|
-
label: 'Flutter',
|
|
39
|
-
filename: 'design_tokens.dart',
|
|
40
|
-
code: `import 'package:flutter/material.dart';
|
|
41
|
-
|
|
42
|
-
class DesignTokens {
|
|
43
|
-
static const Color actionPrimary = Color(0xFF533AFD);
|
|
44
|
-
}`,
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
id: 'wordpress',
|
|
48
|
-
label: 'WordPress',
|
|
49
|
-
filename: 'theme.json',
|
|
50
|
-
code: `{
|
|
51
|
-
"settings": {
|
|
52
|
-
"color": {
|
|
53
|
-
"palette": [
|
|
54
|
-
{ "slug": "action-primary", "color": "#533AFD", "name": "Action Primary" }
|
|
55
|
-
]
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}`,
|
|
59
|
-
},
|
|
60
|
-
];
|
|
61
|
-
|
|
62
|
-
export default function PlatformTabs() {
|
|
63
|
-
const [activeId, setActiveId] = useState(TABS[0].id);
|
|
64
|
-
const [copied, setCopied] = useState(false);
|
|
65
|
-
const tabRefs = useRef({});
|
|
66
|
-
|
|
67
|
-
const active = TABS.find((t) => t.id === activeId) || TABS[0];
|
|
68
|
-
const lines = active.code.split('\n');
|
|
69
|
-
|
|
70
|
-
const onKeyDown = (e) => {
|
|
71
|
-
const idx = TABS.findIndex((t) => t.id === activeId);
|
|
72
|
-
if (e.key === 'ArrowRight') {
|
|
73
|
-
e.preventDefault();
|
|
74
|
-
const next = TABS[(idx + 1) % TABS.length];
|
|
75
|
-
setActiveId(next.id);
|
|
76
|
-
tabRefs.current[next.id]?.focus();
|
|
77
|
-
} else if (e.key === 'ArrowLeft') {
|
|
78
|
-
e.preventDefault();
|
|
79
|
-
const next = TABS[(idx - 1 + TABS.length) % TABS.length];
|
|
80
|
-
setActiveId(next.id);
|
|
81
|
-
tabRefs.current[next.id]?.focus();
|
|
82
|
-
} else if (e.key === 'Home') {
|
|
83
|
-
e.preventDefault();
|
|
84
|
-
setActiveId(TABS[0].id);
|
|
85
|
-
tabRefs.current[TABS[0].id]?.focus();
|
|
86
|
-
} else if (e.key === 'End') {
|
|
87
|
-
e.preventDefault();
|
|
88
|
-
const last = TABS[TABS.length - 1];
|
|
89
|
-
setActiveId(last.id);
|
|
90
|
-
tabRefs.current[last.id]?.focus();
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const onCopy = async () => {
|
|
95
|
-
try {
|
|
96
|
-
await navigator.clipboard.writeText(active.code);
|
|
97
|
-
setCopied(true);
|
|
98
|
-
setTimeout(() => setCopied(false), 1400);
|
|
99
|
-
} catch {
|
|
100
|
-
// noop
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
return (
|
|
105
|
-
<div>
|
|
106
|
-
<div style={{ padding: 'var(--r5) 0 var(--r4)' }}>
|
|
107
|
-
<div className="eyebrow" style={{ marginBottom: 'var(--r3)' }}>§03 Multi-platform emitters</div>
|
|
108
|
-
<h2 className="display">One token. Five languages.</h2>
|
|
109
|
-
<p className="prose" style={{ marginTop: 'var(--r4)', fontSize: 18, maxWidth: '52ch' }}>
|
|
110
|
-
The same semantic token, emitted in five native dialects. No style leaks,
|
|
111
|
-
no translation layer.
|
|
112
|
-
</p>
|
|
113
|
-
</div>
|
|
114
|
-
|
|
115
|
-
{/* Tab strip */}
|
|
116
|
-
<div
|
|
117
|
-
role="tablist"
|
|
118
|
-
aria-label="Platform emitters"
|
|
119
|
-
onKeyDown={onKeyDown}
|
|
120
|
-
style={{
|
|
121
|
-
display: 'flex',
|
|
122
|
-
gap: 0,
|
|
123
|
-
borderBottom: 'var(--hair)',
|
|
124
|
-
}}
|
|
125
|
-
>
|
|
126
|
-
{TABS.map((t) => {
|
|
127
|
-
const selected = t.id === activeId;
|
|
128
|
-
return (
|
|
129
|
-
<button
|
|
130
|
-
key={t.id}
|
|
131
|
-
ref={(el) => (tabRefs.current[t.id] = el)}
|
|
132
|
-
role="tab"
|
|
133
|
-
id={`platform-tab-${t.id}`}
|
|
134
|
-
aria-selected={selected}
|
|
135
|
-
aria-controls={`platform-panel-${t.id}`}
|
|
136
|
-
tabIndex={selected ? 0 : -1}
|
|
137
|
-
onClick={() => setActiveId(t.id)}
|
|
138
|
-
style={{
|
|
139
|
-
padding: '14px 18px 13px',
|
|
140
|
-
fontFamily: 'var(--font-mono)',
|
|
141
|
-
fontSize: 12,
|
|
142
|
-
letterSpacing: '0.08em',
|
|
143
|
-
textTransform: 'uppercase',
|
|
144
|
-
color: selected ? 'var(--ink)' : 'var(--ink-2)',
|
|
145
|
-
borderBottom: selected ? '2px solid var(--accent)' : '2px solid transparent',
|
|
146
|
-
marginBottom: '-1px',
|
|
147
|
-
background: 'transparent',
|
|
148
|
-
}}
|
|
149
|
-
>
|
|
150
|
-
{t.label}
|
|
151
|
-
</button>
|
|
152
|
-
);
|
|
153
|
-
})}
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
|
-
{/* Code panel */}
|
|
157
|
-
<div
|
|
158
|
-
role="tabpanel"
|
|
159
|
-
id={`platform-panel-${active.id}`}
|
|
160
|
-
aria-labelledby={`platform-tab-${active.id}`}
|
|
161
|
-
style={{
|
|
162
|
-
position: 'relative',
|
|
163
|
-
background: 'var(--ink)',
|
|
164
|
-
color: 'var(--paper)',
|
|
165
|
-
border: 'var(--hair)',
|
|
166
|
-
borderTop: 0,
|
|
167
|
-
}}
|
|
168
|
-
>
|
|
169
|
-
<div
|
|
170
|
-
style={{
|
|
171
|
-
display: 'flex',
|
|
172
|
-
justifyContent: 'space-between',
|
|
173
|
-
alignItems: 'baseline',
|
|
174
|
-
padding: '12px 20px',
|
|
175
|
-
borderBottom: '1px solid var(--ink-2)',
|
|
176
|
-
fontFamily: 'var(--font-mono)',
|
|
177
|
-
fontSize: 11,
|
|
178
|
-
color: 'var(--ink-3)',
|
|
179
|
-
letterSpacing: '0.04em',
|
|
180
|
-
}}
|
|
181
|
-
>
|
|
182
|
-
<span>{active.filename}</span>
|
|
183
|
-
<button
|
|
184
|
-
onClick={onCopy}
|
|
185
|
-
style={{
|
|
186
|
-
fontFamily: 'var(--font-mono)',
|
|
187
|
-
fontSize: 11,
|
|
188
|
-
letterSpacing: '0.08em',
|
|
189
|
-
textTransform: 'uppercase',
|
|
190
|
-
color: copied ? 'var(--accent)' : 'var(--paper)',
|
|
191
|
-
padding: '4px 10px',
|
|
192
|
-
border: '1px solid var(--ink-3)',
|
|
193
|
-
}}
|
|
194
|
-
aria-live="polite"
|
|
195
|
-
>
|
|
196
|
-
{copied ? 'Copied' : 'Copy'}
|
|
197
|
-
</button>
|
|
198
|
-
</div>
|
|
199
|
-
|
|
200
|
-
<div
|
|
201
|
-
style={{
|
|
202
|
-
padding: '20px 0',
|
|
203
|
-
fontFamily: 'var(--font-mono)',
|
|
204
|
-
fontSize: 13,
|
|
205
|
-
lineHeight: 1.6,
|
|
206
|
-
fontFeatureSettings: "'zero' 1, 'ss01' 1",
|
|
207
|
-
}}
|
|
208
|
-
>
|
|
209
|
-
{lines.map((line, i) => (
|
|
210
|
-
<div
|
|
211
|
-
key={i}
|
|
212
|
-
style={{
|
|
213
|
-
display: 'grid',
|
|
214
|
-
gridTemplateColumns: '3.5rem 1fr',
|
|
215
|
-
alignItems: 'baseline',
|
|
216
|
-
}}
|
|
217
|
-
>
|
|
218
|
-
<span
|
|
219
|
-
style={{
|
|
220
|
-
color: 'var(--ink-3)',
|
|
221
|
-
textAlign: 'right',
|
|
222
|
-
paddingRight: 14,
|
|
223
|
-
borderRight: '1px solid var(--ink-2)',
|
|
224
|
-
userSelect: 'none',
|
|
225
|
-
}}
|
|
226
|
-
>
|
|
227
|
-
{i + 1}
|
|
228
|
-
</span>
|
|
229
|
-
<pre style={{ margin: 0, paddingLeft: 20, whiteSpace: 'pre', color: 'var(--paper)' }}>
|
|
230
|
-
{line || ' '}
|
|
231
|
-
</pre>
|
|
232
|
-
</div>
|
|
233
|
-
))}
|
|
234
|
-
</div>
|
|
235
|
-
</div>
|
|
236
|
-
|
|
237
|
-
<p
|
|
238
|
-
className="mono"
|
|
239
|
-
style={{
|
|
240
|
-
marginTop: 'var(--r4)',
|
|
241
|
-
fontSize: 12,
|
|
242
|
-
color: 'var(--ink-2)',
|
|
243
|
-
}}
|
|
244
|
-
>
|
|
245
|
-
<code>designlang <url> --platforms all</code> writes these files under{' '}
|
|
246
|
-
<code>./design-extract-output/<platform>/</code>.
|
|
247
|
-
</p>
|
|
248
|
-
</div>
|
|
249
|
-
);
|
|
250
|
-
}
|