@rakeyshgidwani/roger-ui-bank-theme-harvey 0.2.52 → 0.3.1

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 (131) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/dist/components/ui/button.d.ts +3 -1
  3. package/dist/components/ui/button.d.ts.map +1 -1
  4. package/dist/components/ui/button.esm.js +3 -2
  5. package/dist/components/ui/button.js +3 -2
  6. package/dist/components/ui/layout/container.d.ts +57 -0
  7. package/dist/components/ui/layout/container.d.ts.map +1 -0
  8. package/dist/components/ui/layout/container.esm.js +173 -0
  9. package/dist/components/ui/layout/container.js +173 -0
  10. package/dist/components/ui/layout/index.d.ts +9 -0
  11. package/dist/components/ui/layout/index.d.ts.map +1 -0
  12. package/dist/components/ui/layout/index.esm.js +6 -0
  13. package/dist/components/ui/layout/index.js +6 -0
  14. package/dist/components/ui/layout/responsive-grid.d.ts +93 -0
  15. package/dist/components/ui/layout/responsive-grid.d.ts.map +1 -0
  16. package/dist/components/ui/layout/responsive-grid.esm.js +124 -0
  17. package/dist/components/ui/layout/responsive-grid.js +124 -0
  18. package/dist/components/ui/layouts/adaptive-layout.d.ts +1 -0
  19. package/dist/components/ui/layouts/adaptive-layout.d.ts.map +1 -1
  20. package/dist/components/ui/layouts/adaptive-layout.esm.js +2 -2
  21. package/dist/components/ui/layouts/adaptive-layout.js +2 -2
  22. package/dist/components/ui/navigation/index.d.ts +2 -1
  23. package/dist/components/ui/navigation/index.d.ts.map +1 -1
  24. package/dist/components/ui/navigation/index.esm.js +1 -0
  25. package/dist/components/ui/navigation/index.js +1 -0
  26. package/dist/components/ui/navigation/progressive-navigation.d.ts +37 -0
  27. package/dist/components/ui/navigation/progressive-navigation.d.ts.map +1 -0
  28. package/dist/components/ui/navigation/progressive-navigation.esm.js +145 -0
  29. package/dist/components/ui/navigation/progressive-navigation.js +145 -0
  30. package/dist/components/ui/navigation/types.d.ts +21 -0
  31. package/dist/components/ui/navigation/types.d.ts.map +1 -1
  32. package/dist/hooks/use-adaptive-layout.d.ts +2 -1
  33. package/dist/hooks/use-adaptive-layout.d.ts.map +1 -1
  34. package/dist/hooks/use-adaptive-layout.esm.js +13 -8
  35. package/dist/hooks/use-adaptive-layout.js +13 -8
  36. package/dist/hooks/use-device.d.ts +3 -1
  37. package/dist/hooks/use-device.d.ts.map +1 -1
  38. package/dist/hooks/use-device.esm.js +14 -7
  39. package/dist/hooks/use-device.js +14 -7
  40. package/dist/index.d.ts +19 -4
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.esm.js +9 -4
  43. package/dist/index.js +9 -4
  44. package/dist/plugins/css-purge-optimizer.d.ts +25 -0
  45. package/dist/plugins/css-purge-optimizer.d.ts.map +1 -0
  46. package/dist/plugins/css-purge-optimizer.esm.js +414 -0
  47. package/dist/plugins/css-purge-optimizer.js +414 -0
  48. package/dist/plugins/performance-monitor.d.ts +29 -0
  49. package/dist/plugins/performance-monitor.d.ts.map +1 -0
  50. package/dist/plugins/performance-monitor.esm.js +221 -0
  51. package/dist/plugins/performance-monitor.js +221 -0
  52. package/dist/plugins/progressive-css-loader.d.ts +21 -0
  53. package/dist/plugins/progressive-css-loader.d.ts.map +1 -0
  54. package/dist/plugins/progressive-css-loader.esm.js +227 -0
  55. package/dist/plugins/progressive-css-loader.js +227 -0
  56. package/dist/plugins/theme-css-generator.d.ts.map +1 -1
  57. package/dist/plugins/theme-css-generator.esm.js +19 -6
  58. package/dist/plugins/theme-css-generator.js +19 -6
  59. package/dist/styles.css +1027 -112
  60. package/dist/theme.d.ts.map +1 -1
  61. package/dist/theme.esm.js +4 -1
  62. package/dist/theme.js +4 -1
  63. package/dist/themes/phase1-constants.d.ts +23 -0
  64. package/dist/themes/phase1-constants.d.ts.map +1 -0
  65. package/dist/themes/phase1-constants.esm.js +180 -0
  66. package/dist/themes/phase1-constants.js +180 -0
  67. package/dist/themes/themes/default.d.ts.map +1 -1
  68. package/dist/themes/themes/default.esm.js +4 -1
  69. package/dist/themes/themes/default.js +4 -1
  70. package/dist/themes/themes/harvey.d.ts.map +1 -1
  71. package/dist/themes/themes/harvey.esm.js +4 -1
  72. package/dist/themes/themes/harvey.js +4 -1
  73. package/dist/themes/types.d.ts +62 -0
  74. package/dist/themes/types.d.ts.map +1 -1
  75. package/dist/themes/validation.d.ts +17 -0
  76. package/dist/themes/validation.d.ts.map +1 -1
  77. package/dist/themes/validation.esm.js +218 -0
  78. package/dist/themes/validation.js +218 -0
  79. package/dist/types.d.ts +62 -0
  80. package/dist/types.d.ts.map +1 -1
  81. package/dist/utils/progressive-css-injector.d.ts +80 -0
  82. package/dist/utils/progressive-css-injector.d.ts.map +1 -0
  83. package/dist/utils/progressive-css-injector.esm.js +217 -0
  84. package/dist/utils/progressive-css-injector.js +217 -0
  85. package/package.json +1 -1
  86. package/src/components/ui/button.tsx +9 -6
  87. package/src/components/ui/layout/container.tsx +312 -0
  88. package/src/components/ui/layout/index.ts +10 -0
  89. package/src/components/ui/layout/responsive-grid.tsx +286 -0
  90. package/src/components/ui/layouts/adaptive-layout.tsx +3 -1
  91. package/src/components/ui/navigation/index.ts +2 -0
  92. package/src/components/ui/navigation/progressive-navigation.tsx +453 -0
  93. package/src/components/ui/navigation/types.ts +41 -0
  94. package/src/hooks/use-adaptive-layout.ts +13 -9
  95. package/src/hooks/use-device.tsx +17 -10
  96. package/src/index.ts +19 -4
  97. package/src/plugins/css-purge-optimizer.ts +491 -0
  98. package/src/plugins/performance-monitor.ts +292 -0
  99. package/src/plugins/progressive-css-loader.ts +269 -0
  100. package/src/plugins/theme-css-generator.ts +22 -6
  101. package/src/styles/components/base/badge.css +2 -2
  102. package/src/styles/components/base/button.css +238 -35
  103. package/src/styles/components/base/card.css +2 -2
  104. package/src/styles/components/base/checkbox.css +3 -3
  105. package/src/styles/components/base/label.css +3 -3
  106. package/src/styles/components/feedback/skeleton.css +1 -1
  107. package/src/styles/components/feedback/toast.css +1 -1
  108. package/src/styles/components/index.css +3 -0
  109. package/src/styles/components/layout/container.css +466 -0
  110. package/src/styles/components/layout/index.css +5 -0
  111. package/src/styles/components/layout/responsive-grid.css +422 -0
  112. package/src/styles/components/navigation/breadcrumb.css +1 -1
  113. package/src/styles/components/navigation/index.css +1 -0
  114. package/src/styles/components/navigation/menu.css +2 -2
  115. package/src/styles/components/navigation/pagination.css +4 -4
  116. package/src/styles/components/navigation/progressive-navigation.css +633 -0
  117. package/src/styles/components/navigation/sidebar.css +4 -4
  118. package/src/styles/components/navigation/stepper.css +2 -2
  119. package/src/styles/components/navigation/tabs.css +1 -1
  120. package/src/styles/components/ui/theme-toggle.css +2 -2
  121. package/src/styles/progressive.css +17 -0
  122. package/src/styles/themes/harvey.css +103 -19
  123. package/src/styles/utilities/semantic-input-system.css +7 -13
  124. package/src/theme.ts +5 -1
  125. package/src/themes/phase1-constants.ts +189 -0
  126. package/src/themes/themes/default.ts +5 -1
  127. package/src/themes/themes/harvey.ts +5 -1
  128. package/src/themes/types.ts +77 -1
  129. package/src/themes/validation.ts +249 -0
  130. package/src/types.ts +77 -1
  131. package/src/utils/progressive-css-injector.ts +254 -0
