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.
@@ -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