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.
Files changed (82) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +46 -4
  3. package/bin/design-extract.js +28 -2
  4. package/package.json +1 -1
  5. package/src/config.js +4 -1
  6. package/src/crawler.js +376 -6
  7. package/src/extractors/accessibility.js +44 -1
  8. package/src/extractors/colors.js +50 -12
  9. package/src/extractors/interaction-states.js +57 -0
  10. package/src/extractors/modern-css.js +100 -0
  11. package/src/extractors/scoring.js +49 -30
  12. package/src/extractors/token-sources.js +65 -0
  13. package/src/extractors/wide-gamut.js +47 -0
  14. package/src/formatters/routes-reconciliation.js +160 -0
  15. package/src/index.js +29 -0
  16. package/src/utils/color-gamut.js +82 -0
  17. package/.github/FUNDING.yml +0 -1
  18. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -62
  19. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  20. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -28
  21. package/chrome-extension/README.md +0 -41
  22. package/chrome-extension/icons/favicon.svg +0 -7
  23. package/chrome-extension/icons/icon-128.png +0 -0
  24. package/chrome-extension/icons/icon-16.png +0 -0
  25. package/chrome-extension/icons/icon-32.png +0 -0
  26. package/chrome-extension/icons/icon-48.png +0 -0
  27. package/chrome-extension/manifest.json +0 -26
  28. package/chrome-extension/popup.html +0 -167
  29. package/chrome-extension/popup.js +0 -59
  30. package/docs/superpowers/plans/2026-04-18-designlang-v7.md +0 -1121
  31. package/docs/superpowers/specs/2026-04-18-designlang-v7-design.md +0 -150
  32. package/docs/superpowers/specs/2026-04-18-website-redesign-design.md +0 -120
  33. package/docs/superpowers/specs/2026-04-19-designlang-v7-1-design.md +0 -111
  34. package/tests/cli.test.js +0 -84
  35. package/tests/cookies.test.js +0 -98
  36. package/tests/extractors.test.js +0 -792
  37. package/tests/formatters.test.js +0 -709
  38. package/tests/mcp.test.js +0 -68
  39. package/tests/utils.test.js +0 -413
  40. package/website/.claude/launch.json +0 -11
  41. package/website/AGENTS.md +0 -5
  42. package/website/CLAUDE.md +0 -1
  43. package/website/README.md +0 -36
  44. package/website/app/api/extract/route.js +0 -245
  45. package/website/app/components/A11ySlider.js +0 -369
  46. package/website/app/components/Comparison.js +0 -286
  47. package/website/app/components/CssHealth.js +0 -243
  48. package/website/app/components/Extractor.js +0 -184
  49. package/website/app/components/HeroExtractor.js +0 -455
  50. package/website/app/components/Marginalia.js +0 -3
  51. package/website/app/components/McpSection.js +0 -223
  52. package/website/app/components/PlatformTabs.js +0 -250
  53. package/website/app/components/RegionsComponents.js +0 -429
  54. package/website/app/components/Rule.js +0 -13
  55. package/website/app/components/Specimens.js +0 -237
  56. package/website/app/components/StructuredData.js +0 -144
  57. package/website/app/components/TokenBrowser.js +0 -344
  58. package/website/app/components/token-browser-sample.js +0 -65
  59. package/website/app/globals.css +0 -505
  60. package/website/app/icon.svg +0 -7
  61. package/website/app/layout.js +0 -126
  62. package/website/app/opengraph-image.js +0 -170
  63. package/website/app/page.js +0 -352
  64. package/website/app/robots.js +0 -15
  65. package/website/app/seo-config.js +0 -82
  66. package/website/app/sitemap.js +0 -18
  67. package/website/jsconfig.json +0 -7
  68. package/website/lib/cache.js +0 -73
  69. package/website/lib/rate-limit.js +0 -30
  70. package/website/lib/rate-limit.test.js +0 -55
  71. package/website/lib/specimens.json +0 -86
  72. package/website/lib/token-helpers.js +0 -70
  73. package/website/lib/url-safety.js +0 -103
  74. package/website/lib/url-safety.test.js +0 -116
  75. package/website/lib/zip-files.js +0 -15
  76. package/website/next.config.mjs +0 -15
  77. package/website/package-lock.json +0 -1353
  78. package/website/package.json +0 -19
  79. package/website/public/favicon.svg +0 -7
  80. package/website/public/logo-specimen.svg +0 -76
  81. package/website/public/mark.svg +0 -12
  82. package/website/public/site.webmanifest +0 -13
