atlas-pipeline-mcp 1.0.25 → 1.0.26
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/README.md +50 -4
- package/dist/mcp.js +182 -0
- package/dist/mcp.js.map +1 -1
- package/dist/tools/animation-studio.d.ts +83 -0
- package/dist/tools/animation-studio.d.ts.map +1 -0
- package/dist/tools/animation-studio.js +1064 -0
- package/dist/tools/animation-studio.js.map +1 -0
- package/dist/tools/api-integration-helper.d.ts +141 -0
- package/dist/tools/api-integration-helper.d.ts.map +1 -0
- package/dist/tools/api-integration-helper.js +907 -0
- package/dist/tools/api-integration-helper.js.map +1 -0
- package/dist/tools/css-architecture-wizard.d.ts +86 -0
- package/dist/tools/css-architecture-wizard.d.ts.map +1 -0
- package/dist/tools/css-architecture-wizard.js +790 -0
- package/dist/tools/css-architecture-wizard.js.map +1 -0
- package/dist/tools/frontend-performance-doctor.d.ts +108 -0
- package/dist/tools/frontend-performance-doctor.d.ts.map +1 -0
- package/dist/tools/frontend-performance-doctor.js +731 -0
- package/dist/tools/frontend-performance-doctor.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS Architecture Wizard
|
|
3
|
+
*
|
|
4
|
+
* Solves CSS chaos and helps build scalable styling architecture:
|
|
5
|
+
* - Detects specificity conflicts and wars
|
|
6
|
+
* - Suggests BEM/CSS Modules/Tailwind structure
|
|
7
|
+
* - Finds unused and duplicate CSS
|
|
8
|
+
* - Generates design system tokens
|
|
9
|
+
* - Creates consistent spacing/color systems
|
|
10
|
+
* - Converts CSS to CSS-in-JS or vice versa
|
|
11
|
+
*/
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { logger } from '../utils.js';
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Validation Schema
|
|
16
|
+
// ============================================================================
|
|
17
|
+
export const CSSRequestSchema = z.object({
|
|
18
|
+
css: z.string().min(1, 'CSS is required'),
|
|
19
|
+
html: z.string().optional(),
|
|
20
|
+
targetMethodology: z.enum(['bem', 'css-modules', 'tailwind', 'styled-components', 'emotion']).optional(),
|
|
21
|
+
generateTokens: z.boolean().optional().default(true),
|
|
22
|
+
framework: z.enum(['react', 'vue', 'angular', 'svelte']).optional()
|
|
23
|
+
});
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Specificity Calculator
|
|
26
|
+
// ============================================================================
|
|
27
|
+
function calculateSpecificity(selector) {
|
|
28
|
+
let a = 0; // IDs
|
|
29
|
+
let b = 0; // Classes, attributes, pseudo-classes
|
|
30
|
+
let c = 0; // Elements, pseudo-elements
|
|
31
|
+
// Count IDs
|
|
32
|
+
a = (selector.match(/#[\w-]+/g) || []).length;
|
|
33
|
+
// Count classes, attributes, pseudo-classes
|
|
34
|
+
b = (selector.match(/\.[\w-]+/g) || []).length;
|
|
35
|
+
b += (selector.match(/\[[^\]]+\]/g) || []).length;
|
|
36
|
+
b += (selector.match(/:(?!:)[\w-]+/g) || []).length;
|
|
37
|
+
// Count elements and pseudo-elements
|
|
38
|
+
c = (selector.match(/(?:^|[\s>+~])[\w-]+/g) || []).length;
|
|
39
|
+
c += (selector.match(/::[\w-]+/g) || []).length;
|
|
40
|
+
// Remove !important, :not(), etc. contributions
|
|
41
|
+
const score = (a * 100) + (b * 10) + c;
|
|
42
|
+
return { a, b, c, score };
|
|
43
|
+
}
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Color Extraction
|
|
46
|
+
// ============================================================================
|
|
47
|
+
function extractColors(css) {
|
|
48
|
+
const colors = new Map();
|
|
49
|
+
// Match hex colors
|
|
50
|
+
const hexMatches = css.match(/#[0-9A-Fa-f]{3,8}\b/g) || [];
|
|
51
|
+
// Match rgb/rgba colors
|
|
52
|
+
const rgbMatches = css.match(/rgba?\([^)]+\)/g) || [];
|
|
53
|
+
// Match hsl/hsla colors
|
|
54
|
+
const hslMatches = css.match(/hsla?\([^)]+\)/g) || [];
|
|
55
|
+
const allColors = [...hexMatches, ...rgbMatches, ...hslMatches];
|
|
56
|
+
let colorIndex = 1;
|
|
57
|
+
const colorNames = {
|
|
58
|
+
'#000000': 'black',
|
|
59
|
+
'#000': 'black',
|
|
60
|
+
'#ffffff': 'white',
|
|
61
|
+
'#fff': 'white',
|
|
62
|
+
'#f00': 'red',
|
|
63
|
+
'#0f0': 'green',
|
|
64
|
+
'#00f': 'blue',
|
|
65
|
+
};
|
|
66
|
+
for (const color of allColors) {
|
|
67
|
+
const normalizedColor = color.toLowerCase();
|
|
68
|
+
if (!colors.has(normalizedColor)) {
|
|
69
|
+
const name = colorNames[normalizedColor] || `color-${colorIndex++}`;
|
|
70
|
+
colors.set(normalizedColor, {
|
|
71
|
+
name: `--${name}`,
|
|
72
|
+
value: color,
|
|
73
|
+
cssVariable: `var(--${name})`,
|
|
74
|
+
category: 'color'
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return Array.from(colors.values());
|
|
79
|
+
}
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// Spacing Extraction
|
|
82
|
+
// ============================================================================
|
|
83
|
+
function extractSpacing(css) {
|
|
84
|
+
const spacing = new Map();
|
|
85
|
+
// Match pixel values
|
|
86
|
+
const pxMatches = css.match(/:\s*(\d+(?:\.\d+)?px)/g) || [];
|
|
87
|
+
// Match rem values
|
|
88
|
+
const remMatches = css.match(/:\s*(\d+(?:\.\d+)?rem)/g) || [];
|
|
89
|
+
// Match em values
|
|
90
|
+
const emMatches = css.match(/:\s*(\d+(?:\.\d+)?em)/g) || [];
|
|
91
|
+
const allValues = [...pxMatches, ...remMatches, ...emMatches]
|
|
92
|
+
.map(v => v.replace(':', '').trim());
|
|
93
|
+
// Create spacing scale
|
|
94
|
+
const spacingScale = {
|
|
95
|
+
'0px': 'spacing-0',
|
|
96
|
+
'4px': 'spacing-1',
|
|
97
|
+
'8px': 'spacing-2',
|
|
98
|
+
'12px': 'spacing-3',
|
|
99
|
+
'16px': 'spacing-4',
|
|
100
|
+
'20px': 'spacing-5',
|
|
101
|
+
'24px': 'spacing-6',
|
|
102
|
+
'32px': 'spacing-8',
|
|
103
|
+
'40px': 'spacing-10',
|
|
104
|
+
'48px': 'spacing-12',
|
|
105
|
+
'64px': 'spacing-16',
|
|
106
|
+
'0.25rem': 'spacing-1',
|
|
107
|
+
'0.5rem': 'spacing-2',
|
|
108
|
+
'0.75rem': 'spacing-3',
|
|
109
|
+
'1rem': 'spacing-4',
|
|
110
|
+
'1.25rem': 'spacing-5',
|
|
111
|
+
'1.5rem': 'spacing-6',
|
|
112
|
+
'2rem': 'spacing-8',
|
|
113
|
+
};
|
|
114
|
+
for (const value of allValues) {
|
|
115
|
+
if (!spacing.has(value)) {
|
|
116
|
+
const name = spacingScale[value] || `spacing-custom-${spacing.size + 1}`;
|
|
117
|
+
spacing.set(value, {
|
|
118
|
+
name: `--${name}`,
|
|
119
|
+
value,
|
|
120
|
+
cssVariable: `var(--${name})`,
|
|
121
|
+
category: 'spacing'
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return Array.from(spacing.values());
|
|
126
|
+
}
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// Typography Extraction
|
|
129
|
+
// ============================================================================
|
|
130
|
+
function extractTypography(css) {
|
|
131
|
+
const typography = [];
|
|
132
|
+
// Extract font-size values
|
|
133
|
+
const fontSizes = css.match(/font-size:\s*([^;]+)/g) || [];
|
|
134
|
+
const uniqueSizes = [...new Set(fontSizes.map(f => f.replace('font-size:', '').trim()))];
|
|
135
|
+
const sizeScale = {
|
|
136
|
+
'12px': 'text-xs',
|
|
137
|
+
'14px': 'text-sm',
|
|
138
|
+
'16px': 'text-base',
|
|
139
|
+
'18px': 'text-lg',
|
|
140
|
+
'20px': 'text-xl',
|
|
141
|
+
'24px': 'text-2xl',
|
|
142
|
+
'30px': 'text-3xl',
|
|
143
|
+
'36px': 'text-4xl',
|
|
144
|
+
'48px': 'text-5xl',
|
|
145
|
+
'0.75rem': 'text-xs',
|
|
146
|
+
'0.875rem': 'text-sm',
|
|
147
|
+
'1rem': 'text-base',
|
|
148
|
+
'1.125rem': 'text-lg',
|
|
149
|
+
'1.25rem': 'text-xl',
|
|
150
|
+
'1.5rem': 'text-2xl',
|
|
151
|
+
};
|
|
152
|
+
for (const size of uniqueSizes) {
|
|
153
|
+
const name = sizeScale[size] || `text-custom-${typography.length + 1}`;
|
|
154
|
+
typography.push({
|
|
155
|
+
name: `--${name}`,
|
|
156
|
+
value: size,
|
|
157
|
+
cssVariable: `var(--${name})`,
|
|
158
|
+
category: 'typography'
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
// Extract font-family values
|
|
162
|
+
const fontFamilies = css.match(/font-family:\s*([^;]+)/g) || [];
|
|
163
|
+
const uniqueFamilies = [...new Set(fontFamilies.map(f => f.replace('font-family:', '').trim()))];
|
|
164
|
+
let familyIndex = 1;
|
|
165
|
+
for (const family of uniqueFamilies) {
|
|
166
|
+
typography.push({
|
|
167
|
+
name: `--font-${familyIndex === 1 ? 'primary' : familyIndex === 2 ? 'secondary' : `custom-${familyIndex}`}`,
|
|
168
|
+
value: family,
|
|
169
|
+
cssVariable: `var(--font-${familyIndex === 1 ? 'primary' : familyIndex === 2 ? 'secondary' : `custom-${familyIndex}`})`,
|
|
170
|
+
category: 'typography'
|
|
171
|
+
});
|
|
172
|
+
familyIndex++;
|
|
173
|
+
}
|
|
174
|
+
return typography;
|
|
175
|
+
}
|
|
176
|
+
// ============================================================================
|
|
177
|
+
// Main Analysis Function
|
|
178
|
+
// ============================================================================
|
|
179
|
+
export async function analyzeCSS(request) {
|
|
180
|
+
const validated = CSSRequestSchema.parse(request);
|
|
181
|
+
logger.info('Analyzing CSS architecture');
|
|
182
|
+
const specificityIssues = [];
|
|
183
|
+
const duplicateRules = [];
|
|
184
|
+
// Parse CSS rules
|
|
185
|
+
const ruleMatches = validated.css.match(/([^{]+)\{([^}]+)\}/g) || [];
|
|
186
|
+
const totalRules = ruleMatches.length;
|
|
187
|
+
// Extract all selectors
|
|
188
|
+
const selectors = validated.css.match(/[^{}]+(?=\{)/g) || [];
|
|
189
|
+
const totalSelectors = selectors.length;
|
|
190
|
+
// Analyze specificity
|
|
191
|
+
let totalSpecificity = 0;
|
|
192
|
+
for (const selector of selectors) {
|
|
193
|
+
const trimmedSelector = selector.trim();
|
|
194
|
+
if (!trimmedSelector)
|
|
195
|
+
continue;
|
|
196
|
+
const specificity = calculateSpecificity(trimmedSelector);
|
|
197
|
+
totalSpecificity += specificity.score;
|
|
198
|
+
// Check for high specificity issues
|
|
199
|
+
if (specificity.a > 0) {
|
|
200
|
+
specificityIssues.push({
|
|
201
|
+
selector: trimmedSelector,
|
|
202
|
+
specificity: `(${specificity.a},${specificity.b},${specificity.c})`,
|
|
203
|
+
problem: 'Uses ID selector which has very high specificity',
|
|
204
|
+
suggestion: 'Replace ID with class selector for more maintainable CSS',
|
|
205
|
+
refactoredSelector: trimmedSelector.replace(/#([\w-]+)/g, '.$1')
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if (specificity.score > 30) {
|
|
209
|
+
specificityIssues.push({
|
|
210
|
+
selector: trimmedSelector,
|
|
211
|
+
specificity: `(${specificity.a},${specificity.b},${specificity.c})`,
|
|
212
|
+
problem: 'Selector has very high specificity, hard to override',
|
|
213
|
+
suggestion: 'Flatten the selector or use BEM methodology',
|
|
214
|
+
refactoredSelector: generateBEMSelector(trimmedSelector)
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
// Check for !important abuse
|
|
218
|
+
if (validated.css.includes(`${trimmedSelector}`) && validated.css.includes('!important')) {
|
|
219
|
+
specificityIssues.push({
|
|
220
|
+
selector: trimmedSelector,
|
|
221
|
+
specificity: 'Infinity (!important)',
|
|
222
|
+
problem: '!important makes CSS hard to maintain and override',
|
|
223
|
+
suggestion: 'Increase specificity naturally or refactor CSS structure',
|
|
224
|
+
refactoredSelector: trimmedSelector
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Find duplicate properties
|
|
229
|
+
const propertyMap = new Map();
|
|
230
|
+
const propertyMatches = validated.css.match(/[\w-]+:\s*[^;]+/g) || [];
|
|
231
|
+
for (const prop of propertyMatches) {
|
|
232
|
+
const key = prop.trim();
|
|
233
|
+
propertyMap.set(key, (propertyMap.get(key) || 0) + 1);
|
|
234
|
+
}
|
|
235
|
+
for (const [property, count] of propertyMap) {
|
|
236
|
+
if (count > 2) {
|
|
237
|
+
const propName = property.split(':')[0] || 'unknown';
|
|
238
|
+
duplicateRules.push({
|
|
239
|
+
property,
|
|
240
|
+
occurrences: count,
|
|
241
|
+
suggestion: `Extract to CSS variable: var(--${propName.replace(/\s/g, '-')})`
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Extract design tokens
|
|
246
|
+
const colors = validated.generateTokens ? extractColors(validated.css) : [];
|
|
247
|
+
const spacing = validated.generateTokens ? extractSpacing(validated.css) : [];
|
|
248
|
+
const typography = validated.generateTokens ? extractTypography(validated.css) : [];
|
|
249
|
+
// Calculate health score
|
|
250
|
+
const avgSpecificity = totalSelectors > 0 ? totalSpecificity / totalSelectors : 0;
|
|
251
|
+
let healthScore = 100;
|
|
252
|
+
healthScore -= specificityIssues.length * 5;
|
|
253
|
+
healthScore -= duplicateRules.length * 2;
|
|
254
|
+
healthScore -= Math.min(avgSpecificity, 30);
|
|
255
|
+
healthScore = Math.max(0, healthScore);
|
|
256
|
+
const healthGrade = healthScore >= 90 ? 'A' : healthScore >= 80 ? 'B' : healthScore >= 70 ? 'C' : healthScore >= 60 ? 'D' : 'F';
|
|
257
|
+
// Generate refactored CSS
|
|
258
|
+
const refactoredCSS = await generateRefactoredCSS(validated, { colors, spacing, typography });
|
|
259
|
+
// Generate migration guide if target methodology specified
|
|
260
|
+
let migrationGuide;
|
|
261
|
+
if (validated.targetMethodology) {
|
|
262
|
+
migrationGuide = generateMigrationGuide(validated.css, validated.targetMethodology, validated.framework);
|
|
263
|
+
}
|
|
264
|
+
// Find unused classes if HTML provided
|
|
265
|
+
const unusedClasses = [];
|
|
266
|
+
if (validated.html) {
|
|
267
|
+
const cssClasses = (validated.css.match(/\.[\w-]+/g) || []).map(c => c.slice(1));
|
|
268
|
+
for (const className of cssClasses) {
|
|
269
|
+
if (!validated.html.includes(className)) {
|
|
270
|
+
unusedClasses.push(className);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Generate best practices
|
|
275
|
+
const bestPractices = generateBestPractices(specificityIssues, duplicateRules, validated.targetMethodology);
|
|
276
|
+
return {
|
|
277
|
+
summary: {
|
|
278
|
+
totalRules,
|
|
279
|
+
totalSelectors,
|
|
280
|
+
specificityScore: Math.round(avgSpecificity * 10) / 10,
|
|
281
|
+
healthGrade,
|
|
282
|
+
issues: specificityIssues.length + duplicateRules.length
|
|
283
|
+
},
|
|
284
|
+
specificityIssues,
|
|
285
|
+
unusedClasses,
|
|
286
|
+
duplicateRules,
|
|
287
|
+
designTokens: {
|
|
288
|
+
colors,
|
|
289
|
+
spacing,
|
|
290
|
+
typography
|
|
291
|
+
},
|
|
292
|
+
refactoredCSS,
|
|
293
|
+
migrationGuide,
|
|
294
|
+
bestPractices
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
// ============================================================================
|
|
298
|
+
// Helper Functions
|
|
299
|
+
// ============================================================================
|
|
300
|
+
function generateBEMSelector(selector) {
|
|
301
|
+
// Convert complex selector to BEM-style
|
|
302
|
+
const parts = selector.split(/[\s>+~]+/);
|
|
303
|
+
if (parts.length <= 1)
|
|
304
|
+
return selector;
|
|
305
|
+
const block = (parts[0] || 'block').replace(/[#.]/g, '');
|
|
306
|
+
const element = (parts[parts.length - 1] || 'element').replace(/[#.]/g, '');
|
|
307
|
+
return `.${block}__${element}`;
|
|
308
|
+
}
|
|
309
|
+
async function generateRefactoredCSS(request, tokens) {
|
|
310
|
+
let refactored = request.css;
|
|
311
|
+
// Generate CSS variables section
|
|
312
|
+
const cssVariables = `/* Design System Tokens */
|
|
313
|
+
:root {
|
|
314
|
+
/* Colors */
|
|
315
|
+
${tokens.colors.map(c => ` ${c.name}: ${c.value};`).join('\n')}
|
|
316
|
+
|
|
317
|
+
/* Spacing */
|
|
318
|
+
${tokens.spacing.map(s => ` ${s.name}: ${s.value};`).join('\n')}
|
|
319
|
+
|
|
320
|
+
/* Typography */
|
|
321
|
+
${tokens.typography.map(t => ` ${t.name}: ${t.value};`).join('\n')}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* Refactored Styles */
|
|
325
|
+
`;
|
|
326
|
+
// Replace hardcoded values with variables
|
|
327
|
+
for (const color of tokens.colors) {
|
|
328
|
+
refactored = refactored.replace(new RegExp(escapeRegex(color.value), 'gi'), color.cssVariable);
|
|
329
|
+
}
|
|
330
|
+
return cssVariables + refactored;
|
|
331
|
+
}
|
|
332
|
+
function escapeRegex(string) {
|
|
333
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
334
|
+
}
|
|
335
|
+
function generateMigrationGuide(css, target, framework) {
|
|
336
|
+
const guides = {
|
|
337
|
+
'bem': {
|
|
338
|
+
steps: [
|
|
339
|
+
'1. Identify block components (header, card, button, etc.)',
|
|
340
|
+
'2. Rename elements using block__element pattern',
|
|
341
|
+
'3. Add modifiers using block--modifier or block__element--modifier',
|
|
342
|
+
'4. Flatten all nested selectors',
|
|
343
|
+
'5. Remove ID selectors, replace with classes',
|
|
344
|
+
'6. Maximum specificity should be 1-2 classes'
|
|
345
|
+
],
|
|
346
|
+
convertedCode: convertToBEM(css)
|
|
347
|
+
},
|
|
348
|
+
'css-modules': {
|
|
349
|
+
steps: [
|
|
350
|
+
'1. Rename CSS file to *.module.css',
|
|
351
|
+
'2. Import styles: import styles from "./Component.module.css"',
|
|
352
|
+
'3. Use camelCase class names',
|
|
353
|
+
'4. Apply: className={styles.container}',
|
|
354
|
+
'5. For dynamic classes: className={`${styles.button} ${isActive ? styles.active : ""}`}',
|
|
355
|
+
'6. Compose reusable styles using composes:'
|
|
356
|
+
],
|
|
357
|
+
convertedCode: convertToCSSModules(css, framework)
|
|
358
|
+
},
|
|
359
|
+
'tailwind': {
|
|
360
|
+
steps: [
|
|
361
|
+
'1. Install Tailwind CSS: npm install tailwindcss',
|
|
362
|
+
'2. Initialize: npx tailwindcss init',
|
|
363
|
+
'3. Replace CSS classes with Tailwind utilities',
|
|
364
|
+
'4. Use @apply for repeated patterns',
|
|
365
|
+
'5. Configure theme in tailwind.config.js',
|
|
366
|
+
'6. Remove unused custom CSS'
|
|
367
|
+
],
|
|
368
|
+
convertedCode: convertToTailwind(css)
|
|
369
|
+
},
|
|
370
|
+
'styled-components': {
|
|
371
|
+
steps: [
|
|
372
|
+
'1. Install: npm install styled-components',
|
|
373
|
+
'2. Import: import styled from "styled-components"',
|
|
374
|
+
'3. Create styled components for each element',
|
|
375
|
+
'4. Use props for dynamic styling',
|
|
376
|
+
'5. Create theme provider for design tokens',
|
|
377
|
+
'6. Use css helper for conditional styles'
|
|
378
|
+
],
|
|
379
|
+
convertedCode: convertToStyledComponents(css)
|
|
380
|
+
},
|
|
381
|
+
'emotion': {
|
|
382
|
+
steps: [
|
|
383
|
+
'1. Install: npm install @emotion/react @emotion/styled',
|
|
384
|
+
'2. Add babel plugin or use pragma comment',
|
|
385
|
+
'3. Use css prop or styled API',
|
|
386
|
+
'4. Create reusable styles with css function',
|
|
387
|
+
'5. Implement theme with ThemeProvider',
|
|
388
|
+
'6. Use keyframes for animations'
|
|
389
|
+
],
|
|
390
|
+
convertedCode: convertToEmotion(css)
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
const guide = guides[target];
|
|
394
|
+
if (!guide) {
|
|
395
|
+
return {
|
|
396
|
+
from: 'Plain CSS',
|
|
397
|
+
to: target,
|
|
398
|
+
steps: ['No migration guide available for this methodology'],
|
|
399
|
+
convertedCode: css
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
from: 'Plain CSS',
|
|
404
|
+
to: target,
|
|
405
|
+
steps: guide.steps,
|
|
406
|
+
convertedCode: guide.convertedCode
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function convertToBEM(css) {
|
|
410
|
+
// Simple BEM conversion example
|
|
411
|
+
return `/* BEM Converted Styles */
|
|
412
|
+
|
|
413
|
+
/* Block */
|
|
414
|
+
.card {
|
|
415
|
+
display: flex;
|
|
416
|
+
flex-direction: column;
|
|
417
|
+
padding: var(--spacing-4);
|
|
418
|
+
background: var(--color-white);
|
|
419
|
+
border-radius: 8px;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/* Element */
|
|
423
|
+
.card__header {
|
|
424
|
+
font-size: var(--text-xl);
|
|
425
|
+
font-weight: 600;
|
|
426
|
+
margin-bottom: var(--spacing-3);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.card__body {
|
|
430
|
+
flex: 1;
|
|
431
|
+
color: var(--color-gray-600);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.card__footer {
|
|
435
|
+
display: flex;
|
|
436
|
+
gap: var(--spacing-2);
|
|
437
|
+
margin-top: var(--spacing-4);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/* Modifiers */
|
|
441
|
+
.card--featured {
|
|
442
|
+
border: 2px solid var(--color-primary);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.card--compact {
|
|
446
|
+
padding: var(--spacing-2);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.card__button {
|
|
450
|
+
padding: var(--spacing-2) var(--spacing-4);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.card__button--primary {
|
|
454
|
+
background: var(--color-primary);
|
|
455
|
+
color: white;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.card__button--secondary {
|
|
459
|
+
background: transparent;
|
|
460
|
+
border: 1px solid var(--color-gray-300);
|
|
461
|
+
}`;
|
|
462
|
+
}
|
|
463
|
+
function convertToCSSModules(css, framework) {
|
|
464
|
+
const importStatement = framework === 'vue'
|
|
465
|
+
? `<style module>`
|
|
466
|
+
: `import styles from './Component.module.css';`;
|
|
467
|
+
return `/* Component.module.css */
|
|
468
|
+
|
|
469
|
+
.container {
|
|
470
|
+
display: flex;
|
|
471
|
+
flex-direction: column;
|
|
472
|
+
gap: 1rem;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.header {
|
|
476
|
+
font-size: 1.5rem;
|
|
477
|
+
font-weight: 600;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.content {
|
|
481
|
+
flex: 1;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.button {
|
|
485
|
+
padding: 0.5rem 1rem;
|
|
486
|
+
border-radius: 4px;
|
|
487
|
+
cursor: pointer;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.buttonPrimary {
|
|
491
|
+
composes: button;
|
|
492
|
+
background: var(--color-primary);
|
|
493
|
+
color: white;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.buttonSecondary {
|
|
497
|
+
composes: button;
|
|
498
|
+
background: transparent;
|
|
499
|
+
border: 1px solid #ccc;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/* Usage in ${framework || 'React'}: */
|
|
503
|
+
/*
|
|
504
|
+
${importStatement}
|
|
505
|
+
|
|
506
|
+
${framework === 'vue' ? `
|
|
507
|
+
<template>
|
|
508
|
+
<div :class="$style.container">
|
|
509
|
+
<h1 :class="$style.header">Title</h1>
|
|
510
|
+
<button :class="$style.buttonPrimary">Click</button>
|
|
511
|
+
</div>
|
|
512
|
+
</template>
|
|
513
|
+
` : `
|
|
514
|
+
function Component() {
|
|
515
|
+
return (
|
|
516
|
+
<div className={styles.container}>
|
|
517
|
+
<h1 className={styles.header}>Title</h1>
|
|
518
|
+
<button className={styles.buttonPrimary}>Click</button>
|
|
519
|
+
</div>
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
`}
|
|
523
|
+
*/`;
|
|
524
|
+
}
|
|
525
|
+
function convertToTailwind(css) {
|
|
526
|
+
return `<!-- Tailwind CSS Conversion -->
|
|
527
|
+
|
|
528
|
+
<!-- Before (custom CSS) -->
|
|
529
|
+
<div class="card">
|
|
530
|
+
<h2 class="card-title">Title</h2>
|
|
531
|
+
<p class="card-content">Content here</p>
|
|
532
|
+
<button class="btn btn-primary">Click Me</button>
|
|
533
|
+
</div>
|
|
534
|
+
|
|
535
|
+
<!-- After (Tailwind) -->
|
|
536
|
+
<div class="flex flex-col p-4 bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow">
|
|
537
|
+
<h2 class="text-xl font-semibold text-gray-900 mb-3">Title</h2>
|
|
538
|
+
<p class="text-gray-600 flex-1">Content here</p>
|
|
539
|
+
<button class="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md
|
|
540
|
+
hover:bg-blue-700 focus:ring-2 focus:ring-blue-500
|
|
541
|
+
focus:ring-offset-2 transition-colors">
|
|
542
|
+
Click Me
|
|
543
|
+
</button>
|
|
544
|
+
</div>
|
|
545
|
+
|
|
546
|
+
<!-- For repeated patterns, use @apply in CSS -->
|
|
547
|
+
/* globals.css */
|
|
548
|
+
@layer components {
|
|
549
|
+
.btn {
|
|
550
|
+
@apply px-4 py-2 rounded-md font-medium transition-colors
|
|
551
|
+
focus:ring-2 focus:ring-offset-2;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.btn-primary {
|
|
555
|
+
@apply bg-blue-600 text-white hover:bg-blue-700
|
|
556
|
+
focus:ring-blue-500;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.btn-secondary {
|
|
560
|
+
@apply bg-gray-100 text-gray-900 hover:bg-gray-200
|
|
561
|
+
focus:ring-gray-500;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.card {
|
|
565
|
+
@apply flex flex-col p-4 bg-white rounded-lg shadow-md;
|
|
566
|
+
}
|
|
567
|
+
}`;
|
|
568
|
+
}
|
|
569
|
+
function convertToStyledComponents(css) {
|
|
570
|
+
return `// styled-components Conversion
|
|
571
|
+
import styled from 'styled-components';
|
|
572
|
+
|
|
573
|
+
// Theme definition
|
|
574
|
+
export const theme = {
|
|
575
|
+
colors: {
|
|
576
|
+
primary: '#3B82F6',
|
|
577
|
+
secondary: '#6B7280',
|
|
578
|
+
background: '#FFFFFF',
|
|
579
|
+
text: '#1F2937',
|
|
580
|
+
},
|
|
581
|
+
spacing: {
|
|
582
|
+
xs: '0.25rem',
|
|
583
|
+
sm: '0.5rem',
|
|
584
|
+
md: '1rem',
|
|
585
|
+
lg: '1.5rem',
|
|
586
|
+
xl: '2rem',
|
|
587
|
+
},
|
|
588
|
+
borderRadius: {
|
|
589
|
+
sm: '0.25rem',
|
|
590
|
+
md: '0.5rem',
|
|
591
|
+
lg: '1rem',
|
|
592
|
+
},
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
// Styled Components
|
|
596
|
+
export const Card = styled.div\`
|
|
597
|
+
display: flex;
|
|
598
|
+
flex-direction: column;
|
|
599
|
+
padding: \${({ theme }) => theme.spacing.lg};
|
|
600
|
+
background: \${({ theme }) => theme.colors.background};
|
|
601
|
+
border-radius: \${({ theme }) => theme.borderRadius.lg};
|
|
602
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
603
|
+
|
|
604
|
+
&:hover {
|
|
605
|
+
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.15);
|
|
606
|
+
}
|
|
607
|
+
\`;
|
|
608
|
+
|
|
609
|
+
export const CardTitle = styled.h2\`
|
|
610
|
+
font-size: 1.25rem;
|
|
611
|
+
font-weight: 600;
|
|
612
|
+
color: \${({ theme }) => theme.colors.text};
|
|
613
|
+
margin-bottom: \${({ theme }) => theme.spacing.md};
|
|
614
|
+
\`;
|
|
615
|
+
|
|
616
|
+
export const CardContent = styled.p\`
|
|
617
|
+
color: \${({ theme }) => theme.colors.secondary};
|
|
618
|
+
flex: 1;
|
|
619
|
+
\`;
|
|
620
|
+
|
|
621
|
+
export const Button = styled.button\`
|
|
622
|
+
padding: \${({ theme }) => theme.spacing.sm} \${({ theme }) => theme.spacing.md};
|
|
623
|
+
border-radius: \${({ theme }) => theme.borderRadius.md};
|
|
624
|
+
font-weight: 500;
|
|
625
|
+
cursor: pointer;
|
|
626
|
+
transition: all 0.2s ease;
|
|
627
|
+
|
|
628
|
+
\${({ variant, theme }) => variant === 'primary' && \`
|
|
629
|
+
background: \${theme.colors.primary};
|
|
630
|
+
color: white;
|
|
631
|
+
border: none;
|
|
632
|
+
|
|
633
|
+
&:hover {
|
|
634
|
+
background: #2563EB;
|
|
635
|
+
}
|
|
636
|
+
\`}
|
|
637
|
+
|
|
638
|
+
\${({ variant, theme }) => variant === 'secondary' && \`
|
|
639
|
+
background: transparent;
|
|
640
|
+
color: \${theme.colors.text};
|
|
641
|
+
border: 1px solid #E5E7EB;
|
|
642
|
+
|
|
643
|
+
&:hover {
|
|
644
|
+
background: #F9FAFB;
|
|
645
|
+
}
|
|
646
|
+
\`}
|
|
647
|
+
\`;
|
|
648
|
+
|
|
649
|
+
// Usage
|
|
650
|
+
/*
|
|
651
|
+
import { ThemeProvider } from 'styled-components';
|
|
652
|
+
import { theme, Card, CardTitle, CardContent, Button } from './styles';
|
|
653
|
+
|
|
654
|
+
function App() {
|
|
655
|
+
return (
|
|
656
|
+
<ThemeProvider theme={theme}>
|
|
657
|
+
<Card>
|
|
658
|
+
<CardTitle>Welcome</CardTitle>
|
|
659
|
+
<CardContent>This is styled-components</CardContent>
|
|
660
|
+
<Button variant="primary">Get Started</Button>
|
|
661
|
+
</Card>
|
|
662
|
+
</ThemeProvider>
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
*/`;
|
|
666
|
+
}
|
|
667
|
+
function convertToEmotion(css) {
|
|
668
|
+
return `// Emotion CSS-in-JS Conversion
|
|
669
|
+
/** @jsxImportSource @emotion/react */
|
|
670
|
+
import { css, keyframes } from '@emotion/react';
|
|
671
|
+
import styled from '@emotion/styled';
|
|
672
|
+
|
|
673
|
+
// Keyframe animations
|
|
674
|
+
const fadeIn = keyframes\`
|
|
675
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
676
|
+
to { opacity: 1; transform: translateY(0); }
|
|
677
|
+
\`;
|
|
678
|
+
|
|
679
|
+
// Reusable style objects
|
|
680
|
+
const baseButton = css\`
|
|
681
|
+
padding: 0.5rem 1rem;
|
|
682
|
+
border-radius: 0.5rem;
|
|
683
|
+
font-weight: 500;
|
|
684
|
+
cursor: pointer;
|
|
685
|
+
transition: all 0.2s ease;
|
|
686
|
+
|
|
687
|
+
&:focus {
|
|
688
|
+
outline: none;
|
|
689
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5);
|
|
690
|
+
}
|
|
691
|
+
\`;
|
|
692
|
+
|
|
693
|
+
// Theme type
|
|
694
|
+
interface Theme {
|
|
695
|
+
colors: {
|
|
696
|
+
primary: string;
|
|
697
|
+
secondary: string;
|
|
698
|
+
background: string;
|
|
699
|
+
text: string;
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const theme: Theme = {
|
|
704
|
+
colors: {
|
|
705
|
+
primary: '#3B82F6',
|
|
706
|
+
secondary: '#6B7280',
|
|
707
|
+
background: '#FFFFFF',
|
|
708
|
+
text: '#1F2937',
|
|
709
|
+
},
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
// Styled components with Emotion
|
|
713
|
+
const Card = styled.div\`
|
|
714
|
+
display: flex;
|
|
715
|
+
flex-direction: column;
|
|
716
|
+
padding: 1.5rem;
|
|
717
|
+
background: white;
|
|
718
|
+
border-radius: 1rem;
|
|
719
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
720
|
+
animation: \${fadeIn} 0.3s ease-out;
|
|
721
|
+
\`;
|
|
722
|
+
|
|
723
|
+
const PrimaryButton = styled.button\`
|
|
724
|
+
\${baseButton}
|
|
725
|
+
background: \${({ theme }) => theme.colors.primary};
|
|
726
|
+
color: white;
|
|
727
|
+
border: none;
|
|
728
|
+
|
|
729
|
+
&:hover {
|
|
730
|
+
background: #2563EB;
|
|
731
|
+
transform: translateY(-1px);
|
|
732
|
+
}
|
|
733
|
+
\`;
|
|
734
|
+
|
|
735
|
+
// Using css prop directly
|
|
736
|
+
function Component() {
|
|
737
|
+
return (
|
|
738
|
+
<div
|
|
739
|
+
css={css\`
|
|
740
|
+
display: grid;
|
|
741
|
+
gap: 1rem;
|
|
742
|
+
padding: 2rem;
|
|
743
|
+
\`}
|
|
744
|
+
>
|
|
745
|
+
<Card>
|
|
746
|
+
<h2
|
|
747
|
+
css={css\`
|
|
748
|
+
font-size: 1.5rem;
|
|
749
|
+
font-weight: 600;
|
|
750
|
+
margin-bottom: 1rem;
|
|
751
|
+
\`}
|
|
752
|
+
>
|
|
753
|
+
Emotion Example
|
|
754
|
+
</h2>
|
|
755
|
+
<PrimaryButton>Click Me</PrimaryButton>
|
|
756
|
+
</Card>
|
|
757
|
+
</div>
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
export { theme, Card, PrimaryButton };`;
|
|
762
|
+
}
|
|
763
|
+
function generateBestPractices(specificityIssues, duplicateRules, methodology) {
|
|
764
|
+
const practices = [];
|
|
765
|
+
if (specificityIssues.some(i => i.problem.includes('ID'))) {
|
|
766
|
+
practices.push('Avoid ID selectors - they have high specificity and are hard to override');
|
|
767
|
+
}
|
|
768
|
+
if (specificityIssues.some(i => i.problem.includes('!important'))) {
|
|
769
|
+
practices.push('Never use !important - refactor your CSS structure instead');
|
|
770
|
+
}
|
|
771
|
+
if (duplicateRules.length > 3) {
|
|
772
|
+
practices.push('Extract repeated values into CSS custom properties (variables)');
|
|
773
|
+
}
|
|
774
|
+
practices.push('Keep selector specificity as low as possible (max 2-3 classes)');
|
|
775
|
+
practices.push('Use a consistent naming convention (BEM, SUIT CSS, etc.)');
|
|
776
|
+
practices.push('Organize CSS by component, not by property type');
|
|
777
|
+
practices.push('Use CSS custom properties for theming and design tokens');
|
|
778
|
+
practices.push('Consider mobile-first approach with min-width media queries');
|
|
779
|
+
if (methodology === 'tailwind') {
|
|
780
|
+
practices.push('Use @apply sparingly - prefer utility classes in markup');
|
|
781
|
+
practices.push('Configure theme values in tailwind.config.js for consistency');
|
|
782
|
+
}
|
|
783
|
+
if (methodology === 'styled-components' || methodology === 'emotion') {
|
|
784
|
+
practices.push('Co-locate styles with components for better maintainability');
|
|
785
|
+
practices.push('Use ThemeProvider for global design tokens');
|
|
786
|
+
}
|
|
787
|
+
return practices;
|
|
788
|
+
}
|
|
789
|
+
export default { analyzeCSS };
|
|
790
|
+
//# sourceMappingURL=css-architecture-wizard.js.map
|