@@ -0,0 +1,491 @@
1
+ /**
2
+ * CSS Purge Optimizer Plugin
3
+ * Implements Phase 3 Performance Optimization: CSS Bundle Optimization
4
+ *
5
+ * Optimizations:
6
+ * - Removes unused responsive breakpoint styles
7
+ * - Purges unused CSS custom properties
8
+ * - Optimizes CSS layer ordering
9
+ * - Minifies and compresses output
10
+ */
11
+
12
+ import type { Plugin } from 'vite'
13
+ import postcss, { Rule, AtRule, Declaration } from 'postcss'
14
+
15
+ interface CSSPurgeConfig {
16
+ // Breakpoints to analyze for usage
17
+ breakpoints: string[]
18
+ // Custom properties to preserve (even if unused)
19
+ preserveCustomProperties: string[]
20
+ // Selectors to always preserve
21
+ preserveSelectors: string[]
22
+ // Enable aggressive optimization
23
+ aggressive: boolean
24
+ // Enable minification
25
+ minify: boolean
26
+ }
27
+
28
+ interface UsageAnalysis {
29
+ usedBreakpoints: Set<string>
30
+ usedCustomProperties: Set<string>
31
+ usedSelectors: Set<string>
32
+ unusedRules: string[]
33
+ }
34
+
35
+ const DEFAULT_CONFIG: CSSPurgeConfig = {
36
+ breakpoints: ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'],
37
+ preserveCustomProperties: [
38
+ // Core design system variables
39
+ '--cs-*',
40
+ '--tw-*', // Preserve all Tailwind CSS variables
41
+
42
+ // Component design tokens (critical for responsive system)
43
+ '--button-*',
44
+ '--badge-*',
45
+ '--card-*',
46
+ '--input-*',
47
+ '--label-*',
48
+ '--checkbox-*',
49
+ '--menu-*',
50
+ '--sidebar-*',
51
+ '--breadcrumb-*',
52
+ '--pagination-*',
53
+ '--stepper-*',
54
+ '--tabs-*',
55
+ '--progressive-nav-*',
56
+ '--skeleton-*',
57
+ '--toast-*',
58
+ '--popover-*',
59
+ '--tooltip-*',
60
+ '--enterprise-*',
61
+ '--font-*',
62
+ '--user-menu-*',
63
+ '--subscription-*',
64
+ '--list-*',
65
+ '--tree-*',
66
+
67
+ // Layout and utility tokens
68
+ '--container-*',
69
+ '--grid-*',
70
+ '--spacing-*',
71
+ '--breakpoint-*'
72
+ ],
73
+ preserveSelectors: [
74
+ '.sr-only',
75
+ '.focus\\:*',
76
+ '.hover\\:*',
77
+ '.active\\:*',
78
+ '[data-*]'
79
+ ],
80
+ aggressive: false,
81
+ minify: true
82
+ }
83
+
84
+ export default function cssPurgeOptimizer(config: Partial<CSSPurgeConfig> = {}): Plugin {
85
+ const finalConfig = { ...DEFAULT_CONFIG, ...config }
86
+
87
+ return {
88
+ name: 'css-purge-optimizer',
89
+ apply: 'build',
90
+ generateBundle(_options, bundle) {
91
+ // Find CSS assets in bundle
92
+ const cssAssets = Object.keys(bundle).filter(key => key.endsWith('.css'))
93
+
94
+ cssAssets.forEach(assetKey => {
95
+ const asset = bundle[assetKey]
96
+ if (asset.type === 'asset' && typeof asset.source === 'string') {
97
+ const optimizedCSS = optimizeCSS(asset.source, finalConfig)
98
+
99
+ // Calculate optimization savings
100
+ const originalSize = Buffer.byteLength(asset.source, 'utf8')
101
+ const optimizedSize = Buffer.byteLength(optimizedCSS, 'utf8')
102
+ const savings = originalSize - optimizedSize
103
+ const savingsPercent = Math.round((savings / originalSize) * 100)
104
+
105
+ console.log(`🔧 CSS Optimization: ${assetKey}`)
106
+ console.log(` Original: ${Math.round(originalSize / 1024 * 100) / 100}KB`)
107
+ console.log(` Optimized: ${Math.round(optimizedSize / 1024 * 100) / 100}KB`)
108
+ console.log(` Savings: ${Math.round(savings / 1024 * 100) / 100}KB (${savingsPercent}%)`)
109
+
110
+ // Update the asset with optimized CSS
111
+ asset.source = optimizedCSS
112
+ }
113
+ })
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Main CSS optimization function
120
+ */
121
+ function optimizeCSS(cssContent: string, config: CSSPurgeConfig): string {
122
+ let optimizedCSS = cssContent
123
+
124
+ // Step 1: Analyze usage patterns
125
+ const usage = analyzeUsage(cssContent, config)
126
+
127
+ // Step 2: Remove unused responsive breakpoint styles
128
+ optimizedCSS = removeUnusedBreakpoints(optimizedCSS, usage, config)
129
+
130
+ // Step 3: Remove unused custom properties
131
+ optimizedCSS = removeUnusedCustomProperties(optimizedCSS, usage, config)
132
+
133
+ // Step 4: Optimize CSS layers and ordering
134
+ optimizedCSS = optimizeCSSLayers(optimizedCSS)
135
+
136
+ // Step 5: Remove duplicate rules
137
+ optimizedCSS = removeDuplicateRules(optimizedCSS)
138
+
139
+ // Step 6: Minify if enabled
140
+ if (config.minify) {
141
+ optimizedCSS = minifyCSS(optimizedCSS)
142
+ }
143
+
144
+ return optimizedCSS
145
+ }
146
+
147
+ /**
148
+ * Analyzes CSS usage patterns using PostCSS
149
+ */
150
+ function analyzeUsage(cssContent: string, config: CSSPurgeConfig): UsageAnalysis {
151
+ const usage: UsageAnalysis = {
152
+ usedBreakpoints: new Set(),
153
+ usedCustomProperties: new Set(),
154
+ usedSelectors: new Set(),
155
+ unusedRules: []
156
+ }
157
+
158
+ try {
159
+ const root = postcss.parse(cssContent)
160
+
161
+ // Walk through all nodes to analyze usage
162
+ root.walkAtRules('media', (rule: AtRule) => {
163
+ // Analyze media query breakpoints
164
+ const mediaQuery = rule.params
165
+
166
+ config.breakpoints.forEach((breakpoint: string) => {
167
+ const breakpointPixels = getBreakpointPixels(breakpoint)
168
+
169
+ // Check for pixel-based media queries
170
+ if (mediaQuery.includes(`${breakpointPixels}px`)) {
171
+ usage.usedBreakpoints.add(breakpoint)
172
+ }
173
+
174
+ // Check for CSS variable usage
175
+ if (mediaQuery.includes(`--cs-breakpoints-${breakpoint}`)) {
176
+ usage.usedBreakpoints.add(breakpoint)
177
+ }
178
+ })
179
+ })
180
+
181
+ // Analyze custom property declarations and usage
182
+ root.walkDecls((decl: Declaration) => {
183
+ // Find custom property definitions
184
+ if (decl.prop.startsWith('--')) {
185
+ // Check if this property is used elsewhere in the CSS
186
+ const propertyUsage = cssContent.includes(`var(${decl.prop})`)
187
+ if (propertyUsage) {
188
+ usage.usedCustomProperties.add(decl.prop)
189
+ }
190
+ }
191
+
192
+ // Find custom property usage in values
193
+ const varMatches = decl.value.match(/var\(--[^)]+\)/g)
194
+ if (varMatches) {
195
+ varMatches.forEach(match => {
196
+ const propertyName = match.match(/--[^)]+/)?.[0]
197
+ if (propertyName) {
198
+ usage.usedCustomProperties.add(propertyName)
199
+ }
200
+ })
201
+ }
202
+ })
203
+
204
+ // Analyze selectors
205
+ root.walkRules((rule: Rule) => {
206
+ const selectors = rule.selector.split(',')
207
+ selectors.forEach(selector => {
208
+ const trimmedSelector = selector.trim()
209
+ usage.usedSelectors.add(trimmedSelector)
210
+
211
+ // Extract class names
212
+ const classMatches = trimmedSelector.match(/\.[a-zA-Z0-9_-]+/g)
213
+ if (classMatches) {
214
+ classMatches.forEach(className => {
215
+ usage.usedSelectors.add(className)
216
+ })
217
+ }
218
+ })
219
+ })
220
+
221
+ // For safety, always mark critical breakpoints as used
222
+ const criticalBreakpoints: string[] = ['xs', 'sm', 'md']
223
+ criticalBreakpoints.forEach((bp: string) => usage.usedBreakpoints.add(bp))
224
+
225
+ } catch (error) {
226
+ console.warn('PostCSS parsing failed in usage analysis, falling back to regex:', error)
227
+ // Fallback to basic regex analysis
228
+ return analyzeUsageFallback(cssContent, config)
229
+ }
230
+
231
+ return usage
232
+ }
233
+
234
+ /**
235
+ * Fallback regex-based usage analysis
236
+ */
237
+ function analyzeUsageFallback(cssContent: string, config: CSSPurgeConfig): UsageAnalysis {
238
+ const usage: UsageAnalysis = {
239
+ usedBreakpoints: new Set(),
240
+ usedCustomProperties: new Set(),
241
+ usedSelectors: new Set(),
242
+ unusedRules: []
243
+ }
244
+
245
+ // Analyze breakpoint usage in media queries
246
+ config.breakpoints.forEach((breakpoint: string) => {
247
+ const varPattern = new RegExp(`var\\(--cs-breakpoints-${breakpoint}\\)`, 'g')
248
+ const breakpointPixels = getBreakpointPixels(breakpoint)
249
+ const pixelPattern = new RegExp(`(min-width|max-width):\\s*${breakpointPixels}px`, 'g')
250
+ const literalPattern = new RegExp(`\\b${breakpoint}\\b`, 'g')
251
+
252
+ if (varPattern.test(cssContent) ||
253
+ pixelPattern.test(cssContent) ||
254
+ literalPattern.test(cssContent)) {
255
+ usage.usedBreakpoints.add(breakpoint)
256
+ }
257
+ })
258
+
259
+ // For safety, always mark critical breakpoints as used
260
+ const criticalBreakpoints: string[] = ['xs', 'sm', 'md']
261
+ criticalBreakpoints.forEach((bp: string) => usage.usedBreakpoints.add(bp))
262
+
263
+ // Analyze custom property usage
264
+ const customPropertyMatches = cssContent.match(/var\(--[^)]+\)/g) || []
265
+ customPropertyMatches.forEach(match => {
266
+ const propertyName = match.match(/--[^)]+/)?.[0]
267
+ if (propertyName) {
268
+ usage.usedCustomProperties.add(propertyName)
269
+ }
270
+ })
271
+
272
+ // Analyze selector usage
273
+ const selectorMatches = cssContent.match(/\.[a-zA-Z0-9_-]+/g) || []
274
+ selectorMatches.forEach(selector => {
275
+ usage.usedSelectors.add(selector)
276
+ })
277
+
278
+ return usage
279
+ }
280
+
281
+ /**
282
+ * Maps breakpoint names to their pixel values
283
+ */
284
+ function getBreakpointPixels(breakpoint: string): number {
285
+ const breakpointMap: Record<string, number> = {
286
+ xs: 0, // mobile-first, no min-width
287
+ sm: 640,
288
+ md: 768,
289
+ lg: 1024,
290
+ xl: 1280,
291
+ '2xl': 1536,
292
+ '3xl': 1792
293
+ }
294
+ return breakpointMap[breakpoint] || 0
295
+ }
296
+
297
+ /**
298
+ * Removes unused responsive breakpoint styles using PostCSS
299
+ */
300
+ function removeUnusedBreakpoints(cssContent: string, usage: UsageAnalysis, config: CSSPurgeConfig): string {
301
+ if (!config.aggressive) {
302
+ console.log(` Skipping breakpoint removal (non-aggressive mode)`)
303
+ return cssContent
304
+ }
305
+
306
+ try {
307
+ const root = postcss.parse(cssContent)
308
+ let removedCount = 0
309
+
310
+ root.walkAtRules('media', (rule: AtRule) => {
311
+ const mediaQuery = rule.params
312
+
313
+ // Check if this media query uses an unused breakpoint
314
+ const usesUnusedBreakpoint = config.breakpoints.some(breakpoint => {
315
+ // Skip critical breakpoints
316
+ if (['xs', 'sm', 'md'].includes(breakpoint)) {
317
+ return false
318
+ }
319
+
320
+ // Check if breakpoint is unused and referenced in this media query
321
+ if (!usage.usedBreakpoints.has(breakpoint)) {
322
+ const breakpointPixels = getBreakpointPixels(breakpoint)
323
+ return mediaQuery.includes(`${breakpointPixels}px`) ||
324
+ mediaQuery.includes(`--cs-breakpoints-${breakpoint}`)
325
+ }
326
+ return false
327
+ })
328
+
329
+ if (usesUnusedBreakpoint) {
330
+ console.log(` Removed unused breakpoint media query: ${mediaQuery}`)
331
+ rule.remove()
332
+ removedCount++
333
+ }
334
+ })
335
+
336
+ return root.toString()
337
+ } catch (error) {
338
+ console.warn('PostCSS parsing failed in breakpoint removal, using original CSS:', error)
339
+ return cssContent
340
+ }
341
+ }
342
+
343
+ /**
344
+ * Removes unused CSS custom properties using PostCSS
345
+ */
346
+ function removeUnusedCustomProperties(cssContent: string, usage: UsageAnalysis, config: CSSPurgeConfig): string {
347
+ try {
348
+ const root = postcss.parse(cssContent)
349
+ let removedCount = 0
350
+
351
+ root.walkDecls((decl: Declaration) => {
352
+ // Only process custom property declarations
353
+ if (decl.prop.startsWith('--')) {
354
+ // Check if property should be preserved (check this first for Tailwind variables)
355
+ const shouldPreserve = config.preserveCustomProperties.some(pattern => {
356
+ const regex = new RegExp(pattern.replace('*', '.*'))
357
+ return regex.test(decl.prop)
358
+ })
359
+
360
+ // Always preserve whitelisted properties (like --tw-*), otherwise check usage
361
+ if (!shouldPreserve && !usage.usedCustomProperties.has(decl.prop)) {
362
+ console.log(` Removed unused custom property: ${decl.prop}`)
363
+ decl.remove()
364
+ removedCount++
365
+ }
366
+ }
367
+ })
368
+
369
+ return root.toString()
370
+ } catch (error) {
371
+ console.warn('PostCSS parsing failed in custom property removal, using original CSS:', error)
372
+ return cssContent
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Optimizes CSS layer ordering using PostCSS
378
+ */
379
+ function optimizeCSSLayers(cssContent: string): string {
380
+ try {
381
+ const root = postcss.parse(cssContent)
382
+ const layerOrder = ['reset', 'base', 'themes', 'components', 'utilities']
383
+ let hasLayers = false
384
+
385
+ // Remove existing @layer declarations
386
+ root.walkAtRules('layer', (rule: AtRule) => {
387
+ if (!rule.nodes || rule.nodes.length === 0) {
388
+ // This is a layer order declaration, not a layer block
389
+ hasLayers = true
390
+ rule.remove()
391
+ }
392
+ })
393
+
394
+ // Add optimized layer order at the beginning if we found layers
395
+ if (hasLayers) {
396
+ const layerDeclaration = postcss.atRule({
397
+ name: 'layer',
398
+ params: layerOrder.join(', ')
399
+ })
400
+ root.prepend(layerDeclaration)
401
+ root.prepend(postcss.comment({ text: ' Optimized CSS layer order ' }))
402
+ }
403
+
404
+ return root.toString()
405
+ } catch (error) {
406
+ console.warn('PostCSS parsing failed in layer optimization, using original CSS:', error)
407
+ return cssContent
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Removes duplicate CSS rules using PostCSS
413
+ */
414
+ function removeDuplicateRules(cssContent: string): string {
415
+ try {
416
+ const root = postcss.parse(cssContent)
417
+ const ruleMap = new Map<string, string>()
418
+ let removedCount = 0
419
+
420
+ root.walkRules((rule: Rule) => {
421
+ const selector = rule.selector
422
+ const declarations = rule.nodes?.map(node => {
423
+ if (node.type === 'decl') {
424
+ return `${node.prop}: ${node.value}`
425
+ }
426
+ return ''
427
+ }).filter(Boolean).sort().join('; ')
428
+
429
+ const ruleKey = `${selector}|${declarations}`
430
+
431
+ if (ruleMap.has(ruleKey)) {
432
+ console.log(` Removed duplicate rule: ${selector}`)
433
+ rule.remove()
434
+ removedCount++
435
+ } else {
436
+ ruleMap.set(ruleKey, selector)
437
+ }
438
+ })
439
+
440
+ return root.toString()
441
+ } catch (error) {
442
+ console.warn('PostCSS parsing failed in duplicate rule removal, using original CSS:', error)
443
+ return cssContent
444
+ }
445
+ }
446
+
447
+ /**
448
+ * Basic CSS minification
449
+ */
450
+ function minifyCSS(cssContent: string): string {
451
+ return cssContent
452
+ // Remove comments
453
+ .replace(/\/\*[\s\S]*?\*\//g, '')
454
+ // Remove unnecessary whitespace
455
+ .replace(/\s+/g, ' ')
456
+ // Remove whitespace around braces and semicolons
457
+ .replace(/\s*{\s*/g, '{')
458
+ .replace(/\s*}\s*/g, '}')
459
+ .replace(/\s*;\s*/g, ';')
460
+ .replace(/\s*:\s*/g, ':')
461
+ .replace(/\s*,\s*/g, ',')
462
+ // Remove trailing semicolons
463
+ .replace(/;}/g, '}')
464
+ // Remove leading/trailing whitespace
465
+ .trim()
466
+ }
467
+
468
+ /**
469
+ * Generates optimization report
470
+ */
471
+ export function generateOptimizationReport(originalCSS: string, optimizedCSS: string): string {
472
+ const originalSize = Buffer.byteLength(originalCSS, 'utf8')
473
+ const optimizedSize = Buffer.byteLength(optimizedCSS, 'utf8')
474
+ const savings = originalSize - optimizedSize
475
+ const savingsPercent = Math.round((savings / originalSize) * 100)
476
+
477
+ return `
478
+ CSS Optimization Report
479
+ ======================
480
+ Original Size: ${Math.round(originalSize / 1024 * 100) / 100}KB
481
+ Optimized Size: ${Math.round(optimizedSize / 1024 * 100) / 100}KB
482
+ Savings: ${Math.round(savings / 1024 * 100) / 100}KB (${savingsPercent}%)
483
+
484
+ Optimizations Applied:
485
+ - Removed unused responsive breakpoint styles
486
+ - Purged unused CSS custom properties
487
+ - Optimized CSS layer ordering
488
+ - Removed duplicate rules
489
+ - Applied minification
490
+ `
491
+ }