@@ -0,0 +1,82 @@
1
+ // OKLCH / OKLab → sRGB hex conversion utilities.
2
+ // Based on the public OKLab formulas by Björn Ottosson
3
+ // (https://bottosson.github.io/posts/oklab/). Implemented locally (no deps).
4
+
5
+ function clamp01(v) { return Math.max(0, Math.min(1, v)); }
6
+
7
+ function linearToSrgb(x) {
8
+ // Convert linear-light sRGB [0..1] to sRGB gamma-encoded [0..1]
9
+ if (x < 0) x = 0;
10
+ if (x > 1) x = 1;
11
+ return x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055;
12
+ }
13
+
14
+ export function oklabToSrgb(L, a, b) {
15
+ const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
16
+ const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
17
+ const s_ = L - 0.0894841775 * a - 1.2914855480 * b;
18
+
19
+ const l = l_ * l_ * l_;
20
+ const m = m_ * m_ * m_;
21
+ const s = s_ * s_ * s_;
22
+
23
+ const r = +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
24
+ const g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
25
+ const b2 = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
26
+
27
+ return [linearToSrgb(r), linearToSrgb(g), linearToSrgb(b2)];
28
+ }
29
+
30
+ export function oklchToSrgb(L, C, h) {
31
+ const hr = (h * Math.PI) / 180;
32
+ const a = C * Math.cos(hr);
33
+ const b = C * Math.sin(hr);
34
+ return oklabToSrgb(L, a, b);
35
+ }
36
+
37
+ function toHex(v) {
38
+ const n = Math.round(clamp01(v) * 255);
39
+ return n.toString(16).padStart(2, '0');
40
+ }
41
+
42
+ export function rgbToHex(r, g, b) {
43
+ return '#' + toHex(r) + toHex(g) + toHex(b);
44
+ }
45
+
46
+ // Parse an oklch() or oklab() CSS value. Accepts values like:
47
+ // oklch(62.8% 0.258 29.23)
48
+ // oklch(0.628 0.258 29.23)
49
+ // oklab(0.628 0.1 -0.1)
50
+ // Returns { type: 'oklch'|'oklab', L, C, h, a, b, raw } or null.
51
+ export function parseOklchOrOklab(raw) {
52
+ if (!raw || typeof raw !== 'string') return null;
53
+ const m = raw.match(/^\s*(oklch|oklab)\(\s*([^)]+)\)\s*$/i);
54
+ if (!m) return null;
55
+ const type = m[1].toLowerCase();
56
+ // Strip alpha (everything after /)
57
+ const body = m[2].split('/')[0].trim();
58
+ const parts = body.split(/[\s,]+/).filter(Boolean);
59
+ if (parts.length < 3) return null;
60
+
61
+ function parseNum(s, scale = 1) {
62
+ if (s.endsWith('%')) return parseFloat(s) / 100;
63
+ return parseFloat(s) * scale;
64
+ }
65
+
66
+ const p0 = parts[0].endsWith('%') ? parseFloat(parts[0]) / 100 : parseFloat(parts[0]);
67
+ const p1 = parseFloat(parts[1]);
68
+ const p2 = parseFloat(parts[2]);
69
+ if ([p0, p1, p2].some(v => Number.isNaN(v))) return null;
70
+
71
+ if (type === 'oklch') return { type, L: p0, C: p1, h: p2, raw };
72
+ return { type, L: p0, a: p1, b: p2, raw };
73
+ }
74
+
75
+ export function oklchLikeToHex(raw) {
76
+ const parsed = parseOklchOrOklab(raw);
77
+ if (!parsed) return null;
78
+ const [r, g, b] = parsed.type === 'oklch'
79
+ ? oklchToSrgb(parsed.L, parsed.C, parsed.h)
80
+ : oklabToSrgb(parsed.L, parsed.a, parsed.b);
81
+ return rgbToHex(r, g, b);
82
+ }
@@ -1 +0,0 @@
1
- github: [Manavarya09]
@@ -1,62 +0,0 @@
1
- name: Bug report
2
- description: Something in designlang is broken or produces wrong output.
3
- title: "[Bug]: "
4
- labels: ["bug"]
5
- body:
6
- - type: input
7
- id: url
8
- attributes:
9
- label: URL extracted
10
- description: The site you ran designlang against.
11
- placeholder: https://example.com
12
- validations:
13
- required: true
14
- - type: input
15
- id: command
16
- attributes:
17
- label: Command used
18
- description: The exact command you ran.
19
- placeholder: npx designlang https://example.com --full
20
- validations:
21
- required: true
22
- - type: textarea
23
- id: expected
24
- attributes:
25
- label: What you expected
26
- validations:
27
- required: true
28
- - type: textarea
29
- id: actual
30
- attributes:
31
- label: What actually happened
32
- description: Include stack traces, log output, or relevant snippets from output files.
33
- validations:
34
- required: true
35
- - type: input
36
- id: version
37
- attributes:
38
- label: designlang version
39
- description: Output of `npx designlang --version` (or the version in package.json)
40
- placeholder: 7.0.0
41
- validations:
42
- required: true
43
- - type: input
44
- id: node
45
- attributes:
46
- label: Node.js version
47
- description: Output of `node --version`
48
- placeholder: v20.11.0
49
- validations:
50
- required: true
51
- - type: input
52
- id: os
53
- attributes:
54
- label: OS
55
- placeholder: macOS 14.5 / Ubuntu 22.04 / Windows 11
56
- validations:
57
- required: true
58
- - type: textarea
59
- id: extra
60
- attributes:
61
- label: Anything else
62
- description: Screenshots, env variables, network constraints, auth requirements, etc.
@@ -1,8 +0,0 @@
1
- blank_issues_enabled: false
2
- contact_links:
3
- - name: Question or discussion
4
- url: https://github.com/Manavarya09/design-extract/discussions
5
- about: Ask a question, share an extraction, or discuss ideas.
6
- - name: designlang website
7
- url: https://website-five-lime-65.vercel.app
8
- about: Read more about what designlang does.
@@ -1,28 +0,0 @@
1
- name: Feature request
2
- description: Suggest a new extraction, output format, or CLI capability.
3
- title: "[Feature]: "
4
- labels: ["enhancement"]
5
- body:
6
- - type: textarea
7
- id: problem
8
- attributes:
9
- label: Problem
10
- description: What are you trying to do that designlang doesn't support today?
11
- validations:
12
- required: true
13
- - type: textarea
14
- id: proposal
15
- attributes:
16
- label: Proposal
17
- description: What should designlang do? CLI flag, command, new extractor, new output format?
18
- validations:
19
- required: true
20
- - type: textarea
21
- id: alternatives
22
- attributes:
23
- label: Alternatives you've considered
24
- - type: textarea
25
- id: context
26
- attributes:
27
- label: Additional context
28
- description: Example sites, example outputs, links to similar tools, screenshots.
@@ -1,41 +0,0 @@
1
- # designlang — Chrome extension
2
-
3
- Right-click → Extract design on any page. The extension opens
4
- [designlang.manavaryasingh.com](https://designlang.manavaryasingh.com) with the
5
- current tab's URL prefilled, so extraction starts with one click instead of
6
- switching windows and pasting.
7
-
8
- ## Features
9
-
10
- - One-click handoff from any `http(s)://` page to the hosted extractor
11
- - "Copy CLI" button — drops `npx designlang <url>` straight into your clipboard
12
- - Zero tracking, zero analytics, zero permissions beyond `activeTab`
13
-
14
- ## Install (developer mode, pending Chrome Web Store publish)
15
-
16
- 1. Clone this repo.
17
- 2. Open `chrome://extensions` in Chromium / Chrome / Edge / Arc / Brave.
18
- 3. Toggle **Developer mode** (top right).
19
- 4. Click **Load unpacked** and pick the `chrome-extension/` folder in this repo.
20
- 5. Pin the extension from the puzzle-piece menu.
21
-
22
- ## Permissions
23
-
24
- - `activeTab` — to read the URL of the tab you triggered the popup on.
25
- - `host_permissions` scoped to `https://designlang.manavaryasingh.com/*` for the
26
- handoff.
27
-
28
- No content scripts, no remote code, no background service worker. The whole
29
- extension is ~3 KB of static HTML/JS/CSS.
30
-
31
- ## Roadmap
32
-
33
- - Chrome Web Store listing.
34
- - Optional right-click context menu on links: *"Extract design from this link"*.
35
- - DevTools panel that shows the extracted tokens for the current tab alongside
36
- the real DOM selector that produced them.
37
- - Firefox + Edge listings (the same MV3 manifest works for both).
38
-
39
- ## Contributing
40
-
41
- PRs welcome. See the root [`CONTRIBUTING.md`](../CONTRIBUTING.md).
@@ -1,7 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="designlang">
2
- <title>designlang</title>
3
- <rect width="32" height="32" fill="#F3F1EA"/>
4
- <circle cx="13" cy="20" r="7.5" fill="none" stroke="#0A0908" stroke-width="4.5"/>
5
- <rect x="19" y="4" width="4.5" height="25" fill="#0A0908"/>
6
- <rect x="25" y="25" width="4" height="4" fill="#FF4800"/>
7
- </svg>
Binary file
Binary file
Binary file
Binary file
@@ -1,26 +0,0 @@
1
- {
2
- "manifest_version": 3,
3
- "name": "designlang",
4
- "version": "1.0.0",
5
- "description": "Extract any website's complete design system — DTCG tokens, Tailwind, Figma variables, MCP server context, iOS/Android/Flutter/WordPress emitters.",
6
- "author": "Manav Arya Singh",
7
- "homepage_url": "https://designlang.manavaryasingh.com",
8
- "action": {
9
- "default_title": "Extract design system with designlang",
10
- "default_popup": "popup.html",
11
- "default_icon": {
12
- "16": "icons/icon-16.png",
13
- "32": "icons/icon-32.png",
14
- "48": "icons/icon-48.png",
15
- "128": "icons/icon-128.png"
16
- }
17
- },
18
- "icons": {
19
- "16": "icons/icon-16.png",
20
- "32": "icons/icon-32.png",
21
- "48": "icons/icon-48.png",
22
- "128": "icons/icon-128.png"
23
- },
24
- "permissions": ["activeTab"],
25
- "host_permissions": ["https://designlang.manavaryasingh.com/*"]
26
- }
@@ -1,167 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=360" />
6
- <title>designlang</title>
7
- <link rel="icon" href="icons/favicon.svg" type="image/svg+xml" />
8
- <style>
9
- :root {
10
- --paper: #f3f1ea;
11
- --ink: #0a0908;
12
- --ink-2: #403c34;
13
- --ink-3: #8b8778;
14
- --accent: #ff4800;
15
- }
16
- *, *::before, *::after { box-sizing: border-box; }
17
- html, body { margin: 0; padding: 0; }
18
- body {
19
- width: 360px;
20
- background: var(--paper);
21
- color: var(--ink);
22
- font-family: -apple-system, 'Segoe UI', system-ui, sans-serif;
23
- font-size: 14px;
24
- line-height: 1.4;
25
- }
26
- .pad { padding: 18px 18px 20px; }
27
- .bar {
28
- display: flex;
29
- align-items: center;
30
- justify-content: space-between;
31
- padding: 12px 18px 10px;
32
- border-bottom: 1px solid var(--ink);
33
- }
34
- .mono {
35
- font-family: ui-monospace, 'SF Mono', Menlo, monospace;
36
- font-size: 11px;
37
- letter-spacing: 0.12em;
38
- text-transform: uppercase;
39
- color: var(--ink-2);
40
- }
41
- .mark {
42
- display: inline-flex;
43
- align-items: center;
44
- gap: 8px;
45
- font-family: ui-monospace, Menlo, monospace;
46
- font-weight: 600;
47
- }
48
- .mark svg { width: 16px; height: 16px; }
49
- h1 {
50
- margin: 16px 0 10px;
51
- font-family: 'Iowan Old Style', Georgia, serif;
52
- font-size: 22px;
53
- font-weight: 400;
54
- line-height: 1.1;
55
- letter-spacing: -0.02em;
56
- }
57
- h1 em { color: var(--accent); font-style: italic; }
58
- p { margin: 0 0 14px; color: var(--ink-2); }
59
- .url {
60
- display: block;
61
- background: #fff;
62
- border: 1px solid var(--ink);
63
- padding: 10px 12px;
64
- font: 12px ui-monospace, Menlo, monospace;
65
- color: var(--ink);
66
- word-break: break-all;
67
- white-space: pre-wrap;
68
- margin-bottom: 12px;
69
- }
70
- button, a.button {
71
- display: inline-flex;
72
- align-items: center;
73
- justify-content: space-between;
74
- gap: 10px;
75
- width: 100%;
76
- padding: 12px 14px;
77
- font: 13px ui-monospace, Menlo, monospace;
78
- letter-spacing: 0.03em;
79
- background: var(--ink);
80
- color: var(--paper);
81
- border: 1px solid var(--ink);
82
- box-shadow: 4px 4px 0 var(--accent);
83
- cursor: pointer;
84
- text-decoration: none;
85
- transition: transform 120ms, box-shadow 120ms;
86
- }
87
- button:hover, a.button:hover {
88
- transform: translate(-1px, -1px);
89
- box-shadow: 5px 5px 0 var(--accent);
90
- }
91
- button:active, a.button:active {
92
- transform: translate(3px, 3px);
93
- box-shadow: 1px 1px 0 var(--accent);
94
- }
95
- .row {
96
- display: grid;
97
- grid-template-columns: 1fr 1fr;
98
- gap: 8px;
99
- margin-top: 10px;
100
- }
101
- .secondary {
102
- display: inline-flex;
103
- justify-content: center;
104
- padding: 9px 10px;
105
- border: 1px solid var(--ink);
106
- background: transparent;
107
- color: var(--ink);
108
- font: 11px ui-monospace, Menlo, monospace;
109
- letter-spacing: 0.06em;
110
- text-transform: uppercase;
111
- text-decoration: none;
112
- box-shadow: none;
113
- }
114
- .secondary:hover {
115
- background: var(--ink);
116
- color: var(--paper);
117
- transform: none;
118
- box-shadow: none;
119
- }
120
- .hint {
121
- margin-top: 12px;
122
- font: 11px ui-monospace, Menlo, monospace;
123
- color: var(--ink-3);
124
- line-height: 1.5;
125
- }
126
- .hint code { color: var(--ink); }
127
- </style>
128
- </head>
129
- <body>
130
- <div class="bar">
131
- <span class="mark">
132
- <svg viewBox="0 0 32 32" aria-hidden="true">
133
- <rect width="32" height="32" fill="#F3F1EA"/>
134
- <circle cx="13" cy="20" r="7.5" fill="none" stroke="#0A0908" stroke-width="4.5"/>
135
- <rect x="19" y="4" width="4.5" height="25" fill="#0A0908"/>
136
- <rect x="25" y="25" width="4" height="4" fill="#FF4800"/>
137
- </svg>
138
- designlang
139
- </span>
140
- <span class="mono">v7.0</span>
141
- </div>
142
-
143
- <div class="pad">
144
- <h1>Read this page as a <em>design system</em>.</h1>
145
- <p>Extracts DTCG tokens, Tailwind config, Figma variables, an MCP companion, and native iOS / Android / Flutter / WordPress outputs.</p>
146
-
147
- <span class="url" id="url-display">—</span>
148
-
149
- <button id="extract-btn">
150
- Extract on designlang
151
- <span>↗</span>
152
- </button>
153
-
154
- <div class="row">
155
- <a class="secondary" id="copy-cli" href="#">Copy CLI</a>
156
- <a class="secondary" href="https://github.com/Manavarya09/design-extract" target="_blank" rel="noopener">GitHub</a>
157
- </div>
158
-
159
- <div class="hint">
160
- Prefer the CLI? <code>npx designlang &lt;url&gt;</code>. MCP server:
161
- <code>designlang mcp</code>.
162
- </div>
163
- </div>
164
-
165
- <script src="popup.js"></script>
166
- </body>
167
- </html>
@@ -1,59 +0,0 @@
1
- // designlang Chrome extension popup script.
2
- // On open, reads the active tab's URL, shows it, and wires the Extract button
3
- // to hand off to designlang.manavaryasingh.com with the URL prefilled.
4
-
5
- const SITE = 'https://designlang.manavaryasingh.com/';
6
-
7
- function isExtractable(url) {
8
- if (!url) return false;
9
- try {
10
- const u = new URL(url);
11
- return u.protocol === 'http:' || u.protocol === 'https:';
12
- } catch {
13
- return false;
14
- }
15
- }
16
-
17
- async function getActiveTabUrl() {
18
- const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
19
- return tabs[0]?.url || '';
20
- }
21
-
22
- function render(tabUrl) {
23
- const display = document.getElementById('url-display');
24
- const btn = document.getElementById('extract-btn');
25
- const copy = document.getElementById('copy-cli');
26
-
27
- if (!isExtractable(tabUrl)) {
28
- display.textContent = '(open a regular https:// page to extract)';
29
- btn.disabled = true;
30
- btn.style.opacity = '0.55';
31
- btn.style.cursor = 'not-allowed';
32
- copy.setAttribute('aria-disabled', 'true');
33
- copy.style.pointerEvents = 'none';
34
- copy.style.opacity = '0.55';
35
- return;
36
- }
37
-
38
- display.textContent = tabUrl;
39
-
40
- btn.addEventListener('click', () => {
41
- const target = `${SITE}?url=${encodeURIComponent(tabUrl)}&source=chrome`;
42
- chrome.tabs.create({ url: target });
43
- window.close();
44
- });
45
-
46
- copy.addEventListener('click', async (e) => {
47
- e.preventDefault();
48
- const line = `npx designlang ${tabUrl}`;
49
- try {
50
- await navigator.clipboard.writeText(line);
51
- copy.textContent = 'Copied';
52
- setTimeout(() => { copy.textContent = 'Copy CLI'; }, 1200);
53
- } catch {
54
- copy.textContent = 'Clipboard denied';
55
- }
56
- });
57
- }
58
-
59
- getActiveTabUrl().then(render);