ui-ux-pro-max-cli 2.8.8

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 (160) hide show
  1. package/README.md +99 -0
  2. package/assets/data/_sync_all.py +414 -0
  3. package/assets/data/app-interface.csv +31 -0
  4. package/assets/data/charts.csv +26 -0
  5. package/assets/data/colors.csv +162 -0
  6. package/assets/data/design.csv +1776 -0
  7. package/assets/data/draft.csv +1779 -0
  8. package/assets/data/google-fonts.csv +1924 -0
  9. package/assets/data/icons.csv +106 -0
  10. package/assets/data/landing.csv +35 -0
  11. package/assets/data/products.csv +162 -0
  12. package/assets/data/react-performance.csv +45 -0
  13. package/assets/data/stacks/angular.csv +51 -0
  14. package/assets/data/stacks/astro.csv +54 -0
  15. package/assets/data/stacks/flutter.csv +53 -0
  16. package/assets/data/stacks/html-tailwind.csv +56 -0
  17. package/assets/data/stacks/javafx.csv +76 -0
  18. package/assets/data/stacks/jetpack-compose.csv +53 -0
  19. package/assets/data/stacks/laravel.csv +51 -0
  20. package/assets/data/stacks/nextjs.csv +53 -0
  21. package/assets/data/stacks/nuxt-ui.csv +71 -0
  22. package/assets/data/stacks/nuxtjs.csv +59 -0
  23. package/assets/data/stacks/react-native.csv +52 -0
  24. package/assets/data/stacks/react.csv +54 -0
  25. package/assets/data/stacks/shadcn.csv +61 -0
  26. package/assets/data/stacks/svelte.csv +54 -0
  27. package/assets/data/stacks/swiftui.csv +51 -0
  28. package/assets/data/stacks/threejs.csv +54 -0
  29. package/assets/data/stacks/vue.csv +50 -0
  30. package/assets/data/styles.csv +85 -0
  31. package/assets/data/typography.csv +74 -0
  32. package/assets/data/ui-reasoning.csv +162 -0
  33. package/assets/data/ux-guidelines.csv +100 -0
  34. package/assets/scripts/core.py +263 -0
  35. package/assets/scripts/design_system.py +1157 -0
  36. package/assets/scripts/search.py +114 -0
  37. package/assets/skills/banner-design/SKILL.md +196 -0
  38. package/assets/skills/banner-design/references/banner-sizes-and-styles.md +118 -0
  39. package/assets/skills/brand/SKILL.md +97 -0
  40. package/assets/skills/brand/references/approval-checklist.md +169 -0
  41. package/assets/skills/brand/references/asset-organization.md +157 -0
  42. package/assets/skills/brand/references/brand-guideline-template.md +140 -0
  43. package/assets/skills/brand/references/color-palette-management.md +186 -0
  44. package/assets/skills/brand/references/consistency-checklist.md +94 -0
  45. package/assets/skills/brand/references/logo-usage-rules.md +185 -0
  46. package/assets/skills/brand/references/messaging-framework.md +85 -0
  47. package/assets/skills/brand/references/typography-specifications.md +214 -0
  48. package/assets/skills/brand/references/update.md +118 -0
  49. package/assets/skills/brand/references/visual-identity.md +96 -0
  50. package/assets/skills/brand/references/voice-framework.md +88 -0
  51. package/assets/skills/brand/scripts/extract-colors.cjs +341 -0
  52. package/assets/skills/brand/scripts/inject-brand-context.cjs +349 -0
  53. package/assets/skills/brand/scripts/sync-brand-to-tokens.cjs +266 -0
  54. package/assets/skills/brand/scripts/validate-asset.cjs +387 -0
  55. package/assets/skills/brand/templates/brand-guidelines-starter.md +275 -0
  56. package/assets/skills/design/SKILL.md +313 -0
  57. package/assets/skills/design/data/cip/deliverables.csv +51 -0
  58. package/assets/skills/design/data/cip/industries.csv +21 -0
  59. package/assets/skills/design/data/cip/mockup-contexts.csv +21 -0
  60. package/assets/skills/design/data/cip/styles.csv +21 -0
  61. package/assets/skills/design/data/icon/styles.csv +16 -0
  62. package/assets/skills/design/data/logo/colors.csv +56 -0
  63. package/assets/skills/design/data/logo/industries.csv +56 -0
  64. package/assets/skills/design/data/logo/styles.csv +56 -0
  65. package/assets/skills/design/references/banner-sizes-and-styles.md +118 -0
  66. package/assets/skills/design/references/cip-deliverable-guide.md +95 -0
  67. package/assets/skills/design/references/cip-design.md +121 -0
  68. package/assets/skills/design/references/cip-prompt-engineering.md +84 -0
  69. package/assets/skills/design/references/cip-style-guide.md +68 -0
  70. package/assets/skills/design/references/design-routing.md +207 -0
  71. package/assets/skills/design/references/icon-design.md +122 -0
  72. package/assets/skills/design/references/logo-color-psychology.md +101 -0
  73. package/assets/skills/design/references/logo-design.md +92 -0
  74. package/assets/skills/design/references/logo-prompt-engineering.md +158 -0
  75. package/assets/skills/design/references/logo-style-guide.md +109 -0
  76. package/assets/skills/design/references/slides-copywriting-formulas.md +84 -0
  77. package/assets/skills/design/references/slides-create.md +4 -0
  78. package/assets/skills/design/references/slides-html-template.md +295 -0
  79. package/assets/skills/design/references/slides-layout-patterns.md +137 -0
  80. package/assets/skills/design/references/slides-strategies.md +94 -0
  81. package/assets/skills/design/references/slides.md +42 -0
  82. package/assets/skills/design/references/social-photos-design.md +329 -0
  83. package/assets/skills/design/scripts/cip/core.py +215 -0
  84. package/assets/skills/design/scripts/cip/generate.py +484 -0
  85. package/assets/skills/design/scripts/cip/render-html.py +424 -0
  86. package/assets/skills/design/scripts/cip/search.py +127 -0
  87. package/assets/skills/design/scripts/icon/generate.py +487 -0
  88. package/assets/skills/design/scripts/logo/core.py +175 -0
  89. package/assets/skills/design/scripts/logo/generate.py +362 -0
  90. package/assets/skills/design/scripts/logo/search.py +114 -0
  91. package/assets/skills/design-system/SKILL.md +244 -0
  92. package/assets/skills/design-system/data/slide-backgrounds.csv +11 -0
  93. package/assets/skills/design-system/data/slide-charts.csv +26 -0
  94. package/assets/skills/design-system/data/slide-color-logic.csv +14 -0
  95. package/assets/skills/design-system/data/slide-copy.csv +26 -0
  96. package/assets/skills/design-system/data/slide-layout-logic.csv +16 -0
  97. package/assets/skills/design-system/data/slide-layouts.csv +26 -0
  98. package/assets/skills/design-system/data/slide-strategies.csv +16 -0
  99. package/assets/skills/design-system/data/slide-typography.csv +15 -0
  100. package/assets/skills/design-system/references/component-specs.md +236 -0
  101. package/assets/skills/design-system/references/component-tokens.md +214 -0
  102. package/assets/skills/design-system/references/primitive-tokens.md +203 -0
  103. package/assets/skills/design-system/references/semantic-tokens.md +215 -0
  104. package/assets/skills/design-system/references/states-and-variants.md +241 -0
  105. package/assets/skills/design-system/references/tailwind-integration.md +251 -0
  106. package/assets/skills/design-system/references/token-architecture.md +224 -0
  107. package/assets/skills/design-system/scripts/embed-tokens.cjs +99 -0
  108. package/assets/skills/design-system/scripts/fetch-background.py +317 -0
  109. package/assets/skills/design-system/scripts/generate-slide.py +770 -0
  110. package/assets/skills/design-system/scripts/generate-tokens.cjs +205 -0
  111. package/assets/skills/design-system/scripts/html-token-validator.py +327 -0
  112. package/assets/skills/design-system/scripts/search-slides.py +218 -0
  113. package/assets/skills/design-system/scripts/slide-token-validator.py +35 -0
  114. package/assets/skills/design-system/scripts/slide_search_core.py +453 -0
  115. package/assets/skills/design-system/scripts/validate-tokens.cjs +251 -0
  116. package/assets/skills/design-system/templates/design-tokens-starter.json +143 -0
  117. package/assets/skills/slides/SKILL.md +40 -0
  118. package/assets/skills/slides/references/copywriting-formulas.md +84 -0
  119. package/assets/skills/slides/references/create.md +4 -0
  120. package/assets/skills/slides/references/html-template.md +295 -0
  121. package/assets/skills/slides/references/layout-patterns.md +137 -0
  122. package/assets/skills/slides/references/slide-strategies.md +94 -0
  123. package/assets/skills/ui-styling/LICENSE.txt +202 -0
  124. package/assets/skills/ui-styling/SKILL.md +324 -0
  125. package/assets/skills/ui-styling/references/canvas-design-system.md +320 -0
  126. package/assets/skills/ui-styling/references/shadcn-accessibility.md +471 -0
  127. package/assets/skills/ui-styling/references/shadcn-components.md +424 -0
  128. package/assets/skills/ui-styling/references/shadcn-theming.md +373 -0
  129. package/assets/skills/ui-styling/references/tailwind-customization.md +483 -0
  130. package/assets/skills/ui-styling/references/tailwind-responsive.md +382 -0
  131. package/assets/skills/ui-styling/references/tailwind-utilities.md +455 -0
  132. package/assets/skills/ui-styling/scripts/requirements.txt +17 -0
  133. package/assets/skills/ui-styling/scripts/shadcn_add.py +308 -0
  134. package/assets/skills/ui-styling/scripts/tailwind_config_gen.py +473 -0
  135. package/assets/skills/ui-styling/scripts/tests/coverage-ui.json +1 -0
  136. package/assets/skills/ui-styling/scripts/tests/requirements.txt +3 -0
  137. package/assets/skills/ui-styling/scripts/tests/test_shadcn_add.py +266 -0
  138. package/assets/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py +336 -0
  139. package/assets/templates/base/quick-reference.md +297 -0
  140. package/assets/templates/base/skill-content.md +368 -0
  141. package/assets/templates/platforms/agent.json +21 -0
  142. package/assets/templates/platforms/augment.json +18 -0
  143. package/assets/templates/platforms/claude.json +21 -0
  144. package/assets/templates/platforms/codebuddy.json +21 -0
  145. package/assets/templates/platforms/codex.json +21 -0
  146. package/assets/templates/platforms/continue.json +21 -0
  147. package/assets/templates/platforms/copilot.json +21 -0
  148. package/assets/templates/platforms/cursor.json +21 -0
  149. package/assets/templates/platforms/droid.json +21 -0
  150. package/assets/templates/platforms/gemini.json +21 -0
  151. package/assets/templates/platforms/kilocode.json +21 -0
  152. package/assets/templates/platforms/kiro.json +21 -0
  153. package/assets/templates/platforms/opencode.json +21 -0
  154. package/assets/templates/platforms/qoder.json +21 -0
  155. package/assets/templates/platforms/roocode.json +21 -0
  156. package/assets/templates/platforms/trae.json +21 -0
  157. package/assets/templates/platforms/warp.json +18 -0
  158. package/assets/templates/platforms/windsurf.json +21 -0
  159. package/dist/index.js +10630 -0
  160. package/package.json +51 -0
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate CSS variables from design tokens JSON
4
+ *
5
+ * Usage:
6
+ * node generate-tokens.cjs --config tokens.json -o tokens.css
7
+ * node generate-tokens.cjs --config tokens.json --format tailwind
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ /**
14
+ * Parse command line arguments
15
+ */
16
+ function parseArgs() {
17
+ const args = process.argv.slice(2);
18
+ const options = {
19
+ config: null,
20
+ output: null,
21
+ format: 'css' // css | tailwind
22
+ };
23
+
24
+ for (let i = 0; i < args.length; i++) {
25
+ if (args[i] === '--config' || args[i] === '-c') {
26
+ options.config = args[++i];
27
+ } else if (args[i] === '--output' || args[i] === '-o') {
28
+ options.output = args[++i];
29
+ } else if (args[i] === '--format' || args[i] === '-f') {
30
+ options.format = args[++i];
31
+ } else if (args[i] === '--help' || args[i] === '-h') {
32
+ console.log(`
33
+ Usage: node generate-tokens.cjs [options]
34
+
35
+ Options:
36
+ -c, --config <file> Input JSON token file (required)
37
+ -o, --output <file> Output file (default: stdout)
38
+ -f, --format <type> Output format: css | tailwind (default: css)
39
+ -h, --help Show this help
40
+ `);
41
+ process.exit(0);
42
+ }
43
+ }
44
+
45
+ return options;
46
+ }
47
+
48
+ /**
49
+ * Resolve token references like {primitive.color.blue.600}
50
+ */
51
+ function resolveReference(value, tokens) {
52
+ if (typeof value !== 'string' || !value.startsWith('{')) {
53
+ return value;
54
+ }
55
+
56
+ const path = value.slice(1, -1).split('.');
57
+ let result = tokens;
58
+
59
+ for (const key of path) {
60
+ result = result?.[key];
61
+ }
62
+
63
+ if (result?.$value) {
64
+ return resolveReference(result.$value, tokens);
65
+ }
66
+
67
+ return result || value;
68
+ }
69
+
70
+ /**
71
+ * Convert token name to CSS variable name
72
+ */
73
+ function toCssVarName(path) {
74
+ return '--' + path.join('-').replace(/\./g, '-');
75
+ }
76
+
77
+ /**
78
+ * Flatten tokens into CSS variables
79
+ */
80
+ function flattenTokens(obj, tokens, prefix = [], result = {}) {
81
+ for (const [key, value] of Object.entries(obj)) {
82
+ const currentPath = [...prefix, key];
83
+
84
+ if (value && typeof value === 'object') {
85
+ if (value.$value !== undefined) {
86
+ // This is a token
87
+ const cssVar = toCssVarName(currentPath);
88
+ const resolvedValue = resolveReference(value.$value, tokens);
89
+ result[cssVar] = resolvedValue;
90
+ } else {
91
+ // Recurse into nested object
92
+ flattenTokens(value, tokens, currentPath, result);
93
+ }
94
+ }
95
+ }
96
+
97
+ return result;
98
+ }
99
+
100
+ /**
101
+ * Generate CSS output
102
+ */
103
+ function generateCSS(tokens) {
104
+ const primitive = flattenTokens(tokens.primitive || {}, tokens, ['primitive']);
105
+ const semantic = flattenTokens(tokens.semantic || {}, tokens, []);
106
+ const component = flattenTokens(tokens.component || {}, tokens, []);
107
+ const darkSemantic = flattenTokens(tokens.dark?.semantic || {}, tokens, []);
108
+
109
+ let css = `/* Design Tokens - Auto-generated */
110
+ /* Do not edit directly - modify tokens.json instead */
111
+
112
+ /* === PRIMITIVES === */
113
+ :root {
114
+ ${Object.entries(primitive).map(([k, v]) => ` ${k}: ${v};`).join('\n')}
115
+ }
116
+
117
+ /* === SEMANTIC === */
118
+ :root {
119
+ ${Object.entries(semantic).map(([k, v]) => ` ${k}: ${v};`).join('\n')}
120
+ }
121
+
122
+ /* === COMPONENTS === */
123
+ :root {
124
+ ${Object.entries(component).map(([k, v]) => ` ${k}: ${v};`).join('\n')}
125
+ }
126
+ `;
127
+
128
+ if (Object.keys(darkSemantic).length > 0) {
129
+ css += `
130
+ /* === DARK MODE === */
131
+ .dark {
132
+ ${Object.entries(darkSemantic).map(([k, v]) => ` ${k}: ${v};`).join('\n')}
133
+ }
134
+ `;
135
+ }
136
+
137
+ return css;
138
+ }
139
+
140
+ /**
141
+ * Generate Tailwind config output
142
+ */
143
+ function generateTailwind(tokens) {
144
+ const semantic = flattenTokens(tokens.semantic || {}, tokens, []);
145
+
146
+ // Extract colors for Tailwind
147
+ const colors = {};
148
+ for (const [key, value] of Object.entries(semantic)) {
149
+ if (key.includes('color')) {
150
+ const name = key.replace('--color-', '').replace(/-/g, '.');
151
+ colors[name] = `var(${key})`;
152
+ }
153
+ }
154
+
155
+ return `// Tailwind color config - Auto-generated
156
+ // Add to tailwind.config.ts theme.extend.colors
157
+
158
+ module.exports = {
159
+ colors: ${JSON.stringify(colors, null, 2).replace(/"/g, "'")}
160
+ };
161
+ `;
162
+ }
163
+
164
+ /**
165
+ * Main
166
+ */
167
+ function main() {
168
+ const options = parseArgs();
169
+
170
+ if (!options.config) {
171
+ console.error('Error: --config is required');
172
+ process.exit(1);
173
+ }
174
+
175
+ // Resolve config path
176
+ const configPath = path.resolve(process.cwd(), options.config);
177
+
178
+ if (!fs.existsSync(configPath)) {
179
+ console.error(`Error: Config file not found: ${configPath}`);
180
+ process.exit(1);
181
+ }
182
+
183
+ // Read and parse tokens
184
+ const tokens = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
185
+
186
+ // Generate output
187
+ let output;
188
+ if (options.format === 'tailwind') {
189
+ output = generateTailwind(tokens);
190
+ } else {
191
+ output = generateCSS(tokens);
192
+ }
193
+
194
+ // Write output
195
+ if (options.output) {
196
+ const outputPath = path.resolve(process.cwd(), options.output);
197
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
198
+ fs.writeFileSync(outputPath, output);
199
+ console.log(`Generated: ${outputPath}`);
200
+ } else {
201
+ console.log(output);
202
+ }
203
+ }
204
+
205
+ main();
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ HTML Design Token Validator
4
+ Ensures all HTML assets (slides, infographics, etc.) use design tokens.
5
+ Source of truth: assets/design-tokens.css
6
+
7
+ Usage:
8
+ python html-token-validator.py # Validate all HTML assets
9
+ python html-token-validator.py --type slides # Validate only slides
10
+ python html-token-validator.py --type infographics # Validate only infographics
11
+ python html-token-validator.py path/to/file.html # Validate specific file
12
+ python html-token-validator.py --fix # Auto-fix issues (WIP)
13
+ """
14
+
15
+ import re
16
+ import json
17
+ import sys
18
+ from pathlib import Path
19
+ from typing import Dict, List, Tuple, Optional
20
+
21
+ # Project root relative to this script
22
+ PROJECT_ROOT = Path(__file__).parent.parent.parent.parent.parent
23
+ TOKENS_JSON_PATH = PROJECT_ROOT / 'assets' / 'design-tokens.json'
24
+ TOKENS_CSS_PATH = PROJECT_ROOT / 'assets' / 'design-tokens.css'
25
+
26
+ # Asset directories to validate
27
+ ASSET_DIRS = {
28
+ 'slides': PROJECT_ROOT / 'assets' / 'designs' / 'slides',
29
+ 'infographics': PROJECT_ROOT / 'assets' / 'infographics',
30
+ }
31
+
32
+ # Patterns that indicate hardcoded values (should use tokens)
33
+ FORBIDDEN_PATTERNS = [
34
+ (r'#[0-9A-Fa-f]{3,8}\b', 'hex color'),
35
+ (r'rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)', 'rgb color'),
36
+ (r'rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[\d.]+\s*\)', 'rgba color'),
37
+ (r'hsl\([^)]+\)', 'hsl color'),
38
+ (r"font-family:\s*'[^v][^a][^r][^']*',", 'hardcoded font'), # Exclude var()
39
+ (r'font-family:\s*"[^v][^a][^r][^"]*",', 'hardcoded font'),
40
+ ]
41
+
42
+ # Allowed rgba patterns (brand colors with transparency - CSS limitation)
43
+ # These are derived from brand tokens but need rgba for transparency
44
+ ALLOWED_RGBA_PATTERNS = [
45
+ r'rgba\(\s*59\s*,\s*130\s*,\s*246', # --color-primary (#3B82F6)
46
+ r'rgba\(\s*245\s*,\s*158\s*,\s*11', # --color-secondary (#F59E0B)
47
+ r'rgba\(\s*16\s*,\s*185\s*,\s*129', # --color-accent (#10B981)
48
+ r'rgba\(\s*20\s*,\s*184\s*,\s*166', # --color-accent alt (#14B8A6)
49
+ r'rgba\(\s*0\s*,\s*0\s*,\s*0', # black transparency (common)
50
+ r'rgba\(\s*255\s*,\s*255\s*,\s*255', # white transparency (common)
51
+ r'rgba\(\s*15\s*,\s*23\s*,\s*42', # --color-surface (#0F172A)
52
+ r'rgba\(\s*7\s*,\s*11\s*,\s*20', # --color-background (#070B14)
53
+ ]
54
+
55
+ # Allowed exceptions (external images, etc.)
56
+ ALLOWED_EXCEPTIONS = [
57
+ 'pexels.com', 'unsplash.com', 'youtube.com', 'ytimg.com',
58
+ 'googlefonts', 'fonts.googleapis.com', 'fonts.gstatic.com',
59
+ ]
60
+
61
+
62
+ class ValidationResult:
63
+ """Validation result for a single file."""
64
+ def __init__(self, file_path: Path):
65
+ self.file_path = file_path
66
+ self.errors: List[str] = []
67
+ self.warnings: List[str] = []
68
+ self.passed = True
69
+
70
+ def add_error(self, msg: str):
71
+ self.errors.append(msg)
72
+ self.passed = False
73
+
74
+ def add_warning(self, msg: str):
75
+ self.warnings.append(msg)
76
+
77
+
78
+ def load_css_variables() -> Dict[str, str]:
79
+ """Load CSS variables from design-tokens.css."""
80
+ variables = {}
81
+ if TOKENS_CSS_PATH.exists():
82
+ content = TOKENS_CSS_PATH.read_text()
83
+ # Extract --var-name: value patterns
84
+ for match in re.finditer(r'(--[\w-]+):\s*([^;]+);', content):
85
+ variables[match.group(1)] = match.group(2).strip()
86
+ return variables
87
+
88
+
89
+ def is_inside_block(content: str, match_pos: int, open_tag: str, close_tag: str) -> bool:
90
+ """Check if position is inside a specific HTML block."""
91
+ pre = content[:match_pos]
92
+ tag_open = pre.rfind(open_tag)
93
+ tag_close = pre.rfind(close_tag)
94
+ return tag_open > tag_close
95
+
96
+
97
+ def is_allowed_exception(context: str) -> bool:
98
+ """Check if the hardcoded value is in an allowed exception context."""
99
+ context_lower = context.lower()
100
+ return any(exc in context_lower for exc in ALLOWED_EXCEPTIONS)
101
+
102
+
103
+ def is_allowed_rgba(match_text: str) -> bool:
104
+ """Check if rgba pattern uses brand colors (allowed for transparency)."""
105
+ return any(re.match(pattern, match_text) for pattern in ALLOWED_RGBA_PATTERNS)
106
+
107
+
108
+ def get_context(content: str, pos: int, chars: int = 100) -> str:
109
+ """Get surrounding context for a match position."""
110
+ start = max(0, pos - chars)
111
+ end = min(len(content), pos + chars)
112
+ return content[start:end]
113
+
114
+
115
+ def validate_html(content: str, file_path: Path, verbose: bool = False) -> ValidationResult:
116
+ """
117
+ Validate HTML content for design token compliance.
118
+
119
+ Checks:
120
+ 1. design-tokens.css import present
121
+ 2. No hardcoded colors in CSS (except in <script> for Chart.js)
122
+ 3. No hardcoded fonts
123
+ 4. Uses var(--token-name) pattern
124
+ """
125
+ result = ValidationResult(file_path)
126
+
127
+ # 1. Check for design-tokens.css import
128
+ if 'design-tokens.css' not in content:
129
+ result.add_error("Missing design-tokens.css import")
130
+
131
+ # 2. Check for forbidden patterns in CSS
132
+ for pattern, description in FORBIDDEN_PATTERNS:
133
+ for match in re.finditer(pattern, content):
134
+ match_text = match.group()
135
+ match_pos = match.start()
136
+ context = get_context(content, match_pos)
137
+
138
+ # Skip if in <script> block (Chart.js allowed)
139
+ if is_inside_block(content, match_pos, '<script', '</script>'):
140
+ if verbose:
141
+ result.add_warning(f"Allowed in <script>: {match_text}")
142
+ continue
143
+
144
+ # Skip if in allowed exception context (external URLs)
145
+ if is_allowed_exception(context):
146
+ if verbose:
147
+ result.add_warning(f"Allowed external: {match_text}")
148
+ continue
149
+
150
+ # Skip rgba using brand colors (needed for transparency effects)
151
+ if description == 'rgba color' and is_allowed_rgba(match_text):
152
+ if verbose:
153
+ result.add_warning(f"Allowed brand rgba: {match_text}")
154
+ continue
155
+
156
+ # Skip if part of var() reference (false positive)
157
+ if 'var(' in context and match_text in context:
158
+ # Check if it's a fallback value in var()
159
+ var_pattern = rf'var\([^)]*{re.escape(match_text)}[^)]*\)'
160
+ if re.search(var_pattern, context):
161
+ continue
162
+
163
+ # Error if in <style> or inline style
164
+ if is_inside_block(content, match_pos, '<style', '</style>'):
165
+ result.add_error(f"Hardcoded {description} in <style>: {match_text}")
166
+ elif 'style="' in context:
167
+ result.add_error(f"Hardcoded {description} in inline style: {match_text}")
168
+
169
+ # 3. Check for required var() usage indicators
170
+ token_patterns = [
171
+ r'var\(--color-',
172
+ r'var\(--primitive-',
173
+ r'var\(--typography-',
174
+ r'var\(--card-',
175
+ r'var\(--button-',
176
+ ]
177
+ token_count = sum(len(re.findall(p, content)) for p in token_patterns)
178
+
179
+ if token_count < 5:
180
+ result.add_warning(f"Low token usage ({token_count} var() references). Consider using more design tokens.")
181
+
182
+ return result
183
+
184
+
185
+ def validate_file(file_path: Path, verbose: bool = False) -> ValidationResult:
186
+ """Validate a single HTML file."""
187
+ if not file_path.exists():
188
+ result = ValidationResult(file_path)
189
+ result.add_error("File not found")
190
+ return result
191
+
192
+ content = file_path.read_text()
193
+ return validate_html(content, file_path, verbose)
194
+
195
+
196
+ def validate_directory(dir_path: Path, verbose: bool = False) -> List[ValidationResult]:
197
+ """Validate all HTML files in a directory."""
198
+ results = []
199
+ if dir_path.exists():
200
+ for html_file in sorted(dir_path.glob('*.html')):
201
+ results.append(validate_file(html_file, verbose))
202
+ return results
203
+
204
+
205
+ def print_result(result: ValidationResult, verbose: bool = False):
206
+ """Print validation result for a file."""
207
+ status = "✓" if result.passed else "✗"
208
+ print(f" {status} {result.file_path.name}")
209
+
210
+ if result.errors:
211
+ for error in result.errors[:5]: # Limit output
212
+ print(f" ├─ {error}")
213
+ if len(result.errors) > 5:
214
+ print(f" └─ ... and {len(result.errors) - 5} more errors")
215
+
216
+ if verbose and result.warnings:
217
+ for warning in result.warnings[:3]:
218
+ print(f" [warn] {warning}")
219
+
220
+
221
+ def print_summary(all_results: Dict[str, List[ValidationResult]]):
222
+ """Print summary of all validation results."""
223
+ total_files = 0
224
+ total_passed = 0
225
+ total_errors = 0
226
+
227
+ print("\n" + "=" * 60)
228
+ print("HTML DESIGN TOKEN VALIDATION SUMMARY")
229
+ print("=" * 60)
230
+
231
+ for asset_type, results in all_results.items():
232
+ if not results:
233
+ continue
234
+
235
+ passed = sum(1 for r in results if r.passed)
236
+ failed = len(results) - passed
237
+ errors = sum(len(r.errors) for r in results)
238
+
239
+ total_files += len(results)
240
+ total_passed += passed
241
+ total_errors += errors
242
+
243
+ status = "✓" if failed == 0 else "✗"
244
+ print(f"\n{status} {asset_type.upper()}: {passed}/{len(results)} passed")
245
+
246
+ for result in results:
247
+ if not result.passed:
248
+ print_result(result)
249
+
250
+ print("\n" + "-" * 60)
251
+ if total_errors == 0:
252
+ print(f"✓ ALL PASSED: {total_passed}/{total_files} files valid")
253
+ else:
254
+ print(f"✗ FAILED: {total_files - total_passed}/{total_files} files have issues ({total_errors} total errors)")
255
+ print("-" * 60)
256
+
257
+ return total_errors == 0
258
+
259
+
260
+ def main():
261
+ """CLI entry point."""
262
+ import argparse
263
+
264
+ parser = argparse.ArgumentParser(
265
+ description='Validate HTML assets for design token compliance',
266
+ formatter_class=argparse.RawDescriptionHelpFormatter,
267
+ epilog="""
268
+ Examples:
269
+ %(prog)s # Validate all HTML assets
270
+ %(prog)s --type slides # Validate only slides
271
+ %(prog)s --type infographics # Validate only infographics
272
+ %(prog)s path/to/file.html # Validate specific file
273
+ %(prog)s --colors # Show brand colors from tokens
274
+ """
275
+ )
276
+ parser.add_argument('files', nargs='*', help='Specific HTML files to validate')
277
+ parser.add_argument('-t', '--type', choices=['slides', 'infographics', 'all'],
278
+ default='all', help='Asset type to validate')
279
+ parser.add_argument('-v', '--verbose', action='store_true', help='Show warnings')
280
+ parser.add_argument('--colors', action='store_true', help='Print CSS variables from tokens')
281
+ parser.add_argument('--fix', action='store_true', help='Auto-fix issues (experimental)')
282
+
283
+ args = parser.parse_args()
284
+
285
+ # Show colors mode
286
+ if args.colors:
287
+ variables = load_css_variables()
288
+ print("\nDesign Tokens (from design-tokens.css):")
289
+ print("-" * 40)
290
+ for name, value in sorted(variables.items())[:30]:
291
+ print(f" {name}: {value}")
292
+ if len(variables) > 30:
293
+ print(f" ... and {len(variables) - 30} more")
294
+ return
295
+
296
+ all_results: Dict[str, List[ValidationResult]] = {}
297
+
298
+ # Validate specific files
299
+ if args.files:
300
+ results = []
301
+ for file_path in args.files:
302
+ path = Path(file_path)
303
+ if path.exists():
304
+ results.append(validate_file(path, args.verbose))
305
+ else:
306
+ result = ValidationResult(path)
307
+ result.add_error("File not found")
308
+ results.append(result)
309
+ all_results['specified'] = results
310
+ else:
311
+ # Validate by type
312
+ types_to_check = ASSET_DIRS.keys() if args.type == 'all' else [args.type]
313
+
314
+ for asset_type in types_to_check:
315
+ if asset_type in ASSET_DIRS:
316
+ results = validate_directory(ASSET_DIRS[asset_type], args.verbose)
317
+ all_results[asset_type] = results
318
+
319
+ # Print results
320
+ success = print_summary(all_results)
321
+
322
+ if not success:
323
+ sys.exit(1)
324
+
325
+
326
+ if __name__ == '__main__':
327
+ main()