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.
Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +220 -0
  3. package/dist/cli/cleanup.d.ts +5 -0
  4. package/dist/cli/cleanup.d.ts.map +1 -0
  5. package/dist/cli/cleanup.js +8 -0
  6. package/dist/cli/cleanup.js.map +1 -0
  7. package/dist/cli/extract.d.ts +3 -0
  8. package/dist/cli/extract.d.ts.map +1 -0
  9. package/dist/cli/extract.js +105 -0
  10. package/dist/cli/extract.js.map +1 -0
  11. package/dist/cli/index.d.ts +3 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +44 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/cli/install-claude.d.ts +3 -0
  16. package/dist/cli/install-claude.d.ts.map +1 -0
  17. package/dist/cli/install-claude.js +39 -0
  18. package/dist/cli/install-claude.js.map +1 -0
  19. package/dist/cli/report.d.ts +3 -0
  20. package/dist/cli/report.d.ts.map +1 -0
  21. package/dist/cli/report.js +115 -0
  22. package/dist/cli/report.js.map +1 -0
  23. package/dist/cli/tokens.d.ts +3 -0
  24. package/dist/cli/tokens.d.ts.map +1 -0
  25. package/dist/cli/tokens.js +111 -0
  26. package/dist/cli/tokens.js.map +1 -0
  27. package/dist/cli/verify.d.ts +3 -0
  28. package/dist/cli/verify.d.ts.map +1 -0
  29. package/dist/cli/verify.js +95 -0
  30. package/dist/cli/verify.js.map +1 -0
  31. package/dist/extractors/animations.d.ts +6 -0
  32. package/dist/extractors/animations.d.ts.map +1 -0
  33. package/dist/extractors/animations.js +7 -0
  34. package/dist/extractors/animations.js.map +1 -0
  35. package/dist/extractors/assets.d.ts +6 -0
  36. package/dist/extractors/assets.d.ts.map +1 -0
  37. package/dist/extractors/assets.js +7 -0
  38. package/dist/extractors/assets.js.map +1 -0
  39. package/dist/extractors/base.d.ts +13 -0
  40. package/dist/extractors/base.d.ts.map +1 -0
  41. package/dist/extractors/base.js +27 -0
  42. package/dist/extractors/base.js.map +1 -0
  43. package/dist/extractors/behavior.d.ts +6 -0
  44. package/dist/extractors/behavior.d.ts.map +1 -0
  45. package/dist/extractors/behavior.js +7 -0
  46. package/dist/extractors/behavior.js.map +1 -0
  47. package/dist/extractors/components.d.ts +6 -0
  48. package/dist/extractors/components.d.ts.map +1 -0
  49. package/dist/extractors/components.js +7 -0
  50. package/dist/extractors/components.js.map +1 -0
  51. package/dist/extractors/framework.d.ts +6 -0
  52. package/dist/extractors/framework.d.ts.map +1 -0
  53. package/dist/extractors/framework.js +7 -0
  54. package/dist/extractors/framework.js.map +1 -0
  55. package/dist/extractors/layout.d.ts +6 -0
  56. package/dist/extractors/layout.d.ts.map +1 -0
  57. package/dist/extractors/layout.js +7 -0
  58. package/dist/extractors/layout.js.map +1 -0
  59. package/dist/extractors/network.d.ts +6 -0
  60. package/dist/extractors/network.d.ts.map +1 -0
  61. package/dist/extractors/network.js +7 -0
  62. package/dist/extractors/network.js.map +1 -0
  63. package/dist/extractors/orchestrator.d.ts +4 -0
  64. package/dist/extractors/orchestrator.d.ts.map +1 -0
  65. package/dist/extractors/orchestrator.js +96 -0
  66. package/dist/extractors/orchestrator.js.map +1 -0
  67. package/dist/extractors/typography.d.ts +6 -0
  68. package/dist/extractors/typography.d.ts.map +1 -0
  69. package/dist/extractors/typography.js +7 -0
  70. package/dist/extractors/typography.js.map +1 -0
  71. package/dist/extractors/visual.d.ts +6 -0
  72. package/dist/extractors/visual.d.ts.map +1 -0
  73. package/dist/extractors/visual.js +7 -0
  74. package/dist/extractors/visual.js.map +1 -0
  75. package/dist/generators/component-inventory.d.ts +4 -0
  76. package/dist/generators/component-inventory.d.ts.map +1 -0
  77. package/dist/generators/component-inventory.js +218 -0
  78. package/dist/generators/component-inventory.js.map +1 -0
  79. package/dist/generators/design-tokens.d.ts +5 -0
  80. package/dist/generators/design-tokens.d.ts.map +1 -0
  81. package/dist/generators/design-tokens.js +384 -0
  82. package/dist/generators/design-tokens.js.map +1 -0
  83. package/dist/generators/layout-blueprint.d.ts +4 -0
  84. package/dist/generators/layout-blueprint.d.ts.map +1 -0
  85. package/dist/generators/layout-blueprint.js +86 -0
  86. package/dist/generators/layout-blueprint.js.map +1 -0
  87. package/dist/generators/report.d.ts +3 -0
  88. package/dist/generators/report.d.ts.map +1 -0
  89. package/dist/generators/report.js +215 -0
  90. package/dist/generators/report.js.map +1 -0
  91. package/dist/index.d.ts +4 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +3 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/scripts/detect-framework.d.ts +2 -0
  96. package/dist/scripts/detect-framework.d.ts.map +1 -0
  97. package/dist/scripts/detect-framework.js +2 -0
  98. package/dist/scripts/detect-framework.js.map +1 -0
  99. package/dist/scripts/extract-components.d.ts +2 -0
  100. package/dist/scripts/extract-components.d.ts.map +1 -0
  101. package/dist/scripts/extract-components.js +2 -0
  102. package/dist/scripts/extract-components.js.map +1 -0
  103. package/dist/scripts/extract-layout.d.ts +2 -0
  104. package/dist/scripts/extract-layout.d.ts.map +1 -0
  105. package/dist/scripts/extract-layout.js +2 -0
  106. package/dist/scripts/extract-layout.js.map +1 -0
  107. package/dist/scripts/extract-styles.d.ts +2 -0
  108. package/dist/scripts/extract-styles.d.ts.map +1 -0
  109. package/dist/scripts/extract-styles.js +2 -0
  110. package/dist/scripts/extract-styles.js.map +1 -0
  111. package/dist/scripts/extract-typography.d.ts +2 -0
  112. package/dist/scripts/extract-typography.d.ts.map +1 -0
  113. package/dist/scripts/extract-typography.js +2 -0
  114. package/dist/scripts/extract-typography.js.map +1 -0
  115. package/dist/types/config.d.ts +80 -0
  116. package/dist/types/config.d.ts.map +1 -0
  117. package/dist/types/config.js +37 -0
  118. package/dist/types/config.js.map +1 -0
  119. package/dist/types/extraction.d.ts +204 -0
  120. package/dist/types/extraction.d.ts.map +1 -0
  121. package/dist/types/extraction.js +2 -0
  122. package/dist/types/extraction.js.map +1 -0
  123. package/dist/types/index.d.ts +5 -0
  124. package/dist/types/index.d.ts.map +1 -0
  125. package/dist/types/index.js +5 -0
  126. package/dist/types/index.js.map +1 -0
  127. package/dist/types/report.d.ts +18 -0
  128. package/dist/types/report.d.ts.map +1 -0
  129. package/dist/types/report.js +2 -0
  130. package/dist/types/report.js.map +1 -0
  131. package/dist/types/tokens.d.ts +62 -0
  132. package/dist/types/tokens.d.ts.map +1 -0
  133. package/dist/types/tokens.js +2 -0
  134. package/dist/types/tokens.js.map +1 -0
  135. package/dist/utils/color.d.ts +21 -0
  136. package/dist/utils/color.d.ts.map +1 -0
  137. package/dist/utils/color.js +107 -0
  138. package/dist/utils/color.js.map +1 -0
  139. package/dist/utils/css-parser.d.ts +31 -0
  140. package/dist/utils/css-parser.d.ts.map +1 -0
  141. package/dist/utils/css-parser.js +77 -0
  142. package/dist/utils/css-parser.js.map +1 -0
  143. package/dist/utils/dedup.d.ts +12 -0
  144. package/dist/utils/dedup.d.ts.map +1 -0
  145. package/dist/utils/dedup.js +44 -0
  146. package/dist/utils/dedup.js.map +1 -0
  147. package/dist/utils/fs.d.ts +6 -0
  148. package/dist/utils/fs.d.ts.map +1 -0
  149. package/dist/utils/fs.js +17 -0
  150. package/dist/utils/fs.js.map +1 -0
  151. package/dist/utils/screenshot-diff.d.ts +7 -0
  152. package/dist/utils/screenshot-diff.d.ts.map +1 -0
  153. package/dist/utils/screenshot-diff.js +43 -0
  154. package/dist/utils/screenshot-diff.js.map +1 -0
  155. package/package.json +60 -0
@@ -0,0 +1,7 @@
1
+ import { BaseExtractor } from './base.js';
2
+ export class VisualExtractor extends BaseExtractor {
3
+ async extract() {
4
+ throw new Error('VisualExtractor not implemented');
5
+ }
6
+ }
7
+ //# sourceMappingURL=visual.js.map
@@ -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