designlang 12.2.0 → 12.4.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 +76 -0
- package/README.md +12 -4
- package/bin/design-extract.js +124 -1
- package/package.json +1 -1
- package/src/chat.js +0 -14
- package/src/formatters/css-vars.js +0 -2
- package/src/formatters/remix.js +379 -0
- package/src/formatters/tailwind.js +1 -1
- package/src/formatters/vue-theme.js +0 -2
- package/src/history.js +1 -1
- package/src/index.js +1 -2
- package/src/pack.js +376 -0
- package/src/studio.js +2 -2
- package/src/sync.js +17 -6
- package/src/visual-diff.js +0 -1
- package/src/vocabularies/art-deco.js +79 -0
- package/src/vocabularies/brutalist.js +72 -0
- package/src/vocabularies/cyberpunk.js +92 -0
- package/src/vocabularies/editorial.js +75 -0
- package/src/vocabularies/index.js +35 -0
- package/src/vocabularies/soft-ui.js +83 -0
- package/src/vocabularies/swiss.js +60 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,81 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [12.4.0] — 2026-05-05
|
|
4
|
+
|
|
5
|
+
**Pack — one command, one polished design-system bundle.**
|
|
6
|
+
|
|
7
|
+
\`designlang pack <url>\` runs the full extraction once and writes every
|
|
8
|
+
emitter output into a single, signed, layered directory. One artifact a
|
|
9
|
+
designer or dev can clone, drop into a project, or zip up and send to a
|
|
10
|
+
client. Closes [#59](https://github.com/Manavarya09/design-extract/issues/59).
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- New CLI command: \`designlang pack <url> [-o <dir>] [--with-clone] [--open]\`
|
|
15
|
+
- New module \`src/pack.js\` exporting \`buildPack(design, opts)\`
|
|
16
|
+
- Layout:
|
|
17
|
+
\`\`\`
|
|
18
|
+
<host>-design-system/
|
|
19
|
+
├── README.md — bespoke, "Built from <host>" + grade + at-a-glance stats
|
|
20
|
+
├── LICENSE.txt — provenance + usage guidance
|
|
21
|
+
├── tokens/ — DTCG + Tailwind + CSS vars + Figma vars + motion + theme.js
|
|
22
|
+
├── components/ — typed React stubs (anatomy.tsx)
|
|
23
|
+
├── storybook/ — runnable Storybook project
|
|
24
|
+
├── starter/ — minimal HTML starter wired to tokens/variables.css
|
|
25
|
+
├── prompts/ — v0.txt · lovable.txt · cursor.md · claude-artifacts.md
|
|
26
|
+
│ └── recipes/ — recipe-<component>.md cards (named, no longer indexed)
|
|
27
|
+
└── extras/ — voice.json + prompt-pack.md rollup
|
|
28
|
+
\`\`\`
|
|
29
|
+
- 7 new tests covering directory shape, valid JSON outputs, README content,
|
|
30
|
+
starter wiring, recipe filenames, and a regression test for the
|
|
31
|
+
double-stringify bug that broke first integration.
|
|
32
|
+
|
|
33
|
+
### Why
|
|
34
|
+
|
|
35
|
+
The 17+ loose files designlang already emits are the right pieces, but
|
|
36
|
+
asking a user to zip them themselves is friction. \`pack\` is the same
|
|
37
|
+
artifacts as one polished, downloadable, cite-able bundle.
|
|
38
|
+
|
|
39
|
+
## [12.3.0] — 2026-05-05
|
|
40
|
+
|
|
41
|
+
**Remix — restyle any site in a different design vocabulary.**
|
|
42
|
+
|
|
43
|
+
A genuinely new product surface: take an extracted page-shape (sections,
|
|
44
|
+
voice, page-intent, anatomy) and re-render it under one of six
|
|
45
|
+
opinionated design vocabularies. "What would stripe.com look like if it
|
|
46
|
+
had been designed brutalist? Or art-deco? Or cyberpunk?"
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- **`designlang remix <url> --as <vocab>`** — re-renders the audited page
|
|
51
|
+
using the host's *own copy* (headings, ledes, CTA verbs from voice) but
|
|
52
|
+
styled in another vocabulary. Six built-ins:
|
|
53
|
+
- `brutalist` — hard edges, mono type, single screaming accent
|
|
54
|
+
- `swiss` — Helvetica, grids, restraint (post-Bauhaus default)
|
|
55
|
+
- `art-deco` — gold on ink, geometric ornament, vertical type
|
|
56
|
+
- `cyberpunk` — neon on midnight, scanlines, mono with glitch energy
|
|
57
|
+
- `soft-ui` — cushioned shapes, low contrast, Vision-OS-adjacent
|
|
58
|
+
- `editorial` — broadsheet serifs, generous whitespace, ink on paper
|
|
59
|
+
- `--all` flag emits one HTML per vocabulary in a single extraction.
|
|
60
|
+
- `--list` prints the vocabulary registry with blurbs.
|
|
61
|
+
- New formatter: `src/formatters/remix.js` — maps every section role
|
|
62
|
+
(hero, feature-grid, pricing-table, stats, testimonial, faq,
|
|
63
|
+
logo-wall, steps, cta) to vocabulary-styled markup.
|
|
64
|
+
- New module: `src/vocabularies/` — six self-contained vocab definitions
|
|
65
|
+
(tokens + font stack + signature CSS) plus `index.js` registry.
|
|
66
|
+
- Hero-deduplication: real-world section walkers (especially on SPA
|
|
67
|
+
marketing pages) often emit a hero wrapper + an inner hero with the
|
|
68
|
+
same h1. Remix now dedupes by heading and excludes claimed headings
|
|
69
|
+
from the voice pool, so heading-less sections (cta bands, logo walls)
|
|
70
|
+
don't re-render an already-claimed heading.
|
|
71
|
+
- 14 new tests (350 total, all passing). Cover registry shape,
|
|
72
|
+
per-vocab token validity, dedup, XSS escaping, missing-input errors.
|
|
73
|
+
|
|
74
|
+
Why: Grade (v12.1) is the audit, Battle (v12.2) is the comparison,
|
|
75
|
+
Remix is the *transformation*. Pure visual moat — no competitor
|
|
76
|
+
(Dembrandt, Superposition, html.to.design, Builder Visual Copilot)
|
|
77
|
+
ships site-shape-preserving vocabulary swap.
|
|
78
|
+
|
|
3
79
|
## [12.2.0] — 2026-05-02
|
|
4
80
|
|
|
5
81
|
**Battle cards + design score badges — distribution + virality on top of Grade.**
|
package/README.md
CHANGED
|
@@ -26,8 +26,11 @@ It also goes where extractors don't: **layout patterns**, **responsive behavior
|
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
npx designlang https://stripe.com # extract everything
|
|
29
|
-
npx designlang
|
|
30
|
-
npx designlang
|
|
29
|
+
npx designlang pack stripe.com # one polished design-system directory ← v12.4
|
|
30
|
+
npx designlang remix stripe.com --as cyberpunk # restyle in another vocabulary ← v12.3
|
|
31
|
+
npx designlang remix stripe.com --all # emit all 6 vocabs at once ← v12.3
|
|
32
|
+
npx designlang grade https://stripe.com --badge # report card + SVG badge ← v12.2
|
|
33
|
+
npx designlang battle stripe.com vercel.com # head-to-head graded fight ← v12.2
|
|
31
34
|
npx designlang clone https://stripe.com # working Next.js starter
|
|
32
35
|
npx designlang --full https://stripe.com # screenshots + responsive + interactions
|
|
33
36
|
```
|
|
@@ -65,6 +68,7 @@ Each run writes 17+ files to `./design-extract-output/`. The headline outputs:
|
|
|
65
68
|
| `*-grade.html` | **v12.1** Shareable Design Report Card (letter grade + evidence) |
|
|
66
69
|
| `*-grade.svg` | **v12.2** Shields.io-style design-score badge (drop into any README) |
|
|
67
70
|
| `*-battle.html` | **v12.2** Head-to-head graded battle card from `designlang battle` |
|
|
71
|
+
| `*-remix.<vocab>.html` | **v12.3** Site restyled in another vocabulary — brutalist / swiss / art-deco / cyberpunk / soft-ui / editorial |
|
|
68
72
|
|
|
69
73
|
Multi-platform (`--platforms web,ios,android,flutter,wordpress,all`) adds `ios/`, `android/`, `flutter/`, and a WordPress block theme. `--emit-agent-rules` adds Cursor / Claude Code / generic agent rule files.
|
|
70
74
|
|
|
@@ -124,8 +128,10 @@ designlang mcp # stdio MCP server for Cursor / Clau
|
|
|
124
128
|
| Clone | `designlang clone <url>` | Generate a working Next.js starter with extracted design |
|
|
125
129
|
| Score | `designlang score <url>` | Rate design quality with visual bar chart breakdown |
|
|
126
130
|
| Grade (v12.1) | `designlang grade <url>` | Shareable HTML "Design Report Card" — letter grade, 8 dimensions, evidence, strengths + fixes |
|
|
127
|
-
| Battle (
|
|
128
|
-
| Badge (
|
|
131
|
+
| Battle (v12.2) | `designlang battle <A> <B>` | Head-to-head graded battle card with verdict, dimension table, palette comparison |
|
|
132
|
+
| Badge (v12.2) | `designlang grade --badge` | Shields.io-style SVG badge — `design · B · 87` — drop into any README. Live endpoint: `designlang.app/badge/<host>.svg` |
|
|
133
|
+
| Remix (v12.3) | `designlang remix <url> --as <vocab>` | Restyle the audited page in another vocabulary (brutalist / swiss / art-deco / cyberpunk / soft-ui / editorial). `--all` emits all 6 |
|
|
134
|
+
| Pack (NEW v12.4) | `designlang pack <url>` | Bundle every output (tokens / components / Storybook / starter / prompts) into one polished design-system directory |
|
|
129
135
|
| Watch | `designlang watch <url>` | Monitor for design changes on interval |
|
|
130
136
|
| Diff | `designlang diff <A> <B>` | Compare two sites (MD + HTML) |
|
|
131
137
|
| Multi-brand | `designlang brands <urls...>` | N-site comparison matrix |
|
|
@@ -180,6 +186,8 @@ Commands:
|
|
|
180
186
|
score <url> Rate design quality (7 categories, A-F, bar chart)
|
|
181
187
|
grade <url> Generate a shareable HTML Design Report Card (--format html|md|json|svg|all, --badge, --open)
|
|
182
188
|
battle <urlA> <urlB> Head-to-head graded battle card (--format html|md|json|all, --open)
|
|
189
|
+
remix <url> Restyle in another vocabulary (--as brutalist|swiss|art-deco|cyberpunk|soft-ui|editorial, --all, --list, --open)
|
|
190
|
+
pack <url> Bundle every output into one design-system directory (--with-clone, --open)
|
|
183
191
|
watch <url> Monitor for design changes on interval
|
|
184
192
|
diff <urlA> <urlB> Compare two sites' design languages
|
|
185
193
|
brands <urls...> Multi-brand comparison matrix
|
package/bin/design-extract.js
CHANGED
|
@@ -43,11 +43,13 @@ import { syncDesign } from '../src/sync.js';
|
|
|
43
43
|
import { compareBrands, formatBrandMatrix, formatBrandMatrixHtml } from '../src/multibrand.js';
|
|
44
44
|
import { generateClone } from '../src/clone.js';
|
|
45
45
|
import { watchSite } from '../src/watch.js';
|
|
46
|
-
import { diffDarkMode } from '../src/darkdiff.js';
|
|
47
46
|
import { applyDesign } from '../src/apply.js';
|
|
48
47
|
import { formatGrade, formatGradeMarkdown } from '../src/formatters/grade.js';
|
|
49
48
|
import { formatBattle, formatBattleMarkdown } from '../src/formatters/battle.js';
|
|
50
49
|
import { formatScoreBadge } from '../src/formatters/badge.js';
|
|
50
|
+
import { formatRemix } from '../src/formatters/remix.js';
|
|
51
|
+
import { VOCABULARIES, getVocabulary, listVocabularies } from '../src/vocabularies/index.js';
|
|
52
|
+
import { buildPack } from '../src/pack.js';
|
|
51
53
|
import { nameFromUrl } from '../src/utils.js';
|
|
52
54
|
|
|
53
55
|
function validateUrl(url) {
|
|
@@ -1102,6 +1104,127 @@ program
|
|
|
1102
1104
|
}
|
|
1103
1105
|
});
|
|
1104
1106
|
|
|
1107
|
+
// ── Remix command — restyle an extracted page in another vocabulary ─
|
|
1108
|
+
program
|
|
1109
|
+
.command('remix <url>')
|
|
1110
|
+
.description('Restyle a site in a different design vocabulary (brutalist, swiss, art-deco, cyberpunk, soft-ui, editorial)')
|
|
1111
|
+
.option('-o, --out <dir>', 'output directory', './design-extract-output')
|
|
1112
|
+
.option('-n, --name <name>', 'output file prefix (default: derived from URL)')
|
|
1113
|
+
.option('--as <vocab>', 'vocabulary id (run `designlang remix --list` to see all)', 'brutalist')
|
|
1114
|
+
.option('--list', 'list all vocabularies and exit')
|
|
1115
|
+
.option('--all', 'emit one HTML per vocabulary (six files at once)')
|
|
1116
|
+
.option('--open', 'open the result in the default browser')
|
|
1117
|
+
.action(async (url, opts) => {
|
|
1118
|
+
if (opts.list) {
|
|
1119
|
+
console.log('');
|
|
1120
|
+
console.log(chalk.bold(' Vocabularies'));
|
|
1121
|
+
console.log('');
|
|
1122
|
+
for (const v of listVocabularies()) {
|
|
1123
|
+
console.log(` ${chalk.cyan(v.id.padEnd(14))} ${chalk.gray(v.blurb)}`);
|
|
1124
|
+
}
|
|
1125
|
+
console.log('');
|
|
1126
|
+
console.log(chalk.gray(` Use: designlang remix <url> --as <id>`));
|
|
1127
|
+
console.log('');
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
if (!url.startsWith('http')) url = `https://${url}`;
|
|
1131
|
+
validateUrl(url);
|
|
1132
|
+
|
|
1133
|
+
const vocabIds = opts.all ? Object.keys(VOCABULARIES) : [opts.as];
|
|
1134
|
+
// Validate vocab early so we fail before extraction.
|
|
1135
|
+
for (const id of vocabIds) getVocabulary(id);
|
|
1136
|
+
|
|
1137
|
+
const spinner = ora(`Extracting ${url}...`).start();
|
|
1138
|
+
try {
|
|
1139
|
+
const design = await extractDesignLanguage(url);
|
|
1140
|
+
|
|
1141
|
+
const outDir = resolve(opts.out);
|
|
1142
|
+
mkdirSync(outDir, { recursive: true });
|
|
1143
|
+
const prefix = opts.name || nameFromUrl(url);
|
|
1144
|
+
const written = [];
|
|
1145
|
+
|
|
1146
|
+
for (const id of vocabIds) {
|
|
1147
|
+
spinner.text = `Rendering ${id}...`;
|
|
1148
|
+
const vocab = getVocabulary(id);
|
|
1149
|
+
const html = formatRemix(design, vocab, { vocabId: id, version: PKG_VERSION });
|
|
1150
|
+
const p = join(outDir, `${prefix}.remix.${id}.html`);
|
|
1151
|
+
writeFileSync(p, html);
|
|
1152
|
+
written.push(p);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
spinner.stop();
|
|
1156
|
+
console.log('');
|
|
1157
|
+
console.log(` ${chalk.bold('Remixed')} ${chalk.gray('·')} ${chalk.cyan(vocabIds.join(', '))} ${chalk.gray('·')} ${chalk.gray(url)}`);
|
|
1158
|
+
console.log('');
|
|
1159
|
+
for (const f of written) console.log(` ${chalk.green('✓')} ${chalk.gray(f)}`);
|
|
1160
|
+
console.log('');
|
|
1161
|
+
console.log(chalk.gray(` Open the .html in a browser. One file per vocabulary, fully self-contained.`));
|
|
1162
|
+
console.log('');
|
|
1163
|
+
|
|
1164
|
+
if (opts.open && written.length > 0) {
|
|
1165
|
+
const { spawn } = await import('child_process');
|
|
1166
|
+
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
1167
|
+
spawn(cmd, [written[0]], { detached: true, stdio: 'ignore' }).unref();
|
|
1168
|
+
}
|
|
1169
|
+
} catch (err) {
|
|
1170
|
+
spinner.fail('Remix failed');
|
|
1171
|
+
console.error(chalk.red(`\n ${err.message}\n`));
|
|
1172
|
+
process.exit(1);
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
// ── Pack command — bundle every emitter into one design-system directory
|
|
1177
|
+
program
|
|
1178
|
+
.command('pack <url>')
|
|
1179
|
+
.description('Bundle every output (tokens, components, storybook, prompts, starter) into a single design-system directory')
|
|
1180
|
+
.option('-o, --out <dir>', 'output directory (default: ./<host>-design-system)')
|
|
1181
|
+
.option('--with-clone', 'include the full Next.js clone as the starter (slower; otherwise emits a minimal HTML starter)')
|
|
1182
|
+
.option('--open', 'open the starter index.html in the default browser')
|
|
1183
|
+
.action(async (url, opts) => {
|
|
1184
|
+
if (!url.startsWith('http')) url = `https://${url}`;
|
|
1185
|
+
validateUrl(url);
|
|
1186
|
+
|
|
1187
|
+
const spinner = ora(`Extracting ${url}...`).start();
|
|
1188
|
+
try {
|
|
1189
|
+
const design = await extractDesignLanguage(url);
|
|
1190
|
+
|
|
1191
|
+
const defaultDirName = `${nameFromUrl(url)}-design-system`;
|
|
1192
|
+
const outDir = resolve(opts.out || defaultDirName);
|
|
1193
|
+
|
|
1194
|
+
spinner.text = 'Packing artifacts...';
|
|
1195
|
+
const { files } = buildPack(design, {
|
|
1196
|
+
outDir,
|
|
1197
|
+
version: PKG_VERSION,
|
|
1198
|
+
withClone: !!opts.withClone,
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
spinner.stop();
|
|
1202
|
+
console.log('');
|
|
1203
|
+
console.log(` ${chalk.bold('Packed')} ${chalk.gray('·')} ${chalk.cyan(files.length)} files ${chalk.gray('·')} ${chalk.gray(url)}`);
|
|
1204
|
+
console.log('');
|
|
1205
|
+
console.log(` ${chalk.green('✓')} ${chalk.bold(outDir)}`);
|
|
1206
|
+
console.log('');
|
|
1207
|
+
console.log(chalk.gray(' Top-level layout:'));
|
|
1208
|
+
const top = ['README.md', 'LICENSE.txt', 'tokens/', 'components/', 'storybook/', 'starter/', 'prompts/', 'extras/'];
|
|
1209
|
+
for (const t of top) console.log(` ${chalk.gray('·')} ${t}`);
|
|
1210
|
+
console.log('');
|
|
1211
|
+
console.log(chalk.gray(` Zip it: cd ${outDir} && zip -r ../${defaultDirName}.zip .`));
|
|
1212
|
+
console.log(chalk.gray(` Storybook: cd ${outDir}/storybook && npm install && npm run storybook`));
|
|
1213
|
+
console.log('');
|
|
1214
|
+
|
|
1215
|
+
if (opts.open) {
|
|
1216
|
+
const starter = join(outDir, 'starter', 'index.html');
|
|
1217
|
+
const { spawn } = await import('child_process');
|
|
1218
|
+
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
1219
|
+
spawn(cmd, [starter], { detached: true, stdio: 'ignore' }).unref();
|
|
1220
|
+
}
|
|
1221
|
+
} catch (err) {
|
|
1222
|
+
spinner.fail('Pack failed');
|
|
1223
|
+
console.error(chalk.red(`\n ${err.message}\n`));
|
|
1224
|
+
process.exit(1);
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1105
1228
|
// ── Apply command ──────────────────────────────────────────
|
|
1106
1229
|
program
|
|
1107
1230
|
.command('apply <url>')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "designlang",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.4.0",
|
|
4
4
|
"description": "Extract the complete design language from any website and ship it — clone to a working Next.js starter, guard tokens with a CI drift bot, or browse everything in a local studio. Outputs W3C DTCG tokens, motion tokens, typed anatomy stubs, Tailwind config, and ready-to-paste v0 / Lovable / Cursor / Claude-Artifacts prompts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/chat.js
CHANGED
|
@@ -22,20 +22,6 @@ function isHex(s) {
|
|
|
22
22
|
return typeof s === 'string' && /^#[0-9a-f]{3,8}$/i.test(s.trim());
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
function hexToRgb(hex) {
|
|
26
|
-
const m = String(hex).trim().toLowerCase().replace(/^#/, '');
|
|
27
|
-
const full = m.length === 3 ? m.split('').map((c) => c + c).join('') : m.slice(0, 6);
|
|
28
|
-
return {
|
|
29
|
-
r: parseInt(full.slice(0, 2), 16) || 0,
|
|
30
|
-
g: parseInt(full.slice(2, 4), 16) || 0,
|
|
31
|
-
b: parseInt(full.slice(4, 6), 16) || 0,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function rgbToHex({ r, g, b }) {
|
|
36
|
-
return '#' + [r, g, b].map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0')).join('');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
25
|
function opSharpenRadii(design, factor = 0.5) {
|
|
40
26
|
const radii = design.borders?.radii || [];
|
|
41
27
|
const next = radii.map((r) => ({ ...r, value: Math.max(0, Math.round((r.value || 0) * factor)) }));
|