designlang 7.0.0 → 7.2.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 (77) hide show
  1. package/.github/og-preview.png +0 -0
  2. package/.github/workflows/manavarya-bot.yml +17 -0
  3. package/.vercel/README.txt +11 -0
  4. package/.vercel/project.json +1 -0
  5. package/CHANGELOG.md +29 -0
  6. package/CONTRIBUTING.md +25 -0
  7. package/README.md +38 -11
  8. package/bin/design-extract.js +41 -2
  9. package/chrome-extension/README.md +41 -0
  10. package/chrome-extension/icons/favicon.svg +7 -0
  11. package/chrome-extension/icons/icon-128.png +0 -0
  12. package/chrome-extension/icons/icon-16.png +0 -0
  13. package/chrome-extension/icons/icon-32.png +0 -0
  14. package/chrome-extension/icons/icon-48.png +0 -0
  15. package/chrome-extension/manifest.json +26 -0
  16. package/chrome-extension/popup.html +167 -0
  17. package/chrome-extension/popup.js +59 -0
  18. package/docs/superpowers/specs/2026-04-18-website-redesign-design.md +120 -0
  19. package/docs/superpowers/specs/2026-04-19-designlang-v7-1-design.md +111 -0
  20. package/package.json +1 -1
  21. package/src/config.js +5 -1
  22. package/src/crawler.js +361 -2
  23. package/src/extractors/interaction-states.js +57 -0
  24. package/src/extractors/modern-css.js +100 -0
  25. package/src/extractors/token-sources.js +65 -0
  26. package/src/extractors/wide-gamut.js +47 -0
  27. package/src/formatters/routes-reconciliation.js +160 -0
  28. package/src/index.js +29 -0
  29. package/src/utils/color-gamut.js +82 -0
  30. package/src/utils-cookies.js +73 -0
  31. package/tests/cookies.test.js +98 -0
  32. package/tests/interaction-states.test.js +62 -0
  33. package/tests/modern-css.test.js +104 -0
  34. package/tests/routes-reconciliation.test.js +120 -0
  35. package/tests/wide-gamut.test.js +90 -0
  36. package/website/app/api/extract/route.js +216 -56
  37. package/website/app/components/A11ySlider.js +369 -0
  38. package/website/app/components/Comparison.js +286 -0
  39. package/website/app/components/CssHealth.js +243 -0
  40. package/website/app/components/HeroExtractor.js +455 -0
  41. package/website/app/components/Marginalia.js +3 -0
  42. package/website/app/components/McpSection.js +223 -0
  43. package/website/app/components/PlatformTabs.js +250 -0
  44. package/website/app/components/RegionsComponents.js +429 -0
  45. package/website/app/components/Rule.js +13 -0
  46. package/website/app/components/Specimens.js +237 -0
  47. package/website/app/components/StructuredData.js +144 -0
  48. package/website/app/components/TokenBrowser.js +344 -0
  49. package/website/app/components/token-browser-sample.js +65 -0
  50. package/website/app/globals.css +415 -633
  51. package/website/app/icon.svg +7 -0
  52. package/website/app/layout.js +113 -6
  53. package/website/app/opengraph-image.js +170 -0
  54. package/website/app/page.js +372 -148
  55. package/website/app/robots.js +15 -0
  56. package/website/app/seo-config.js +82 -0
  57. package/website/app/sitemap.js +18 -0
  58. package/website/lib/cache.js +73 -0
  59. package/website/lib/rate-limit.js +30 -0
  60. package/website/lib/rate-limit.test.js +55 -0
  61. package/website/lib/specimens.json +86 -0
  62. package/website/lib/token-helpers.js +70 -0
  63. package/website/lib/url-safety.js +103 -0
  64. package/website/lib/url-safety.test.js +116 -0
  65. package/website/lib/zip-files.js +15 -0
  66. package/website/package-lock.json +85 -0
  67. package/website/package.json +1 -0
  68. package/website/public/favicon.svg +7 -0
  69. package/website/public/logo-specimen.svg +76 -0
  70. package/website/public/mark.svg +12 -0
  71. package/website/public/site.webmanifest +13 -0
  72. package/website/app/favicon.ico +0 -0
  73. package/website/public/file.svg +0 -1
  74. package/website/public/globe.svg +0 -1
  75. package/website/public/next.svg +0 -1
  76. package/website/public/vercel.svg +0 -1
  77. package/website/public/window.svg +0 -1
