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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// designlang visual-diff <before> <after>
|
|
2
|
+
// Captures screenshots for two URLs (or re-snapshots one URL across a time delta)
|
|
3
|
+
// and emits a side-by-side HTML diff with token deltas and file-size/dimension signals.
|
|
4
|
+
|
|
5
|
+
import { crawlPage } from './crawler.js';
|
|
6
|
+
import { extractDesignLanguage } from './index.js';
|
|
7
|
+
import { diffDesigns } from './diff.js';
|
|
8
|
+
import { nameFromUrl } from './utils.js';
|
|
9
|
+
import { statSync, existsSync, readFileSync } from 'fs';
|
|
10
|
+
import { basename } from 'path';
|
|
11
|
+
|
|
12
|
+
function fileKb(p) {
|
|
13
|
+
try { return Math.round(statSync(p).size / 1024); } catch { return 0; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function toDataUri(p) {
|
|
17
|
+
try {
|
|
18
|
+
const buf = readFileSync(p);
|
|
19
|
+
return 'data:image/png;base64,' + buf.toString('base64');
|
|
20
|
+
} catch { return ''; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function captureFor(url, options = {}) {
|
|
24
|
+
const raw = await crawlPage(url, { ...options, screenshots: true });
|
|
25
|
+
const design = await extractDesignLanguage(url, { ...options, screenshots: true });
|
|
26
|
+
return { raw, design };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function visualDiff({ beforeUrl, afterUrl, options = {} } = {}) {
|
|
30
|
+
const before = await captureFor(beforeUrl, options);
|
|
31
|
+
const after = await captureFor(afterUrl, options);
|
|
32
|
+
const tokenDelta = diffDesigns(before.design, after.design);
|
|
33
|
+
|
|
34
|
+
const shotsBefore = before.raw.componentScreenshots || {};
|
|
35
|
+
const shotsAfter = after.raw.componentScreenshots || {};
|
|
36
|
+
const keys = [...new Set([...Object.keys(shotsBefore), ...Object.keys(shotsAfter)])];
|
|
37
|
+
|
|
38
|
+
const pairs = keys.map(k => {
|
|
39
|
+
const a = shotsBefore[k];
|
|
40
|
+
const b = shotsAfter[k];
|
|
41
|
+
return {
|
|
42
|
+
key: k,
|
|
43
|
+
before: a && existsSync(a) ? { path: a, sizeKb: fileKb(a) } : null,
|
|
44
|
+
after: b && existsSync(b) ? { path: b, sizeKb: fileKb(b) } : null,
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return { pairs, tokenDelta, before: before.design, after: after.design };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function formatVisualDiffHtml(result, { beforeLabel, afterLabel } = {}) {
|
|
52
|
+
const { pairs, tokenDelta, before, after } = result;
|
|
53
|
+
const bL = beforeLabel || nameFromUrl(before.meta.url);
|
|
54
|
+
const aL = afterLabel || nameFromUrl(after.meta.url);
|
|
55
|
+
|
|
56
|
+
const pairHtml = pairs.map(p => {
|
|
57
|
+
const deltaKb = (p.after?.sizeKb || 0) - (p.before?.sizeKb || 0);
|
|
58
|
+
const tag = deltaKb === 0 ? 'identical-size' : Math.abs(deltaKb) > 50 ? 'major-delta' : 'minor-delta';
|
|
59
|
+
return `
|
|
60
|
+
<section class="pair" data-status="${tag}">
|
|
61
|
+
<header>
|
|
62
|
+
<h3>${p.key}</h3>
|
|
63
|
+
<span class="delta">${deltaKb >= 0 ? '+' : ''}${deltaKb} KB</span>
|
|
64
|
+
</header>
|
|
65
|
+
<div class="frames">
|
|
66
|
+
<figure><figcaption>${bL}</figcaption>${p.before ? `<img src="${toDataUri(p.before.path)}" alt="${p.key} before">` : '<div class="missing">missing</div>'}</figure>
|
|
67
|
+
<figure><figcaption>${aL}</figcaption>${p.after ? `<img src="${toDataUri(p.after.path)}" alt="${p.key} after">` : '<div class="missing">missing</div>'}</figure>
|
|
68
|
+
</div>
|
|
69
|
+
</section>`;
|
|
70
|
+
}).join('');
|
|
71
|
+
|
|
72
|
+
const changedColors = (tokenDelta.colors?.changed || []).slice(0, 20);
|
|
73
|
+
const tokenRows = changedColors.map(c => `<tr><td>${c.token || c.key || ''}</td><td style="background:${c.before};">${c.before}</td><td style="background:${c.after};">${c.after}</td></tr>`).join('');
|
|
74
|
+
|
|
75
|
+
return `<!doctype html>
|
|
76
|
+
<html lang="en">
|
|
77
|
+
<head>
|
|
78
|
+
<meta charset="utf-8">
|
|
79
|
+
<title>designlang · visual-diff · ${bL} → ${aL}</title>
|
|
80
|
+
<style>
|
|
81
|
+
:root { --ink: #0A0908; --paper: #F3F1EA; --accent: #FF4800; }
|
|
82
|
+
* { box-sizing: border-box; }
|
|
83
|
+
body { margin: 0; font: 14px/1.5 ui-sans-serif, system-ui; background: var(--paper); color: var(--ink); }
|
|
84
|
+
header.top { padding: 32px 40px; border-bottom: 1px solid var(--ink); display: flex; justify-content: space-between; align-items: baseline; }
|
|
85
|
+
header.top h1 { font-size: 28px; margin: 0; letter-spacing: -0.02em; }
|
|
86
|
+
header.top .meta { font-variant-numeric: tabular-nums; opacity: 0.7; }
|
|
87
|
+
main { padding: 32px 40px; max-width: 1400px; margin: 0 auto; }
|
|
88
|
+
.pair { margin-bottom: 48px; border: 1px solid var(--ink); background: white; }
|
|
89
|
+
.pair header { display: flex; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--ink); }
|
|
90
|
+
.pair[data-status="major-delta"] header { background: var(--accent); color: white; }
|
|
91
|
+
.pair[data-status="identical-size"] .delta::after { content: " — identical"; opacity: 0.6; }
|
|
92
|
+
.pair h3 { margin: 0; text-transform: uppercase; letter-spacing: 0.04em; }
|
|
93
|
+
.frames { display: grid; grid-template-columns: 1fr 1fr; }
|
|
94
|
+
figure { margin: 0; padding: 16px; border-right: 1px solid var(--ink); }
|
|
95
|
+
figure:last-child { border-right: 0; }
|
|
96
|
+
figcaption { font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; opacity: 0.6; margin-bottom: 8px; }
|
|
97
|
+
figure img { max-width: 100%; display: block; border: 1px solid #ddd; }
|
|
98
|
+
.missing { padding: 40px; text-align: center; opacity: 0.4; font-style: italic; border: 1px dashed #999; }
|
|
99
|
+
table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
|
|
100
|
+
th, td { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--ink); }
|
|
101
|
+
h2 { margin-top: 48px; font-size: 20px; }
|
|
102
|
+
</style>
|
|
103
|
+
</head>
|
|
104
|
+
<body>
|
|
105
|
+
<header class="top">
|
|
106
|
+
<h1>visual-diff</h1>
|
|
107
|
+
<div class="meta">${bL} → ${aL} · ${pairs.length} components</div>
|
|
108
|
+
</header>
|
|
109
|
+
<main>
|
|
110
|
+
<h2>Component snapshots</h2>
|
|
111
|
+
${pairHtml || '<p>No component screenshots captured.</p>'}
|
|
112
|
+
${changedColors.length ? `<h2>Color token changes</h2><table><thead><tr><th>token</th><th>before</th><th>after</th></tr></thead><tbody>${tokenRows}</tbody></table>` : ''}
|
|
113
|
+
</main>
|
|
114
|
+
</body>
|
|
115
|
+
</html>`;
|
|
116
|
+
}
|
package/.github/FUNDING.yml
DELETED
|
@@ -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.
|
package/.github/og-preview.png
DELETED
|
Binary file
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Managed by bot-manavarya/reviewer — edits will be overwritten.
|
|
2
|
-
name: manavarya-bot review
|
|
3
|
-
|
|
4
|
-
on:
|
|
5
|
-
pull_request:
|
|
6
|
-
types: [opened, synchronize, reopened, ready_for_review]
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
review:
|
|
10
|
-
if: ${{ github.event.pull_request.draft == false }}
|
|
11
|
-
uses: bot-manavarya/reviewer/.github/workflows/review.yml@main
|
|
12
|
-
with:
|
|
13
|
-
provider: 'gemini'
|
|
14
|
-
model: 'gemini-2.5-flash'
|
|
15
|
-
secrets:
|
|
16
|
-
bot-token: ${{ secrets.MANAVARYA_BOT_TOKEN }}
|
|
17
|
-
gemini-api-key: ${{ secrets.GEMINI_API_KEY }}
|
|
@@ -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 <url></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);
|