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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,74 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [9.0.0] — 2026-04-21
|
|
4
|
+
|
|
5
|
+
**The Motion & Voice release.** Six new capabilities that push designlang past "extract the paint" and into "extract the *feel*, the *anatomy*, and the *voice*." No competing tool does any of these. All work ships with tests (282/282 passing).
|
|
6
|
+
|
|
7
|
+
### Added — extraction
|
|
8
|
+
|
|
9
|
+
- **Motion language extractor** (`src/extractors/motion.js`) — easings are classified into families (`ease-in`, `ease-in-out`, `ease-out`, `linear`, `steps`, `spring`, `custom`) via cubic-bezier geometry, durations are bucketed into a named scale (`instant`/`xs`/`sm`/`md`/`lg`/`xl`/`xxl`), spring/overshoot cubic-beziers are surfaced, scroll-linked animation usage is detected via `animation-timeline` / `view-timeline-name` / `scroll-timeline-name`, and each `@keyframes` rule is classified by kind (`slide-x`, `slide-y`, `fade`, `reveal`, `rotate`, `scale`, `pulse`, `custom`). A one-word `feel` fingerprint (`springy`/`responsive`/`smooth`/`mechanical`/`mixed`) summarizes the whole system.
|
|
10
|
+
- **Motion tokens formatter** (`src/formatters/motion-tokens.js`) — emits `*-motion-tokens.json` in a DTCG-flavored shape with `$type: duration` / `$type: cubicBezier`.
|
|
11
|
+
- **Component Anatomy v2** (`src/extractors/component-anatomy.js`) — groups components by variant-class hints, infers slot roles (icon / label / badge / heading / media / footer), builds a variant × size × state matrix, captures sample button labels, and emits typed React stubs via `formatAnatomyStubs`. Output: `*-anatomy.tsx`.
|
|
12
|
+
- **Brand voice extractor** (`src/extractors/voice.js`) — classifies tone (friendly / formal / technical / playful / neutral) from lexical markers, picks pronoun posture (`we→you`, `you-only`, `we-only`, `third-person`), detects heading style, top CTA verbs, and microcopy patterns. Output: `*-voice.json`.
|
|
13
|
+
- **Crawler extensions** (`src/crawler.js`) — per-element `animation-timeline`, view/scroll timeline names; per-candidate `text`, `slots[]`, `disabled`, `variantHint`, `sizeHint` to feed anatomy + voice.
|
|
14
|
+
|
|
15
|
+
### Added — new commands
|
|
16
|
+
|
|
17
|
+
- **`designlang lint <file>`** — audits DTCG / flat-JSON / CSS-vars token files for color sprawl, spacing-scale drift, radius/shadow bloat, and WCAG AA fg/bg contrast. Exits non-zero on `error`-level findings. CI-ready.
|
|
18
|
+
- **`designlang drift <url> --tokens <file>`** — compares local tokens against a live site, reports `in-sync` / `minor-drift` / `notable-drift` / `major-drift` with a drift ratio. `--fail-on <level>` controls CI exit code.
|
|
19
|
+
- **`designlang visual-diff <before> <after>`** — single-file HTML side-by-side report with embedded base64 screenshots, file-size deltas, and a changed-color-tokens table.
|
|
20
|
+
|
|
21
|
+
### Added — markdown output
|
|
22
|
+
|
|
23
|
+
Three new sections in `*-design-language.md`: **Motion Language**, **Component Anatomy**, **Brand Voice**.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Default extraction now writes **11+ files** (up from 8): `*-motion-tokens.json`, `*-anatomy.tsx` (when candidates exist), `*-voice.json`.
|
|
28
|
+
- `bin/design-extract.js` version → `9.0.0`.
|
|
29
|
+
- `package.json` — description refreshed; new keywords: `motion`, `animation`, `component-anatomy`, `brand-voice`, `token-lint`, `visual-diff`.
|
|
30
|
+
- README: "What's New in v9" hero block, new feature sections 24-29, new CLI entries (`lint`, `drift`, `visual-diff`).
|
|
31
|
+
|
|
32
|
+
### Tests
|
|
33
|
+
|
|
34
|
+
- New `tests/v9-features.test.js` — 7 suites, 21 assertions across motion, anatomy, voice, and lint.
|
|
35
|
+
- Full suite: **282/282 passing**.
|
|
36
|
+
|
|
37
|
+
## [8.0.0] — 2026-04-20
|
|
38
|
+
|
|
39
|
+
A credibility-and-distribution release. Three reliability bugs that hurt trust on real sites are fixed; three DX flags close the most-requested CLI gaps; five new surfaces (VS Code, Raycast, Figma, GitHub Actions, MCP registry) ship alongside.
|
|
40
|
+
|
|
41
|
+
### Reliability
|
|
42
|
+
|
|
43
|
+
- **Brand / primary color detection rewritten** — the extractor now ranks chromatic clusters by `interactiveBg × 100 + saturation × 2 + log(usage)` and requires either HSL saturation > 25 or an interactive-bg hit to qualify as chromatic. Previously the extractor picked the most-counted color, which on neutral-heavy sites like Linear meant the "Primary" was a pale gray (`#d0d6e0`). v8 correctly picks Linear's lime CTA (`#e4f222`) and Stripe's purple (`#533afd`). `src/extractors/colors.js`.
|
|
44
|
+
- **Accessibility scoring defused** — the crawler now emits a `hasText` boolean per element (a direct text-node child with visible characters), and the WCAG extractor filters out decorative glyph wrappers, transparent/overlay pairs, and non-text containers. Linear's WCAG score moved from 25% (171 failing pairs) to 83% (1 failing pair). `src/extractors/accessibility.js`, `src/crawler.js`.
|
|
45
|
+
- **Design-system score recalibrated** — thresholds for color count, shadow count, border-radii count, typography weight, and type-scale size were re-fit against ground-truth sites (Stripe, Linear, Vercel, GitHub, Apple). `cssHealth` is now weighted in the overall (8/100). Linear 47→76, Stripe 81→88, Apple 83→86, Vercel 64→76. `src/extractors/scoring.js`.
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- **`--selector <css>`** — scopes extraction to a DOM subtree (e.g. `designlang https://stripe.com --selector "footer"`). Stripe full-page extraction drops from 2,409 elements to 112 when scoped to the footer. Falls back to the full document if the selector is invalid or empty.
|
|
50
|
+
- **`--system-chrome`** — forces Playwright to use the locally installed Chrome (`channel: 'chrome'`) instead of the ~150 MB bundled Chromium, for faster `npx` first-runs in environments that already have Chrome.
|
|
51
|
+
- **`--json` output mode** — full extraction payload written to stdout (suppresses progress UI) for piping into other tools. This was a partial implementation in v7; v8 makes it first-class and adds it to the CLI reference.
|
|
52
|
+
- **VS Code extension** (`vscode-extension/`) — `designlang: Extract design from URL` and `designlang: Extract and inject into workspace` commands.
|
|
53
|
+
- **Raycast extension** (`raycast-extension/`) — Extract, Score, and "Copy CLI command for URL" commands.
|
|
54
|
+
- **Figma plugin** (`figma-plugin/`) — URL or paste-JSON → Figma Variables collections (MV for Figma's `figma.variables` API, with multi-mode support).
|
|
55
|
+
- **GitHub Action** (`github-action/`) — "Design regression guard": runs `designlang` on a URL, diffs tokens vs a committed baseline, and comments the delta on the pull request. Optional `fail-on-change`.
|
|
56
|
+
- **Smithery + MCP registry** (`smithery.yaml`, `smithery.dockerfile`, `docs/MCP-REGISTRY.md`) — one-command install in Smithery; checklist for the official MCP registry, Cursor, and Claude Desktop.
|
|
57
|
+
- **Chrome Web Store + Firefox + Edge listing prep** (`chrome-extension/PRIVACY.md`, `chrome-extension/STORE_LISTING.md`) — privacy policy and store copy.
|
|
58
|
+
- **README hero demo tape** (`docs/demo.tape`) — VHS script that renders an animated terminal GIF into `website/public/demo.gif`.
|
|
59
|
+
- **Launch kit** (`docs/LAUNCH.md`) — Product Hunt / Show HN / Twitter copy + day-of checklist.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- README: hero image now references the animated demo (with static PNG fallback), adds an "Install everywhere" table covering all surfaces, documents `--selector`, `--system-chrome`, and `--json`.
|
|
64
|
+
- `.npmignore`: excludes all companion-surface directories (`vscode-extension/`, `raycast-extension/`, `figma-plugin/`, `github-action/`, `chrome-extension/`, `website/`) and test fixtures so the npm tarball stays small — each surface publishes to its own registry.
|
|
65
|
+
- `bin/design-extract.js`: reports `8.0.0` from `--version`.
|
|
66
|
+
- `src/config.js`: whitelists `selector` and `systemChrome` from CLI/config.
|
|
67
|
+
|
|
68
|
+
### Thanks
|
|
69
|
+
|
|
70
|
+
- To everyone who flagged that Linear's primary was coming out as light gray — that single complaint drove the brand-color rewrite.
|
|
71
|
+
|
|
3
72
|
## [7.2.0] — 2026-04-19
|
|
4
73
|
|
|
5
74
|
### Added
|
package/README.md
CHANGED
|
@@ -15,9 +15,20 @@
|
|
|
15
15
|
<img src="designlang.png" alt="designlang in action" width="100%">
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
[](https://www.npmjs.com/package/designlang)
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
**designlang** crawls any website with a headless browser, extracts every computed style from the live DOM, and generates **11+ output files** — including an AI-optimized markdown file, visual HTML preview, Tailwind config, React theme, shadcn/ui theme, Figma variables, W3C design tokens, CSS custom properties, **motion tokens**, **typed component anatomy stubs**, and a **brand voice** summary.
|
|
21
|
+
|
|
22
|
+
But unlike every other tool out there, it also extracts **layout patterns** (grids, flexbox, containers), **motion language** (durations, easings, springs, scroll-linked animations), **component anatomy** (slots, variant × size × state matrices), **brand voice** (tone, CTA verbs, heading style), captures **responsive behavior** across 4 breakpoints, records **interaction states** (hover, focus, active), scores **WCAG accessibility**, lints your own token files, and lets you **drift-check a codebase against a live site**, **visual-diff two URLs**, **compare multiple brands**, or **sync live sites to local tokens**.
|
|
23
|
+
|
|
24
|
+
## What's New in v9 — The Motion & Voice Release
|
|
25
|
+
|
|
26
|
+
- **Motion Language** — durations bucketed into semantic tokens (`instant`/`xs`/`sm`/`md`/`lg`/`xl`), easings classified into families (ease-out, spring-overshoot, steps), scroll-linked animation detection (`animation-timeline`, `view-timeline-name`), keyframe kind classification (slide / fade / reveal / rotate / scale / pulse), and a `feel` fingerprint — *springy*, *responsive*, *smooth*, *mechanical*, or *mixed*.
|
|
27
|
+
- **Component Anatomy v2** — every component cluster is now an *anatomy tree* with slots (label, icon, badge, heading, media), variant × size × state matrices, and an emitted `*-anatomy.tsx` file of typed React stubs you can wire into your design system.
|
|
28
|
+
- **Brand Voice** — extracts tone (friendly / formal / technical / playful / neutral), pronoun posture (`we→you` / `you-only` / `we-only` / `third-person`), heading style (Title Case / Sentence case / all-lowercase), top CTA verbs, and a microcopy inventory. Feeds LLMs the *voice*, not just the paint.
|
|
29
|
+
- **`designlang lint`** — audit your own `design-tokens.json` (DTCG or flat) or `variables.css` for color sprawl, spacing-scale drift, radius/shadow bloat, and WCAG fg/bg contrast fails. Exits non-zero on errors — CI-ready.
|
|
30
|
+
- **`designlang drift`** — point at a live site, pass your local token file, and get a verdict: `in-sync` / `minor-drift` / `notable-drift` / `major-drift`. Integrates cleanly with the existing GitHub Action.
|
|
31
|
+
- **`designlang visual-diff`** — capture two URLs side-by-side and emit a single-file HTML report with component screenshots, file-size deltas, and changed color tokens. No heavy pixel-diff dependencies — runs in pure Node + Playwright.
|
|
21
32
|
|
|
22
33
|
## Quick Start
|
|
23
34
|
|
|
@@ -31,7 +42,7 @@ Get everything at once:
|
|
|
31
42
|
npx designlang https://stripe.com --full
|
|
32
43
|
```
|
|
33
44
|
|
|
34
|
-
## What You Get (
|
|
45
|
+
## What You Get (11+ Files)
|
|
35
46
|
|
|
36
47
|
| File | What it is |
|
|
37
48
|
|------|------------|
|
|
@@ -43,6 +54,9 @@ npx designlang https://stripe.com --full
|
|
|
43
54
|
| `*-figma-variables.json` | Figma Variables import (with dark mode support) |
|
|
44
55
|
| `*-theme.js` | React/CSS-in-JS theme (Chakra, Stitches, Vanilla Extract) |
|
|
45
56
|
| `*-shadcn-theme.css` | shadcn/ui globals.css variables |
|
|
57
|
+
| `*-motion-tokens.json` | **(v9)** Motion tokens — durations, easings, springs, scroll-linked flag |
|
|
58
|
+
| `*-anatomy.tsx` | **(v9)** Typed React stubs for every detected component + variants |
|
|
59
|
+
| `*-voice.json` | **(v9)** Brand voice fingerprint — tone, CTA verbs, heading style |
|
|
46
60
|
|
|
47
61
|
The markdown output has **19 sections**: Color Palette, Typography, Spacing, Border Radii, Box Shadows, CSS Custom Properties, Breakpoints, Transitions & Animations, Component Patterns (with full CSS snippets), Layout System, Responsive Design, Interaction States, Accessibility (WCAG 2.1), Gradients, Z-Index Map, SVG Icons, Font Files, Image Style Patterns, and Quick Start.
|
|
48
62
|
|
|
@@ -319,7 +333,112 @@ A Manifest-v3 popup lives in [`chrome-extension/`](chrome-extension/). One click
|
|
|
319
333
|
- **Install:** toggle developer mode at `chrome://extensions`, click *Load unpacked*, pick the `chrome-extension/` folder.
|
|
320
334
|
- **Firefox + Edge** work with the same MV3 manifest.
|
|
321
335
|
|
|
322
|
-
###
|
|
336
|
+
### 24. Motion Language (NEW in v9)
|
|
337
|
+
|
|
338
|
+
Extracts the full motion fingerprint, not just transition strings:
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
designlang https://linear.app
|
|
342
|
+
# emits linear-app-motion-tokens.json
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
Motion: feel = springy, 2 spring easings, scroll-linked = yes
|
|
347
|
+
Durations: instant (80ms), xs (150ms), sm (220ms), md (380ms)
|
|
348
|
+
Easings: ease-out (61%), spring-overshoot (18%), ease-in-out (21%)
|
|
349
|
+
Keyframes: fade-up (slide-y, used 18x), scale-in (reveal, used 4x)
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### 25. Component Anatomy v2 (NEW in v9)
|
|
353
|
+
|
|
354
|
+
Every detected component becomes an anatomy tree with typed React stubs:
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
designlang https://stripe.com
|
|
358
|
+
# emits stripe-com-anatomy.tsx
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
```tsx
|
|
362
|
+
export interface ButtonProps {
|
|
363
|
+
variant?: 'primary' | 'secondary' | 'ghost';
|
|
364
|
+
size?: 'sm' | 'md' | 'lg';
|
|
365
|
+
disabled?: boolean;
|
|
366
|
+
leadingIcon?: React.ReactNode;
|
|
367
|
+
badge?: React.ReactNode;
|
|
368
|
+
children?: React.ReactNode;
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### 26. Brand Voice (NEW in v9)
|
|
373
|
+
|
|
374
|
+
Pulls the voice alongside the visual:
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
designlang https://vercel.com
|
|
378
|
+
# emits vercel-com-voice.json + a Brand Voice section in the markdown
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
```
|
|
382
|
+
Tone: technical · Pronoun: we→you · Headings: Sentence case (tight)
|
|
383
|
+
Top CTA verbs: start (14), get (8), deploy (5), try (3)
|
|
384
|
+
Sample headings:
|
|
385
|
+
> Develop. Preview. Ship.
|
|
386
|
+
> The React framework for the web.
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### 27. `designlang lint` — Token Quality Linter (NEW in v9)
|
|
390
|
+
|
|
391
|
+
Audit your own token file with the same rules the scorer runs against live sites:
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
designlang lint ./src/tokens/design-tokens.json
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
```
|
|
398
|
+
Score: 74/100 Grade: C Tokens: 126
|
|
399
|
+
|
|
400
|
+
colorDiscipline ██████████████░░░░░░ 72
|
|
401
|
+
spacingSystem ████████████████░░░░ 84
|
|
402
|
+
borderRadii ████████████░░░░░░░░ 60
|
|
403
|
+
shadows ██████████░░░░░░░░░░ 50
|
|
404
|
+
accessibility █████████████████░░░ 88
|
|
405
|
+
|
|
406
|
+
WARN [color-sprawl] 3 near-duplicate color pair(s) within 8 RGB units
|
|
407
|
+
ERROR [contrast-wcag-aa] 2 fg/bg pair(s) fail WCAG AA (4.5:1)
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Exits non-zero on any `error`-level finding — drop into CI.
|
|
411
|
+
|
|
412
|
+
### 28. `designlang drift` — Codebase ↔ Live Site Sync Check (NEW in v9)
|
|
413
|
+
|
|
414
|
+
Point at a deployed site, pass your local tokens, and get a verdict:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
designlang drift https://yourapp.com --tokens ./src/tokens.json --tolerance 8
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
Verdict: notable-drift (drift ratio: 0.24)
|
|
422
|
+
|
|
423
|
+
| token | local | nearest live | Δ |
|
|
424
|
+
|----------------|----------|--------------------|----|
|
|
425
|
+
| color.primary | #4338CA | #5B4CF5 (primary) | 22 |
|
|
426
|
+
| color.border | #D4D4D8 | #E5E5EA (surface) | 18 |
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Configurable `--fail-on <level>` for CI: `minor-drift` / `notable-drift` / `major-drift`.
|
|
430
|
+
|
|
431
|
+
### 29. `designlang visual-diff` — Two-URL Side-by-Side (NEW in v9)
|
|
432
|
+
|
|
433
|
+
Capture screenshots + token deltas for two URLs in a single self-contained HTML report:
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
designlang visual-diff https://staging.app.com https://app.com
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Emits `visual-diff-<timestamp>.html` with embedded images (base64), file-size deltas, and a changed-color-tokens table. Nothing else to serve — just open the file.
|
|
440
|
+
|
|
441
|
+
### 30. Better Auth + Network Control (v7.1)
|
|
323
442
|
|
|
324
443
|
Extracting from authenticated, self-signed, or non-default environments now takes one flag:
|
|
325
444
|
|
|
@@ -396,6 +515,9 @@ Options:
|
|
|
396
515
|
--header <headers...> Custom headers (name:value)
|
|
397
516
|
--user-agent <ua> Override the browser User-Agent string
|
|
398
517
|
--insecure Ignore HTTPS/SSL certificate errors (self-signed, dev, proxies)
|
|
518
|
+
--selector <css> Only extract from elements matching this CSS selector (e.g. ".pricing-card")
|
|
519
|
+
--system-chrome Use the system Chrome install instead of the bundled Chromium (skips 150MB download)
|
|
520
|
+
--json Print full extraction as JSON to stdout (for piping into other tools)
|
|
399
521
|
--framework <type> Only generate specific theme (react, shadcn)
|
|
400
522
|
--platforms <csv> Additional platforms: web,ios,android,flutter,wordpress,all (additive)
|
|
401
523
|
--emit-agent-rules Emit Cursor / Claude Code / CLAUDE.md / agents.md rule files
|
|
@@ -404,15 +526,18 @@ Options:
|
|
|
404
526
|
--verbose Detailed progress output
|
|
405
527
|
|
|
406
528
|
Commands:
|
|
407
|
-
apply <url>
|
|
408
|
-
clone <url>
|
|
409
|
-
score <url>
|
|
410
|
-
watch <url>
|
|
411
|
-
diff <urlA> <urlB>
|
|
412
|
-
brands <urls...>
|
|
413
|
-
sync <url>
|
|
414
|
-
history <url>
|
|
415
|
-
mcp
|
|
529
|
+
apply <url> Extract and apply design directly to your project
|
|
530
|
+
clone <url> Generate a working Next.js starter from extracted design
|
|
531
|
+
score <url> Rate design quality (7 categories, A-F, bar chart)
|
|
532
|
+
watch <url> Monitor for design changes on interval
|
|
533
|
+
diff <urlA> <urlB> Compare two sites' design languages
|
|
534
|
+
brands <urls...> Multi-brand comparison matrix
|
|
535
|
+
sync <url> Sync local tokens with live site
|
|
536
|
+
history <url> View design change history
|
|
537
|
+
mcp Launch stdio MCP server (--output-dir <dir>)
|
|
538
|
+
lint <file> (v9) Audit a local token file (.json/.css) — CI-ready
|
|
539
|
+
drift <url> --tokens <file> (v9) Check local tokens for drift against a live site
|
|
540
|
+
visual-diff <before> <after> (v9) Side-by-side HTML diff of two URLs
|
|
416
541
|
```
|
|
417
542
|
|
|
418
543
|
## Example Output
|
|
@@ -468,6 +593,20 @@ Running `designlang https://vercel.com --full`:
|
|
|
468
593
|
5. **Score** — Accessibility extractor calculates WCAG contrast ratios for all color pairs
|
|
469
594
|
6. **Capture** — Optional: screenshots, responsive viewport crawling, interaction state recording
|
|
470
595
|
|
|
596
|
+
## Install Everywhere
|
|
597
|
+
|
|
598
|
+
designlang ships surfaces beyond the CLI:
|
|
599
|
+
|
|
600
|
+
| Surface | Path | Description |
|
|
601
|
+
|---------|------|-------------|
|
|
602
|
+
| **CLI** | `npx designlang <url>` | Main entry point. |
|
|
603
|
+
| **VS Code extension** | [`vscode-extension/`](vscode-extension/) | "Extract design from URL" command + auto-inject into workspace. |
|
|
604
|
+
| **Raycast extension** | [`raycast-extension/`](raycast-extension/) | Extract, score, and "copy CLI command" from Raycast. |
|
|
605
|
+
| **Figma plugin** | [`figma-plugin/`](figma-plugin/) | Paste a URL inside Figma, get a full Variables collection. |
|
|
606
|
+
| **GitHub Action** | [`github-action/`](github-action/) | "Design regression guard" — diffs tokens on every PR and comments. |
|
|
607
|
+
| **Chrome extension** | [`chrome-extension/`](chrome-extension/) | One-click handoff from any tab (MV3, `activeTab` only). |
|
|
608
|
+
| **MCP server** | `npx designlang mcp` | Exposes the extracted design as MCP resources + tools for Cursor, Claude Code, Windsurf, etc. See [`docs/MCP-REGISTRY.md`](docs/MCP-REGISTRY.md). |
|
|
609
|
+
|
|
471
610
|
## Agent Skill
|
|
472
611
|
|
|
473
612
|
Works with **Claude Code, Cursor, Codex, and 40+ AI coding agents** via the skills ecosystem:
|
|
@@ -489,3 +628,5 @@ See [CONTRIBUTING.md](CONTRIBUTING.md). PRs welcome!
|
|
|
489
628
|
## License
|
|
490
629
|
|
|
491
630
|
[MIT](LICENSE) - Manav Arya Singh
|
|
631
|
+
|
|
632
|
+
|
package/bin/design-extract.js
CHANGED
|
@@ -48,7 +48,7 @@ const program = new Command();
|
|
|
48
48
|
program
|
|
49
49
|
.name('designlang')
|
|
50
50
|
.description('Extract the complete design language from any website')
|
|
51
|
-
.version('
|
|
51
|
+
.version('9.0.0');
|
|
52
52
|
|
|
53
53
|
// ── Main command: extract ──────────────────────────────────────
|
|
54
54
|
program
|
|
@@ -72,6 +72,8 @@ program
|
|
|
72
72
|
.option('--user-agent <ua>', 'override the browser User-Agent string')
|
|
73
73
|
.option('--insecure', 'ignore HTTPS/SSL certificate errors (self-signed, dev, proxies)')
|
|
74
74
|
.option('--ignore <selectors...>', 'CSS selectors to remove before extraction')
|
|
75
|
+
.option('--selector <css>', 'only extract design from elements matching this CSS selector (e.g. ".pricing-card")')
|
|
76
|
+
.option('--system-chrome', 'use the system Chrome install instead of the bundled Chromium (skips the 150MB Playwright download)')
|
|
75
77
|
.option('--tokens-legacy', 'Emit pre-v7 flat token JSON (backward compat)')
|
|
76
78
|
.option('--platforms <csv>', 'Additional platforms: web,ios,android,flutter,wordpress,all (web is always emitted)', 'web')
|
|
77
79
|
.option('--emit-agent-rules', 'Emit Cursor/Claude Code/generic agent rules')
|
|
@@ -155,6 +157,8 @@ program
|
|
|
155
157
|
insecure: merged.insecure || false,
|
|
156
158
|
userAgent: merged.userAgent,
|
|
157
159
|
deepInteract: merged.deepInteract || merged.full,
|
|
160
|
+
selector: merged.selector,
|
|
161
|
+
channel: merged.systemChrome ? 'chrome' : undefined,
|
|
158
162
|
});
|
|
159
163
|
|
|
160
164
|
// Responsive capture
|
|
@@ -217,6 +221,15 @@ program
|
|
|
217
221
|
};
|
|
218
222
|
files.push({ name: `${prefix}-mcp.json`, content: JSON.stringify(mcpPayload, null, 2), label: 'MCP companion' });
|
|
219
223
|
|
|
224
|
+
// v9: motion tokens + component anatomy stubs + voice
|
|
225
|
+
const { formatMotionTokens } = await import('../src/formatters/motion-tokens.js');
|
|
226
|
+
const { formatAnatomyStubs } = await import('../src/extractors/component-anatomy.js');
|
|
227
|
+
files.push({ name: `${prefix}-motion-tokens.json`, content: formatMotionTokens(design.motion), label: 'Motion Tokens' });
|
|
228
|
+
if ((design.componentAnatomy || []).length) {
|
|
229
|
+
files.push({ name: `${prefix}-anatomy.tsx`, content: formatAnatomyStubs(design.componentAnatomy), label: 'Component Anatomy (stubs)' });
|
|
230
|
+
}
|
|
231
|
+
files.push({ name: `${prefix}-voice.json`, content: JSON.stringify(design.voice || {}, null, 2), label: 'Brand Voice' });
|
|
232
|
+
|
|
220
233
|
for (const file of files) {
|
|
221
234
|
writeFileSync(join(outDir, file.name), file.content, 'utf-8');
|
|
222
235
|
}
|
|
@@ -800,6 +813,86 @@ program
|
|
|
800
813
|
}
|
|
801
814
|
});
|
|
802
815
|
|
|
816
|
+
// ── Token lint (v9) ────────────────────────────────────────
|
|
817
|
+
program
|
|
818
|
+
.command('lint <file>')
|
|
819
|
+
.description('Audit a local token file (.json / .css) for color sprawl, scale drift, contrast fails')
|
|
820
|
+
.option('--json', 'emit machine-readable JSON')
|
|
821
|
+
.action(async (file, opts) => {
|
|
822
|
+
try {
|
|
823
|
+
const { lintTokens } = await import('../src/lint.js');
|
|
824
|
+
const r = lintTokens(resolve(file));
|
|
825
|
+
if (opts.json) { process.stdout.write(JSON.stringify(r, null, 2) + '\n'); return; }
|
|
826
|
+
console.log('');
|
|
827
|
+
console.log(chalk.bold(` designlang lint — ${file}`));
|
|
828
|
+
console.log(` Score: ${chalk.bold(r.score + '/100')} Grade: ${chalk.bold(r.grade)} Tokens: ${r.tokenCount}`);
|
|
829
|
+
console.log('');
|
|
830
|
+
for (const [k, v] of Object.entries(r.scorecard)) {
|
|
831
|
+
const bar = '█'.repeat(Math.round(v / 5)) + '░'.repeat(20 - Math.round(v / 5));
|
|
832
|
+
console.log(` ${k.padEnd(20)} ${bar} ${v}`);
|
|
833
|
+
}
|
|
834
|
+
console.log('');
|
|
835
|
+
for (const f of r.findings) {
|
|
836
|
+
const color = f.severity === 'error' ? chalk.red : f.severity === 'warn' ? chalk.yellow : chalk.cyan;
|
|
837
|
+
console.log(` ${color(f.severity.toUpperCase())} [${f.rule}] ${f.message}`);
|
|
838
|
+
}
|
|
839
|
+
if (!r.findings.length) console.log(chalk.green(' ✓ no issues found'));
|
|
840
|
+
console.log('');
|
|
841
|
+
process.exit(r.findings.some(f => f.severity === 'error') ? 1 : 0);
|
|
842
|
+
} catch (err) {
|
|
843
|
+
process.stderr.write(chalk.red(`\n Error: ${err.message}\n\n`));
|
|
844
|
+
process.exit(1);
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
// ── Drift (v9) ─────────────────────────────────────────────
|
|
849
|
+
program
|
|
850
|
+
.command('drift <url>')
|
|
851
|
+
.description('Compare local tokens against a live site and report drift (CI-friendly)')
|
|
852
|
+
.requiredOption('--tokens <file>', 'local tokens file (.json or .css)')
|
|
853
|
+
.option('--tolerance <n>', 'color distance tolerance (0-50)', parseInt, 8)
|
|
854
|
+
.option('--fail-on <level>', 'exit non-zero on: minor-drift | notable-drift | major-drift', 'notable-drift')
|
|
855
|
+
.option('--json', 'emit machine-readable JSON')
|
|
856
|
+
.action(async (url, opts) => {
|
|
857
|
+
if (!url.startsWith('http')) url = `https://${url}`;
|
|
858
|
+
validateUrl(url);
|
|
859
|
+
try {
|
|
860
|
+
const { checkDrift, formatDriftMarkdown } = await import('../src/drift.js');
|
|
861
|
+
const r = await checkDrift(url, { tokens: resolve(opts.tokens), tolerance: opts.tolerance });
|
|
862
|
+
if (opts.json) { process.stdout.write(JSON.stringify(r, null, 2) + '\n'); }
|
|
863
|
+
else { console.log('\n' + formatDriftMarkdown(r) + '\n'); }
|
|
864
|
+
const order = ['in-sync', 'minor-drift', 'notable-drift', 'major-drift'];
|
|
865
|
+
if (order.indexOf(r.verdict) >= order.indexOf(opts.failOn)) process.exit(1);
|
|
866
|
+
} catch (err) {
|
|
867
|
+
process.stderr.write(chalk.red(`\n Error: ${err.message}\n\n`));
|
|
868
|
+
process.exit(1);
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
// ── Visual diff (v9) ───────────────────────────────────────
|
|
873
|
+
program
|
|
874
|
+
.command('visual-diff <before> <after>')
|
|
875
|
+
.description('Side-by-side HTML diff of two URLs with screenshots + token changes')
|
|
876
|
+
.option('-o, --out <dir>', 'output directory', './design-extract-output')
|
|
877
|
+
.action(async (before, after, opts) => {
|
|
878
|
+
if (!before.startsWith('http')) before = `https://${before}`;
|
|
879
|
+
if (!after.startsWith('http')) after = `https://${after}`;
|
|
880
|
+
validateUrl(before); validateUrl(after);
|
|
881
|
+
const spinner = ora('Capturing before + after').start();
|
|
882
|
+
try {
|
|
883
|
+
const { visualDiff, formatVisualDiffHtml } = await import('../src/visual-diff.js');
|
|
884
|
+
const r = await visualDiff({ beforeUrl: before, afterUrl: after });
|
|
885
|
+
const html = formatVisualDiffHtml(r);
|
|
886
|
+
mkdirSync(resolve(opts.out), { recursive: true });
|
|
887
|
+
const path = join(resolve(opts.out), `visual-diff-${Date.now()}.html`);
|
|
888
|
+
writeFileSync(path, html, 'utf8');
|
|
889
|
+
spinner.succeed(`Visual diff written → ${path}`);
|
|
890
|
+
} catch (err) {
|
|
891
|
+
spinner.fail(err.message);
|
|
892
|
+
process.exit(1);
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
|
|
803
896
|
// ── MCP server command ─────────────────────────────────────
|
|
804
897
|
program
|
|
805
898
|
.command('mcp')
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "designlang",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Extract the complete design language from any website — colors, typography, spacing, shadows, and
|
|
3
|
+
"version": "9.0.0",
|
|
4
|
+
"description": "Extract the complete design language from any website — colors, typography, spacing, shadows, motion, component anatomy, and brand voice. Outputs AI-optimized markdown, W3C design tokens, motion tokens, typed component stubs, Tailwind config, and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"designlang": "./bin/design-extract.js"
|
|
@@ -38,7 +38,13 @@
|
|
|
38
38
|
"vue",
|
|
39
39
|
"svelte",
|
|
40
40
|
"json",
|
|
41
|
-
"ci-cd"
|
|
41
|
+
"ci-cd",
|
|
42
|
+
"motion",
|
|
43
|
+
"animation",
|
|
44
|
+
"component-anatomy",
|
|
45
|
+
"brand-voice",
|
|
46
|
+
"token-lint",
|
|
47
|
+
"visual-diff"
|
|
42
48
|
],
|
|
43
49
|
"author": "masyv",
|
|
44
50
|
"license": "MIT"
|
package/src/config.js
CHANGED
|
@@ -39,6 +39,8 @@ export function mergeConfig(cliOpts, config) {
|
|
|
39
39
|
cookieFile: cliOpts.cookieFile || config.cookieFile,
|
|
40
40
|
insecure: cliOpts.insecure || config.insecure || false,
|
|
41
41
|
userAgent: cliOpts.userAgent || config.userAgent,
|
|
42
|
+
selector: cliOpts.selector || config.selector,
|
|
43
|
+
systemChrome: cliOpts.systemChrome || config.systemChrome || false,
|
|
42
44
|
};
|
|
43
45
|
}
|
|
44
46
|
|
package/src/crawler.js
CHANGED
|
@@ -24,6 +24,8 @@ export async function crawlPage(url, options = {}) {
|
|
|
24
24
|
insecure = false,
|
|
25
25
|
userAgent,
|
|
26
26
|
deepInteract = false,
|
|
27
|
+
selector,
|
|
28
|
+
channel,
|
|
27
29
|
} = options;
|
|
28
30
|
|
|
29
31
|
const launchArgs = [
|
|
@@ -39,6 +41,9 @@ export async function crawlPage(url, options = {}) {
|
|
|
39
41
|
const browser = await chromium.launch({
|
|
40
42
|
headless: true,
|
|
41
43
|
...(executablePath && { executablePath }),
|
|
44
|
+
// channel: 'chrome' forces Playwright to use the system Chrome install
|
|
45
|
+
// instead of the 150MB bundled Chromium — see --system-chrome.
|
|
46
|
+
...(channel && { channel }),
|
|
42
47
|
args: launchArgs,
|
|
43
48
|
});
|
|
44
49
|
try {
|
|
@@ -97,7 +102,7 @@ export async function crawlPage(url, options = {}) {
|
|
|
97
102
|
interactState = await runInteractionPass(page).catch(() => null);
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
const lightData = await extractPageData(page, ignore);
|
|
105
|
+
const lightData = await extractPageData(page, ignore, selector);
|
|
101
106
|
lightData.cssCoverage = cssCoverage;
|
|
102
107
|
if (interactState) lightData.interactState = interactState;
|
|
103
108
|
|
|
@@ -388,8 +393,8 @@ async function runInteractionPass(page) {
|
|
|
388
393
|
return state;
|
|
389
394
|
}
|
|
390
395
|
|
|
391
|
-
async function extractPageData(page, ignoreSelectors) {
|
|
392
|
-
const data = await page.evaluate(({ maxElements, ignoreSelectors }) => {
|
|
396
|
+
async function extractPageData(page, ignoreSelectors, scopeSelector) {
|
|
397
|
+
const data = await page.evaluate(({ maxElements, ignoreSelectors, scopeSelector }) => {
|
|
393
398
|
// Remove ignored elements before extraction
|
|
394
399
|
if (ignoreSelectors && ignoreSelectors.length > 0) {
|
|
395
400
|
for (const sel of ignoreSelectors) {
|
|
@@ -420,7 +425,23 @@ async function extractPageData(page, ignoreSelectors) {
|
|
|
420
425
|
}
|
|
421
426
|
return collected;
|
|
422
427
|
}
|
|
423
|
-
|
|
428
|
+
|
|
429
|
+
// If --selector was provided, scope element collection to the matching
|
|
430
|
+
// subtrees only. Falls back to the full document if the selector is
|
|
431
|
+
// invalid or returns no matches.
|
|
432
|
+
let scopeRoots = [document];
|
|
433
|
+
if (scopeSelector) {
|
|
434
|
+
try {
|
|
435
|
+
const matches = Array.from(document.querySelectorAll(scopeSelector));
|
|
436
|
+
if (matches.length > 0) scopeRoots = matches;
|
|
437
|
+
} catch { /* invalid selector → use document */ }
|
|
438
|
+
}
|
|
439
|
+
const elements = [];
|
|
440
|
+
for (const root of scopeRoots) {
|
|
441
|
+
if (root !== document && root.nodeType === 1) elements.push(root);
|
|
442
|
+
collectElements(root, elements);
|
|
443
|
+
if (elements.length >= maxElements) break;
|
|
444
|
+
}
|
|
424
445
|
|
|
425
446
|
// Build a lightweight index: stylesheet URL + their top selectors.
|
|
426
447
|
// Used to attribute each element's primary source stylesheet.
|
|
@@ -502,8 +523,16 @@ async function extractPageData(page, ignoreSelectors) {
|
|
|
502
523
|
sourceAttrBudget--;
|
|
503
524
|
}
|
|
504
525
|
|
|
526
|
+
// hasText: at least one direct text-node child with visible characters —
|
|
527
|
+
// lets downstream extractors filter decorative spans/divs out of WCAG
|
|
528
|
+
// contrast accounting.
|
|
529
|
+
let hasText = false;
|
|
530
|
+
for (const node of el.childNodes) {
|
|
531
|
+
if (node.nodeType === 3 && node.textContent && node.textContent.trim()) { hasText = true; break; }
|
|
532
|
+
}
|
|
533
|
+
|
|
505
534
|
results.computedStyles.push({
|
|
506
|
-
tag, classList, role, area,
|
|
535
|
+
tag, classList, role, area, hasText,
|
|
507
536
|
color: cs.color,
|
|
508
537
|
backgroundColor: cs.backgroundColor,
|
|
509
538
|
backgroundImage: cs.backgroundImage,
|
|
@@ -530,6 +559,11 @@ async function extractPageData(page, ignoreSelectors) {
|
|
|
530
559
|
zIndex: cs.zIndex,
|
|
531
560
|
transition: cs.transition,
|
|
532
561
|
animation: cs.animation,
|
|
562
|
+
animationTimeline: cs.animationTimeline || cs.getPropertyValue('animation-timeline') || '',
|
|
563
|
+
animationRangeStart: cs.getPropertyValue('animation-range-start') || '',
|
|
564
|
+
animationRangeEnd: cs.getPropertyValue('animation-range-end') || '',
|
|
565
|
+
viewTimelineName: cs.getPropertyValue('view-timeline-name') || '',
|
|
566
|
+
scrollTimelineName: cs.getPropertyValue('scroll-timeline-name') || '',
|
|
533
567
|
display: cs.display,
|
|
534
568
|
position: cs.position,
|
|
535
569
|
flexDirection: cs.flexDirection,
|
|
@@ -728,10 +762,25 @@ async function extractPageData(page, ignoreSelectors) {
|
|
|
728
762
|
parseFloat(cs.fontSize) || 0,
|
|
729
763
|
parseFloat(cs.fontWeight) || 0,
|
|
730
764
|
];
|
|
765
|
+
const text = ((el.innerText || el.textContent || '') + '').trim().slice(0, 160);
|
|
766
|
+
const slots = Array.from(el.children).slice(0, 8).map(c => {
|
|
767
|
+
const tagName = c.tagName.toLowerCase();
|
|
768
|
+
let role = 'content';
|
|
769
|
+
if (tagName === 'svg' || tagName === 'img' || c.querySelector?.('svg,img')) role = 'icon';
|
|
770
|
+
else if (/badge|pill|tag|chip/i.test(c.className || '')) role = 'badge';
|
|
771
|
+
else if (/h[1-6]/.test(tagName) || /title|heading/i.test(c.className || '')) role = 'heading';
|
|
772
|
+
else if (/description|subtitle|text|body/i.test(c.className || '')) role = 'text';
|
|
773
|
+
return { tag: tagName, role, text: ((c.innerText || c.textContent || '') + '').trim().slice(0, 80) };
|
|
774
|
+
});
|
|
731
775
|
results.componentCandidates.push({
|
|
732
776
|
kind,
|
|
733
777
|
structuralHash: structuralHashOf(el),
|
|
734
778
|
styleVector,
|
|
779
|
+
text,
|
|
780
|
+
slots,
|
|
781
|
+
disabled: el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true',
|
|
782
|
+
variantHint: (cls.match(/\b(primary|secondary|tertiary|ghost|outline|solid|destructive|danger|success|warning|link|subtle)\b/) || [])[1] || '',
|
|
783
|
+
sizeHint: (cls.match(/\b(xs|sm|md|lg|xl|small|medium|large)\b/) || [])[1] || '',
|
|
735
784
|
css: {
|
|
736
785
|
background: cs.backgroundColor,
|
|
737
786
|
color: cs.color,
|
|
@@ -840,7 +889,7 @@ async function extractPageData(page, ignoreSelectors) {
|
|
|
840
889
|
}
|
|
841
890
|
|
|
842
891
|
return results;
|
|
843
|
-
}, { maxElements: MAX_ELEMENTS, ignoreSelectors: ignoreSelectors || [] });
|
|
892
|
+
}, { maxElements: MAX_ELEMENTS, ignoreSelectors: ignoreSelectors || [], scopeSelector: scopeSelector || null });
|
|
844
893
|
|
|
845
894
|
// Fetch and parse cross-origin stylesheets
|
|
846
895
|
if (data.crossOriginSheets && data.crossOriginSheets.length > 0) {
|