diff --git a/cli/assets/scripts/core.py b/cli/assets/scripts/core.py index a173cce..ad200d1 100644 --- a/cli/assets/scripts/core.py +++ b/cli/assets/scripts/core.py @@ -18,7 +18,7 @@ CSV_CONFIG = { "style": { "file": "styles.csv", "search_cols": ["Style Category", "Keywords", "Best For", "Type", "AI Prompt Keywords"], - "output_cols": ["Style Category", "Type", "Keywords", "Primary Colors", "Effects & Animation", "Best For", "Performance", "Accessibility", "Framework Compatibility", "Complexity", "AI Prompt Keywords", "CSS/Technical Keywords", "Implementation Checklist", "Design System Variables"] + "output_cols": ["Style Category", "Type", "Keywords", "Primary Colors", "Effects & Animation", "Best For", "Light Mode ✓", "Dark Mode ✓", "Performance", "Accessibility", "Framework Compatibility", "Complexity", "AI Prompt Keywords", "CSS/Technical Keywords", "Implementation Checklist", "Design System Variables"] }, "color": { "file": "colors.csv", @@ -64,6 +64,11 @@ CSV_CONFIG = { "file": "app-interface.csv", "search_cols": ["Category", "Issue", "Keywords", "Description"], "output_cols": ["Category", "Issue", "Platform", "Description", "Do", "Don't", "Code Example Good", "Code Example Bad", "Severity"] + }, + "google-fonts": { + "file": "google-fonts.csv", + "search_cols": ["Family", "Category", "Stroke", "Classifications", "Keywords", "Subsets", "Designers"], + "output_cols": ["Family", "Category", "Stroke", "Classifications", "Styles", "Variable Axes", "Subsets", "Designers", "Popularity Rank", "Google Fonts URL"] } } @@ -186,13 +191,14 @@ def detect_domain(query): "product": ["saas", "ecommerce", "e-commerce", "fintech", "healthcare", "gaming", "portfolio", "crypto", "dashboard", "fitness", "restaurant", "hotel", "travel", "music", "education", "learning", "legal", "insurance", "medical", "beauty", "pharmacy", "dental", "pet", "dating", "wedding", "recipe", "delivery", "ride", "booking", "calendar", "timer", "tracker", "diary", "note", "chat", "messenger", "crm", "invoice", "parking", "transit", "vpn", "alarm", "weather", "sleep", "meditation", "fasting", "habit", "grocery", "meme", "wardrobe", "plant care", "reading", "flashcard", "puzzle", "trivia", "arcade", "photography", "streaming", "podcast", "newsletter", "marketplace", "freelancer", "coworking", "airline", "museum", "theater", "church", "non-profit", "charity", "kindergarten", "daycare", "senior care", "veterinary", "florist", "bakery", "brewery", "construction", "automotive", "real estate", "logistics", "agriculture", "coding bootcamp"], "style": ["style", "design", "ui", "minimalism", "glassmorphism", "neumorphism", "brutalism", "dark mode", "flat", "aurora", "prompt", "css", "implementation", "variable", "checklist", "tailwind"], "ux": ["ux", "usability", "accessibility", "wcag", "touch", "scroll", "animation", "keyboard", "navigation", "mobile"], - "typography": ["font", "typography", "heading", "serif", "sans"], + "typography": ["font pairing", "typography pairing", "heading font", "body font"], + "google-fonts": ["google font", "font family", "font weight", "font style", "variable font", "noto", "font for", "find font", "font subset", "font language", "monospace font", "serif font", "sans serif font", "display font", "handwriting font", "font", "typography", "serif", "sans"], "icons": ["icon", "icons", "lucide", "heroicons", "symbol", "glyph", "pictogram", "svg icon"], "react": ["react", "next.js", "nextjs", "suspense", "memo", "usecallback", "useeffect", "rerender", "bundle", "waterfall", "barrel", "dynamic import", "rsc", "server component"], "web": ["aria", "focus", "outline", "semantic", "virtualize", "autocomplete", "form", "input type", "preconnect"] } - scores = {domain: sum(1 for kw in keywords if kw in query_lower) for domain, keywords in domain_keywords.items()} + scores = {domain: sum(1 for kw in keywords if re.search(r'\b' + re.escape(kw) + r'\b', query_lower)) for domain, keywords in domain_keywords.items()} best = max(scores, key=scores.get) return best if scores[best] > 0 else "style" diff --git a/cli/assets/scripts/design_system.py b/cli/assets/scripts/design_system.py index 209de20..d3152e5 100644 --- a/cli/assets/scripts/design_system.py +++ b/cli/assets/scripts/design_system.py @@ -211,15 +211,25 @@ class DesignSystemGenerator: "keywords": best_style.get("Keywords", ""), "best_for": best_style.get("Best For", ""), "performance": best_style.get("Performance", ""), - "accessibility": best_style.get("Accessibility", "") + "accessibility": best_style.get("Accessibility", ""), + "light_mode": best_style.get("Light Mode ✓", ""), + "dark_mode": best_style.get("Dark Mode ✓", ""), }, "colors": { - "primary": best_color.get("Primary (Hex)", "#2563EB"), - "secondary": best_color.get("Secondary (Hex)", "#3B82F6"), - "cta": best_color.get("CTA (Hex)", "#F97316"), - "background": best_color.get("Background (Hex)", "#F8FAFC"), - "text": best_color.get("Text (Hex)", "#1E293B"), - "notes": best_color.get("Notes", "") + "primary": best_color.get("Primary", "#2563EB"), + "on_primary": best_color.get("On Primary", ""), + "secondary": best_color.get("Secondary", "#3B82F6"), + "accent": best_color.get("Accent", "#F97316"), + "background": best_color.get("Background", "#F8FAFC"), + "foreground": best_color.get("Foreground", "#1E293B"), + "muted": best_color.get("Muted", ""), + "border": best_color.get("Border", ""), + "destructive": best_color.get("Destructive", ""), + "ring": best_color.get("Ring", ""), + "notes": best_color.get("Notes", ""), + # Keep legacy keys for backward compat in MASTER.md + "cta": best_color.get("Accent", "#F97316"), + "text": best_color.get("Foreground", "#1E293B"), }, "typography": { "heading": best_typography.get("Heading Font", "Inter"), @@ -239,8 +249,38 @@ class DesignSystemGenerator: # ============ OUTPUT FORMATTERS ============ BOX_WIDTH = 90 # Wider box for more content + +def hex_to_ansi(hex_color: str) -> str: + """Convert hex color to ANSI True Color swatch (██) with fallback.""" + if not hex_color or not hex_color.startswith('#'): + return "" + colorterm = os.environ.get('COLORTERM', '') + if colorterm not in ('truecolor', '24bit'): + return "" + hex_color = hex_color.lstrip('#') + if len(hex_color) != 6: + return "" + r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16) + return f"\033[38;2;{r};{g};{b}m██\033[0m " + + +def ansi_ljust(s: str, width: int) -> str: + """Like str.ljust but accounts for zero-width ANSI escape sequences.""" + import re + visible_len = len(re.sub(r'\033\[[0-9;]*m', '', s)) + pad = width - visible_len + return s + (" " * max(0, pad)) + + +def section_header(name: str, width: int) -> str: + """Create a Unicode section separator: ├─── NAME ───...┤""" + label = f"─── {name} " + fill = "─" * (width - len(label) - 1) + return f"├{label}{fill}┤" + + def format_ascii_box(design_system: dict) -> str: - """Format design system as ASCII box with emojis (MCP-style).""" + """Format design system as Unicode box with ANSI color swatches.""" project = design_system.get("project_name", "PROJECT") pattern = design_system.get("pattern", {}) style = design_system.get("style", {}) @@ -275,77 +315,93 @@ def format_ascii_box(design_system: dict) -> str: lines = [] w = BOX_WIDTH - 1 - lines.append("+" + "-" * w + "+") - lines.append(f"| TARGET: {project} - RECOMMENDED DESIGN SYSTEM".ljust(BOX_WIDTH) + "|") - lines.append("+" + "-" * w + "+") - lines.append("|" + " " * BOX_WIDTH + "|") + # Header with double-line box + lines.append("╔" + "═" * w + "╗") + lines.append(ansi_ljust(f"║ TARGET: {project} - RECOMMENDED DESIGN SYSTEM", BOX_WIDTH) + "║") + lines.append("╚" + "═" * w + "╝") + lines.append("┌" + "─" * w + "┐") # Pattern section - lines.append(f"| PATTERN: {pattern.get('name', '')}".ljust(BOX_WIDTH) + "|") + lines.append(section_header("PATTERN", BOX_WIDTH + 1)) + lines.append(f"│ Name: {pattern.get('name', '')}".ljust(BOX_WIDTH) + "│") if pattern.get('conversion'): - lines.append(f"| Conversion: {pattern.get('conversion', '')}".ljust(BOX_WIDTH) + "|") + lines.append(f"│ Conversion: {pattern.get('conversion', '')}".ljust(BOX_WIDTH) + "│") if pattern.get('cta_placement'): - lines.append(f"| CTA: {pattern.get('cta_placement', '')}".ljust(BOX_WIDTH) + "|") - lines.append("| Sections:".ljust(BOX_WIDTH) + "|") + lines.append(f"│ CTA: {pattern.get('cta_placement', '')}".ljust(BOX_WIDTH) + "│") + lines.append("│ Sections:".ljust(BOX_WIDTH) + "│") for i, section in enumerate(sections, 1): - lines.append(f"| {i}. {section}".ljust(BOX_WIDTH) + "|") - lines.append("|" + " " * BOX_WIDTH + "|") + lines.append(f"│ {i}. {section}".ljust(BOX_WIDTH) + "│") # Style section - lines.append(f"| STYLE: {style.get('name', '')}".ljust(BOX_WIDTH) + "|") + lines.append(section_header("STYLE", BOX_WIDTH + 1)) + lines.append(f"│ Name: {style.get('name', '')}".ljust(BOX_WIDTH) + "│") + light = style.get("light_mode", "") + dark = style.get("dark_mode", "") + if light or dark: + lines.append(f"│ Mode Support: Light {light} Dark {dark}".ljust(BOX_WIDTH) + "│") if style.get("keywords"): - for line in wrap_text(f"Keywords: {style.get('keywords', '')}", "| ", BOX_WIDTH): - lines.append(line.ljust(BOX_WIDTH) + "|") + for line in wrap_text(f"Keywords: {style.get('keywords', '')}", "│ ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "│") if style.get("best_for"): - for line in wrap_text(f"Best For: {style.get('best_for', '')}", "| ", BOX_WIDTH): - lines.append(line.ljust(BOX_WIDTH) + "|") + for line in wrap_text(f"Best For: {style.get('best_for', '')}", "│ ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "│") if style.get("performance") or style.get("accessibility"): perf_a11y = f"Performance: {style.get('performance', '')} | Accessibility: {style.get('accessibility', '')}" - lines.append(f"| {perf_a11y}".ljust(BOX_WIDTH) + "|") - lines.append("|" + " " * BOX_WIDTH + "|") + lines.append(f"│ {perf_a11y}".ljust(BOX_WIDTH) + "│") - # Colors section - lines.append("| COLORS:".ljust(BOX_WIDTH) + "|") - lines.append(f"| Primary: {colors.get('primary', '')}".ljust(BOX_WIDTH) + "|") - lines.append(f"| Secondary: {colors.get('secondary', '')}".ljust(BOX_WIDTH) + "|") - lines.append(f"| CTA: {colors.get('cta', '')}".ljust(BOX_WIDTH) + "|") - lines.append(f"| Background: {colors.get('background', '')}".ljust(BOX_WIDTH) + "|") - lines.append(f"| Text: {colors.get('text', '')}".ljust(BOX_WIDTH) + "|") + # Colors section (extended palette with ANSI swatches) + lines.append(section_header("COLORS", BOX_WIDTH + 1)) + color_entries = [ + ("Primary", "primary", "--color-primary"), + ("On Primary", "on_primary", "--color-on-primary"), + ("Secondary", "secondary", "--color-secondary"), + ("Accent/CTA", "accent", "--color-accent"), + ("Background", "background", "--color-background"), + ("Foreground", "foreground", "--color-foreground"), + ("Muted", "muted", "--color-muted"), + ("Border", "border", "--color-border"), + ("Destructive", "destructive", "--color-destructive"), + ("Ring", "ring", "--color-ring"), + ] + for label, key, css_var in color_entries: + hex_val = colors.get(key, "") + if not hex_val: + continue + swatch = hex_to_ansi(hex_val) + content = f"│ {swatch}{label + ':':14s} {hex_val:10s} ({css_var})" + lines.append(ansi_ljust(content, BOX_WIDTH) + "│") if colors.get("notes"): - for line in wrap_text(f"Notes: {colors.get('notes', '')}", "| ", BOX_WIDTH): - lines.append(line.ljust(BOX_WIDTH) + "|") - lines.append("|" + " " * BOX_WIDTH + "|") + for line in wrap_text(f"Notes: {colors.get('notes', '')}", "│ ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "│") # Typography section - lines.append(f"| TYPOGRAPHY: {typography.get('heading', '')} / {typography.get('body', '')}".ljust(BOX_WIDTH) + "|") + lines.append(section_header("TYPOGRAPHY", BOX_WIDTH + 1)) + lines.append(f"│ {typography.get('heading', '')} / {typography.get('body', '')}".ljust(BOX_WIDTH) + "│") if typography.get("mood"): - for line in wrap_text(f"Mood: {typography.get('mood', '')}", "| ", BOX_WIDTH): - lines.append(line.ljust(BOX_WIDTH) + "|") + for line in wrap_text(f"Mood: {typography.get('mood', '')}", "│ ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "│") if typography.get("best_for"): - for line in wrap_text(f"Best For: {typography.get('best_for', '')}", "| ", BOX_WIDTH): - lines.append(line.ljust(BOX_WIDTH) + "|") + for line in wrap_text(f"Best For: {typography.get('best_for', '')}", "│ ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "│") if typography.get("google_fonts_url"): - lines.append(f"| Google Fonts: {typography.get('google_fonts_url', '')}".ljust(BOX_WIDTH) + "|") + lines.append(f"│ Google Fonts: {typography.get('google_fonts_url', '')}".ljust(BOX_WIDTH) + "│") if typography.get("css_import"): - lines.append(f"| CSS Import: {typography.get('css_import', '')[:70]}...".ljust(BOX_WIDTH) + "|") - lines.append("|" + " " * BOX_WIDTH + "|") + lines.append(f"│ CSS Import: {typography.get('css_import', '')[:70]}...".ljust(BOX_WIDTH) + "│") # Key Effects section if effects: - lines.append("| KEY EFFECTS:".ljust(BOX_WIDTH) + "|") - for line in wrap_text(effects, "| ", BOX_WIDTH): - lines.append(line.ljust(BOX_WIDTH) + "|") - lines.append("|" + " " * BOX_WIDTH + "|") + lines.append(section_header("KEY EFFECTS", BOX_WIDTH + 1)) + for line in wrap_text(effects, "│ ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "│") # Anti-patterns section if anti_patterns: - lines.append("| AVOID (Anti-patterns):".ljust(BOX_WIDTH) + "|") - for line in wrap_text(anti_patterns, "| ", BOX_WIDTH): - lines.append(line.ljust(BOX_WIDTH) + "|") - lines.append("|" + " " * BOX_WIDTH + "|") + lines.append(section_header("AVOID", BOX_WIDTH + 1)) + for line in wrap_text(anti_patterns, "│ ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "│") # Pre-Delivery Checklist section - lines.append("| PRE-DELIVERY CHECKLIST:".ljust(BOX_WIDTH) + "|") + lines.append(section_header("PRE-DELIVERY CHECKLIST", BOX_WIDTH + 1)) checklist_items = [ "[ ] No emojis as icons (use SVG: Heroicons/Lucide)", "[ ] cursor-pointer on all clickable elements", @@ -356,10 +412,9 @@ def format_ascii_box(design_system: dict) -> str: "[ ] Responsive: 375px, 768px, 1024px, 1440px" ] for item in checklist_items: - lines.append(f"| {item}".ljust(BOX_WIDTH) + "|") - lines.append("|" + " " * BOX_WIDTH + "|") + lines.append(f"│ {item}".ljust(BOX_WIDTH) + "│") - lines.append("+" + "-" * w + "+") + lines.append("└" + "─" * w + "┘") return "\n".join(lines) @@ -393,6 +448,10 @@ def format_markdown(design_system: dict) -> str: # Style section lines.append("### Style") lines.append(f"- **Name:** {style.get('name', '')}") + light = style.get("light_mode", "") + dark = style.get("dark_mode", "") + if light or dark: + lines.append(f"- **Mode Support:** Light {light} | Dark {dark}") if style.get('keywords'): lines.append(f"- **Keywords:** {style.get('keywords', '')}") if style.get('best_for'): @@ -401,15 +460,26 @@ def format_markdown(design_system: dict) -> str: lines.append(f"- **Performance:** {style.get('performance', '')} | **Accessibility:** {style.get('accessibility', '')}") lines.append("") - # Colors section + # Colors section (extended palette) lines.append("### Colors") - lines.append(f"| Role | Hex |") - lines.append(f"|------|-----|") - lines.append(f"| Primary | {colors.get('primary', '')} |") - lines.append(f"| Secondary | {colors.get('secondary', '')} |") - lines.append(f"| CTA | {colors.get('cta', '')} |") - lines.append(f"| Background | {colors.get('background', '')} |") - lines.append(f"| Text | {colors.get('text', '')} |") + lines.append("| Role | Hex | CSS Variable |") + lines.append("|------|-----|--------------|") + md_color_entries = [ + ("Primary", "primary", "--color-primary"), + ("On Primary", "on_primary", "--color-on-primary"), + ("Secondary", "secondary", "--color-secondary"), + ("Accent/CTA", "accent", "--color-accent"), + ("Background", "background", "--color-background"), + ("Foreground", "foreground", "--color-foreground"), + ("Muted", "muted", "--color-muted"), + ("Border", "border", "--color-border"), + ("Destructive", "destructive", "--color-destructive"), + ("Ring", "ring", "--color-ring"), + ] + for label, key, css_var in md_color_entries: + hex_val = colors.get(key, "") + if hex_val: + lines.append(f"| {label} | `{hex_val}` | `{css_var}` |") if colors.get("notes"): lines.append(f"\n*Notes: {colors.get('notes', '')}*") lines.append("") @@ -578,11 +648,22 @@ def format_master_md(design_system: dict) -> str: lines.append("") lines.append("| Role | Hex | CSS Variable |") lines.append("|------|-----|--------------|") - lines.append(f"| Primary | `{colors.get('primary', '#2563EB')}` | `--color-primary` |") - lines.append(f"| Secondary | `{colors.get('secondary', '#3B82F6')}` | `--color-secondary` |") - lines.append(f"| CTA/Accent | `{colors.get('cta', '#F97316')}` | `--color-cta` |") - lines.append(f"| Background | `{colors.get('background', '#F8FAFC')}` | `--color-background` |") - lines.append(f"| Text | `{colors.get('text', '#1E293B')}` | `--color-text` |") + master_color_entries = [ + ("Primary", "primary", "--color-primary"), + ("On Primary", "on_primary", "--color-on-primary"), + ("Secondary", "secondary", "--color-secondary"), + ("Accent/CTA", "accent", "--color-accent"), + ("Background", "background", "--color-background"), + ("Foreground", "foreground", "--color-foreground"), + ("Muted", "muted", "--color-muted"), + ("Border", "border", "--color-border"), + ("Destructive", "destructive", "--color-destructive"), + ("Ring", "ring", "--color-ring"), + ] + for label, key, css_var in master_color_entries: + hex_val = colors.get(key, "") + if hex_val: + lines.append(f"| {label} | `{hex_val}` | `{css_var}` |") lines.append("") if colors.get("notes"): lines.append(f"**Color Notes:** {colors.get('notes', '')}") diff --git a/src/ui-ux-pro-max/scripts/core.py b/src/ui-ux-pro-max/scripts/core.py index f3286f8..ad200d1 100644 --- a/src/ui-ux-pro-max/scripts/core.py +++ b/src/ui-ux-pro-max/scripts/core.py @@ -18,7 +18,7 @@ CSV_CONFIG = { "style": { "file": "styles.csv", "search_cols": ["Style Category", "Keywords", "Best For", "Type", "AI Prompt Keywords"], - "output_cols": ["Style Category", "Type", "Keywords", "Primary Colors", "Effects & Animation", "Best For", "Performance", "Accessibility", "Framework Compatibility", "Complexity", "AI Prompt Keywords", "CSS/Technical Keywords", "Implementation Checklist", "Design System Variables"] + "output_cols": ["Style Category", "Type", "Keywords", "Primary Colors", "Effects & Animation", "Best For", "Light Mode ✓", "Dark Mode ✓", "Performance", "Accessibility", "Framework Compatibility", "Complexity", "AI Prompt Keywords", "CSS/Technical Keywords", "Implementation Checklist", "Design System Variables"] }, "color": { "file": "colors.csv", diff --git a/src/ui-ux-pro-max/scripts/design_system.py b/src/ui-ux-pro-max/scripts/design_system.py index 209de20..d3152e5 100644 --- a/src/ui-ux-pro-max/scripts/design_system.py +++ b/src/ui-ux-pro-max/scripts/design_system.py @@ -211,15 +211,25 @@ class DesignSystemGenerator: "keywords": best_style.get("Keywords", ""), "best_for": best_style.get("Best For", ""), "performance": best_style.get("Performance", ""), - "accessibility": best_style.get("Accessibility", "") + "accessibility": best_style.get("Accessibility", ""), + "light_mode": best_style.get("Light Mode ✓", ""), + "dark_mode": best_style.get("Dark Mode ✓", ""), }, "colors": { - "primary": best_color.get("Primary (Hex)", "#2563EB"), - "secondary": best_color.get("Secondary (Hex)", "#3B82F6"), - "cta": best_color.get("CTA (Hex)", "#F97316"), - "background": best_color.get("Background (Hex)", "#F8FAFC"), - "text": best_color.get("Text (Hex)", "#1E293B"), - "notes": best_color.get("Notes", "") + "primary": best_color.get("Primary", "#2563EB"), + "on_primary": best_color.get("On Primary", ""), + "secondary": best_color.get("Secondary", "#3B82F6"), + "accent": best_color.get("Accent", "#F97316"), + "background": best_color.get("Background", "#F8FAFC"), + "foreground": best_color.get("Foreground", "#1E293B"), + "muted": best_color.get("Muted", ""), + "border": best_color.get("Border", ""), + "destructive": best_color.get("Destructive", ""), + "ring": best_color.get("Ring", ""), + "notes": best_color.get("Notes", ""), + # Keep legacy keys for backward compat in MASTER.md + "cta": best_color.get("Accent", "#F97316"), + "text": best_color.get("Foreground", "#1E293B"), }, "typography": { "heading": best_typography.get("Heading Font", "Inter"), @@ -239,8 +249,38 @@ class DesignSystemGenerator: # ============ OUTPUT FORMATTERS ============ BOX_WIDTH = 90 # Wider box for more content + +def hex_to_ansi(hex_color: str) -> str: + """Convert hex color to ANSI True Color swatch (██) with fallback.""" + if not hex_color or not hex_color.startswith('#'): + return "" + colorterm = os.environ.get('COLORTERM', '') + if colorterm not in ('truecolor', '24bit'): + return "" + hex_color = hex_color.lstrip('#') + if len(hex_color) != 6: + return "" + r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16) + return f"\033[38;2;{r};{g};{b}m██\033[0m " + + +def ansi_ljust(s: str, width: int) -> str: + """Like str.ljust but accounts for zero-width ANSI escape sequences.""" + import re + visible_len = len(re.sub(r'\033\[[0-9;]*m', '', s)) + pad = width - visible_len + return s + (" " * max(0, pad)) + + +def section_header(name: str, width: int) -> str: + """Create a Unicode section separator: ├─── NAME ───...┤""" + label = f"─── {name} " + fill = "─" * (width - len(label) - 1) + return f"├{label}{fill}┤" + + def format_ascii_box(design_system: dict) -> str: - """Format design system as ASCII box with emojis (MCP-style).""" + """Format design system as Unicode box with ANSI color swatches.""" project = design_system.get("project_name", "PROJECT") pattern = design_system.get("pattern", {}) style = design_system.get("style", {}) @@ -275,77 +315,93 @@ def format_ascii_box(design_system: dict) -> str: lines = [] w = BOX_WIDTH - 1 - lines.append("+" + "-" * w + "+") - lines.append(f"| TARGET: {project} - RECOMMENDED DESIGN SYSTEM".ljust(BOX_WIDTH) + "|") - lines.append("+" + "-" * w + "+") - lines.append("|" + " " * BOX_WIDTH + "|") + # Header with double-line box + lines.append("╔" + "═" * w + "╗") + lines.append(ansi_ljust(f"║ TARGET: {project} - RECOMMENDED DESIGN SYSTEM", BOX_WIDTH) + "║") + lines.append("╚" + "═" * w + "╝") + lines.append("┌" + "─" * w + "┐") # Pattern section - lines.append(f"| PATTERN: {pattern.get('name', '')}".ljust(BOX_WIDTH) + "|") + lines.append(section_header("PATTERN", BOX_WIDTH + 1)) + lines.append(f"│ Name: {pattern.get('name', '')}".ljust(BOX_WIDTH) + "│") if pattern.get('conversion'): - lines.append(f"| Conversion: {pattern.get('conversion', '')}".ljust(BOX_WIDTH) + "|") + lines.append(f"│ Conversion: {pattern.get('conversion', '')}".ljust(BOX_WIDTH) + "│") if pattern.get('cta_placement'): - lines.append(f"| CTA: {pattern.get('cta_placement', '')}".ljust(BOX_WIDTH) + "|") - lines.append("| Sections:".ljust(BOX_WIDTH) + "|") + lines.append(f"│ CTA: {pattern.get('cta_placement', '')}".ljust(BOX_WIDTH) + "│") + lines.append("│ Sections:".ljust(BOX_WIDTH) + "│") for i, section in enumerate(sections, 1): - lines.append(f"| {i}. {section}".ljust(BOX_WIDTH) + "|") - lines.append("|" + " " * BOX_WIDTH + "|") + lines.append(f"│ {i}. {section}".ljust(BOX_WIDTH) + "│") # Style section - lines.append(f"| STYLE: {style.get('name', '')}".ljust(BOX_WIDTH) + "|") + lines.append(section_header("STYLE", BOX_WIDTH + 1)) + lines.append(f"│ Name: {style.get('name', '')}".ljust(BOX_WIDTH) + "│") + light = style.get("light_mode", "") + dark = style.get("dark_mode", "") + if light or dark: + lines.append(f"│ Mode Support: Light {light} Dark {dark}".ljust(BOX_WIDTH) + "│") if style.get("keywords"): - for line in wrap_text(f"Keywords: {style.get('keywords', '')}", "| ", BOX_WIDTH): - lines.append(line.ljust(BOX_WIDTH) + "|") + for line in wrap_text(f"Keywords: {style.get('keywords', '')}", "│ ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "│") if style.get("best_for"): - for line in wrap_text(f"Best For: {style.get('best_for', '')}", "| ", BOX_WIDTH): - lines.append(line.ljust(BOX_WIDTH) + "|") + for line in wrap_text(f"Best For: {style.get('best_for', '')}", "│ ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "│") if style.get("performance") or style.get("accessibility"): perf_a11y = f"Performance: {style.get('performance', '')} | Accessibility: {style.get('accessibility', '')}" - lines.append(f"| {perf_a11y}".ljust(BOX_WIDTH) + "|") - lines.append("|" + " " * BOX_WIDTH + "|") + lines.append(f"│ {perf_a11y}".ljust(BOX_WIDTH) + "│") - # Colors section - lines.append("| COLORS:".ljust(BOX_WIDTH) + "|") - lines.append(f"| Primary: {colors.get('primary', '')}".ljust(BOX_WIDTH) + "|") - lines.append(f"| Secondary: {colors.get('secondary', '')}".ljust(BOX_WIDTH) + "|") - lines.append(f"| CTA: {colors.get('cta', '')}".ljust(BOX_WIDTH) + "|") - lines.append(f"| Background: {colors.get('background', '')}".ljust(BOX_WIDTH) + "|") - lines.append(f"| Text: {colors.get('text', '')}".ljust(BOX_WIDTH) + "|") + # Colors section (extended palette with ANSI swatches) + lines.append(section_header("COLORS", BOX_WIDTH + 1)) + color_entries = [ + ("Primary", "primary", "--color-primary"), + ("On Primary", "on_primary", "--color-on-primary"), + ("Secondary", "secondary", "--color-secondary"), + ("Accent/CTA", "accent", "--color-accent"), + ("Background", "background", "--color-background"), + ("Foreground", "foreground", "--color-foreground"), + ("Muted", "muted", "--color-muted"), + ("Border", "border", "--color-border"), + ("Destructive", "destructive", "--color-destructive"), + ("Ring", "ring", "--color-ring"), + ] + for label, key, css_var in color_entries: + hex_val = colors.get(key, "") + if not hex_val: + continue + swatch = hex_to_ansi(hex_val) + content = f"│ {swatch}{label + ':':14s} {hex_val:10s} ({css_var})" + lines.append(ansi_ljust(content, BOX_WIDTH) + "│") if colors.get("notes"): - for line in wrap_text(f"Notes: {colors.get('notes', '')}", "| ", BOX_WIDTH): - lines.append(line.ljust(BOX_WIDTH) + "|") - lines.append("|" + " " * BOX_WIDTH + "|") + for line in wrap_text(f"Notes: {colors.get('notes', '')}", "│ ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "│") # Typography section - lines.append(f"| TYPOGRAPHY: {typography.get('heading', '')} / {typography.get('body', '')}".ljust(BOX_WIDTH) + "|") + lines.append(section_header("TYPOGRAPHY", BOX_WIDTH + 1)) + lines.append(f"│ {typography.get('heading', '')} / {typography.get('body', '')}".ljust(BOX_WIDTH) + "│") if typography.get("mood"): - for line in wrap_text(f"Mood: {typography.get('mood', '')}", "| ", BOX_WIDTH): - lines.append(line.ljust(BOX_WIDTH) + "|") + for line in wrap_text(f"Mood: {typography.get('mood', '')}", "│ ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "│") if typography.get("best_for"): - for line in wrap_text(f"Best For: {typography.get('best_for', '')}", "| ", BOX_WIDTH): - lines.append(line.ljust(BOX_WIDTH) + "|") + for line in wrap_text(f"Best For: {typography.get('best_for', '')}", "│ ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "│") if typography.get("google_fonts_url"): - lines.append(f"| Google Fonts: {typography.get('google_fonts_url', '')}".ljust(BOX_WIDTH) + "|") + lines.append(f"│ Google Fonts: {typography.get('google_fonts_url', '')}".ljust(BOX_WIDTH) + "│") if typography.get("css_import"): - lines.append(f"| CSS Import: {typography.get('css_import', '')[:70]}...".ljust(BOX_WIDTH) + "|") - lines.append("|" + " " * BOX_WIDTH + "|") + lines.append(f"│ CSS Import: {typography.get('css_import', '')[:70]}...".ljust(BOX_WIDTH) + "│") # Key Effects section if effects: - lines.append("| KEY EFFECTS:".ljust(BOX_WIDTH) + "|") - for line in wrap_text(effects, "| ", BOX_WIDTH): - lines.append(line.ljust(BOX_WIDTH) + "|") - lines.append("|" + " " * BOX_WIDTH + "|") + lines.append(section_header("KEY EFFECTS", BOX_WIDTH + 1)) + for line in wrap_text(effects, "│ ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "│") # Anti-patterns section if anti_patterns: - lines.append("| AVOID (Anti-patterns):".ljust(BOX_WIDTH) + "|") - for line in wrap_text(anti_patterns, "| ", BOX_WIDTH): - lines.append(line.ljust(BOX_WIDTH) + "|") - lines.append("|" + " " * BOX_WIDTH + "|") + lines.append(section_header("AVOID", BOX_WIDTH + 1)) + for line in wrap_text(anti_patterns, "│ ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "│") # Pre-Delivery Checklist section - lines.append("| PRE-DELIVERY CHECKLIST:".ljust(BOX_WIDTH) + "|") + lines.append(section_header("PRE-DELIVERY CHECKLIST", BOX_WIDTH + 1)) checklist_items = [ "[ ] No emojis as icons (use SVG: Heroicons/Lucide)", "[ ] cursor-pointer on all clickable elements", @@ -356,10 +412,9 @@ def format_ascii_box(design_system: dict) -> str: "[ ] Responsive: 375px, 768px, 1024px, 1440px" ] for item in checklist_items: - lines.append(f"| {item}".ljust(BOX_WIDTH) + "|") - lines.append("|" + " " * BOX_WIDTH + "|") + lines.append(f"│ {item}".ljust(BOX_WIDTH) + "│") - lines.append("+" + "-" * w + "+") + lines.append("└" + "─" * w + "┘") return "\n".join(lines) @@ -393,6 +448,10 @@ def format_markdown(design_system: dict) -> str: # Style section lines.append("### Style") lines.append(f"- **Name:** {style.get('name', '')}") + light = style.get("light_mode", "") + dark = style.get("dark_mode", "") + if light or dark: + lines.append(f"- **Mode Support:** Light {light} | Dark {dark}") if style.get('keywords'): lines.append(f"- **Keywords:** {style.get('keywords', '')}") if style.get('best_for'): @@ -401,15 +460,26 @@ def format_markdown(design_system: dict) -> str: lines.append(f"- **Performance:** {style.get('performance', '')} | **Accessibility:** {style.get('accessibility', '')}") lines.append("") - # Colors section + # Colors section (extended palette) lines.append("### Colors") - lines.append(f"| Role | Hex |") - lines.append(f"|------|-----|") - lines.append(f"| Primary | {colors.get('primary', '')} |") - lines.append(f"| Secondary | {colors.get('secondary', '')} |") - lines.append(f"| CTA | {colors.get('cta', '')} |") - lines.append(f"| Background | {colors.get('background', '')} |") - lines.append(f"| Text | {colors.get('text', '')} |") + lines.append("| Role | Hex | CSS Variable |") + lines.append("|------|-----|--------------|") + md_color_entries = [ + ("Primary", "primary", "--color-primary"), + ("On Primary", "on_primary", "--color-on-primary"), + ("Secondary", "secondary", "--color-secondary"), + ("Accent/CTA", "accent", "--color-accent"), + ("Background", "background", "--color-background"), + ("Foreground", "foreground", "--color-foreground"), + ("Muted", "muted", "--color-muted"), + ("Border", "border", "--color-border"), + ("Destructive", "destructive", "--color-destructive"), + ("Ring", "ring", "--color-ring"), + ] + for label, key, css_var in md_color_entries: + hex_val = colors.get(key, "") + if hex_val: + lines.append(f"| {label} | `{hex_val}` | `{css_var}` |") if colors.get("notes"): lines.append(f"\n*Notes: {colors.get('notes', '')}*") lines.append("") @@ -578,11 +648,22 @@ def format_master_md(design_system: dict) -> str: lines.append("") lines.append("| Role | Hex | CSS Variable |") lines.append("|------|-----|--------------|") - lines.append(f"| Primary | `{colors.get('primary', '#2563EB')}` | `--color-primary` |") - lines.append(f"| Secondary | `{colors.get('secondary', '#3B82F6')}` | `--color-secondary` |") - lines.append(f"| CTA/Accent | `{colors.get('cta', '#F97316')}` | `--color-cta` |") - lines.append(f"| Background | `{colors.get('background', '#F8FAFC')}` | `--color-background` |") - lines.append(f"| Text | `{colors.get('text', '#1E293B')}` | `--color-text` |") + master_color_entries = [ + ("Primary", "primary", "--color-primary"), + ("On Primary", "on_primary", "--color-on-primary"), + ("Secondary", "secondary", "--color-secondary"), + ("Accent/CTA", "accent", "--color-accent"), + ("Background", "background", "--color-background"), + ("Foreground", "foreground", "--color-foreground"), + ("Muted", "muted", "--color-muted"), + ("Border", "border", "--color-border"), + ("Destructive", "destructive", "--color-destructive"), + ("Ring", "ring", "--color-ring"), + ] + for label, key, css_var in master_color_entries: + hex_val = colors.get(key, "") + if hex_val: + lines.append(f"| {label} | `{hex_val}` | `{css_var}` |") lines.append("") if colors.get("notes"): lines.append(f"**Color Notes:** {colors.get('notes', '')}")