Binary file
@@ -0,0 +1,17 @@
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 }}
@@ -0,0 +1,11 @@
1
+ > Why do I have a folder named ".vercel" in my project?
2
+ The ".vercel" folder is created when you link a directory to a Vercel project.
3
+
4
+ > What does the "project.json" file contain?
5
+ The "project.json" file contains:
6
+ - The ID of the Vercel project that you linked ("projectId")
7
+ - The ID of the user or team your Vercel project is owned by ("orgId")
8
+
9
+ > Should I commit the ".vercel" folder?
10
+ No, you should not share the ".vercel" folder with anyone.
11
+ Upon creation, it will be automatically added to your ".gitignore" file.
@@ -0,0 +1 @@
1
+ {"projectId":"prj_g6gBbA5TU1e3vLtU6w8FHJhy9Wmk","orgId":"team_NOd0BEOy4vNKC9vTwIRo0qdF","projectName":"website"}
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## [7.2.0] — 2026-04-19
4
+
5
+ ### Added
6
+
7
+ - **Modern CSS surfacing (Tier 1a)** — crawler now captures pseudo-elements, variable-font axes (`font-variation-settings`), `@container` queries, and `env()` usage. Surfaced on `design.modernCss`. (#33)
8
+ - **Wide-gamut color + CSS source attribution (Tier 1b)** — `oklch()`, `oklab()`, `color-mix()`, `light-dark()`, Display P3, and Rec2020 references are collected on `design.wideGamut`. A new `design.tokenSources` maps each extracted token to the stylesheet URL it first appeared in. (#34)
9
+ - **Auto-interact pass (Tier 2)** — new `--deep-interact` flag (implied by `--full`) runs an interaction pass before extraction: full-page scroll in 4 steps, menu/dropdown opens, hover snapshots for the first batch of buttons/links with computed-style diffs, accordion clicks, and first-match modal trigger. Results populate `design.interactionStates` (hover deltas, menu/modal snapshots). Every step is wrapped in try/catch with per-step timeouts so interaction failures never kill the crawl.
10
+ - **Multi-page token reconciliation (Tier 2)** — when `--depth >= 1` the extractor now emits three new artifacts alongside the merged baseline: `*-tokens-shared.json` (tokens shared across every route), `*-tokens-routes/<slug>.json` (per-route `added` and `changed` deltas), and `*-routes-report.md` (readable summary). Slugs are derived from the route path (`/` → `index`) with automatic collision handling.
11
+
12
+ ### Changed
13
+
14
+ - `--full` now also enables `--deep-interact`.
15
+ - `--depth <n>` description updated to mention the new reconciliation outputs.
16
+
17
+ ## [7.1.0] — 2026-04-19
18
+
19
+ ### Added
20
+
21
+ - **Cookie file support** — `--cookie-file <path>` loads cookies from a JSON array, a Playwright `storageState.json`, or a Netscape `cookies.txt` (browser extensions, curl exports). The new loader lives in `src/utils-cookies.js` and merges cleanly with the existing `--cookie name=value` flag.
22
+ - **`--insecure`** — ignores HTTPS/SSL certificate errors. Useful for self-signed dev servers, internal staging environments behind corporate proxies, and local extraction through MITM tools. Passes `ignoreHTTPSErrors: true` to the Playwright context plus the matching Chromium launch flags.
23
+ - **`--user-agent <ua>`** — override the browser User-Agent string for extraction.
24
+ - **Chrome extension** — `chrome-extension/` ships a Manifest v3 popup that hands the current tab off to [designlang.manavaryasingh.com](https://designlang.manavaryasingh.com) with the URL prefilled. Also emits a "Copy CLI" button that drops `npx designlang <url>` into the clipboard. Developer-mode install for now; Chrome Web Store listing pending.
25
+ - **Website URL query parameter** — the extractor input on the hosted site now honours `?url=<encoded>` so the Chrome extension (and any deep link) can prefill.
26
+ - **CONTRIBUTING**: "Good first issues" and "Credits" sections.
27
+
28
+ ### Thanks
29
+
30
+ - A developer from China opened a conversation proposing cookie-file handling, SSL bypass, and a Chrome packaging story — this release ships all three.
31
+
3
32
  ## [7.0.0] — 2026-04-18
4
33
 
5
34
  ### Breaking
package/CONTRIBUTING.md CHANGED
@@ -61,3 +61,28 @@ When filing a bug, please include:
61
61
  - One feature/fix per PR
62
62
  - Keep PRs focused and small when possible
63
63
  - Add a brief description of what changed and why
64
+
65
+ ## Good first issues
66
+
67
+ Some work that's already scoped and ready for someone to pick up:
68
+
69
+ - **Extra cookie formats** — add support for browser-specific exports (EditThisCookie, Cookie-Editor) on top of the existing JSON / Playwright / Netscape loaders in `src/utils-cookies.js`.
70
+ - **Proxy support** — add `--proxy <url>` that passes through to Playwright's `proxy` launch option, paired with the existing `--insecure` flag for corporate networks.
71
+ - **Chrome extension: DevTools panel** — extend `chrome-extension/` with a DevTools panel that shows tokens for the current tab without leaving the browser. See the roadmap in `chrome-extension/README.md`.
72
+ - **Firefox + Edge packaging** — the manifest v3 extension already works in both; we just need store listings and icons.
73
+ - **Additional CSS-in-JS emitters** — Emotion, Stitches, Vanilla Extract, Panda, styled-components. Each is a thin formatter module under `src/formatters/`.
74
+
75
+ ## Credits
76
+
77
+ Shipped thanks to a few specific contributors — PRs welcome for the list:
78
+
79
+ - [@Manavarya09](https://github.com/Manavarya09) — author, maintainer.
80
+ - Community suggestions that landed as features: cookie-file loading, `--insecure` SSL bypass, and the first Chrome extension popup came from an external developer's proposal in mid-April 2026.
81
+
82
+ If you open a PR or file an issue that materially shapes a release, we'll add you here.
83
+
84
+ ## Code of conduct
85
+
86
+ Be decent. Disagreements about design, code, or approach are welcome — personal
87
+ attacks are not. Maintainer reserves the right to close issues and PRs that
88
+ don't engage in good faith.
package/README.md CHANGED
@@ -1,13 +1,12 @@
1
1
  <p align="center">
2
- <h1 align="center">DESIGNLANG</h1>
3
- <p align="center">Reverse-engineer any website's complete design system in one command.</p>
2
+ <img src="website/public/logo-specimen.svg" alt="designlang — reads a website the way a developer reads a stylesheet" width="900">
4
3
  </p>
5
4
 
6
5
  <p align="center">
7
- <a href="https://www.npmjs.com/package/designlang"><img src="https://img.shields.io/npm/v/designlang?color=blue&label=npm" alt="npm version"></a>
8
- <a href="https://github.com/Manavarya09/design-extract/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Manavarya09/design-extract" alt="license"></a>
9
- <a href="https://nodejs.org"><img src="https://img.shields.io/node/v/designlang" alt="node version"></a>
10
- <a href="https://website-five-lime-65.vercel.app"><img src="https://img.shields.io/badge/website-live-red" alt="website"></a>
6
+ <a href="https://www.npmjs.com/package/designlang"><img src="https://img.shields.io/npm/v/designlang?color=0A0908&labelColor=F3F1EA&label=npm" alt="npm version"></a>
7
+ <a href="https://github.com/Manavarya09/design-extract/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Manavarya09/design-extract?color=0A0908&labelColor=F3F1EA" alt="license"></a>
8
+ <a href="https://nodejs.org"><img src="https://img.shields.io/node/v/designlang?color=0A0908&labelColor=F3F1EA" alt="node version"></a>
9
+ <a href="https://designlang.manavaryasingh.com/"><img src="https://img.shields.io/badge/website-live-FF4800?labelColor=F3F1EA" alt="website"></a>
11
10
  </p>
12
11
 
13
12
  ---
@@ -312,6 +311,26 @@ A dedicated audit pass surfaced on `design.cssHealth`:
312
311
 
313
312
  Also contributes a `cssHealth` dimension to the overall design score.
314
313
 
314
+ ### 22. Chrome Extension (NEW in v7.1)
315
+
316
+ A Manifest-v3 popup lives in [`chrome-extension/`](chrome-extension/). One click on any tab opens `designlang.manavaryasingh.com` with the URL prefilled — no copy-paste, no context switch. There is also a **Copy CLI** button that puts `npx designlang <url>` in your clipboard.
317
+
318
+ - **Permissions:** `activeTab` only, plus host access to the hosted extractor.
319
+ - **Install:** toggle developer mode at `chrome://extensions`, click *Load unpacked*, pick the `chrome-extension/` folder.
320
+ - **Firefox + Edge** work with the same MV3 manifest.
321
+
322
+ ### 23. Better Auth + Network Control (NEW in v7.1)
323
+
324
+ Extracting from authenticated, self-signed, or non-default environments now takes one flag:
325
+
326
+ - **`--cookie-file <path>`** — loads cookies from JSON array, Playwright `storageState.json`, or Netscape `cookies.txt` (browser extensions, curl exports). Merges cleanly with the existing `--cookie name=value` flag.
327
+ - **`--insecure`** — ignore HTTPS/SSL certificate errors for self-signed dev servers, corporate staging, or MITM tools.
328
+ - **`--user-agent <ua>`** — override the browser User-Agent string.
329
+
330
+ ```bash
331
+ designlang https://staging.internal --cookie-file ./session.json --insecure
332
+ ```
333
+
315
334
  ## All Features
316
335
 
317
336
  | Feature | Flag / Command | Description |
@@ -326,12 +345,16 @@ Also contributes a `cssHealth` dimension to the overall design score.
326
345
  | Font files | automatic | Source detection (Google/self-hosted/CDN/system), @font-face CSS |
327
346
  | Image styles | automatic | Aspect ratios, shapes, filters, pattern classification |
328
347
  | Dark mode | `--dark` | Extracts dark color scheme + light/dark diff |
329
- | Auth pages | `--cookie`, `--header` | Extract from authenticated/protected pages |
330
- | Multi-page | `--depth <n>` | Crawl N internal pages for site-wide tokens |
348
+ | Auth pages | `--cookie`, `--cookie-file`, `--header` | Extract from authenticated/protected pages; cookie files in JSON / Playwright storageState / Netscape formats |
349
+ | Self-signed / dev TLS | `--insecure` | Ignore HTTPS/SSL certificate errors |
350
+ | User-Agent override | `--user-agent <ua>` | Set a custom User-Agent string |
351
+ | Chrome extension | `chrome-extension/` | One-click handoff from any tab, MV3, `activeTab` only |
352
+ | Multi-page | `--depth <n>` | Crawl N internal pages; emits shared-vs-per-route token reconciliation (`*-tokens-shared.json`, `*-tokens-routes/<slug>.json`, `*-routes-report.md`) |
331
353
  | Screenshots | `--screenshots` | Capture buttons, cards, inputs, nav, hero, full page |
332
354
  | Responsive | `--responsive` | Crawl at 4 viewports, map breakpoint changes |
333
355
  | Interactions | `--interactions` | Capture hover/focus/active state transitions |
334
- | Everything | `--full` | Enable screenshots + responsive + interactions |
356
+ | Auto-interact | `--deep-interact` | Scroll, open menus/modals/accordions, hover CTAs before extraction |
357
+ | Everything | `--full` | Enable screenshots + responsive + interactions + deep-interact |
335
358
  | Apply | `designlang apply <url>` | Auto-detect framework and write tokens to your project |
336
359
  | Clone | `designlang clone <url>` | Generate a working Next.js starter with extracted design |
337
360
  | Score | `designlang score <url>` | Rate design quality with visual bar chart breakdown |
@@ -366,9 +389,13 @@ Options:
366
389
  --screenshots Capture component screenshots
367
390
  --responsive Capture at multiple breakpoints
368
391
  --interactions Capture hover/focus/active states
369
- --full Enable all captures
392
+ --deep-interact Auto-interact pass (scroll, menus, modals, accordions, hover CTAs)
393
+ --full Enable all captures (implies --deep-interact)
370
394
  --cookie <cookies...> Cookies for authenticated pages (name=value)
395
+ --cookie-file <path> Load cookies from JSON / storageState / Netscape cookies.txt
371
396
  --header <headers...> Custom headers (name:value)
397
+ --user-agent <ua> Override the browser User-Agent string
398
+ --insecure Ignore HTTPS/SSL certificate errors (self-signed, dev, proxies)
372
399
  --framework <type> Only generate specific theme (react, shadcn)
373
400
  --platforms <csv> Additional platforms: web,ios,android,flutter,wordpress,all (additive)
374
401
  --emit-agent-rules Emit Cursor / Claude Code / CLAUDE.md / agents.md rule files
@@ -453,7 +480,7 @@ In Claude Code, use `/extract-design <url>`.
453
480
 
454
481
  ## Website
455
482
 
456
- **[website-five-lime-65.vercel.app](https://website-five-lime-65.vercel.app)** — the brutalist product page.
483
+ **[design-extract-beta.vercel.app](https://design-extract-beta.vercel.app)** — the brutalist product page.
457
484
 
458
485
  ## Contributing
459
486
 
@@ -21,6 +21,7 @@ import { formatFlutterDart } from '../src/formatters/flutter-dart.js';
21
21
  import { formatVueTheme } from '../src/formatters/vue-theme.js';
22
22
  import { formatSvelteTheme } from '../src/formatters/svelte-theme.js';
23
23
  import { formatAgentRules } from '../src/formatters/agent-rules.js';
24
+ import { reconcileRoutes, formatRoutesReport } from '../src/formatters/routes-reconciliation.js';
24
25
  import { loadConfig, mergeConfig } from '../src/config.js';
25
26
  import { diffDesigns, formatDiffMarkdown, formatDiffHtml } from '../src/diff.js';
26
27
  import { saveSnapshot, getHistory, formatHistoryMarkdown } from '../src/history.js';
@@ -63,9 +64,13 @@ program
63
64
  .option('--framework <type>', 'generate framework theme (react, shadcn, vue, svelte)')
64
65
  .option('--responsive', 'capture design at multiple breakpoints')
65
66
  .option('--interactions', 'capture hover/focus/active states')
66
- .option('--full', 'enable all extra captures (screenshots, responsive, interactions)')
67
+ .option('--deep-interact', 'auto-interact pass: scroll, open menus/modals/accordions, hover CTAs (implies --interactions)')
68
+ .option('--full', 'enable all extra captures (screenshots, responsive, interactions, deep-interact)')
67
69
  .option('--cookie <cookies...>', 'cookies for authenticated pages (name=value)')
70
+ .option('--cookie-file <path>', 'load cookies from JSON, Playwright storageState, or Netscape cookies.txt')
68
71
  .option('--header <headers...>', 'custom headers (name:value)')
72
+ .option('--user-agent <ua>', 'override the browser User-Agent string')
73
+ .option('--insecure', 'ignore HTTPS/SSL certificate errors (self-signed, dev, proxies)')
69
74
  .option('--ignore <selectors...>', 'CSS selectors to remove before extraction')
70
75
  .option('--tokens-legacy', 'Emit pre-v7 flat token JSON (backward compat)')
71
76
  .option('--platforms <csv>', 'Additional platforms: web,ios,android,flutter,wordpress,all (web is always emitted)', 'web')
@@ -115,7 +120,19 @@ program
115
120
  try {
116
121
  spinner.text = `Crawling${merged.depth > 0 ? ` (depth: ${merged.depth})` : ''}...`;
117
122
  // Parse auth options
118
- const cookies = merged.cookie || [];
123
+ const cliCookies = merged.cookie || [];
124
+ const fileCookies = [];
125
+ if (merged.cookieFile) {
126
+ try {
127
+ const { loadCookiesFromFile } = await import('../src/utils-cookies.js');
128
+ fileCookies.push(...loadCookiesFromFile(resolve(merged.cookieFile), url));
129
+ } catch (e) {
130
+ console.error(chalk.red(`\n cookie-file load failed: ${e.message}\n`));
131
+ process.exit(1);
132
+ }
133
+ }
134
+ const { mergeCookies } = await import('../src/utils-cookies.js');
135
+ const cookies = mergeCookies(cliCookies, fileCookies, url);
119
136
  const headers = {};
120
137
  if (merged.header) {
121
138
  for (const h of merged.header) {
@@ -135,6 +152,9 @@ program
135
152
  ignore: merged.ignore,
136
153
  cookies: cookies.length > 0 ? cookies : undefined,
137
154
  headers: Object.keys(headers).length > 0 ? headers : undefined,
155
+ insecure: merged.insecure || false,
156
+ userAgent: merged.userAgent,
157
+ deepInteract: merged.deepInteract || merged.full,
138
158
  });
139
159
 
140
160
  // Responsive capture
@@ -241,6 +261,25 @@ program
241
261
  }
242
262
  }
243
263
 
264
+ // Multi-route token reconciliation (Tier 2). Only when --depth >= 1 and
265
+ // the crawler actually returned per-route token data.
266
+ if (merged.depth >= 1 && Array.isArray(design.routes) && design.routes.length > 0) {
267
+ const reconciled = reconcileRoutes(design.routes);
268
+ const sharedPath = join(outDir, `${prefix}-tokens-shared.json`);
269
+ writeFileSync(sharedPath, JSON.stringify(reconciled.shared, null, 2), 'utf-8');
270
+ platformFiles.push({ path: sharedPath, label: 'Shared tokens (multi-route)' });
271
+ const routesDir = join(outDir, `${prefix}-tokens-routes`);
272
+ mkdirSync(routesDir, { recursive: true });
273
+ for (const [slug, entry] of Object.entries(reconciled.perRoute)) {
274
+ const rp = join(routesDir, `${slug}.json`);
275
+ writeFileSync(rp, JSON.stringify({ url: entry.url, path: entry.path, added: entry.added, changed: entry.changed }, null, 2), 'utf-8');
276
+ platformFiles.push({ path: rp, label: `Route tokens (${slug})` });
277
+ }
278
+ const reportPath = join(outDir, `${prefix}-routes-report.md`);
279
+ writeFileSync(reportPath, formatRoutesReport(reconciled), 'utf-8');
280
+ platformFiles.push({ path: reportPath, label: 'Routes report (markdown)' });
281
+ }
282
+
244
283
  // Agent rules (opt-in, also enabled by --full)
245
284
  if (merged.emitAgentRules || merged.full) {
246
285
  const agentFiles = formatAgentRules({ design, tokens: dtcgTokens, url });
@@ -0,0 +1,41 @@
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).
@@ -0,0 +1,7 @@
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>
@@ -0,0 +1,26 @@
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
+ }
@@ -0,0 +1,167 @@
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>
@@ -0,0 +1,59 @@
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);