designmaxxing 0.1.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/LICENSE +21 -0
- package/README.md +220 -0
- package/dist/cli/cleanup.d.ts +5 -0
- package/dist/cli/cleanup.d.ts.map +1 -0
- package/dist/cli/cleanup.js +8 -0
- package/dist/cli/cleanup.js.map +1 -0
- package/dist/cli/extract.d.ts +3 -0
- package/dist/cli/extract.d.ts.map +1 -0
- package/dist/cli/extract.js +105 -0
- package/dist/cli/extract.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +44 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/install-claude.d.ts +3 -0
- package/dist/cli/install-claude.d.ts.map +1 -0
- package/dist/cli/install-claude.js +39 -0
- package/dist/cli/install-claude.js.map +1 -0
- package/dist/cli/report.d.ts +3 -0
- package/dist/cli/report.d.ts.map +1 -0
- package/dist/cli/report.js +115 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/tokens.d.ts +3 -0
- package/dist/cli/tokens.d.ts.map +1 -0
- package/dist/cli/tokens.js +111 -0
- package/dist/cli/tokens.js.map +1 -0
- package/dist/cli/verify.d.ts +3 -0
- package/dist/cli/verify.d.ts.map +1 -0
- package/dist/cli/verify.js +95 -0
- package/dist/cli/verify.js.map +1 -0
- package/dist/extractors/animations.d.ts +6 -0
- package/dist/extractors/animations.d.ts.map +1 -0
- package/dist/extractors/animations.js +7 -0
- package/dist/extractors/animations.js.map +1 -0
- package/dist/extractors/assets.d.ts +6 -0
- package/dist/extractors/assets.d.ts.map +1 -0
- package/dist/extractors/assets.js +7 -0
- package/dist/extractors/assets.js.map +1 -0
- package/dist/extractors/base.d.ts +13 -0
- package/dist/extractors/base.d.ts.map +1 -0
- package/dist/extractors/base.js +27 -0
- package/dist/extractors/base.js.map +1 -0
- package/dist/extractors/behavior.d.ts +6 -0
- package/dist/extractors/behavior.d.ts.map +1 -0
- package/dist/extractors/behavior.js +7 -0
- package/dist/extractors/behavior.js.map +1 -0
- package/dist/extractors/components.d.ts +6 -0
- package/dist/extractors/components.d.ts.map +1 -0
- package/dist/extractors/components.js +7 -0
- package/dist/extractors/components.js.map +1 -0
- package/dist/extractors/framework.d.ts +6 -0
- package/dist/extractors/framework.d.ts.map +1 -0
- package/dist/extractors/framework.js +7 -0
- package/dist/extractors/framework.js.map +1 -0
- package/dist/extractors/layout.d.ts +6 -0
- package/dist/extractors/layout.d.ts.map +1 -0
- package/dist/extractors/layout.js +7 -0
- package/dist/extractors/layout.js.map +1 -0
- package/dist/extractors/network.d.ts +6 -0
- package/dist/extractors/network.d.ts.map +1 -0
- package/dist/extractors/network.js +7 -0
- package/dist/extractors/network.js.map +1 -0
- package/dist/extractors/orchestrator.d.ts +4 -0
- package/dist/extractors/orchestrator.d.ts.map +1 -0
- package/dist/extractors/orchestrator.js +96 -0
- package/dist/extractors/orchestrator.js.map +1 -0
- package/dist/extractors/typography.d.ts +6 -0
- package/dist/extractors/typography.d.ts.map +1 -0
- package/dist/extractors/typography.js +7 -0
- package/dist/extractors/typography.js.map +1 -0
- package/dist/extractors/visual.d.ts +6 -0
- package/dist/extractors/visual.d.ts.map +1 -0
- package/dist/extractors/visual.js +7 -0
- package/dist/extractors/visual.js.map +1 -0
- package/dist/generators/component-inventory.d.ts +4 -0
- package/dist/generators/component-inventory.d.ts.map +1 -0
- package/dist/generators/component-inventory.js +218 -0
- package/dist/generators/component-inventory.js.map +1 -0
- package/dist/generators/design-tokens.d.ts +5 -0
- package/dist/generators/design-tokens.d.ts.map +1 -0
- package/dist/generators/design-tokens.js +384 -0
- package/dist/generators/design-tokens.js.map +1 -0
- package/dist/generators/layout-blueprint.d.ts +4 -0
- package/dist/generators/layout-blueprint.d.ts.map +1 -0
- package/dist/generators/layout-blueprint.js +86 -0
- package/dist/generators/layout-blueprint.js.map +1 -0
- package/dist/generators/report.d.ts +3 -0
- package/dist/generators/report.d.ts.map +1 -0
- package/dist/generators/report.js +215 -0
- package/dist/generators/report.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/scripts/detect-framework.d.ts +2 -0
- package/dist/scripts/detect-framework.d.ts.map +1 -0
- package/dist/scripts/detect-framework.js +2 -0
- package/dist/scripts/detect-framework.js.map +1 -0
- package/dist/scripts/extract-components.d.ts +2 -0
- package/dist/scripts/extract-components.d.ts.map +1 -0
- package/dist/scripts/extract-components.js +2 -0
- package/dist/scripts/extract-components.js.map +1 -0
- package/dist/scripts/extract-layout.d.ts +2 -0
- package/dist/scripts/extract-layout.d.ts.map +1 -0
- package/dist/scripts/extract-layout.js +2 -0
- package/dist/scripts/extract-layout.js.map +1 -0
- package/dist/scripts/extract-styles.d.ts +2 -0
- package/dist/scripts/extract-styles.d.ts.map +1 -0
- package/dist/scripts/extract-styles.js +2 -0
- package/dist/scripts/extract-styles.js.map +1 -0
- package/dist/scripts/extract-typography.d.ts +2 -0
- package/dist/scripts/extract-typography.d.ts.map +1 -0
- package/dist/scripts/extract-typography.js +2 -0
- package/dist/scripts/extract-typography.js.map +1 -0
- package/dist/types/config.d.ts +80 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +37 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/extraction.d.ts +204 -0
- package/dist/types/extraction.d.ts.map +1 -0
- package/dist/types/extraction.js +2 -0
- package/dist/types/extraction.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/report.d.ts +18 -0
- package/dist/types/report.d.ts.map +1 -0
- package/dist/types/report.js +2 -0
- package/dist/types/report.js.map +1 -0
- package/dist/types/tokens.d.ts +62 -0
- package/dist/types/tokens.d.ts.map +1 -0
- package/dist/types/tokens.js +2 -0
- package/dist/types/tokens.js.map +1 -0
- package/dist/utils/color.d.ts +21 -0
- package/dist/utils/color.d.ts.map +1 -0
- package/dist/utils/color.js +107 -0
- package/dist/utils/color.js.map +1 -0
- package/dist/utils/css-parser.d.ts +31 -0
- package/dist/utils/css-parser.d.ts.map +1 -0
- package/dist/utils/css-parser.js +77 -0
- package/dist/utils/css-parser.js.map +1 -0
- package/dist/utils/dedup.d.ts +12 -0
- package/dist/utils/dedup.d.ts.map +1 -0
- package/dist/utils/dedup.js +44 -0
- package/dist/utils/dedup.js.map +1 -0
- package/dist/utils/fs.d.ts +6 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +17 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/screenshot-diff.d.ts +7 -0
- package/dist/utils/screenshot-diff.d.ts.map +1 -0
- package/dist/utils/screenshot-diff.js +43 -0
- package/dist/utils/screenshot-diff.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual.js","sourceRoot":"","sources":["../../src/extractors/visual.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAGzC,MAAM,OAAO,eAAgB,SAAQ,aAAqC;IACxE,KAAK,CAAC,OAAO;QACX,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;IACpD,CAAC;CACF"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ComponentExtractionResult } from '../types/extraction.js';
|
|
2
|
+
import type { DesignTokens, ComponentInventory } from '../types/tokens.js';
|
|
3
|
+
export declare function generateComponentInventory(components: ComponentExtractionResult, tokens?: DesignTokens): ComponentInventory;
|
|
4
|
+
//# sourceMappingURL=component-inventory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-inventory.d.ts","sourceRoot":"","sources":["../../src/generators/component-inventory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAkB,MAAM,wBAAwB,CAAA;AACvF,OAAO,KAAK,EAAE,YAAY,EAA8C,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAyNtH,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,yBAAyB,EACrC,MAAM,CAAC,EAAE,YAAY,GACpB,kBAAkB,CA0CpB"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Color helpers (parse rgb(r, g, b) → hex for token matching)
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
function rgbStringToHex(rgb) {
|
|
5
|
+
const match = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/.exec(rgb.trim());
|
|
6
|
+
if (!match)
|
|
7
|
+
return null;
|
|
8
|
+
const r = parseInt(match[1], 10);
|
|
9
|
+
const g = parseInt(match[2], 10);
|
|
10
|
+
const b = parseInt(match[3], 10);
|
|
11
|
+
return '#' + [r, g, b].map(n => n.toString(16).padStart(2, '0')).join('');
|
|
12
|
+
}
|
|
13
|
+
function normalizeColorToHex(value) {
|
|
14
|
+
const trimmed = value.trim();
|
|
15
|
+
if (/^#[0-9a-fA-F]{6}$/.test(trimmed))
|
|
16
|
+
return trimmed.toLowerCase();
|
|
17
|
+
const fromRgb = rgbStringToHex(trimmed);
|
|
18
|
+
if (fromRgb)
|
|
19
|
+
return fromRgb.toLowerCase();
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Categorization
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
function extractTagFromSelector(selector) {
|
|
26
|
+
// Take the first word-like segment (tag name) from the selector
|
|
27
|
+
const match = /^([a-zA-Z][a-zA-Z0-9]*)/.exec(selector.trim());
|
|
28
|
+
return match ? match[1].toLowerCase() : '';
|
|
29
|
+
}
|
|
30
|
+
function extractClassesFromSelector(selector) {
|
|
31
|
+
// Collect all .className parts from the selector
|
|
32
|
+
const matches = selector.match(/\.([a-zA-Z0-9_-]+)/g);
|
|
33
|
+
return matches ? matches.map(c => c.slice(1)).join(' ') : '';
|
|
34
|
+
}
|
|
35
|
+
function selectorContains(selector, term) {
|
|
36
|
+
return selector.toLowerCase().includes(term.toLowerCase());
|
|
37
|
+
}
|
|
38
|
+
function classContains(classes, term) {
|
|
39
|
+
return classes.toLowerCase().split(/\s+/).some(c => c.includes(term.toLowerCase()));
|
|
40
|
+
}
|
|
41
|
+
function categorize(component) {
|
|
42
|
+
const selector = component.selector;
|
|
43
|
+
const tag = extractTagFromSelector(selector);
|
|
44
|
+
const classes = extractClassesFromSelector(selector);
|
|
45
|
+
// button
|
|
46
|
+
if (tag === 'button' ||
|
|
47
|
+
selectorContains(selector, '[role="button"]') ||
|
|
48
|
+
classContains(classes, 'btn')) {
|
|
49
|
+
return 'button';
|
|
50
|
+
}
|
|
51
|
+
// input
|
|
52
|
+
if (tag === 'input' || tag === 'select' || tag === 'textarea' || tag === 'form') {
|
|
53
|
+
return 'input';
|
|
54
|
+
}
|
|
55
|
+
// card
|
|
56
|
+
if (classContains(classes, 'card') ||
|
|
57
|
+
classContains(classes, 'panel') ||
|
|
58
|
+
classContains(classes, 'tile')) {
|
|
59
|
+
return 'card';
|
|
60
|
+
}
|
|
61
|
+
// navigation
|
|
62
|
+
if (tag === 'nav' ||
|
|
63
|
+
tag === 'header' ||
|
|
64
|
+
tag === 'footer' ||
|
|
65
|
+
selectorContains(selector, '[role="navigation"]') ||
|
|
66
|
+
classContains(classes, 'nav') ||
|
|
67
|
+
classContains(classes, 'menu') ||
|
|
68
|
+
classContains(classes, 'sidebar')) {
|
|
69
|
+
return 'navigation';
|
|
70
|
+
}
|
|
71
|
+
// media
|
|
72
|
+
if (tag === 'img' ||
|
|
73
|
+
tag === 'video' ||
|
|
74
|
+
tag === 'svg' ||
|
|
75
|
+
tag === 'picture' ||
|
|
76
|
+
tag === 'figure' ||
|
|
77
|
+
selectorContains(selector, '[role="img"]')) {
|
|
78
|
+
return 'media';
|
|
79
|
+
}
|
|
80
|
+
// typography
|
|
81
|
+
if (tag === 'p' ||
|
|
82
|
+
tag === 'h1' || tag === 'h2' || tag === 'h3' ||
|
|
83
|
+
tag === 'h4' || tag === 'h5' || tag === 'h6' ||
|
|
84
|
+
tag === 'span' ||
|
|
85
|
+
tag === 'label' ||
|
|
86
|
+
classContains(classes, 'text') ||
|
|
87
|
+
classContains(classes, 'title') ||
|
|
88
|
+
classContains(classes, 'heading')) {
|
|
89
|
+
return 'typography';
|
|
90
|
+
}
|
|
91
|
+
// layout
|
|
92
|
+
if (tag === 'div' ||
|
|
93
|
+
tag === 'section' ||
|
|
94
|
+
tag === 'main' ||
|
|
95
|
+
tag === 'article' ||
|
|
96
|
+
tag === 'aside' ||
|
|
97
|
+
selectorContains(selector, 'container') ||
|
|
98
|
+
selectorContains(selector, 'wrapper') ||
|
|
99
|
+
selectorContains(selector, 'layout') ||
|
|
100
|
+
selectorContains(selector, 'grid') ||
|
|
101
|
+
selectorContains(selector, 'flex')) {
|
|
102
|
+
return 'layout';
|
|
103
|
+
}
|
|
104
|
+
return 'other';
|
|
105
|
+
}
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Name generation
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
function selectorToName(selector) {
|
|
110
|
+
// Strip CSS specificity symbols
|
|
111
|
+
const stripped = selector.replace(/[#.\[\]:>+~\s]/g, '');
|
|
112
|
+
if (stripped.length > 0)
|
|
113
|
+
return stripped;
|
|
114
|
+
// Fallback to tag name
|
|
115
|
+
return extractTagFromSelector(selector) || 'component';
|
|
116
|
+
}
|
|
117
|
+
function deduplicateNames(names) {
|
|
118
|
+
const counts = new Map();
|
|
119
|
+
const seen = new Map();
|
|
120
|
+
const result = [];
|
|
121
|
+
// Count occurrences first
|
|
122
|
+
for (const name of names) {
|
|
123
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
124
|
+
}
|
|
125
|
+
for (const name of names) {
|
|
126
|
+
if ((counts.get(name) ?? 0) > 1) {
|
|
127
|
+
const index = (seen.get(name) ?? 0) + 1;
|
|
128
|
+
seen.set(name, index);
|
|
129
|
+
result.push(index === 1 ? name : `${name}-${index}`);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
result.push(name);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Token mapping
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
function buildTokenMappings(component, tokens) {
|
|
141
|
+
if (!tokens)
|
|
142
|
+
return {};
|
|
143
|
+
// Collect all CSS property values from all states.
|
|
144
|
+
// Use Object.create(null) to prevent prototype pollution from user-controlled CSS property keys.
|
|
145
|
+
const allProps = Object.create(null);
|
|
146
|
+
for (const stateStyles of Object.values(component.states)) {
|
|
147
|
+
for (const [prop, value] of Object.entries(stateStyles)) {
|
|
148
|
+
if (Object.prototype.hasOwnProperty.call(Object.prototype, prop))
|
|
149
|
+
continue;
|
|
150
|
+
allProps[prop] = value;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (Object.keys(allProps).length === 0)
|
|
154
|
+
return {};
|
|
155
|
+
// Build a lookup: hex → token name for all color tokens
|
|
156
|
+
const hexToTokenName = new Map();
|
|
157
|
+
for (const [tokenName, tokenValue] of Object.entries(tokens.colors)) {
|
|
158
|
+
const hex = normalizeColorToHex(tokenValue);
|
|
159
|
+
if (hex)
|
|
160
|
+
hexToTokenName.set(hex, tokenName);
|
|
161
|
+
}
|
|
162
|
+
const mappings = Object.create(null);
|
|
163
|
+
for (const [prop, value] of Object.entries(allProps)) {
|
|
164
|
+
const hex = normalizeColorToHex(value);
|
|
165
|
+
if (hex) {
|
|
166
|
+
const tokenName = hexToTokenName.get(hex);
|
|
167
|
+
if (tokenName) {
|
|
168
|
+
mappings[prop] = `var(--color-${tokenName})`;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
mappings[prop] = value;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return mappings;
|
|
176
|
+
}
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Public API
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
export function generateComponentInventory(components, tokens) {
|
|
181
|
+
const entries = components.components;
|
|
182
|
+
if (entries.length === 0) {
|
|
183
|
+
const emptyCategories = {
|
|
184
|
+
button: 0, input: 0, card: 0, navigation: 0,
|
|
185
|
+
layout: 0, typography: 0, media: 0, other: 0,
|
|
186
|
+
};
|
|
187
|
+
return {
|
|
188
|
+
components: [],
|
|
189
|
+
categories: emptyCategories,
|
|
190
|
+
totalComponents: 0,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
// Generate raw names before dedup
|
|
194
|
+
const rawNames = entries.map(c => selectorToName(c.selector));
|
|
195
|
+
const deduped = deduplicateNames(rawNames);
|
|
196
|
+
const inventoryEntries = entries.map((component, i) => ({
|
|
197
|
+
name: deduped[i],
|
|
198
|
+
category: categorize(component),
|
|
199
|
+
selector: component.selector,
|
|
200
|
+
html: component.html,
|
|
201
|
+
tokenMappings: buildTokenMappings(component, tokens),
|
|
202
|
+
states: component.states,
|
|
203
|
+
dimensions: component.dimensions,
|
|
204
|
+
}));
|
|
205
|
+
const categoryCounts = {
|
|
206
|
+
button: 0, input: 0, card: 0, navigation: 0,
|
|
207
|
+
layout: 0, typography: 0, media: 0, other: 0,
|
|
208
|
+
};
|
|
209
|
+
for (const entry of inventoryEntries) {
|
|
210
|
+
categoryCounts[entry.category]++;
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
components: inventoryEntries,
|
|
214
|
+
categories: categoryCounts,
|
|
215
|
+
totalComponents: inventoryEntries.length,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=component-inventory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-inventory.js","sourceRoot":"","sources":["../../src/generators/component-inventory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { VisualExtractionResult, TypographyExtractionResult, LayoutExtractionResult } from '../types/extraction.js';
|
|
2
|
+
import type { DesignTokens, TokenOutputFormat } from '../types/tokens.js';
|
|
3
|
+
export declare function generateDesignTokens(visual: VisualExtractionResult, typography: TypographyExtractionResult, layout?: LayoutExtractionResult): DesignTokens;
|
|
4
|
+
export declare function formatTokens(tokens: DesignTokens, format: TokenOutputFormat): string;
|
|
5
|
+
//# sourceMappingURL=design-tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"design-tokens.d.ts","sourceRoot":"","sources":["../../src/generators/design-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,0BAA0B,EAC1B,sBAAsB,EAGvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,YAAY,EAAmB,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAuS1F,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,sBAAsB,EAC9B,UAAU,EAAE,0BAA0B,EACtC,MAAM,CAAC,EAAE,sBAAsB,GAC9B,YAAY,CAoBd;AAyID,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAOpF"}
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Color helpers
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
function parseHex(hex) {
|
|
5
|
+
const clean = hex.replace('#', '');
|
|
6
|
+
if (clean.length !== 6)
|
|
7
|
+
return null;
|
|
8
|
+
const r = parseInt(clean.slice(0, 2), 16);
|
|
9
|
+
const g = parseInt(clean.slice(2, 4), 16);
|
|
10
|
+
const b = parseInt(clean.slice(4, 6), 16);
|
|
11
|
+
if (isNaN(r) || isNaN(g) || isNaN(b))
|
|
12
|
+
return null;
|
|
13
|
+
return [r, g, b];
|
|
14
|
+
}
|
|
15
|
+
function colorDistance(a, b) {
|
|
16
|
+
const pa = parseHex(a);
|
|
17
|
+
const pb = parseHex(b);
|
|
18
|
+
if (!pa || !pb)
|
|
19
|
+
return Infinity;
|
|
20
|
+
return Math.sqrt(Math.pow(pa[0] - pb[0], 2) +
|
|
21
|
+
Math.pow(pa[1] - pb[1], 2) +
|
|
22
|
+
Math.pow(pa[2] - pb[2], 2));
|
|
23
|
+
}
|
|
24
|
+
function clusterColors(colors) {
|
|
25
|
+
const clusters = [];
|
|
26
|
+
for (const color of colors) {
|
|
27
|
+
let placed = false;
|
|
28
|
+
for (const cluster of clusters) {
|
|
29
|
+
const representative = cluster[0];
|
|
30
|
+
if (colorDistance(color.hex, representative.hex) < 50) {
|
|
31
|
+
cluster.push(color);
|
|
32
|
+
placed = true;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (!placed) {
|
|
37
|
+
clusters.push([color]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Pick the most-used entry from each cluster as canonical
|
|
41
|
+
return clusters.map(cluster => cluster.reduce((best, c) => (c.usageCount > best.usageCount ? c : best), cluster[0]));
|
|
42
|
+
}
|
|
43
|
+
function buildColorTokens(colors) {
|
|
44
|
+
if (colors.length === 0)
|
|
45
|
+
return {};
|
|
46
|
+
const sorted = [...colors].sort((a, b) => b.usageCount - a.usageCount);
|
|
47
|
+
const canonical = clusterColors(sorted).sort((a, b) => b.usageCount - a.usageCount);
|
|
48
|
+
const tokens = {};
|
|
49
|
+
let colorIndex = 1;
|
|
50
|
+
let bgAssigned = false;
|
|
51
|
+
let textAssigned = false;
|
|
52
|
+
for (const entry of canonical) {
|
|
53
|
+
const isBackground = entry.properties.some(p => p.includes('background') || p.includes('Background'));
|
|
54
|
+
const isText = entry.properties.some(p => p.includes('color') && !p.includes('background') && !p.includes('Background'));
|
|
55
|
+
if (isBackground && !bgAssigned) {
|
|
56
|
+
tokens['bg-primary'] = entry.hex;
|
|
57
|
+
bgAssigned = true;
|
|
58
|
+
}
|
|
59
|
+
else if (isText && !textAssigned) {
|
|
60
|
+
tokens['text-primary'] = entry.hex;
|
|
61
|
+
textAssigned = true;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
tokens[`color-${colorIndex}`] = entry.hex;
|
|
65
|
+
colorIndex++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return tokens;
|
|
69
|
+
}
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Typography helpers
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
const SIZE_NAMES = [
|
|
74
|
+
{ name: 'text-xs', px: 12 },
|
|
75
|
+
{ name: 'text-sm', px: 14 },
|
|
76
|
+
{ name: 'text-base', px: 16 },
|
|
77
|
+
{ name: 'text-lg', px: 18 },
|
|
78
|
+
{ name: 'text-xl', px: 20 },
|
|
79
|
+
{ name: 'text-2xl', px: 24 },
|
|
80
|
+
{ name: 'text-3xl', px: 30 },
|
|
81
|
+
];
|
|
82
|
+
function closestSizeName(px) {
|
|
83
|
+
let best = SIZE_NAMES[0];
|
|
84
|
+
let bestDist = Math.abs(px - best.px);
|
|
85
|
+
for (const s of SIZE_NAMES) {
|
|
86
|
+
const d = Math.abs(px - s.px);
|
|
87
|
+
if (d < bestDist) {
|
|
88
|
+
bestDist = d;
|
|
89
|
+
best = s;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return best.name;
|
|
93
|
+
}
|
|
94
|
+
function parsePx(value) {
|
|
95
|
+
const match = /^([\d.]+)px$/.exec(value.trim());
|
|
96
|
+
if (!match)
|
|
97
|
+
return null;
|
|
98
|
+
return parseFloat(match[1]);
|
|
99
|
+
}
|
|
100
|
+
function buildTypographyTokens(scale) {
|
|
101
|
+
if (scale.length === 0)
|
|
102
|
+
return {};
|
|
103
|
+
// --- Font families ---
|
|
104
|
+
const familyCount = new Map();
|
|
105
|
+
for (const entry of scale) {
|
|
106
|
+
const fam = entry.fontFamily.trim();
|
|
107
|
+
if (fam) {
|
|
108
|
+
familyCount.set(fam, (familyCount.get(fam) ?? 0) + entry.usageCount);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const familiesByFreq = [...familyCount.entries()].sort((a, b) => b[1] - a[1]);
|
|
112
|
+
const familyTokens = {};
|
|
113
|
+
let bodyAssigned = false;
|
|
114
|
+
let headingAssigned = false;
|
|
115
|
+
for (const [fam] of familiesByFreq) {
|
|
116
|
+
const lower = fam.toLowerCase();
|
|
117
|
+
if (lower.includes('mono') || lower.includes('courier') || lower.includes('code')) {
|
|
118
|
+
if (!familyTokens['font-mono'])
|
|
119
|
+
familyTokens['font-mono'] = fam;
|
|
120
|
+
}
|
|
121
|
+
else if (!bodyAssigned) {
|
|
122
|
+
familyTokens['font-body'] = fam;
|
|
123
|
+
bodyAssigned = true;
|
|
124
|
+
}
|
|
125
|
+
else if (!headingAssigned) {
|
|
126
|
+
familyTokens['font-heading'] = fam;
|
|
127
|
+
headingAssigned = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// --- Font sizes ---
|
|
131
|
+
const sizeCount = new Map();
|
|
132
|
+
for (const entry of scale) {
|
|
133
|
+
const sz = entry.fontSize.trim();
|
|
134
|
+
if (sz) {
|
|
135
|
+
sizeCount.set(sz, (sizeCount.get(sz) ?? 0) + entry.usageCount);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const sizesByFreq = [...sizeCount.entries()].sort((a, b) => {
|
|
139
|
+
const pa = parsePx(a[0]) ?? 0;
|
|
140
|
+
const pb = parsePx(b[0]) ?? 0;
|
|
141
|
+
return pb - pa; // descending by numeric value
|
|
142
|
+
});
|
|
143
|
+
const usedSizeNames = new Set();
|
|
144
|
+
const sizeTokens = {};
|
|
145
|
+
for (const [sz] of sizesByFreq) {
|
|
146
|
+
const px = parsePx(sz);
|
|
147
|
+
if (px === null)
|
|
148
|
+
continue;
|
|
149
|
+
let name = closestSizeName(px);
|
|
150
|
+
// If name already used, append incrementing suffix
|
|
151
|
+
if (usedSizeNames.has(name)) {
|
|
152
|
+
let i = 2;
|
|
153
|
+
while (usedSizeNames.has(`${name}-${i}`))
|
|
154
|
+
i++;
|
|
155
|
+
name = `${name}-${i}`;
|
|
156
|
+
}
|
|
157
|
+
usedSizeNames.add(name);
|
|
158
|
+
sizeTokens[name] = sz;
|
|
159
|
+
}
|
|
160
|
+
// --- Build TypographyToken records by font-family role ---
|
|
161
|
+
const tokens = {};
|
|
162
|
+
// Emit family tokens as simple TypographyTokens (size/weight/etc left as defaults)
|
|
163
|
+
for (const [role, fam] of Object.entries(familyTokens)) {
|
|
164
|
+
tokens[role] = {
|
|
165
|
+
fontFamily: fam,
|
|
166
|
+
fontSize: '1rem',
|
|
167
|
+
fontWeight: '400',
|
|
168
|
+
lineHeight: '1.5',
|
|
169
|
+
letterSpacing: '0',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
// Emit size tokens
|
|
173
|
+
for (const [role, sz] of Object.entries(sizeTokens)) {
|
|
174
|
+
// Find a representative entry for this size
|
|
175
|
+
const rep = scale.find(e => e.fontSize.trim() === sz.trim()) ?? scale[0];
|
|
176
|
+
tokens[role] = {
|
|
177
|
+
fontFamily: rep.fontFamily,
|
|
178
|
+
fontSize: sz,
|
|
179
|
+
fontWeight: rep.fontWeight,
|
|
180
|
+
lineHeight: rep.lineHeight,
|
|
181
|
+
letterSpacing: rep.letterSpacing,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return tokens;
|
|
185
|
+
}
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Spacing helpers
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
function pxToRem(px) {
|
|
190
|
+
return `${(px / 16).toFixed(4).replace(/\.?0+$/, '')}rem`;
|
|
191
|
+
}
|
|
192
|
+
function buildSpacingTokens(visual) {
|
|
193
|
+
// VisualExtractionResult has no dedicated spacing array; return empty.
|
|
194
|
+
void visual;
|
|
195
|
+
return {};
|
|
196
|
+
}
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// Border radius helpers
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
function normalizeBorderRadiusName(value, index) {
|
|
201
|
+
const trimmed = value.trim();
|
|
202
|
+
if (trimmed === '0' || trimmed === '0px')
|
|
203
|
+
return 'rounded-none';
|
|
204
|
+
const px = parsePx(trimmed);
|
|
205
|
+
if (px !== null) {
|
|
206
|
+
if (px >= 9999)
|
|
207
|
+
return 'rounded-full';
|
|
208
|
+
if (px <= 2)
|
|
209
|
+
return 'rounded-sm';
|
|
210
|
+
if (px <= 4)
|
|
211
|
+
return 'rounded';
|
|
212
|
+
if (px <= 6)
|
|
213
|
+
return 'rounded-md';
|
|
214
|
+
if (px <= 12)
|
|
215
|
+
return 'rounded-lg';
|
|
216
|
+
return `rounded-${index + 1}`;
|
|
217
|
+
}
|
|
218
|
+
if (trimmed === '50%')
|
|
219
|
+
return 'rounded-full';
|
|
220
|
+
return `rounded-${index + 1}`;
|
|
221
|
+
}
|
|
222
|
+
function buildBorderRadiusTokens(borderRadii) {
|
|
223
|
+
if (borderRadii.length === 0)
|
|
224
|
+
return {};
|
|
225
|
+
const unique = [...new Set(borderRadii.map(r => r.trim()).filter(r => r))];
|
|
226
|
+
const sorted = unique.sort((a, b) => {
|
|
227
|
+
const pa = parsePx(a) ?? 0;
|
|
228
|
+
const pb = parsePx(b) ?? 0;
|
|
229
|
+
return pa - pb;
|
|
230
|
+
});
|
|
231
|
+
const tokens = {};
|
|
232
|
+
const usedNames = new Set();
|
|
233
|
+
sorted.forEach((val, i) => {
|
|
234
|
+
let name = normalizeBorderRadiusName(val, i);
|
|
235
|
+
if (usedNames.has(name)) {
|
|
236
|
+
let j = 2;
|
|
237
|
+
while (usedNames.has(`${name}-${j}`))
|
|
238
|
+
j++;
|
|
239
|
+
name = `${name}-${j}`;
|
|
240
|
+
}
|
|
241
|
+
usedNames.add(name);
|
|
242
|
+
tokens[name] = val;
|
|
243
|
+
});
|
|
244
|
+
return tokens;
|
|
245
|
+
}
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
// Shadow helpers
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
const SHADOW_NAMES = ['shadow-sm', 'shadow', 'shadow-md', 'shadow-lg', 'shadow-xl'];
|
|
250
|
+
function buildShadowTokens(shadows) {
|
|
251
|
+
if (shadows.length === 0)
|
|
252
|
+
return {};
|
|
253
|
+
const unique = [...new Set(shadows.map(s => s.trim()).filter(s => s && s !== 'none'))];
|
|
254
|
+
const tokens = {};
|
|
255
|
+
unique.forEach((val, i) => {
|
|
256
|
+
const name = i < SHADOW_NAMES.length ? SHADOW_NAMES[i] : `shadow-${i + 1}`;
|
|
257
|
+
tokens[name] = val;
|
|
258
|
+
});
|
|
259
|
+
return tokens;
|
|
260
|
+
}
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
// Public API
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
export function generateDesignTokens(visual, typography, layout) {
|
|
265
|
+
const breakpoints = {};
|
|
266
|
+
if (layout) {
|
|
267
|
+
for (const bp of layout.breakpoints) {
|
|
268
|
+
if (bp.minWidth !== null) {
|
|
269
|
+
breakpoints[`screen-min-${bp.minWidth}`] = `${bp.minWidth}px`;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
colors: buildColorTokens(visual.colors),
|
|
275
|
+
typography: buildTypographyTokens(typography.scale),
|
|
276
|
+
spacing: buildSpacingTokens(visual),
|
|
277
|
+
borderRadius: buildBorderRadiusTokens(visual.borderRadii),
|
|
278
|
+
shadows: buildShadowTokens(visual.shadows),
|
|
279
|
+
breakpoints,
|
|
280
|
+
transitions: {},
|
|
281
|
+
zIndex: {},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
// Format helpers
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
function toCssVarName(category, key) {
|
|
288
|
+
return `--${category}-${key}`.replace(/[^a-zA-Z0-9-]/g, '-');
|
|
289
|
+
}
|
|
290
|
+
function formatJson(tokens) {
|
|
291
|
+
return JSON.stringify(tokens, null, 2);
|
|
292
|
+
}
|
|
293
|
+
function formatCss(tokens) {
|
|
294
|
+
const lines = [':root {'];
|
|
295
|
+
// Strip newlines and closing-brace sequences from CSS values to prevent ruleset injection.
|
|
296
|
+
const escCss = (s) => s.replace(/\r?\n/g, ' ').replace(/}/g, '');
|
|
297
|
+
function section(comment, entries) {
|
|
298
|
+
if (entries.length === 0)
|
|
299
|
+
return;
|
|
300
|
+
lines.push(` /* ${comment} */`);
|
|
301
|
+
for (const [key, value] of entries) {
|
|
302
|
+
lines.push(` ${key}: ${escCss(value)};`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
section('Colors', Object.entries(tokens.colors).map(([k, v]) => [toCssVarName('color', k), v]));
|
|
306
|
+
section('Typography', Object.entries(tokens.typography).flatMap(([k, v]) => [
|
|
307
|
+
[toCssVarName('font-family', k), v.fontFamily],
|
|
308
|
+
[toCssVarName('font-size', k), v.fontSize],
|
|
309
|
+
[toCssVarName('font-weight', k), v.fontWeight],
|
|
310
|
+
[toCssVarName('line-height', k), v.lineHeight],
|
|
311
|
+
[toCssVarName('letter-spacing', k), v.letterSpacing],
|
|
312
|
+
]));
|
|
313
|
+
section('Spacing', Object.entries(tokens.spacing).map(([k, v]) => [toCssVarName('spacing', k), v]));
|
|
314
|
+
section('Border Radius', Object.entries(tokens.borderRadius).map(([k, v]) => [toCssVarName('radius', k), v]));
|
|
315
|
+
section('Shadows', Object.entries(tokens.shadows).map(([k, v]) => [toCssVarName('shadow', k), v]));
|
|
316
|
+
section('Breakpoints', Object.entries(tokens.breakpoints).map(([k, v]) => [toCssVarName('breakpoint', k), v]));
|
|
317
|
+
lines.push('}');
|
|
318
|
+
return lines.join('\n');
|
|
319
|
+
}
|
|
320
|
+
function formatTailwind(tokens) {
|
|
321
|
+
const colorEntries = Object.entries(tokens.colors);
|
|
322
|
+
const spacingEntries = Object.entries(tokens.spacing);
|
|
323
|
+
const borderRadiusEntries = Object.entries(tokens.borderRadius);
|
|
324
|
+
const shadowEntries = Object.entries(tokens.shadows);
|
|
325
|
+
const breakpointEntries = Object.entries(tokens.breakpoints);
|
|
326
|
+
const fontFamilyEntries = Object.entries(tokens.typography).map(([k, v]) => [k, v.fontFamily]);
|
|
327
|
+
const fontSizeEntries = Object.entries(tokens.typography).map(([k, v]) => [k, v.fontSize]);
|
|
328
|
+
// Escape single quotes so that user-controlled values (e.g. font family names)
|
|
329
|
+
// cannot break out of the string literal in the generated TypeScript config file.
|
|
330
|
+
const escTs = (s) => s.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\r?\n/g, ' ');
|
|
331
|
+
function obj(entries) {
|
|
332
|
+
if (entries.length === 0)
|
|
333
|
+
return '{}';
|
|
334
|
+
const inner = entries.map(([k, v]) => ` '${escTs(k)}': '${escTs(v)}'`).join(',\n');
|
|
335
|
+
return `{\n${inner}\n }`;
|
|
336
|
+
}
|
|
337
|
+
return [
|
|
338
|
+
'export default {',
|
|
339
|
+
' theme: {',
|
|
340
|
+
' extend: {',
|
|
341
|
+
` colors: ${obj(colorEntries)},`,
|
|
342
|
+
` fontFamily: ${obj(fontFamilyEntries)},`,
|
|
343
|
+
` fontSize: ${obj(fontSizeEntries)},`,
|
|
344
|
+
` spacing: ${obj(spacingEntries)},`,
|
|
345
|
+
` borderRadius: ${obj(borderRadiusEntries)},`,
|
|
346
|
+
` boxShadow: ${obj(shadowEntries)},`,
|
|
347
|
+
` screens: ${obj(breakpointEntries)},`,
|
|
348
|
+
' },',
|
|
349
|
+
' },',
|
|
350
|
+
'}',
|
|
351
|
+
].join('\n');
|
|
352
|
+
}
|
|
353
|
+
function formatScss(tokens) {
|
|
354
|
+
const lines = [];
|
|
355
|
+
// Strip newlines and semicolons from SCSS values to prevent property injection.
|
|
356
|
+
const escScss = (s) => s.replace(/\r?\n/g, ' ').replace(/;/g, '');
|
|
357
|
+
function section(comment, entries) {
|
|
358
|
+
if (entries.length === 0)
|
|
359
|
+
return;
|
|
360
|
+
lines.push(`// ${comment}`);
|
|
361
|
+
for (const [key, value] of entries) {
|
|
362
|
+
const varName = `$${key}`.replace(/[^a-zA-Z0-9-_$]/g, '-');
|
|
363
|
+
lines.push(`${varName}: ${escScss(value)};`);
|
|
364
|
+
}
|
|
365
|
+
lines.push('');
|
|
366
|
+
}
|
|
367
|
+
section('Colors', Object.entries(tokens.colors));
|
|
368
|
+
section('Typography — Font Families', Object.entries(tokens.typography).map(([k, v]) => [`${k}-family`, v.fontFamily]));
|
|
369
|
+
section('Typography — Font Sizes', Object.entries(tokens.typography).map(([k, v]) => [`${k}-size`, v.fontSize]));
|
|
370
|
+
section('Spacing', Object.entries(tokens.spacing));
|
|
371
|
+
section('Border Radius', Object.entries(tokens.borderRadius));
|
|
372
|
+
section('Shadows', Object.entries(tokens.shadows));
|
|
373
|
+
section('Breakpoints', Object.entries(tokens.breakpoints));
|
|
374
|
+
return lines.join('\n').trimEnd();
|
|
375
|
+
}
|
|
376
|
+
export function formatTokens(tokens, format) {
|
|
377
|
+
switch (format) {
|
|
378
|
+
case 'json': return formatJson(tokens);
|
|
379
|
+
case 'css': return formatCss(tokens);
|
|
380
|
+
case 'tailwind': return formatTailwind(tokens);
|
|
381
|
+
case 'scss': return formatScss(tokens);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
//# sourceMappingURL=design-tokens.js.map
|