@musashishao/agent-kit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @musashishao/agent-kit might be problematic. Click here for more details.
- package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +487 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +76 -0
- package/.agent/ARCHITECTURE.md +225 -0
- package/.agent/CONTEXT.md +229 -0
- package/.agent/FEATURE_ROADMAP.md +435 -0
- package/.agent/PROMPT_TEMPLATES.md +261 -0
- package/.agent/agents/backend-specialist.md +263 -0
- package/.agent/agents/database-architect.md +226 -0
- package/.agent/agents/debugger.md +225 -0
- package/.agent/agents/devops-engineer.md +242 -0
- package/.agent/agents/documentation-writer.md +104 -0
- package/.agent/agents/explorer-agent.md +73 -0
- package/.agent/agents/frontend-specialist.md +556 -0
- package/.agent/agents/game-developer.md +162 -0
- package/.agent/agents/mobile-developer.md +377 -0
- package/.agent/agents/orchestrator.md +416 -0
- package/.agent/agents/penetration-tester.md +188 -0
- package/.agent/agents/performance-optimizer.md +187 -0
- package/.agent/agents/project-planner.md +403 -0
- package/.agent/agents/security-auditor.md +170 -0
- package/.agent/agents/seo-specialist.md +111 -0
- package/.agent/agents/test-engineer.md +158 -0
- package/.agent/rules/GEMINI.md +251 -0
- package/.agent/skills/api-patterns/SKILL.md +81 -0
- package/.agent/skills/api-patterns/api-style.md +42 -0
- package/.agent/skills/api-patterns/auth.md +24 -0
- package/.agent/skills/api-patterns/documentation.md +26 -0
- package/.agent/skills/api-patterns/graphql.md +41 -0
- package/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/.agent/skills/api-patterns/response.md +37 -0
- package/.agent/skills/api-patterns/rest.md +40 -0
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agent/skills/api-patterns/security-testing.md +122 -0
- package/.agent/skills/api-patterns/trpc.md +41 -0
- package/.agent/skills/api-patterns/versioning.md +22 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +71 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +40 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +82 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +100 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +106 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +101 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +93 -0
- package/.agent/skills/architecture/SKILL.md +55 -0
- package/.agent/skills/architecture/context-discovery.md +43 -0
- package/.agent/skills/architecture/examples.md +94 -0
- package/.agent/skills/architecture/pattern-selection.md +68 -0
- package/.agent/skills/architecture/patterns-reference.md +50 -0
- package/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/.agent/skills/bash-linux/SKILL.md +199 -0
- package/.agent/skills/behavioral-modes/SKILL.md +242 -0
- package/.agent/skills/brainstorming/SKILL.md +163 -0
- package/.agent/skills/brainstorming/dynamic-questioning.md +350 -0
- package/.agent/skills/clean-code/SKILL.md +201 -0
- package/.agent/skills/code-review-checklist/SKILL.md +109 -0
- package/.agent/skills/database-design/SKILL.md +52 -0
- package/.agent/skills/database-design/database-selection.md +43 -0
- package/.agent/skills/database-design/indexing.md +39 -0
- package/.agent/skills/database-design/migrations.md +48 -0
- package/.agent/skills/database-design/optimization.md +36 -0
- package/.agent/skills/database-design/orm-selection.md +30 -0
- package/.agent/skills/database-design/schema-design.md +56 -0
- package/.agent/skills/database-design/scripts/schema_validator.py +172 -0
- package/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/.agent/skills/doc.md +177 -0
- package/.agent/skills/docker-expert/SKILL.md +409 -0
- package/.agent/skills/documentation-templates/SKILL.md +194 -0
- package/.agent/skills/frontend-design/SKILL.md +396 -0
- package/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/.agent/skills/frontend-design/color-system.md +311 -0
- package/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
- package/.agent/skills/frontend-design/typography-system.md +345 -0
- package/.agent/skills/frontend-design/ux-psychology.md +541 -0
- package/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/.agent/skills/game-development/2d-games/SKILL.md +119 -0
- package/.agent/skills/game-development/3d-games/SKILL.md +135 -0
- package/.agent/skills/game-development/SKILL.md +167 -0
- package/.agent/skills/game-development/game-art/SKILL.md +185 -0
- package/.agent/skills/game-development/game-audio/SKILL.md +190 -0
- package/.agent/skills/game-development/game-design/SKILL.md +129 -0
- package/.agent/skills/game-development/mobile-games/SKILL.md +108 -0
- package/.agent/skills/game-development/multiplayer/SKILL.md +132 -0
- package/.agent/skills/game-development/pc-games/SKILL.md +144 -0
- package/.agent/skills/game-development/vr-ar/SKILL.md +123 -0
- package/.agent/skills/game-development/web-games/SKILL.md +150 -0
- package/.agent/skills/geo-fundamentals/SKILL.md +156 -0
- package/.agent/skills/geo-fundamentals/scripts/geo_checker.py +289 -0
- package/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agent/skills/lint-and-validate/SKILL.md +45 -0
- package/.agent/skills/lint-and-validate/scripts/lint_runner.py +172 -0
- package/.agent/skills/lint-and-validate/scripts/type_coverage.py +173 -0
- package/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/.agent/skills/mobile-design/SKILL.md +394 -0
- package/.agent/skills/mobile-design/decision-trees.md +516 -0
- package/.agent/skills/mobile-design/mobile-backend.md +491 -0
- package/.agent/skills/mobile-design/mobile-color-system.md +420 -0
- package/.agent/skills/mobile-design/mobile-debugging.md +122 -0
- package/.agent/skills/mobile-design/mobile-design-thinking.md +357 -0
- package/.agent/skills/mobile-design/mobile-navigation.md +458 -0
- package/.agent/skills/mobile-design/mobile-performance.md +767 -0
- package/.agent/skills/mobile-design/mobile-testing.md +356 -0
- package/.agent/skills/mobile-design/mobile-typography.md +433 -0
- package/.agent/skills/mobile-design/platform-android.md +666 -0
- package/.agent/skills/mobile-design/platform-ios.md +561 -0
- package/.agent/skills/mobile-design/scripts/mobile_audit.py +670 -0
- package/.agent/skills/mobile-design/touch-psychology.md +537 -0
- package/.agent/skills/nestjs-expert/SKILL.md +552 -0
- package/.agent/skills/nextjs-best-practices/SKILL.md +203 -0
- package/.agent/skills/nodejs-best-practices/SKILL.md +333 -0
- package/.agent/skills/parallel-agents/SKILL.md +175 -0
- package/.agent/skills/performance-profiling/SKILL.md +143 -0
- package/.agent/skills/performance-profiling/scripts/lighthouse_audit.py +76 -0
- package/.agent/skills/plan-writing/SKILL.md +152 -0
- package/.agent/skills/powershell-windows/SKILL.md +167 -0
- package/.agent/skills/prisma-expert/SKILL.md +355 -0
- package/.agent/skills/python-patterns/SKILL.md +441 -0
- package/.agent/skills/react-patterns/SKILL.md +198 -0
- package/.agent/skills/red-team-tactics/SKILL.md +199 -0
- package/.agent/skills/seo-fundamentals/SKILL.md +129 -0
- package/.agent/skills/seo-fundamentals/scripts/seo_checker.py +219 -0
- package/.agent/skills/server-management/SKILL.md +161 -0
- package/.agent/skills/systematic-debugging/SKILL.md +109 -0
- package/.agent/skills/tailwind-patterns/SKILL.md +269 -0
- package/.agent/skills/tdd-workflow/SKILL.md +149 -0
- package/.agent/skills/testing-patterns/SKILL.md +178 -0
- package/.agent/skills/testing-patterns/scripts/test_runner.py +219 -0
- package/.agent/skills/typescript-expert/SKILL.md +429 -0
- package/.agent/skills/typescript-expert/references/tsconfig-strict.json +92 -0
- package/.agent/skills/typescript-expert/references/typescript-cheatsheet.md +383 -0
- package/.agent/skills/typescript-expert/references/utility-types.ts +335 -0
- package/.agent/skills/typescript-expert/scripts/ts_diagnostic.py +203 -0
- package/.agent/skills/ui-ux-pro-max/SKILL.md +351 -0
- package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/skills/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/scripts/core.py +257 -0
- package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +487 -0
- package/.agent/skills/ui-ux-pro-max/scripts/search.py +76 -0
- package/.agent/skills/vulnerability-scanner/SKILL.md +276 -0
- package/.agent/skills/vulnerability-scanner/checklists.md +121 -0
- package/.agent/skills/vulnerability-scanner/scripts/security_scan.py +458 -0
- package/.agent/skills/webapp-testing/SKILL.md +187 -0
- package/.agent/skills/webapp-testing/scripts/playwright_runner.py +173 -0
- package/.agent/workflows/brainstorm.md +113 -0
- package/.agent/workflows/create.md +59 -0
- package/.agent/workflows/debug.md +103 -0
- package/.agent/workflows/deploy.md +176 -0
- package/.agent/workflows/enhance.md +63 -0
- package/.agent/workflows/orchestrate.md +237 -0
- package/.agent/workflows/plan.md +89 -0
- package/.agent/workflows/preview.md +80 -0
- package/.agent/workflows/status.md +86 -0
- package/.agent/workflows/test.md +144 -0
- package/.agent/workflows/ui-ux-pro-max.md +231 -0
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/bin/cli.js +235 -0
- package/index.js +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
UX Audit Script - Full Frontend Design Coverage
|
|
4
|
+
|
|
5
|
+
Analyzes code for compliance with:
|
|
6
|
+
|
|
7
|
+
1. CORE PSYCHOLOGY LAWS:
|
|
8
|
+
- Hick's Law (nav items, form complexity)
|
|
9
|
+
- Fitts' Law (target sizes, touch targets)
|
|
10
|
+
- Miller's Law (chunking, memory limits)
|
|
11
|
+
- Von Restorff Effect (primary CTA visibility)
|
|
12
|
+
- Serial Position Effect (important items at start/end)
|
|
13
|
+
|
|
14
|
+
2. EMOTIONAL DESIGN (Don Norman):
|
|
15
|
+
- Visceral (first impressions, gradients, animations)
|
|
16
|
+
- Behavioral (feedback, usability, performance)
|
|
17
|
+
- Reflective (brand story, values, identity)
|
|
18
|
+
|
|
19
|
+
3. TRUST BUILDING:
|
|
20
|
+
- Security signals (SSL, encryption on forms)
|
|
21
|
+
- Social proof (testimonials, reviews, logos)
|
|
22
|
+
- Authority indicators (certifications, awards, media)
|
|
23
|
+
|
|
24
|
+
4. COGNITIVE LOAD MANAGEMENT:
|
|
25
|
+
- Progressive disclosure (accordion, tabs, "Advanced")
|
|
26
|
+
- Visual noise (too many colors/borders)
|
|
27
|
+
- Familiar patterns (labels, standard conventions)
|
|
28
|
+
|
|
29
|
+
5. PERSUASIVE DESIGN (Ethical):
|
|
30
|
+
- Smart defaults (pre-selected options)
|
|
31
|
+
- Anchoring (original vs discount price)
|
|
32
|
+
- Social proof (live indicators, numbers)
|
|
33
|
+
- Progress indicators (progress bars, steps)
|
|
34
|
+
|
|
35
|
+
6. TYPOGRAPHY SYSTEM (9 sections):
|
|
36
|
+
- Font Pairing (max 3 families)
|
|
37
|
+
- Line Length (45-75ch)
|
|
38
|
+
- Line Height (proper ratios)
|
|
39
|
+
- Letter Spacing (uppercase, display text)
|
|
40
|
+
- Weight and Emphasis (contrast levels)
|
|
41
|
+
- Responsive Typography (clamp())
|
|
42
|
+
- Hierarchy (sequential headings)
|
|
43
|
+
- Modular Scale (consistent ratios)
|
|
44
|
+
- Readability (chunking, subheadings)
|
|
45
|
+
|
|
46
|
+
7. VISUAL EFFECTS (10 sections):
|
|
47
|
+
- Glassmorphism (blur + transparency)
|
|
48
|
+
- Neomorphism (dual shadows, inset)
|
|
49
|
+
- Shadow Hierarchy (elevation levels)
|
|
50
|
+
- Gradients (usage, overuse)
|
|
51
|
+
- Border Effects (complexity check)
|
|
52
|
+
- Glow Effects (text-shadow, box-shadow)
|
|
53
|
+
- Overlay Techniques (image text readability)
|
|
54
|
+
- GPU Acceleration (transform/opacity vs layout)
|
|
55
|
+
- Performance (will-change usage)
|
|
56
|
+
- Effect Selection (purpose over decoration)
|
|
57
|
+
|
|
58
|
+
8. COLOR SYSTEM (7 sections):
|
|
59
|
+
- PURPLE BAN (Critical Maestro rule - #8B5CF6, #A855F7, etc.)
|
|
60
|
+
- 60-30-10 Rule (dominant, secondary, accent)
|
|
61
|
+
- Color Scheme Patterns (monochromatic, analogous)
|
|
62
|
+
- Dark Mode Compliance (no pure black/white)
|
|
63
|
+
- WCAG Contrast (low-contrast detection)
|
|
64
|
+
- Color Psychology Context (food + blue = bad)
|
|
65
|
+
- HSL-Based Palettes (recommended approach)
|
|
66
|
+
|
|
67
|
+
9. ANIMATION GUIDE (6 sections):
|
|
68
|
+
- Duration Appropriateness (50ms minimum, 1s max transitions)
|
|
69
|
+
- Easing Functions (ease-out for entry, ease-in for exit)
|
|
70
|
+
- Micro-interactions (hover/focus feedback)
|
|
71
|
+
- Loading States (skeleton, spinner, progress)
|
|
72
|
+
- Page Transitions (fade/slide for routing)
|
|
73
|
+
- Scroll Animation Performance (no layout properties)
|
|
74
|
+
|
|
75
|
+
10. MOTION GRAPHICS (7 sections):
|
|
76
|
+
- Lottie Animations (reduced motion fallbacks)
|
|
77
|
+
- GSAP Memory Leaks (kill/revert on unmount)
|
|
78
|
+
- SVG Animation Performance (stroke-dashoffset sparingly)
|
|
79
|
+
- 3D Transforms (perspective parent, mobile warning)
|
|
80
|
+
- Particle Effects (mobile fallback)
|
|
81
|
+
- Scroll-Driven Animations (throttle with rAF)
|
|
82
|
+
- Motion Decision Tree (functional vs decorative)
|
|
83
|
+
|
|
84
|
+
11. ACCESSIBILITY:
|
|
85
|
+
- Alt text for images
|
|
86
|
+
- Reduced motion checks
|
|
87
|
+
- Form labels
|
|
88
|
+
|
|
89
|
+
Total: 80+ checks across all design principles
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
import sys
|
|
93
|
+
import os
|
|
94
|
+
import re
|
|
95
|
+
import json
|
|
96
|
+
from pathlib import Path
|
|
97
|
+
|
|
98
|
+
class UXAuditor:
|
|
99
|
+
def __init__(self):
|
|
100
|
+
self.issues = []
|
|
101
|
+
self.warnings = []
|
|
102
|
+
self.passed_count = 0
|
|
103
|
+
self.files_checked = 0
|
|
104
|
+
|
|
105
|
+
def audit_file(self, filepath: str) -> None:
|
|
106
|
+
try:
|
|
107
|
+
with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
|
|
108
|
+
content = f.read()
|
|
109
|
+
except: return
|
|
110
|
+
|
|
111
|
+
self.files_checked += 1
|
|
112
|
+
filename = os.path.basename(filepath)
|
|
113
|
+
|
|
114
|
+
# Pre-calculate common flags
|
|
115
|
+
has_long_text = bool(re.search(r'<p|<div.*class=.*text|article|<span.*text', content, re.IGNORECASE))
|
|
116
|
+
has_form = bool(re.search(r'<form|<input|password|credit|card|payment', content, re.IGNORECASE))
|
|
117
|
+
complex_elements = len(re.findall(r'<input|<select|<textarea|<option', content, re.IGNORECASE))
|
|
118
|
+
|
|
119
|
+
# --- 1. PSYCHOLOGY LAWS ---
|
|
120
|
+
# Hick's Law
|
|
121
|
+
nav_items = len(re.findall(r'<NavLink|<Link|<a\s+href|nav-item', content, re.IGNORECASE))
|
|
122
|
+
if nav_items > 7:
|
|
123
|
+
self.issues.append(f"[Hick's Law] {filename}: {nav_items} nav items (Max 7)")
|
|
124
|
+
|
|
125
|
+
# Fitts' Law
|
|
126
|
+
if re.search(r'height:\s*([0-3]\d)px', content) or re.search(r'h-[1-9]\b|h-10\b', content):
|
|
127
|
+
self.warnings.append(f"[Fitts' Law] {filename}: Small targets (< 44px)")
|
|
128
|
+
|
|
129
|
+
# Miller's Law
|
|
130
|
+
form_fields = len(re.findall(r'<input|<select|<textarea', content, re.IGNORECASE))
|
|
131
|
+
if form_fields > 7 and not re.search(r'step|wizard|stage', content, re.IGNORECASE):
|
|
132
|
+
self.warnings.append(f"[Miller's Law] {filename}: Complex form ({form_fields} fields)")
|
|
133
|
+
|
|
134
|
+
# Von Restorff
|
|
135
|
+
if 'button' in content.lower() and not re.search(r'primary|bg-primary|Button.*primary|variant=["\']primary', content, re.IGNORECASE):
|
|
136
|
+
self.warnings.append(f"[Von Restorff] {filename}: No primary CTA")
|
|
137
|
+
|
|
138
|
+
# Serial Position Effect - Important items at beginning/end
|
|
139
|
+
if nav_items > 3:
|
|
140
|
+
# Check if last nav item is important (contact, login, etc.)
|
|
141
|
+
nav_content = re.findall(r'<NavLink|<Link|<a\s+href[^>]*>([^<]+)</a>', content, re.IGNORECASE)
|
|
142
|
+
if nav_content and len(nav_content) > 2:
|
|
143
|
+
last_item = nav_content[-1].lower() if nav_content else ''
|
|
144
|
+
if not any(x in last_item for x in ['contact', 'login', 'sign', 'get started', 'cta', 'button']):
|
|
145
|
+
self.warnings.append(f"[Serial Position] {filename}: Last nav item may not be important. Place key actions at start/end.")
|
|
146
|
+
|
|
147
|
+
# --- 1.5 EMOTIONAL DESIGN (Don Norman) ---
|
|
148
|
+
|
|
149
|
+
# Visceral: First impressions (aesthetics, gradients, animations)
|
|
150
|
+
has_hero = bool(re.search(r'hero|<h1|banner', content, re.IGNORECASE))
|
|
151
|
+
if has_hero:
|
|
152
|
+
# Check for visual appeal elements
|
|
153
|
+
has_gradient = bool(re.search(r'gradient|linear-gradient|radial-gradient', content))
|
|
154
|
+
has_animation = bool(re.search(r'@keyframes|transition:|animate-', content))
|
|
155
|
+
has_visual_interest = has_gradient or has_animation
|
|
156
|
+
|
|
157
|
+
if not has_visual_interest and not re.search(r'background:|bg-', content):
|
|
158
|
+
self.warnings.append(f"[Visceral] {filename}: Hero section lacks visual appeal. Consider gradients or subtle animations.")
|
|
159
|
+
|
|
160
|
+
# Behavioral: Instant feedback and usability
|
|
161
|
+
if 'onClick' in content or '@click' in content or 'onclick' in content:
|
|
162
|
+
has_feedback = re.search(r'transition|animate|hover:|focus:|disabled|loading|spinner', content, re.IGNORECASE)
|
|
163
|
+
has_state_change = re.search(r'setState|useState|disabled|loading', content)
|
|
164
|
+
|
|
165
|
+
if not has_feedback and not has_state_change:
|
|
166
|
+
self.warnings.append(f"[Behavioral] {filename}: Interactive elements lack immediate feedback. Add hover/focus/disabled states.")
|
|
167
|
+
|
|
168
|
+
# Reflective: Brand story, values, identity
|
|
169
|
+
has_reflective = bool(re.search(r'about|story|mission|values|why we|our journey|testimonials', content, re.IGNORECASE))
|
|
170
|
+
if has_long_text and not has_reflective:
|
|
171
|
+
self.warnings.append(f"[Reflective] {filename}: Long-form content without brand story/values. Add 'About' or 'Why We Exist' section.")
|
|
172
|
+
|
|
173
|
+
# --- 1.6 TRUST BUILDING (Enhanced) ---
|
|
174
|
+
|
|
175
|
+
# Security signals
|
|
176
|
+
if has_form:
|
|
177
|
+
security_signals = re.findall(r'ssl|secure|encrypt|lock|padlock|https', content, re.IGNORECASE)
|
|
178
|
+
if len(security_signals) == 0 and not re.search(r'checkout|payment', content, re.IGNORECASE):
|
|
179
|
+
self.warnings.append(f"[Trust] {filename}: Form without security indicators. Add 'SSL Secure' or lock icon.")
|
|
180
|
+
|
|
181
|
+
# Social proof elements
|
|
182
|
+
social_proof = re.findall(r'review|testimonial|rating|star|trust|trusted by|customer|logo', content, re.IGNORECASE)
|
|
183
|
+
if len(social_proof) > 0:
|
|
184
|
+
self.passed_count += 1
|
|
185
|
+
else:
|
|
186
|
+
if has_long_text:
|
|
187
|
+
self.warnings.append(f"[Trust] {filename}: No social proof detected. Consider adding testimonials, ratings, or 'Trusted by' logos.")
|
|
188
|
+
|
|
189
|
+
# Authority indicators
|
|
190
|
+
has_footer = bool(re.search(r'footer|<footer', content, re.IGNORECASE))
|
|
191
|
+
if has_footer:
|
|
192
|
+
authority = re.findall(r'certif|award|media|press|featured|as seen in', content, re.IGNORECASE)
|
|
193
|
+
if len(authority) == 0:
|
|
194
|
+
self.warnings.append(f"[Trust] {filename}: Footer lacks authority signals. Add certifications, awards, or media mentions.")
|
|
195
|
+
|
|
196
|
+
# --- 1.7 COGNITIVE LOAD MANAGEMENT ---
|
|
197
|
+
|
|
198
|
+
# Progressive disclosure
|
|
199
|
+
if complex_elements > 5:
|
|
200
|
+
has_progressive = re.search(r'step|wizard|stage|accordion|collapsible|tab|more\.\.\.|advanced|show more', content, re.IGNORECASE)
|
|
201
|
+
if not has_progressive:
|
|
202
|
+
self.warnings.append(f"[Cognitive Load] {filename}: Many form elements without progressive disclosure. Consider accordion, tabs, or 'Advanced' toggle.")
|
|
203
|
+
|
|
204
|
+
# Visual noise check
|
|
205
|
+
has_many_colors = len(re.findall(r'#[0-9a-fA-F]{3,6}|rgb|hsl', content)) > 15
|
|
206
|
+
has_many_borders = len(re.findall(r'border:|border-', content)) > 10
|
|
207
|
+
if has_many_colors and has_many_borders:
|
|
208
|
+
self.warnings.append(f"[Cognitive Load] {filename}: High visual noise detected. Many colors and borders increase cognitive load.")
|
|
209
|
+
|
|
210
|
+
# Familiar patterns
|
|
211
|
+
if has_form:
|
|
212
|
+
has_standard_labels = bool(re.search(r'<label|placeholder|aria-label', content, re.IGNORECASE))
|
|
213
|
+
if not has_standard_labels:
|
|
214
|
+
self.issues.append(f"[Cognitive Load] {filename}: Form inputs without labels. Use <label> for accessibility and clarity.")
|
|
215
|
+
|
|
216
|
+
# --- 1.8 PERSUASIVE DESIGN (Ethical) ---
|
|
217
|
+
|
|
218
|
+
# Smart defaults
|
|
219
|
+
if has_form:
|
|
220
|
+
has_defaults = bool(re.search(r'checked|selected|default|value=["\'].*["\']', content))
|
|
221
|
+
radio_inputs = len(re.findall(r'type=["\']radio', content, re.IGNORECASE))
|
|
222
|
+
if radio_inputs > 0 and not has_defaults:
|
|
223
|
+
self.warnings.append(f"[Persuasion] {filename}: Radio buttons without default selection. Pre-select recommended option.")
|
|
224
|
+
|
|
225
|
+
# Anchoring (showing original price)
|
|
226
|
+
if re.search(r'price|pricing|cost|\$\d+', content, re.IGNORECASE):
|
|
227
|
+
has_anchor = bool(re.search(r'original|was|strike|del|save \d+%', content, re.IGNORECASE))
|
|
228
|
+
if not has_anchor:
|
|
229
|
+
self.warnings.append(f"[Persuasion] {filename}: Prices without anchoring. Show original price to frame discount value.")
|
|
230
|
+
|
|
231
|
+
# Social proof live indicators
|
|
232
|
+
has_social = bool(re.search(r'join|subscriber|member|user', content, re.IGNORECASE))
|
|
233
|
+
if has_social:
|
|
234
|
+
has_count = bool(re.findall(r'\d+[+kmb]|\d+,\d+', content))
|
|
235
|
+
if not has_count:
|
|
236
|
+
self.warnings.append(f"[Persuasion] {filename}: Social proof without specific numbers. Use 'Join 10,000+' format.")
|
|
237
|
+
|
|
238
|
+
# Progress indicators
|
|
239
|
+
if has_form:
|
|
240
|
+
has_progress = bool(re.search(r'progress|step \d+|complete|%|bar', content, re.IGNORECASE))
|
|
241
|
+
if complex_elements > 5 and not has_progress:
|
|
242
|
+
self.warnings.append(f"[Persuasion] {filename}: Long form without progress indicator. Add progress bar or 'Step X of Y'.")
|
|
243
|
+
|
|
244
|
+
# --- 2. TYPOGRAPHY SYSTEM (Complete Coverage) ---
|
|
245
|
+
|
|
246
|
+
# 2.1 Font Pairing - Too many font families
|
|
247
|
+
font_families = set()
|
|
248
|
+
# Check for @font-face, Google Fonts, font-family declarations
|
|
249
|
+
font_faces = re.findall(r'@font-face\s*\{[^}]*family:\s*["\']?([^;"\'\s}]+)', content, re.IGNORECASE)
|
|
250
|
+
google_fonts = re.findall(r'fonts\.googleapis\.com[^"\']*family=([^"&]+)', content, re.IGNORECASE)
|
|
251
|
+
font_family_css = re.findall(r'font-family:\s*([^;]+)', content, re.IGNORECASE)
|
|
252
|
+
|
|
253
|
+
for font in font_faces: font_families.add(font.strip().lower())
|
|
254
|
+
for font in google_fonts:
|
|
255
|
+
for f in font.replace('+', ' ').split('|'):
|
|
256
|
+
font_families.add(f.split(':')[0].strip().lower())
|
|
257
|
+
for family in font_family_css:
|
|
258
|
+
# Extract first font from stack
|
|
259
|
+
first_font = family.split(',')[0].strip().strip('"\'')
|
|
260
|
+
|
|
261
|
+
if first_font.lower() not in {'sans-serif', 'serif', 'monospace', 'cursive', 'fantasy', 'system-ui', 'inherit', 'arial', 'georgia', 'times new roman', 'courier new', 'verdana', 'helvetica', 'tahoma'}:
|
|
262
|
+
font_families.add(first_font.lower())
|
|
263
|
+
|
|
264
|
+
if len(font_families) > 3:
|
|
265
|
+
self.issues.append(f"[Typography] {filename}: {len(font_families)} font families detected. Limit to 2-3 for cohesion.")
|
|
266
|
+
|
|
267
|
+
# 2.2 Line Length - Character-based width
|
|
268
|
+
if has_long_text and not re.search(r'max-w-(?:prose|[\[\\]?\d+ch[\]\\]?)|max-width:\s*\d+ch', content):
|
|
269
|
+
self.warnings.append(f"[Typography] {filename}: No line length constraint (45-75ch). Use max-w-prose or max-w-[65ch].")
|
|
270
|
+
|
|
271
|
+
# 2.3 Line Height - Proper leading ratios
|
|
272
|
+
# Check for text without proper line-height
|
|
273
|
+
text_elements = len(re.findall(r'<p|<span|<div.*text|<h[1-6]', content, re.IGNORECASE))
|
|
274
|
+
if text_elements > 0 and not re.search(r'leading-|line-height:', content):
|
|
275
|
+
self.warnings.append(f"[Typography] {filename}: Text elements found without line-height. Body: 1.4-1.6, Headings: 1.1-1.3")
|
|
276
|
+
|
|
277
|
+
# Check for heading-specific line height issues
|
|
278
|
+
if re.search(r'<h[1-6]|text-(?:xl|2xl|3xl|4xl|5xl|6xl)', content, re.IGNORECASE):
|
|
279
|
+
# Extract line-height values
|
|
280
|
+
line_heights = re.findall(r'(?:leading-|line-height:\s*)([\d.]+)', content)
|
|
281
|
+
for lh in line_heights:
|
|
282
|
+
if float(lh) > 1.5:
|
|
283
|
+
self.warnings.append(f"[Typography] {filename}: Heading has line-height {lh} (>1.3). Headings should be tighter (1.1-1.3).")
|
|
284
|
+
|
|
285
|
+
# 2.4 Letter Spacing (Tracking)
|
|
286
|
+
# Uppercase without tracking
|
|
287
|
+
if re.search(r'uppercase|text-transform:\s*uppercase', content, re.IGNORECASE):
|
|
288
|
+
if not re.search(r'tracking-|letter-spacing:', content):
|
|
289
|
+
self.warnings.append(f"[Typography] {filename}: Uppercase text without tracking. ALL CAPS needs +5-10% spacing.")
|
|
290
|
+
|
|
291
|
+
# Large text (display/hero) should have negative tracking
|
|
292
|
+
if re.search(r'text-(?:4xl|5xl|6xl|7xl|8xl|9xl)|font-size:\s*[3-9]\dpx', content):
|
|
293
|
+
if not re.search(r'tracking-tight|letter-spacing:\s*-[0-9]', content):
|
|
294
|
+
self.warnings.append(f"[Typography] {filename}: Large display text without tracking-tight. Big text needs -1% to -4% spacing.")
|
|
295
|
+
|
|
296
|
+
# 2.5 Weight and Emphasis - Contrast levels
|
|
297
|
+
# Check for adjacent weight levels (poor contrast)
|
|
298
|
+
weights = re.findall(r'font-weight:\s*(\d+)|font-(?:thin|extralight|light|normal|medium|semibold|bold|extrabold|black)|fw-(\d+)', content, re.IGNORECASE)
|
|
299
|
+
weight_values = []
|
|
300
|
+
for w in weights:
|
|
301
|
+
val = w[0] or w[1]
|
|
302
|
+
if val:
|
|
303
|
+
# Map named weights to numbers
|
|
304
|
+
weight_map = {'thin': '100', 'extralight': '200', 'light': '300', 'normal': '400', 'medium': '500', 'semibold': '600', 'bold': '700', 'extrabold': '800', 'black': '900'}
|
|
305
|
+
val = weight_map.get(val.lower(), val)
|
|
306
|
+
try:
|
|
307
|
+
weight_values.append(int(val))
|
|
308
|
+
except: pass
|
|
309
|
+
|
|
310
|
+
# Check for adjacent weights (400/500, 500/600, etc.)
|
|
311
|
+
for i in range(len(weight_values) - 1):
|
|
312
|
+
diff = abs(weight_values[i] - weight_values[i+1])
|
|
313
|
+
if diff == 100:
|
|
314
|
+
self.warnings.append(f"[Typography] {filename}: Adjacent font weights ({weight_values[i]}/{weight_values[i+1]}). Skip at least 2 levels for contrast.")
|
|
315
|
+
|
|
316
|
+
# Too many weight levels
|
|
317
|
+
unique_weights = set(weight_values)
|
|
318
|
+
if len(unique_weights) > 4:
|
|
319
|
+
self.warnings.append(f"[Typography] {filename}: {len(unique_weights)} font weights. Limit to 3-4 per page.")
|
|
320
|
+
|
|
321
|
+
# 2.6 Responsive Typography - Fluid sizing with clamp()
|
|
322
|
+
has_font_sizes = bool(re.search(r'font-size:|text-(?:xs|sm|base|lg|xl|2xl)', content))
|
|
323
|
+
if has_font_sizes and not re.search(r'clamp\(|responsive:', content):
|
|
324
|
+
self.warnings.append(f"[Typography] {filename}: Fixed font sizes without clamp(). Consider fluid typography: clamp(MIN, PREFERRED, MAX)")
|
|
325
|
+
|
|
326
|
+
# 2.7 Hierarchy - Heading structure
|
|
327
|
+
headings = re.findall(r'<(h[1-6])', content, re.IGNORECASE)
|
|
328
|
+
if headings:
|
|
329
|
+
# Check for skipped levels (h1 -> h3)
|
|
330
|
+
for i in range(len(headings) - 1):
|
|
331
|
+
curr = int(headings[i][1])
|
|
332
|
+
next_h = int(headings[i+1][1])
|
|
333
|
+
if next_h > curr + 1:
|
|
334
|
+
self.warnings.append(f"[Typography] {filename}: Skipped heading level (h{curr} -> h{next_h}). Maintain sequential hierarchy.")
|
|
335
|
+
|
|
336
|
+
# Check if h1 exists for main content
|
|
337
|
+
if 'h1' not in [h.lower() for h in headings] and has_long_text:
|
|
338
|
+
self.warnings.append(f"[Typography] {filename}: No h1 found. Each page should have one primary heading.")
|
|
339
|
+
|
|
340
|
+
# 2.8 Modular Scale - Consistent sizing
|
|
341
|
+
# Extract font-size values
|
|
342
|
+
font_sizes = re.findall(r'font-size:\s*(\d+(?:\.\d+)?)(px|rem|em)', content)
|
|
343
|
+
size_values = []
|
|
344
|
+
for size, unit in font_sizes:
|
|
345
|
+
if unit == 'rem' or unit == 'em':
|
|
346
|
+
size_values.append(float(size))
|
|
347
|
+
elif unit == 'px':
|
|
348
|
+
size_values.append(float(size) / 16) # Normalize to rem
|
|
349
|
+
|
|
350
|
+
if len(size_values) > 2:
|
|
351
|
+
# Check if sizes follow a modular scale roughly
|
|
352
|
+
sorted_sizes = sorted(set(size_values))
|
|
353
|
+
ratios = []
|
|
354
|
+
for i in range(1, len(sorted_sizes)):
|
|
355
|
+
if sorted_sizes[i-1] > 0:
|
|
356
|
+
ratios.append(sorted_sizes[i] / sorted_sizes[i-1])
|
|
357
|
+
|
|
358
|
+
# Common scale ratios: 1.067, 1.125, 1.2, 1.25, 1.333, 1.5, 1.618
|
|
359
|
+
common_ratios = {1.067, 1.125, 1.2, 1.25, 1.333, 1.5, 1.618}
|
|
360
|
+
for ratio in ratios[:3]: # Check first 3 ratios
|
|
361
|
+
if not any(abs(ratio - cr) < 0.05 for cr in common_ratios):
|
|
362
|
+
self.warnings.append(f"[Typography] {filename}: Font sizes may not follow modular scale (ratio: {ratio:.2f}). Consider consistent ratio like 1.25 (Major Third).")
|
|
363
|
+
break
|
|
364
|
+
|
|
365
|
+
# 2.9 Readability - Content chunking
|
|
366
|
+
# Check for very long paragraphs (>5 lines estimated)
|
|
367
|
+
paragraphs = re.findall(r'<p[^>]*>([^<]+)</p>', content, re.IGNORECASE)
|
|
368
|
+
for p in paragraphs:
|
|
369
|
+
word_count = len(p.split())
|
|
370
|
+
if word_count > 100: # ~5-6 lines
|
|
371
|
+
self.warnings.append(f"[Typography] {filename}: Long paragraph detected ({word_count} words). Break into 3-4 line chunks for readability.")
|
|
372
|
+
|
|
373
|
+
# Check for missing subheadings in long content
|
|
374
|
+
if len(paragraphs) > 5:
|
|
375
|
+
subheadings = len(re.findall(r'<h[2-6]', content, re.IGNORECASE))
|
|
376
|
+
if subheadings == 0:
|
|
377
|
+
self.warnings.append(f"[Typography] {filename}: Long content without subheadings. Add h2/h3 to break up text.")
|
|
378
|
+
|
|
379
|
+
# --- 3. VISUAL EFFECTS (visual-effects.md) ---
|
|
380
|
+
|
|
381
|
+
# Glassmorphism Check
|
|
382
|
+
if 'backdrop-filter' in content or 'blur(' in content:
|
|
383
|
+
if not re.search(r'background:\s*rgba|bg-opacity|bg-[a-z0-9]+\/\d+', content):
|
|
384
|
+
self.warnings.append(f"[Visual] {filename}: Blur used without semi-transparent background (Glassmorphism fail)")
|
|
385
|
+
|
|
386
|
+
# GPU Acceleration / Performance
|
|
387
|
+
if re.search(r'@keyframes|transition:', content):
|
|
388
|
+
expensive_props = re.findall(r'width|height|top|left|right|bottom|margin|padding', content)
|
|
389
|
+
if expensive_props:
|
|
390
|
+
self.warnings.append(f"[Performance] {filename}: Animating expensive properties ({', '.join(set(expensive_props))}). Use transform/opacity where possible.")
|
|
391
|
+
|
|
392
|
+
# Reduced Motion
|
|
393
|
+
if not re.search(r'prefers-reduced-motion', content):
|
|
394
|
+
self.warnings.append(f"[Accessibility] {filename}: Animations found without prefers-reduced-motion check")
|
|
395
|
+
|
|
396
|
+
# Natural Shadows
|
|
397
|
+
shadows = re.findall(r'box-shadow:\s*([^;]+)', content)
|
|
398
|
+
for shadow in shadows:
|
|
399
|
+
# Check if natural (Y > X) or multiple layers
|
|
400
|
+
if ',' not in shadow and not re.search(r'\d+px\s+[1-9]\d*px', shadow): # Simple heuristic for Y-offset
|
|
401
|
+
self.warnings.append(f"[Visual] {filename}: Simple/Unnatural shadow detected. Consider multiple layers or Y > X offset for realism.")
|
|
402
|
+
|
|
403
|
+
# --- 3.1 NEOMORPHISM CHECK ---
|
|
404
|
+
# Check for neomorphism patterns (dual shadows with opposite directions)
|
|
405
|
+
neo_shadows = re.findall(r'box-shadow:\s*([^;]+)', content)
|
|
406
|
+
for shadow in neo_shadows:
|
|
407
|
+
# Neomorphism has two shadows: positive offset + negative offset
|
|
408
|
+
if ',' in shadow and '-' in shadow:
|
|
409
|
+
# Check for inset pattern (pressed state)
|
|
410
|
+
if 'inset' in shadow:
|
|
411
|
+
self.warnings.append(f"[Visual] {filename}: Neomorphism inset detected. Ensure adequate contrast for accessibility.")
|
|
412
|
+
|
|
413
|
+
# --- 3.2 SHADOW HIERARCHY ---
|
|
414
|
+
# Count shadow levels to check for elevation consistency
|
|
415
|
+
shadow_count = len(shadows)
|
|
416
|
+
if shadow_count > 0:
|
|
417
|
+
# Check for shadow opacity levels (should indicate hierarchy)
|
|
418
|
+
opacities = re.findall(r'rgba?\([^)]+,\s*([\d.]+)\)', content)
|
|
419
|
+
shadow_opacities = [float(o) for o in opacities if float(o) < 0.5]
|
|
420
|
+
if shadow_count >= 3 and len(shadow_opacities) > 0:
|
|
421
|
+
# Check if there's variety in shadow opacities for different elevations
|
|
422
|
+
unique_opacities = len(set(shadow_opacities))
|
|
423
|
+
if unique_opacities < 2:
|
|
424
|
+
self.warnings.append(f"[Visual] {filename}: All shadows at same opacity level. Vary shadow intensity for elevation hierarchy.")
|
|
425
|
+
|
|
426
|
+
# --- 3.3 GRADIENT CHECKS ---
|
|
427
|
+
# Check for gradient usage
|
|
428
|
+
has_gradient = bool(re.search(r'gradient|linear-gradient|radial-gradient|conic-gradient', content))
|
|
429
|
+
if has_gradient:
|
|
430
|
+
# Warn about mesh/aurora gradients (can be overused)
|
|
431
|
+
gradient_count = len(re.findall(r'gradient', content, re.IGNORECASE))
|
|
432
|
+
if gradient_count > 5:
|
|
433
|
+
self.warnings.append(f"[Visual] {filename}: Many gradients detected ({gradient_count}). Ensure this serves purpose, not decoration.")
|
|
434
|
+
else:
|
|
435
|
+
# Check if hero section exists without gradient
|
|
436
|
+
if has_hero and not re.search(r'background:|bg-', content):
|
|
437
|
+
self.warnings.append(f"[Visual] {filename}: Hero section without visual interest. Consider gradient for depth.")
|
|
438
|
+
|
|
439
|
+
# --- 3.4 BORDER EFFECTS ---
|
|
440
|
+
# Check for gradient borders or animated borders
|
|
441
|
+
has_border = bool(re.search(r'border:|border-', content))
|
|
442
|
+
if has_border:
|
|
443
|
+
# Check for overly complex borders
|
|
444
|
+
border_count = len(re.findall(r'border:', content))
|
|
445
|
+
if border_count > 8:
|
|
446
|
+
self.warnings.append(f"[Visual] {filename}: Many border declarations ({border_count}). Simplify for cleaner look.")
|
|
447
|
+
|
|
448
|
+
# --- 3.5 GLOW EFFECTS ---
|
|
449
|
+
# Check for text-shadow or multiple box-shadow layers (glow effects)
|
|
450
|
+
text_shadows = re.findall(r'text-shadow:', content)
|
|
451
|
+
for ts in text_shadows:
|
|
452
|
+
# Multiple text-shadow layers indicate glow
|
|
453
|
+
if ',' in ts:
|
|
454
|
+
self.warnings.append(f"[Visual] {filename}: Text glow effect detected. Ensure readability is maintained.")
|
|
455
|
+
|
|
456
|
+
# Check for box-shadow glow (multiple layers with 0 offset)
|
|
457
|
+
glow_shadows = re.findall(r'box-shadow:\s*[^;]*0\s+0\s+', content)
|
|
458
|
+
if len(glow_shadows) > 2:
|
|
459
|
+
self.warnings.append(f"[Visual] {filename}: Multiple glow effects detected. Use sparingly for emphasis only.")
|
|
460
|
+
|
|
461
|
+
# --- 3.6 OVERLAY TECHNIQUES ---
|
|
462
|
+
# Check for image overlays (for readability)
|
|
463
|
+
has_images = bool(re.search(r'<img|background-image:|bg-\[url', content))
|
|
464
|
+
if has_images and has_long_text:
|
|
465
|
+
has_overlay = bool(re.search(r'overlay|rgba\(0|gradient.*transparent|::after|::before', content))
|
|
466
|
+
if not has_overlay:
|
|
467
|
+
self.warnings.append(f"[Visual] {filename}: Text over image without overlay. Add gradient overlay for readability.")
|
|
468
|
+
|
|
469
|
+
# --- 3.7 PERFORMANCE: will-change ---
|
|
470
|
+
# Check for will-change usage
|
|
471
|
+
if re.search(r'will-change:', content):
|
|
472
|
+
will_change_props = re.findall(r'will-change:\s*([^;]+)', content)
|
|
473
|
+
for prop in will_change_props:
|
|
474
|
+
prop = prop.strip().lower()
|
|
475
|
+
if prop in ['width', 'height', 'top', 'left', 'right', 'bottom', 'margin', 'padding']:
|
|
476
|
+
self.issues.append(f"[Performance] {filename}: will-change on '{prop}' (layout property). Use only for transform/opacity.")
|
|
477
|
+
|
|
478
|
+
# Check for excessive will-change usage
|
|
479
|
+
will_change_count = len(re.findall(r'will-change:', content))
|
|
480
|
+
if will_change_count > 3:
|
|
481
|
+
self.warnings.append(f"[Performance] {filename}: Many will-change declarations ({will_change_count}). Use sparingly, only for heavy animations.")
|
|
482
|
+
|
|
483
|
+
# --- 3.8 EFFECT SELECTION ---
|
|
484
|
+
# Check for effect overuse (too many visual effects)
|
|
485
|
+
effect_count = (
|
|
486
|
+
(1 if has_gradient else 0) +
|
|
487
|
+
shadow_count +
|
|
488
|
+
len(re.findall(r'backdrop-filter|blur\(', content)) +
|
|
489
|
+
len(re.findall(r'text-shadow:', content))
|
|
490
|
+
)
|
|
491
|
+
if effect_count > 10:
|
|
492
|
+
self.warnings.append(f"[Visual] {filename}: Many visual effects ({effect_count}). Ensure effects serve purpose, not decoration.")
|
|
493
|
+
|
|
494
|
+
# Check for static/flat design (no depth)
|
|
495
|
+
if has_long_text and effect_count == 0:
|
|
496
|
+
self.warnings.append(f"[Visual] {filename}: Flat design with no depth. Consider shadows or subtle gradients for hierarchy.")
|
|
497
|
+
|
|
498
|
+
# --- 4. COLOR SYSTEM (color-system.md) ---
|
|
499
|
+
|
|
500
|
+
# 4.1 PURPLE BAN - Critical check from color-system.md
|
|
501
|
+
purple_hexes = ['#8B5CF6', '#A855F7', '#9333EA', '#7C3AED', '#6D28D9',
|
|
502
|
+
'#8B5CF6', '#A78BFA', '#C4B5FD', '#DDD6FE', '#EDE9FE',
|
|
503
|
+
'#8b5cf6', '#a855f7', '#9333ea', '#7c3aed', '#6d28d9',
|
|
504
|
+
'purple', 'violet', 'fuchsia', 'magenta', 'lavender']
|
|
505
|
+
for purple in purple_hexes:
|
|
506
|
+
if purple.lower() in content.lower():
|
|
507
|
+
self.issues.append(f"[Color] {filename}: PURPLE DETECTED ('{purple}'). Banned by Maestro rules. Use Teal/Cyan/Emerald instead.")
|
|
508
|
+
break
|
|
509
|
+
|
|
510
|
+
# 4.2 60-30-10 Rule check
|
|
511
|
+
# Count color usage to estimate ratio
|
|
512
|
+
color_hex_count = len(re.findall(r'#[0-9a-fA-F]{3,6}', content))
|
|
513
|
+
hsl_count = len(re.findall(r'hsl\(', content))
|
|
514
|
+
total_colors = color_hex_count + hsl_count
|
|
515
|
+
if total_colors > 3:
|
|
516
|
+
# Check for dominant colors (should be ~60%)
|
|
517
|
+
bg_declarations = re.findall(r'(?:background|bg-|bg\[)([^;}\s]+)', content)
|
|
518
|
+
text_declarations = re.findall(r'(?:color|text-)([^;}\s]+)', content)
|
|
519
|
+
if len(bg_declarations) > 0 and len(text_declarations) > 0:
|
|
520
|
+
# Just warn if too many distinct colors
|
|
521
|
+
unique_hexes = set(re.findall(r'#[0-9a-fA-F]{6}', content))
|
|
522
|
+
if len(unique_hexes) > 5:
|
|
523
|
+
self.warnings.append(f"[Color] {filename}: {len(unique_hexes)} distinct colors. Consider 60-30-10 rule: dominant (60%), secondary (30%), accent (10%).")
|
|
524
|
+
|
|
525
|
+
# 4.3 Color Scheme Pattern Detection
|
|
526
|
+
# Detect monochromatic (same hue, different lightness)
|
|
527
|
+
hsl_matches = re.findall(r'hsl\((\d+),\s*\d+%,\s*\d+%\)', content)
|
|
528
|
+
if len(hsl_matches) >= 3:
|
|
529
|
+
hues = [int(h) for h in hsl_matches]
|
|
530
|
+
hue_range = max(hues) - min(hues)
|
|
531
|
+
if hue_range < 10:
|
|
532
|
+
self.warnings.append(f"[Color] {filename}: Monochromatic palette detected (hue variance: {hue_range}deg). Ensure adequate contrast.")
|
|
533
|
+
|
|
534
|
+
# 4.4 Dark Mode Compliance
|
|
535
|
+
# Check for pure black (#000000) or pure white (#FFFFFF) text (forbidden)
|
|
536
|
+
if re.search(r'color:\s*#000000|#000\b', content):
|
|
537
|
+
self.warnings.append(f"[Color] {filename}: Pure black (#000000) detected. Use #1a1a1a or darker grays for better dark mode.")
|
|
538
|
+
if re.search(r'background:\s*#ffffff|#fff\b', content) and re.search(r'dark:\s*|dark:', content):
|
|
539
|
+
self.warnings.append(f"[Color] {filename}: Pure white background in dark mode context. Use slight off-white (#f9fafb) for reduced eye strain.")
|
|
540
|
+
|
|
541
|
+
# 4.5 WCAG Contrast Pattern Check
|
|
542
|
+
# Look for potential low-contrast combinations
|
|
543
|
+
light_bg_light_text = bool(re.search(r'bg-(?:gray|slate|zinc)-50|bg-white.*text-(?:gray|slate)-[12]', content))
|
|
544
|
+
dark_bg_dark_text = bool(re.search(r'bg-(?:gray|slate|zinct)-9|bg-black.*text-(?:gray|slate)-[89]', content))
|
|
545
|
+
if light_bg_light_text or dark_bg_dark_text:
|
|
546
|
+
self.warnings.append(f"[Color] {filename}: Possible low-contrast combination detected. Verify WCAG AA (4.5:1 for text).")
|
|
547
|
+
|
|
548
|
+
# 4.6 Color Psychology Context Check
|
|
549
|
+
# Warn if blue used for food/restaurant context
|
|
550
|
+
has_blue = bool(re.search(r'bg-blue|text-blue|from-blue|#[0-9a-fA-F]*00[0-9A-Fa-f]{2}|#[0-9a-fA-F]*1[0-9A-Fa-f]{2}', content))
|
|
551
|
+
has_food_context = bool(re.search(r'restaurant|food|cooking|recipe|menu|dish|meal', content, re.IGNORECASE))
|
|
552
|
+
if has_blue and has_food_context:
|
|
553
|
+
self.warnings.append(f"[Color] {filename}: Blue color in food context. Blue suppresses appetite; consider warm colors (red, orange, yellow).")
|
|
554
|
+
|
|
555
|
+
# 4.7 HSL-Based Palette Detection
|
|
556
|
+
# Check if using HSL for palette (recommended in color-system.md)
|
|
557
|
+
has_color_vars = bool(re.search(r'--color-|color-|primary-|secondary-', content))
|
|
558
|
+
if has_color_vars and not re.search(r'hsl\(', content):
|
|
559
|
+
self.warnings.append(f"[Color] {filename}: Color variables without HSL. Consider HSL for easier palette adjustment (Hue, Saturation, Lightness).")
|
|
560
|
+
|
|
561
|
+
# --- 5. ANIMATION GUIDE (animation-guide.md) ---
|
|
562
|
+
|
|
563
|
+
# 5.1 Duration Appropriateness
|
|
564
|
+
# Check for excessively long or short animations
|
|
565
|
+
durations = re.findall(r'(?:duration|animation-duration|transition-duration):\s*([\d.]+)(s|ms)', content)
|
|
566
|
+
for duration, unit in durations:
|
|
567
|
+
duration_ms = float(duration) * (1000 if unit == 's' else 1)
|
|
568
|
+
if duration_ms < 50:
|
|
569
|
+
self.warnings.append(f"[Animation] {filename}: Very fast animation ({duration}{unit}). Minimum 50ms for visibility.")
|
|
570
|
+
elif duration_ms > 1000 and 'transition' in content.lower():
|
|
571
|
+
self.warnings.append(f"[Animation] {filename}: Long transition ({duration}{unit}). Transitions should be 100-300ms for responsiveness.")
|
|
572
|
+
|
|
573
|
+
# 5.2 Easing Function Correctness
|
|
574
|
+
# Check for incorrect easing patterns
|
|
575
|
+
if re.search(r'ease-in\s+.*entry|fade-in.*ease-in', content):
|
|
576
|
+
self.warnings.append(f"[Animation] {filename}: Entry animation with ease-in. Entry should use ease-out for snappy feel.")
|
|
577
|
+
if re.search(r'ease-out\s+.*exit|fade-out.*ease-out', content):
|
|
578
|
+
self.warnings.append(f"[Animation] {filename}: Exit animation with ease-out. Exit should use ease-in for natural feel.")
|
|
579
|
+
|
|
580
|
+
# 5.3 Micro-interaction Feedback Patterns
|
|
581
|
+
# Check for interactive elements without hover/focus states
|
|
582
|
+
interactive_elements = len(re.findall(r'<button|<a\s+href|onClick|@click', content))
|
|
583
|
+
has_hover_focus = bool(re.search(r'hover:|focus:|:hover|:focus', content))
|
|
584
|
+
if interactive_elements > 2 and not has_hover_focus:
|
|
585
|
+
self.warnings.append(f"[Animation] {filename}: Interactive elements without hover/focus states. Add micro-interactions for feedback.")
|
|
586
|
+
|
|
587
|
+
# 5.4 Loading State Indicators
|
|
588
|
+
# Check for loading patterns
|
|
589
|
+
has_async = bool(re.search(r'async|await|fetch|axios|loading|isLoading', content))
|
|
590
|
+
has_loading_indicator = bool(re.search(r'skeleton|spinner|progress|loading|<circle.*animate', content))
|
|
591
|
+
if has_async and not has_loading_indicator:
|
|
592
|
+
self.warnings.append(f"[Animation] {filename}: Async operations without loading indicator. Add skeleton or spinner for perceived performance.")
|
|
593
|
+
|
|
594
|
+
# 5.5 Page Transition Patterns
|
|
595
|
+
# Check for page/view transitions
|
|
596
|
+
has_routing = bool(re.search(r'router|navigate|Link.*to|useHistory', content))
|
|
597
|
+
has_page_transition = bool(re.search(r'AnimatePresence|motion\.|transition.*page|fade.*route', content))
|
|
598
|
+
if has_routing and not has_page_transition:
|
|
599
|
+
self.warnings.append(f"[Animation] {filename}: Routing detected without page transitions. Consider fade/slide for context continuity.")
|
|
600
|
+
|
|
601
|
+
# 5.6 Scroll Animation Performance
|
|
602
|
+
# Check for scroll-driven animations
|
|
603
|
+
has_scroll_anim = bool(re.search(r'onScroll|scroll.*trigger|IntersectionObserver', content))
|
|
604
|
+
if has_scroll_anim:
|
|
605
|
+
# Check if using expensive properties in scroll handlers
|
|
606
|
+
if re.search(r'onScroll.*[^\w](width|height|top|left)', content):
|
|
607
|
+
self.issues.append(f"[Animation] {filename}: Scroll handler animating layout properties. Use transform/opacity for 60fps.")
|
|
608
|
+
|
|
609
|
+
# --- 6. MOTION GRAPHICS (motion-graphics.md) ---
|
|
610
|
+
|
|
611
|
+
# 6.1 Lottie Animation Checks
|
|
612
|
+
has_lottie = bool(re.search(r'lottie|Lottie|@lottie-react', content))
|
|
613
|
+
if has_lottie:
|
|
614
|
+
# Check for reduced motion fallback
|
|
615
|
+
has_lottie_fallback = bool(re.search(r'prefers-reduced-motion.*lottie|lottie.*isPaused|lottie.*stop', content))
|
|
616
|
+
if not has_lottie_fallback:
|
|
617
|
+
self.warnings.append(f"[Motion] {filename}: Lottie animation without reduced-motion fallback. Add pause/stop for accessibility.")
|
|
618
|
+
|
|
619
|
+
# 6.2 GSAP Memory Leak Risks
|
|
620
|
+
has_gsap = bool(re.search(r'gsap|ScrollTrigger|from\(.*gsap', content))
|
|
621
|
+
if has_gsap:
|
|
622
|
+
# Check for cleanup patterns
|
|
623
|
+
has_gsap_cleanup = bool(re.search(r'kill\(|revert\(|useEffect.*return.*gsap', content))
|
|
624
|
+
if not has_gsap_cleanup:
|
|
625
|
+
self.issues.append(f"[Motion] {filename}: GSAP animation without cleanup (kill/revert). Memory leak risk on unmount.")
|
|
626
|
+
|
|
627
|
+
# 6.3 SVG Animation Performance
|
|
628
|
+
svg_animations = re.findall(r'<animate|<animateTransform|stroke-dasharray|stroke-dashoffset', content)
|
|
629
|
+
if len(svg_animations) > 3:
|
|
630
|
+
self.warnings.append(f"[Motion] {filename}: Multiple SVG animations detected. Ensure stroke-dashoffset is used sparingly for mobile performance.")
|
|
631
|
+
|
|
632
|
+
# 6.4 3D Transform Performance
|
|
633
|
+
has_3d_transform = bool(re.search(r'transform3d|perspective\(|rotate3d|translate3d', content))
|
|
634
|
+
if has_3d_transform:
|
|
635
|
+
# Check for perspective on parent
|
|
636
|
+
has_perspective_parent = bool(re.search(r'perspective:\s*\d+px|perspective\s*\(', content))
|
|
637
|
+
if not has_perspective_parent:
|
|
638
|
+
self.warnings.append(f"[Motion] {filename}: 3D transform without perspective parent. Add perspective: 1000px for realistic depth.")
|
|
639
|
+
|
|
640
|
+
# Warn about mobile performance
|
|
641
|
+
self.warnings.append(f"[Motion] {filename}: 3D transforms detected. Test on mobile; can impact performance on low-end devices.")
|
|
642
|
+
|
|
643
|
+
# 6.5 Particle Effect Warnings
|
|
644
|
+
# Check for canvas/WebGL particle systems
|
|
645
|
+
has_particles = bool(re.search(r'particle|canvas.*loop|requestAnimationFrame.*draw|Three\.js', content))
|
|
646
|
+
if has_particles:
|
|
647
|
+
self.warnings.append(f"[Motion] {filename}: Particle effects detected. Ensure fallback or reduced-quality option for mobile devices.")
|
|
648
|
+
|
|
649
|
+
# 6.6 Scroll-Driven Animation Performance
|
|
650
|
+
has_scroll_driven = bool(re.search(r'IntersectionObserver.*animate|scroll.*progress|view-timeline', content))
|
|
651
|
+
if has_scroll_driven:
|
|
652
|
+
# Check for throttling/debouncing
|
|
653
|
+
has_throttle = bool(re.search(r'throttle|debounce|requestAnimationFrame', content))
|
|
654
|
+
if not has_throttle:
|
|
655
|
+
self.issues.append(f"[Motion] {filename}: Scroll-driven animation without throttling. Add requestAnimationFrame for 60fps.")
|
|
656
|
+
|
|
657
|
+
# 6.7 Motion Decision Tree - Context Check
|
|
658
|
+
# Check if animation serves purpose (not just decoration)
|
|
659
|
+
total_animations = (
|
|
660
|
+
len(re.findall(r'@keyframes|transition:|animate-', content)) +
|
|
661
|
+
(1 if has_lottie else 0) +
|
|
662
|
+
(1 if has_gsap else 0)
|
|
663
|
+
)
|
|
664
|
+
if total_animations > 5:
|
|
665
|
+
# Check if animations are functional
|
|
666
|
+
functional_animations = len(re.findall(r'hover:|focus:|disabled|loading|error|success', content))
|
|
667
|
+
if functional_animations < total_animations / 2:
|
|
668
|
+
self.warnings.append(f"[Motion] {filename}: Many animations ({total_animations}). Ensure majority serve functional purpose (feedback, guidance), not decoration.")
|
|
669
|
+
|
|
670
|
+
# --- 7. ACCESSIBILITY ---
|
|
671
|
+
if re.search(r'<img(?![^>]*alt=)[^>]*>', content):
|
|
672
|
+
self.issues.append(f"[Accessibility] {filename}: Missing img alt text")
|
|
673
|
+
|
|
674
|
+
def audit_directory(self, directory: str) -> None:
|
|
675
|
+
extensions = {'.tsx', '.jsx', '.html', '.vue', '.svelte', '.css'}
|
|
676
|
+
for root, dirs, files in os.walk(directory):
|
|
677
|
+
dirs[:] = [d for d in dirs if d not in {'node_modules', '.git', 'dist', 'build', '.next'}]
|
|
678
|
+
for file in files:
|
|
679
|
+
if Path(file).suffix in extensions:
|
|
680
|
+
self.audit_file(os.path.join(root, file))
|
|
681
|
+
|
|
682
|
+
def get_report(self):
|
|
683
|
+
return {
|
|
684
|
+
"files_checked": self.files_checked,
|
|
685
|
+
"issues": self.issues,
|
|
686
|
+
"warnings": self.warnings,
|
|
687
|
+
"passed_checks": self.passed_count,
|
|
688
|
+
"compliant": len(self.issues) == 0
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
def main():
|
|
692
|
+
if len(sys.argv) < 2: sys.exit(1)
|
|
693
|
+
|
|
694
|
+
path = sys.argv[1]
|
|
695
|
+
is_json = "--json" in sys.argv
|
|
696
|
+
|
|
697
|
+
auditor = UXAuditor()
|
|
698
|
+
if os.path.isfile(path): auditor.audit_file(path)
|
|
699
|
+
else: auditor.audit_directory(path)
|
|
700
|
+
|
|
701
|
+
report = auditor.get_report()
|
|
702
|
+
|
|
703
|
+
if is_json:
|
|
704
|
+
print(json.dumps(report))
|
|
705
|
+
else:
|
|
706
|
+
# Use ASCII-safe output for Windows console compatibility
|
|
707
|
+
print(f"\n[UX AUDIT] {report['files_checked']} files checked")
|
|
708
|
+
print("-" * 50)
|
|
709
|
+
if report['issues']:
|
|
710
|
+
print(f"[!] ISSUES ({len(report['issues'])}):")
|
|
711
|
+
for i in report['issues'][:10]: print(f" - {i}")
|
|
712
|
+
if report['warnings']:
|
|
713
|
+
print(f"[*] WARNINGS ({len(report['warnings'])}):")
|
|
714
|
+
for w in report['warnings'][:15]: print(f" - {w}")
|
|
715
|
+
print(f"[+] PASSED CHECKS: {report['passed_checks']}")
|
|
716
|
+
status = "PASS" if report['compliant'] else "FAIL"
|
|
717
|
+
print(f"STATUS: {status}")
|
|
718
|
+
|
|
719
|
+
sys.exit(0 if report['compliant'] else 1)
|
|
720
|
+
|
|
721
|
+
if __name__ == "__main__":
|
|
722
|
+
main()
|