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 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 grade https://stripe.com --badge # report card + SVG badge ← v12.2
30
- npx designlang battle stripe.com vercel.com # head-to-head graded fight ← v12.2
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 (NEW v12.2) | `designlang battle <A> <B>` | Head-to-head graded battle card with verdict, dimension table, palette comparison |
128
- | Badge (NEW v12.2) | `designlang grade --badge` | Shields.io-style SVG badge — `design · B · 87` — drop into any README. Live endpoint: `designlang.app/badge/<host>.svg` |
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
@@ -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.2.0",
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)) }));
@@ -1,5 +1,3 @@
1
- import { pxToRem } from '../utils.js';
2
-
3
1
  export function formatCssVars(design) {
4
2
  const lines = [':root {'];
5
3