diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 2cc8a4e1d..35c561df4 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -5,15 +5,15 @@ "name": "nextlevelbuilder" }, "metadata": { - "description": "UI/UX design intelligence skill with 67 styles, 96 palettes, 57 font pairings, 25 charts, and 13 stack guidelines", - "version": "2.2.1" + "description": "UI/UX design intelligence skill with 84 styles, 161 palettes, 73 font pairings, 25 charts, and 17 stack guidelines", + "version": "2.6.2" }, "plugins": [ { "name": "ui-ux-pro-max", "source": "./", - "description": "Professional UI/UX design intelligence for AI coding assistants. Includes searchable databases of styles, colors, typography, charts, and UX guidelines for React, Next.js, Astro, Vue, Nuxt.js, Nuxt UI, Svelte, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui, and Jetpack Compose.", - "version": "2.2.1", + "description": "Professional UI/UX design intelligence for AI coding assistants. Includes searchable databases of styles, colors, typography, charts, and UX guidelines for React, Next.js, Astro, Vue, Nuxt.js, Nuxt UI, Svelte, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui, Jetpack Compose, Angular, Laravel, JavaFX, and Three.js.", + "version": "2.6.2", "author": { "name": "nextlevelbuilder" }, diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 30f37fb49..066bad502 100755 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,11 +1,11 @@ { "name": "ui-ux-pro-max", - "description": "UI/UX design intelligence. 67 styles, 161 palettes, 57 font pairings, 25 charts, 15 stacks (React, Next.js, Vue, Svelte, Astro, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui, Nuxt, Jetpack Compose). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient.", - "version": "2.5.0", + "description": "UI/UX design intelligence. 84 styles, 161 palettes, 73 font pairings, 25 charts, 17 stacks (React, Next.js, Vue, Nuxt.js, Nuxt UI, Svelte, Astro, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui, Jetpack Compose, Angular, Laravel, JavaFX, Three.js). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient.", + "version": "2.6.2", "author": { "name": "nextlevelbuilder" }, "license": "MIT", "keywords": ["ui", "ux", "design", "styles", "typography", "color-palette", "accessibility", "charts", "components"], - "skills": ["./.claude/skills/ui-ux-pro-max"] + "skills": "./.claude/skills/" } diff --git a/.claude/skills/banner-design/SKILL.md b/.claude/skills/banner-design/SKILL.md index 18fdac634..ee935a5c4 100644 --- a/.claude/skills/banner-design/SKILL.md +++ b/.claude/skills/banner-design/SKILL.md @@ -1,5 +1,5 @@ --- -name: ckm:banner-design +name: banner-design description: "Design banners for social media, ads, website heroes, creative assets, and print. Multiple art direction options with AI-generated visuals. Actions: design, create, generate banner. Platforms: Facebook, Twitter/X, LinkedIn, YouTube, Instagram, Google Display, website hero, print. Styles: minimalist, gradient, bold typography, photo-based, illustrated, geometric, retro, glassmorphism, 3D, neon, duotone, editorial, collage. Uses ui-ux-pro-max, frontend-design, ai-artist, ai-multimodal skills." argument-hint: "[platform] [style] [dimensions]" license: MIT @@ -21,6 +21,10 @@ Design banners across social, ads, web, and print formats. Generates multiple ar - Event/print banner design - Creative asset generation for campaigns +## Prerequisites + +**Python:** This skill uses Python scripts. On Windows, use `python` instead of `python3` (e.g., `python scripts/search.py` instead of `python3 scripts/search.py`). + ## Workflow ### Step 1: Gather Requirements (AskUserQuestion) diff --git a/.claude/skills/brand/SKILL.md b/.claude/skills/brand/SKILL.md index 035a1960a..336e8ef93 100644 --- a/.claude/skills/brand/SKILL.md +++ b/.claude/skills/brand/SKILL.md @@ -1,5 +1,5 @@ --- -name: ckm:brand +name: brand description: Brand voice, visual identity, messaging frameworks, asset management, brand consistency. Activate for branded content, tone of voice, marketing assets, brand compliance, style guides. argument-hint: "[update|review|create] [args]" metadata: diff --git a/.claude/skills/brand/scripts/sync-brand-to-tokens.cjs b/.claude/skills/brand/scripts/sync-brand-to-tokens.cjs index 86c19e880..013fa6ff9 100644 --- a/.claude/skills/brand/scripts/sync-brand-to-tokens.cjs +++ b/.claude/skills/brand/scripts/sync-brand-to-tokens.cjs @@ -11,7 +11,7 @@ const fs = require('fs'); const path = require('path'); -const { execSync } = require('child_process'); +const { execFileSync } = require('child_process'); // Paths const BRAND_GUIDELINES = 'docs/brand-guidelines.md'; @@ -29,61 +29,47 @@ function extractColorsFromMarkdown(content) { accent: { name: 'accent', shades: {} } }; - // Extract primary color name and hex from Quick Reference table - const quickRefMatch = content.match(/Primary Color\s*\|\s*#([A-Fa-f0-9]{6})\s*\(([^)]+)\)/); - if (quickRefMatch) { - colors.primary.name = quickRefMatch[2].toLowerCase().replace(/\s+/g, '-'); - colors.primary.base = `#${quickRefMatch[1]}`; - } - - const secondaryMatch = content.match(/Secondary Color\s*\|\s*#([A-Fa-f0-9]{6})\s*\(([^)]+)\)/); - if (secondaryMatch) { - colors.secondary.name = secondaryMatch[2].toLowerCase().replace(/\s+/g, '-'); - colors.secondary.base = `#${secondaryMatch[1]}`; - } + // Match a "| Label | #hex |" markdown table row. Bold around the label + // (**Label**) is optional, so this handles both the bundled starter template + // ("| Primary Blue | #2563EB |") and bolded variants. + const rowRe = /\|\s*\*{0,2}([^*|]+?)\*{0,2}\s*\|\s*#([A-Fa-f0-9]{6})\b/g; - const accentMatch = content.match(/Accent Color\s*\|\s*#([A-Fa-f0-9]{6})\s*\(([^)]+)\)/); - if (accentMatch) { - colors.accent.name = accentMatch[2].toLowerCase().replace(/\s+/g, '-'); - colors.accent.base = `#${accentMatch[1]}`; - } - - // Extract all shades from Primary Colors table - const primarySection = content.match(/### Primary Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i); - if (primarySection) { - const hexMatches = primarySection[0].matchAll(/\*\*([^*]+)\*\*\s*\|\s*#([A-Fa-f0-9]{6})/g); - for (const match of hexMatches) { - const name = match[1].trim().toLowerCase(); - const hex = `#${match[2]}`; - if (name.includes('dark')) colors.primary.dark = hex; - else if (name.includes('light')) colors.primary.light = hex; - else colors.primary.base = hex; - } + // 1) Quick Reference table — hex only, no parenthesized name required. + const quickRef = { + primary: /Primary Color\s*\|\s*#([A-Fa-f0-9]{6})/i, + secondary: /Secondary Color\s*\|\s*#([A-Fa-f0-9]{6})/i, + accent: /Accent Color\s*\|\s*#([A-Fa-f0-9]{6})/i + }; + for (const key of Object.keys(quickRef)) { + const m = content.match(quickRef[key]); + if (m) colors[key].base = `#${m[1]}`; } - // Extract secondary shades - const secondarySection = content.match(/### Secondary Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i); - if (secondarySection) { - const hexMatches = secondarySection[0].matchAll(/\*\*([^*]+)\*\*\s*\|\s*#([A-Fa-f0-9]{6})/g); - for (const match of hexMatches) { - const name = match[1].trim().toLowerCase(); - const hex = `#${match[2]}`; - if (name.includes('dark')) colors.secondary.dark = hex; - else if (name.includes('light')) colors.secondary.light = hex; - else colors.secondary.base = hex; + // 2) Dedicated "### Colors" tables — assign base/dark/light by the + // row label keyword. + const assignFromSection = (heading, target) => { + const section = content.match(new RegExp(`### ${heading}[\\s\\S]*?(?=\\n###|$)`, 'i')); + if (!section) return; + for (const m of section[0].matchAll(rowRe)) { + const label = m[1].trim().toLowerCase(); + const hex = `#${m[2]}`; + if (label.includes('dark')) target.dark = hex; + else if (label.includes('light')) target.light = hex; + else if (!target.base) target.base = hex; } - } - - // Extract accent shades - const accentSection = content.match(/### Accent Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i); - if (accentSection) { - const hexMatches = accentSection[0].matchAll(/\*\*([^*]+)\*\*\s*\|\s*#([A-Fa-f0-9]{6})/g); - for (const match of hexMatches) { - const name = match[1].trim().toLowerCase(); - const hex = `#${match[2]}`; - if (name.includes('dark')) colors.accent.dark = hex; - else if (name.includes('light')) colors.accent.light = hex; - else colors.accent.base = hex; + }; + assignFromSection('Primary Colors', colors.primary); + assignFromSection('Secondary Colors', colors.secondary); + assignFromSection('Accent Colors', colors.accent); + + // 3) Fallback: an accent swatch may live in another table (the starter + // lists "Accent Green" under Secondary Colors). + if (!colors.accent.base) { + for (const m of content.matchAll(rowRe)) { + if (m[1].trim().toLowerCase().includes('accent')) { + colors.accent.base = `#${m[2]}`; + break; + } } } @@ -113,6 +99,7 @@ function generateColorScale(baseHex, darkHex, lightHex) { * Adjust hex color brightness */ function adjustBrightness(hex, percent) { + if (typeof hex !== 'string') return '#000000'; const num = parseInt(hex.replace('#', ''), 16); const r = Math.min(255, Math.max(0, (num >> 16) + Math.round(255 * percent))); const g = Math.min(255, Math.max(0, ((num >> 8) & 0x00FF) + Math.round(255 * percent))); @@ -129,29 +116,24 @@ function updateDesignTokens(tokens, colors) { tokens.brand = brandName; // Update primitive colors with new names - const primitiveColors = tokens.primitive?.color || {}; + tokens.primitive = tokens.primitive || {}; + const primitiveColors = tokens.primitive.color || {}; // Remove old color keys, add new ones delete primitiveColors.coral; delete primitiveColors.purple; delete primitiveColors.mint; - // Add new named colors - primitiveColors[colors.primary.name] = generateColorScale( - colors.primary.base, - colors.primary.dark, - colors.primary.light - ); - primitiveColors[colors.secondary.name] = generateColorScale( - colors.secondary.base, - colors.secondary.dark, - colors.secondary.light - ); - primitiveColors[colors.accent.name] = generateColorScale( - colors.accent.base, - colors.accent.dark, - colors.accent.light - ); + // Add new named colors. Skip any role with no base hex rather than crashing + // on an unexpected guidelines format. + for (const role of ['primary', 'secondary', 'accent']) { + const c = colors[role]; + if (!c.base) { + console.warn(`⚠️ No base hex found for ${role} color — skipping its token scale.`); + continue; + } + primitiveColors[c.name] = generateColorScale(c.base, c.dark, c.light); + } tokens.primitive.color = primitiveColors; @@ -191,7 +173,7 @@ function updateDesignTokens(tokens, colors) { } // Update component references (button uses primary color with opacity) - if (tokens.component?.button?.secondary) { + if (tokens.component?.button?.secondary && colors.primary.base) { const primaryBase = colors.primary.base; tokens.component.button.secondary['bg-hover'] = { "$value": `${primaryBase}1A`, @@ -250,7 +232,7 @@ function main() { const generateScript = path.resolve(process.cwd(), GENERATE_TOKENS_SCRIPT); if (fs.existsSync(generateScript)) { try { - execSync(`node ${generateScript} --config ${DESIGN_TOKENS_JSON} -o ${DESIGN_TOKENS_CSS}`, { + execFileSync('node', [generateScript, '--config', DESIGN_TOKENS_JSON, '-o', DESIGN_TOKENS_CSS], { cwd: process.cwd(), stdio: 'inherit' }); diff --git a/.claude/skills/brand/scripts/tests/test_sync_brand_to_tokens.py b/.claude/skills/brand/scripts/tests/test_sync_brand_to_tokens.py new file mode 100644 index 000000000..e01075696 --- /dev/null +++ b/.claude/skills/brand/scripts/tests/test_sync_brand_to_tokens.py @@ -0,0 +1,52 @@ +"""Regression test for sync-brand-to-tokens.cjs. + +The color parser required a parenthesized name in the Quick Reference row +(`#2563EB (name)`) and a bolded label in the color tables (`**Primary Blue**`), +neither of which the bundled starter template uses. As a result the base hex +came back `undefined` and `adjustBrightness(undefined)` threw a TypeError — +i.e. the script crashed on its own documented happy path. This test runs the +sync against the bundled starter template and asserts it completes and writes +the expected base colors. It is pytest-based so the existing pytest CI runs it. +""" + +import json +import shutil +import subprocess +from pathlib import Path + +import pytest + +SCRIPTS = Path(__file__).resolve().parent.parent +SCRIPT = SCRIPTS / "sync-brand-to-tokens.cjs" +BRAND_STARTER = SCRIPTS.parent / "templates" / "brand-guidelines-starter.md" +TOKENS_STARTER = ( + SCRIPTS.parent.parent / "design-system" / "templates" / "design-tokens-starter.json" +) + + +def test_sync_parses_bundled_starter_template(tmp_path): + node = shutil.which("node") + if not node: + pytest.skip("node not available") + + (tmp_path / "docs").mkdir() + (tmp_path / "assets").mkdir() + shutil.copy(BRAND_STARTER, tmp_path / "docs" / "brand-guidelines.md") + shutil.copy(TOKENS_STARTER, tmp_path / "assets" / "design-tokens.json") + + result = subprocess.run( + [node, str(SCRIPT)], + cwd=tmp_path, + capture_output=True, + text=True, + ) + + # Must not crash (the bug raised an unhandled TypeError). + assert "TypeError" not in result.stderr, result.stderr + assert result.returncode == 0, result.stderr + result.stdout + + tokens = json.loads((tmp_path / "assets" / "design-tokens.json").read_text()) + primitive = tokens["primitive"]["color"] + assert primitive["primary"]["500"]["$value"] == "#2563EB" + assert primitive["secondary"]["500"]["$value"] == "#8B5CF6" + assert primitive["accent"]["500"]["$value"] == "#10B981" diff --git a/.claude/skills/design-system/SKILL.md b/.claude/skills/design-system/SKILL.md index cfbcba0c8..4397c7f8b 100644 --- a/.claude/skills/design-system/SKILL.md +++ b/.claude/skills/design-system/SKILL.md @@ -1,5 +1,5 @@ --- -name: ckm:design-system +name: design-system description: Token architecture, component specifications, and slide generation. Three-layer tokens (primitive→semantic→component), CSS variables, spacing/typography scales, component specs, strategic slide creation. Use for design tokens, systematic design, brand-compliant presentations. argument-hint: "[component or token]" license: MIT diff --git a/.claude/skills/design-system/scripts/generate-slide.py b/.claude/skills/design-system/scripts/generate-slide.py index 228a50a3a..2de390d90 100644 --- a/.claude/skills/design-system/scripts/generate-slide.py +++ b/.claude/skills/design-system/scripts/generate-slide.py @@ -8,9 +8,26 @@ import argparse import json +from html import escape from pathlib import Path from datetime import datetime + +def _e(value, default=''): + """HTML-escape a user-supplied value for safe embedding in HTML content.""" + return escape(str(value if value is not None else default)) + + +def _safe_url(url, default='#'): + """Validate and escape a URL for use in href attributes. + + Only allows http://, https://, #, and / schemes to prevent + javascript: URI injection (CWE-79). + """ + if url and str(url).strip().lower().startswith(('http://', 'https://', '#', '/')): + return escape(str(url), quote=True) + return default + # Paths SCRIPT_DIR = Path(__file__).parent DATA_DIR = SCRIPT_DIR.parent / "data" @@ -412,16 +429,16 @@ def generate_title_slide(data): """Title slide with gradient headline""" return f'''
-
{data.get('badge', 'Pitch Deck')}
-

{data.get('title', 'Your Title Here')}

-

{data.get('subtitle', 'Your compelling subtitle')}

+
{_e(data.get('badge', 'Pitch Deck'))}
+

{_e(data.get('title', 'Your Title Here'))}

+

{_e(data.get('subtitle', 'Your compelling subtitle'))}

''' @@ -432,27 +449,27 @@ def generate_problem_slide(data): return f'''
The Problem
-

{data.get('headline', 'The problem your audience faces')}

+

{_e(data.get('headline', 'The problem your audience faces'))}

01
-

{data.get('pain_1_title', 'Pain Point 1')}

-

{data.get('pain_1_desc', 'Description of the first pain point')}

+

{_e(data.get('pain_1_title', 'Pain Point 1'))}

+

{_e(data.get('pain_1_desc', 'Description of the first pain point'))}

02
-

{data.get('pain_2_title', 'Pain Point 2')}

-

{data.get('pain_2_desc', 'Description of the second pain point')}

+

{_e(data.get('pain_2_title', 'Pain Point 2'))}

+

{_e(data.get('pain_2_desc', 'Description of the second pain point'))}

03
-

{data.get('pain_3_title', 'Pain Point 3')}

-

{data.get('pain_3_desc', 'Description of the third pain point')}

+

{_e(data.get('pain_3_title', 'Pain Point 3'))}

+

{_e(data.get('pain_3_desc', 'Description of the third pain point'))}

''' @@ -463,28 +480,28 @@ def generate_solution_slide(data): return f'''
The Solution
-

{data.get('headline', 'How we solve this')}

+

{_e(data.get('headline', 'How we solve this'))}

-

{data.get('feature_1_title', 'Feature 1')}

-

{data.get('feature_1_desc', 'Description of feature 1')}

+

{_e(data.get('feature_1_title', 'Feature 1'))}

+

{_e(data.get('feature_1_desc', 'Description of feature 1'))}

-

{data.get('feature_2_title', 'Feature 2')}

-

{data.get('feature_2_desc', 'Description of feature 2')}

+

{_e(data.get('feature_2_title', 'Feature 2'))}

+

{_e(data.get('feature_2_desc', 'Description of feature 2'))}

-

{data.get('feature_3_title', 'Feature 3')}

-

{data.get('feature_3_desc', 'Description of feature 3')}

+

{_e(data.get('feature_3_title', 'Feature 3'))}

+

{_e(data.get('feature_3_desc', 'Description of feature 3'))}

@@ -496,8 +513,8 @@ def generate_solution_slide(data):
''' @@ -514,21 +531,21 @@ def generate_metrics_slide(data): metrics_html = ''.join([f'''
-
{m['value']}
-
{m['label']}
+
{_e(m.get('value', ''))}
+
{_e(m.get('label', ''))}
''' for m in metrics[:4]]) return f'''
Traction
-

{data.get('headline', 'Our Growth')}

+

{_e(data.get('headline', 'Our Growth'))}

{metrics_html}
''' @@ -544,25 +561,25 @@ def generate_chart_slide(data): ]) bars_html = ''.join([f''' -
- {b.get('display', str(b['value']) + '%')} - {b['label']} +
+ {_e(b.get('display', str(b.get('value', 0)) + '%'))} + {_e(b.get('label', ''))}
''' for b in bars]) return f'''
-
{data.get('badge', 'Growth')}
-

{data.get('headline', 'Revenue Growth')}

+
{_e(data.get('badge', 'Growth'))}
+

{_e(data.get('headline', 'Revenue Growth'))}

-
{data.get('chart_title', 'Quarterly Revenue')}
+
{_e(data.get('chart_title', 'Quarterly Revenue'))}
{bars_html}
''' @@ -574,13 +591,13 @@ def generate_testimonial_slide(data):
What They Say
-

"{data.get('quote', 'This product changed how we work. Incredible results.')}"

-

{data.get('author', 'Jane Doe')}

-

{data.get('role', 'CEO, Example Company')}

+

"{_e(data.get('quote', 'This product changed how we work. Incredible results.'))}"

+

{_e(data.get('author', 'Jane Doe'))}

+

{_e(data.get('role', 'CEO, Example Company'))}

''' @@ -590,14 +607,14 @@ def generate_cta_slide(data): """Closing CTA slide""" return f'''
-

{data.get('headline', 'Ready to get started?')}

-

{data.get('subheadline', 'Join thousands of teams already using our solution.')}

+

{_e(data.get('headline', 'Ready to get started?'))}

+

{_e(data.get('subheadline', 'Join thousands of teams already using our solution.'))}

''' @@ -632,7 +649,7 @@ def generate_deck(slides_data, title="Pitch Deck"): tokens_rel_path = "../../../assets/design-tokens.css" return SLIDE_TEMPLATE.format( - title=title, + title=escape(str(title)), tokens_css_path=tokens_rel_path, slides_content=slides_html ) diff --git a/.claude/skills/design-system/scripts/tests/test_validate_tokens.py b/.claude/skills/design-system/scripts/tests/test_validate_tokens.py new file mode 100644 index 000000000..bbde4ec8c --- /dev/null +++ b/.claude/skills/design-system/scripts/tests/test_validate_tokens.py @@ -0,0 +1,48 @@ +"""Regression tests for validate-tokens.cjs. + +The validator used to skip any line containing ``var(--`` outright, so a +hardcoded value sharing a line with a token reference (extremely common in +real CSS, and universal in minified CSS where everything is one line) went +undetected. These tests drive the CLI via ``node`` and assert it flags such +cases. They are pytest-based so the repository's existing pytest CI runs them. +""" + +import shutil +import subprocess +from pathlib import Path + +import pytest + +SCRIPT = Path(__file__).resolve().parent.parent / "validate-tokens.cjs" + + +def _run(tmp_path: Path, css: str) -> subprocess.CompletedProcess: + node = shutil.which("node") + if not node: + pytest.skip("node not available") + (tmp_path / "sample.css").write_text(css) + return subprocess.run( + [node, str(SCRIPT), "--dir", str(tmp_path)], + capture_output=True, + text=True, + ) + + +def test_flags_hardcoded_hex_sharing_line_with_token(tmp_path): + """A hardcoded hex on the same line as a var() token is still a violation.""" + result = _run( + tmp_path, + ".btn { background: #FF6B6B; color: var(--color-primary); }\n", + ) + assert "#FF6B6B" in result.stdout, result.stdout + assert result.returncode == 1 + + +def test_token_only_line_reports_no_violation(tmp_path): + """A line that references only tokens produces no false positives.""" + result = _run( + tmp_path, + ".btn { background: var(--color-bg); color: var(--color-primary); }\n", + ) + assert "No token violations" in result.stdout, result.stdout + assert result.returncode == 0 diff --git a/.claude/skills/design-system/scripts/validate-tokens.cjs b/.claude/skills/design-system/scripts/validate-tokens.cjs index e37866582..9839ed248 100644 --- a/.claude/skills/design-system/scripts/validate-tokens.cjs +++ b/.claude/skills/design-system/scripts/validate-tokens.cjs @@ -137,11 +137,6 @@ function scanFile(filePath) { return; } - // Skip lines that already use CSS variables - if (line.includes('var(--')) { - return; - } - for (const [name, pattern] of Object.entries(patterns)) { const matches = line.match(pattern.regex); if (matches) { diff --git a/.claude/skills/design/SKILL.md b/.claude/skills/design/SKILL.md index 1c4a37fb2..2437221d3 100644 --- a/.claude/skills/design/SKILL.md +++ b/.claude/skills/design/SKILL.md @@ -1,5 +1,5 @@ --- -name: ckm:design +name: design description: "Comprehensive design skill: brand identity, design tokens, UI styling, logo generation (55 styles, Gemini AI), corporate identity program (50 deliverables, CIP mockups), HTML presentations (Chart.js), banner design (22 styles, social/ads/web/print), icon design (15 styles, SVG, Gemini 3.1 Pro), social photos (HTML→screenshot, multi-platform). Actions: design logo, create CIP, generate mockups, build slides, design banner, generate icon, create social photos, social media images, brand identity, design system. Platforms: Facebook, Twitter, LinkedIn, YouTube, Instagram, Pinterest, TikTok, Threads, Google Ads." argument-hint: "[design-type] [context]" license: MIT @@ -289,6 +289,15 @@ Load `references/social-photos-design.md` for sizes, templates, best practices. | `scripts/cip/core.py` | BM25 search engine for CIP data | | `scripts/icon/generate.py` | Generate SVG icons with Gemini 3.1 Pro | +## Prerequisites + +**Python:** This skill uses Python scripts. On Windows, use `python` instead of `python3` (e.g., `python scripts/logo/search.py` instead of `python3 scripts/logo/search.py`). + +Check if Python is installed: +```bash +python3 --version || python --version +``` + ## Setup ```bash @@ -296,6 +305,8 @@ export GEMINI_API_KEY="your-key" # https://aistudio.google.com/apikey pip install google-genai pillow ``` +> **Note for Windows:** Use `python` instead of `pip` where needed (e.g., `python -m pip install ...`). + ## Integration **External sub-skills:** brand, design-system, ui-styling diff --git a/.claude/skills/slides/SKILL.md b/.claude/skills/slides/SKILL.md index ef4500a08..38750ff1b 100644 --- a/.claude/skills/slides/SKILL.md +++ b/.claude/skills/slides/SKILL.md @@ -1,5 +1,5 @@ --- -name: ckm:slides +name: slides description: Create strategic HTML presentations with Chart.js, design tokens, responsive layouts, copywriting formulas, and contextual slide strategies. argument-hint: "[topic] [slide-count]" metadata: @@ -11,8 +11,6 @@ metadata: Strategic HTML presentation design with data visualization. -$ARGUMENTS - ## When to Use - Marketing presentations and pitch decks diff --git a/.claude/skills/ui-styling/SKILL.md b/.claude/skills/ui-styling/SKILL.md index f2a8f8dd2..5824efeeb 100644 --- a/.claude/skills/ui-styling/SKILL.md +++ b/.claude/skills/ui-styling/SKILL.md @@ -1,5 +1,5 @@ --- -name: ckm:ui-styling +name: ui-styling description: Create beautiful, accessible user interfaces with shadcn/ui components (built on Radix UI + Tailwind), Tailwind CSS utility-first styling, and canvas-based visual designs. Use when building user interfaces, implementing design systems, creating responsive layouts, adding accessible components (dialogs, dropdowns, forms, tables), customizing themes and colors, implementing dark mode, generating visual designs and posters, or establishing consistent styling patterns across applications. argument-hint: "[component or layout]" license: MIT diff --git a/.claude/skills/ui-styling/scripts/shadcn_add.py b/.claude/skills/ui-styling/scripts/shadcn_add.py index e2a979980..6168df7f9 100644 --- a/.claude/skills/ui-styling/scripts/shadcn_add.py +++ b/.claude/skills/ui-styling/scripts/shadcn_add.py @@ -64,6 +64,20 @@ def get_installed_components(self) -> List[str]: except (json.JSONDecodeError, KeyError, OSError): return [] + def _get_shadcn_version(self) -> str: + """Read shadcn version from project package.json; fall back to a pinned default.""" + pkg_json = self.project_root / "package.json" + if pkg_json.exists(): + try: + pkg = json.loads(pkg_json.read_text()) + for section in ("dependencies", "devDependencies"): + version = pkg.get(section, {}).get("shadcn") + if version: + return version.lstrip("^~>=<").split()[0] + except (json.JSONDecodeError, KeyError): + pass + return "2.3.0" # pinned fallback; update when newer stable release is needed + def add_components( self, components: List[str], overwrite: bool = False ) -> tuple[bool, str]: @@ -98,7 +112,8 @@ def add_components( ) # Build command - cmd = ["npx", "shadcn@latest", "add"] + components + shadcn_version = self._get_shadcn_version() + cmd = ["npx", f"shadcn@{shadcn_version}", "add"] + components if overwrite: cmd.append("--overwrite") @@ -144,7 +159,8 @@ def add_all_components(self, overwrite: bool = False) -> tuple[bool, str]: "shadcn not initialized. Run 'npx shadcn@latest init' first", ) - cmd = ["npx", "shadcn@latest", "add", "--all"] + shadcn_version = self._get_shadcn_version() + cmd = ["npx", f"shadcn@{shadcn_version}", "add", "--all"] if overwrite: cmd.append("--overwrite") diff --git a/.claude/skills/ui-styling/scripts/tailwind_config_gen.py b/.claude/skills/ui-styling/scripts/tailwind_config_gen.py index 51093111c..093c625fd 100644 --- a/.claude/skills/ui-styling/scripts/tailwind_config_gen.py +++ b/.claude/skills/ui-styling/scripts/tailwind_config_gen.py @@ -8,10 +8,16 @@ import argparse import json +import re import sys from pathlib import Path from typing import Any, Dict, List, Optional +# Valid npm package name pattern: optional @scope/, then package name with +# optional subpath. Only allows alphanumeric, hyphens, dots, underscores, +# and forward slashes — no quotes, parens, or semicolons. +_VALID_PLUGIN_NAME = re.compile(r'^(@[a-zA-Z0-9_-]+/)?[a-zA-Z0-9_-]+(/[a-zA-Z0-9_.-]+)*$') + class TailwindConfigGenerator: """Generate Tailwind CSS configuration files.""" @@ -207,7 +213,7 @@ def _generate_typescript(self) -> str: return f"""import type {{ Config }} from 'tailwindcss' const config: Config = {{ -{self._indent_json(config_json, 1)} +{self._indent_json(config_json, 1)}, plugins: [{plugins_str}], }} @@ -224,19 +230,30 @@ def _generate_javascript(self) -> str: return f"""/** @type {{import('tailwindcss').Config}} */ module.exports = {{ -{self._indent_json(config_json, 1)} +{self._indent_json(config_json, 1)}, plugins: [{plugins_str}], }} """ def _format_plugins(self) -> str: - """Format plugins array for config.""" + """Format plugins array for config. + + Validates each plugin name against a strict allowlist pattern + to prevent code injection via crafted require() statements + (see: CWE-94). + """ if not self.config["plugins"]: return "" - plugin_requires = [ - f"require('{plugin}')" for plugin in self.config["plugins"] - ] + plugin_requires = [] + for plugin in self.config["plugins"]: + if not _VALID_PLUGIN_NAME.match(plugin): + raise ValueError( + f"Invalid plugin name: {plugin!r}. " + "Plugin names must be valid npm package names " + "(e.g. '@tailwindcss/typography')." + ) + plugin_requires.append(f"require('{plugin}')") return ", ".join(plugin_requires) def _indent_json(self, json_str: str, level: int) -> str: diff --git a/.claude/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py b/.claude/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py index a08414ee7..2facdbd08 100644 --- a/.claude/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py +++ b/.claude/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py @@ -1,5 +1,7 @@ """Tests for tailwind_config_gen.py""" +import shutil +import subprocess from pathlib import Path import pytest @@ -334,3 +336,59 @@ def test_full_configuration_javascript(self, tmp_path): assert "module.exports" in content assert "primary" in content assert "@tailwindcss/forms" in content + + +def _strip_to_object(config_str: str) -> str: + """Reduce a generated TS/JS config to a bare assignable object so it can be + handed to `node --check` without a TypeScript loader.""" + lines = [] + for line in config_str.splitlines(): + if line.startswith("import type"): + continue + if line.strip() == "export default config": + continue + line = line.replace("const config: Config =", "const config =") + line = line.replace("module.exports =", "const config =") + lines.append(line) + return "\n".join(lines) + + +class TestGeneratedConfigIsValidJs: + """Regression guard for the missing-comma bug between the ``theme`` block and + ``plugins`` that produced syntactically invalid config files. The data-shape + tests above all passed while the emitted string was unparseable, so these + tests validate the serialized output itself.""" + + @pytest.mark.parametrize("typescript", [True, False]) + def test_property_before_plugins_is_comma_terminated(self, typescript): + """The property preceding ``plugins`` must end with a comma (pure-Python + check, so the regression is caught even where node is unavailable).""" + generator = TailwindConfigGenerator(typescript=typescript) + generator.add_colors({"brand": "#6366F1"}) + generator.add_breakpoints({"3xl": "1920px"}) + config = generator.generate_config_string() + + assert "}\n plugins:" not in config, "missing comma before plugins" + assert "},\n plugins:" in config + + @pytest.mark.parametrize("typescript", [True, False]) + def test_node_check_parses_generated_config(self, typescript, tmp_path): + """The emitted config parses as valid JS via ``node --check``.""" + node = shutil.which("node") + if not node: + pytest.skip("node not available") + + generator = TailwindConfigGenerator(typescript=typescript) + generator.add_colors({"brand": "#6366F1", "accent": "#10B981"}) + generator.add_fonts({"sans": ["Inter"]}) + generator.add_breakpoints({"3xl": "1920px"}) + generator.add_plugins(["tailwindcss-animate"]) + + snippet = _strip_to_object(generator.generate_config_string()) + path = tmp_path / "config.cjs" + path.write_text(snippet) + + result = subprocess.run( + [node, "--check", str(path)], capture_output=True, text=True + ) + assert result.returncode == 0, result.stderr diff --git a/.claude/skills/ui-ux-pro-max/SKILL.md b/.claude/skills/ui-ux-pro-max/SKILL.md index a64d6c233..6f9bc4cc0 100644 --- a/.claude/skills/ui-ux-pro-max/SKILL.md +++ b/.claude/skills/ui-ux-pro-max/SKILL.md @@ -330,6 +330,8 @@ sudo apt update && sudo apt install python3 winget install Python.Python.3.12 ``` +> **Note:** On Windows, use `python` instead of `python3` to run scripts (e.g., `python scripts/search.py` instead of `python3 scripts/search.py`). + --- ## How to Use This Skill @@ -356,7 +358,7 @@ Extract key information from user request: - **Product type**: Entertainment (social, video, music, gaming), Tool (scanner, editor, converter), Productivity (task manager, notes, calendar), or hybrid - **Target audience**: C-end consumer users; consider age group, usage context (commute, leisure, work) - **Style keywords**: playful, vibrant, minimal, dark mode, content-first, immersive, etc. -- **Stack**: React Native (this project's only tech stack) +- **Stack**: Match the project's framework. The engine ships guidance for many stacks (see [Available Stacks](#available-stacks) below) — pass the matching `--stack` (e.g. `nextjs`, `react`, `shadcn`, `vue`, `svelte`, `astro`, `swiftui`, `flutter`, `react-native`). ### Step 2: Generate Design System (REQUIRED) @@ -411,6 +413,29 @@ If not, use the Master rules exclusively. Now, generate the code... ``` +### Step 2c: Design Dials (optional) + +Three optional 1-10 sliders that tune `--design-system` output without changing your query. Add any combination of them to the same command: + +```bash +python3 skills/ui-ux-pro-max/scripts/search.py "" --design-system --variance <1-10> --motion <1-10> --density <1-10> +``` + +| Dial | Low (1-3) | Mid (4-7) | High (8-10) | +|------|-----------|-----------|-------------| +| `--variance` | Centered / minimal (biases toward Minimalism-style categories) | Balanced / modern | Bold / asymmetric (biases toward Brutalism, Bento Grids) | +| `--motion` | Subtle micro-interactions | Standard scroll/stagger motion | Complex choreography (pin, Flip, SplitText) | +| `--density` | Spacious (24-96px spacing scale) | Standard (16-64px, current default) | Dense/dashboard (8-32px spacing scale) | + +- `--motion` attaches a ready-to-use GSAP snippet (with framework notes, Do/Don't, and performance notes) pulled from `--domain gsap`, matched to the resolved tier (Subtle/Standard/Complex). +- `--density` overrides the `--space-*` CSS variable table in the ASCII/markdown/MASTER.md output — use it for dashboards (high) vs. marketing pages (low) without hand-editing tokens. +- Leaving a dial unset keeps that part of the output exactly as it was before (no behavior change). + +**Example:** +```bash +python3 skills/ui-ux-pro-max/scripts/search.py "internal analytics dashboard" --design-system --variance 8 --motion 7 --density 8 -p "Ops Console" +``` + ### Step 3: Supplement with Detailed Searches (as needed) After getting the design system, use domain searches to get additional details: @@ -436,12 +461,14 @@ python3 skills/ui-ux-pro-max/scripts/search.py "" --domain [-n | App interface a11y | `web` | `--domain web "accessibilityLabel touch safe-areas"` | | AI prompt / CSS keywords | `prompt` | `--domain prompt "minimalism"` | -### Step 4: Stack Guidelines (React Native) +### Step 4: Stack Guidelines (match your framework) -Get React Native implementation-specific best practices: +Get implementation-specific best practices for the stack you're building in. +Pass the `--stack` that matches the project's framework: ```bash -python3 skills/ui-ux-pro-max/scripts/search.py "" --stack react-native +python3 skills/ui-ux-pro-max/scripts/search.py "" --stack +# e.g. --stack nextjs | react | shadcn | vue | svelte | astro | swiftui | flutter | react-native ``` --- @@ -459,6 +486,7 @@ python3 skills/ui-ux-pro-max/scripts/search.py "" --stack react-native | `landing` | Page structure, CTA strategies | hero, hero-centric, testimonial, pricing, social-proof | | `chart` | Chart types, library recommendations | trend, comparison, timeline, funnel, pie | | `ux` | Best practices, anti-patterns | animation, accessibility, z-index, loading | +| `gsap` | GSAP animation skeletons by intensity tier | scroll reveal, stagger, magnetic cursor, page transition | | `google-fonts` | Individual Google Fonts lookup | sans serif, monospace, japanese, variable font, popular | | `react` | React/Next.js performance | waterfall, bundle, suspense, memo, rerender, cache | | `web` | App interface guidelines (iOS/Android/React Native) | accessibilityLabel, touch targets, safe areas, Dynamic Type | @@ -466,9 +494,26 @@ python3 skills/ui-ux-pro-max/scripts/search.py "" --stack react-native ### Available Stacks +Run `ls /data/stacks/` to see the live set. Shipped stacks: + | Stack | Focus | |-------|-------| +| `react` | Components, hooks, render performance | +| `nextjs` | App Router, RSC, Server Actions, rendering | +| `vue` | Components, Composition API, reactivity | +| `nuxtjs` | Nuxt app patterns, SSR data fetching | +| `nuxt-ui` | Nuxt UI component patterns | +| `svelte` | Components, stores, transitions | +| `astro` | Islands, content, partial hydration | +| `shadcn` | shadcn/ui primitives, composition | +| `html-tailwind` | Tailwind utility patterns | +| `angular` | Components, signals, services | +| `laravel` | Blade / server-rendered UI patterns | +| `swiftui` | Views, state, navigation (iOS/macOS) | +| `flutter` | Widgets, state, navigation | +| `jetpack-compose` | Composables, state, navigation (Android) | | `react-native` | Components, Navigation, Lists | +| `threejs` | 3D scenes, materials, performance | --- @@ -480,7 +525,7 @@ python3 skills/ui-ux-pro-max/scripts/search.py "" --stack react-native - Product type: Tool (AI search engine) - Target audience: C-end users looking for fast, intelligent search - Style keywords: modern, minimal, content-first, dark mode -- Stack: React Native +- Stack: Next.js (a homepage is a web surface; use a web `--stack`) ### Step 2: Generate Design System (REQUIRED) @@ -503,7 +548,7 @@ python3 skills/ui-ux-pro-max/scripts/search.py "search loading animation" --doma ### Step 4: Stack Guidelines ```bash -python3 skills/ui-ux-pro-max/scripts/search.py "list performance navigation" --stack react-native +python3 skills/ui-ux-pro-max/scripts/search.py "list performance navigation" --stack nextjs ``` **Then:** Synthesize design system + detailed searches and implement the design. @@ -531,7 +576,7 @@ python3 skills/ui-ux-pro-max/scripts/search.py "fintech crypto" --design-system - Use **multi-dimensional keywords** — combine product + industry + tone + density: `"entertainment social vibrant content-dense"` not just `"app"` - Try different keywords for the same need: `"playful neon"` → `"vibrant dark"` → `"content-first minimal"` - Use `--design-system` first for full recommendations, then `--domain` to deep-dive any dimension you're unsure about -- Always add `--stack react-native` for implementation-specific guidance +- Add the `--stack` that matches the project's framework for implementation-specific guidance ### Common Sticking Points diff --git a/.claude/skills/ui-ux-pro-max/data b/.claude/skills/ui-ux-pro-max/data deleted file mode 120000 index e5b94699c..000000000 --- a/.claude/skills/ui-ux-pro-max/data +++ /dev/null @@ -1 +0,0 @@ -../../../src/ui-ux-pro-max/data \ No newline at end of file diff --git a/.claude/skills/ui-ux-pro-max/data/_sync_all.py b/.claude/skills/ui-ux-pro-max/data/_sync_all.py new file mode 100644 index 000000000..37f7c3ac3 --- /dev/null +++ b/.claude/skills/ui-ux-pro-max/data/_sync_all.py @@ -0,0 +1,414 @@ +#!/usr/bin/env python3 +""" +Sync colors.csv and ui-reasoning.csv with the updated products.csv (161 entries). +- Remove deleted product types +- Rename mismatched entries +- Add new entries for missing product types +- Keep colors.csv aligned 1:1 with products.csv +- Renumber everything +""" +import csv, os, json + +BASE = os.path.dirname(os.path.abspath(__file__)) + +# ─── Color derivation helpers ──────────────────────────────────────────────── +def h2r(h): + h = h.lstrip("#") + return tuple(int(h[i:i+2], 16) for i in (0, 2, 4)) + +def r2h(r, g, b): + return f"#{max(0,min(255,int(r))):02X}{max(0,min(255,int(g))):02X}{max(0,min(255,int(b))):02X}" + +def lum(h): + r, g, b = [x/255.0 for x in h2r(h)] + r, g, b = [(x/12.92 if x<=0.03928 else ((x+0.055)/1.055)**2.4) for x in (r, g, b)] + return 0.2126*r + 0.7152*g + 0.0722*b + +def is_dark(bg): + return lum(bg) < 0.18 + +def on_color(bg): + return "#FFFFFF" if lum(bg) < 0.4 else "#0F172A" + +def blend(a, b, f=0.15): + ra, ga, ba = h2r(a) + rb, gb, bb = h2r(b) + return r2h(ra+(rb-ra)*f, ga+(gb-ga)*f, ba+(bb-ba)*f) + +def shift(h, n): + r, g, b = h2r(h) + return r2h(r+n, g+n, b+n) + +def derive_row(pt, pri, sec, acc, bg, notes=""): + """Generate full 16-token color row from 4 base colors.""" + dark = is_dark(bg) + fg = "#FFFFFF" if dark else "#0F172A" + on_pri = on_color(pri) + on_sec = on_color(sec) + on_acc = on_color(acc) + card = shift(bg, 10) if dark else "#FFFFFF" + card_fg = "#FFFFFF" if dark else "#0F172A" + muted = blend(bg, pri, 0.08) if dark else blend("#FFFFFF", pri, 0.06) + muted_fg = "#94A3B8" if dark else "#64748B" + border = f"rgba(255,255,255,0.08)" if dark else blend("#FFFFFF", pri, 0.12) + destr = "#DC2626" + on_destr = "#FFFFFF" + ring = pri + return [pt, pri, on_pri, sec, on_sec, acc, on_acc, bg, fg, card, card_fg, muted, muted_fg, border, destr, on_destr, ring, notes] + +# ─── Rename maps ───────────────────────────────────────────────────────────── +COLOR_RENAMES = { + "Quantum Computing": "Quantum Computing Interface", + "Biohacking / Longevity": "Biohacking / Longevity App", + "Autonomous Systems": "Autonomous Drone Fleet Manager", + "Generative AI Art": "Generative Art Platform", + "Spatial / Vision OS": "Spatial Computing OS / App", + "Climate Tech": "Sustainable Energy / Climate Tech", +} +UI_RENAMES = { + "Architecture/Interior": "Architecture / Interior", + "Autonomous Drone Fleet": "Autonomous Drone Fleet Manager", + "B2B SaaS Enterprise": "B2B Service", + "Biohacking/Longevity App": "Biohacking / Longevity App", + "Biotech/Life Sciences": "Biotech / Life Sciences", + "Developer Tool/IDE": "Developer Tool / IDE", + "Education": "Educational App", + "Fintech (Banking)": "Fintech/Crypto", + "Government/Public": "Government/Public Service", + "Home Services": "Home Services (Plumber/Electrician)", + "Micro-Credentials/Badges": "Micro-Credentials/Badges Platform", + "Music/Entertainment": "Music Streaming", + "Quantum Computing": "Quantum Computing Interface", + "Real Estate": "Real Estate/Property", + "Remote Work/Collaboration": "Remote Work/Collaboration Tool", + "Restaurant/Food": "Restaurant/Food Service", + "SaaS Dashboard": "Analytics Dashboard", + "Space Tech/Aerospace": "Space Tech / Aerospace", + "Spatial Computing OS": "Spatial Computing OS / App", + "Startup Landing": "Micro SaaS", + "Sustainable Energy/Climate": "Sustainable Energy / Climate Tech", + "Travel/Tourism": "Travel/Tourism Agency", + "Wellness/Mental Health": "Mental Health App", +} + +REMOVE_TYPES = { + "Service Landing Page", "Sustainability/ESG Platform", + "Cleaning Service", "Coffee Shop", + "Consulting Firm", "Conference/Webinar Platform", +} + +# ─── New color definitions: (primary, secondary, accent, bg, notes) ────────── +# Grouped by category for clarity. Each tuple generates a full 16-token row. +NEW_COLORS = { + # ── Old #97-#116 that never got colors ── + "Todo & Task Manager": ("#2563EB","#3B82F6","#059669","#F8FAFC","Functional blue + progress green"), + "Personal Finance Tracker": ("#1E40AF","#3B82F6","#059669","#0F172A","Trust blue + profit green on dark"), + "Chat & Messaging App": ("#2563EB","#6366F1","#059669","#FFFFFF","Messenger blue + online green"), + "Notes & Writing App": ("#78716C","#A8A29E","#D97706","#FFFBEB","Warm ink + amber accent on cream"), + "Habit Tracker": ("#D97706","#F59E0B","#059669","#FFFBEB","Streak amber + habit green"), + "Food Delivery / On-Demand": ("#EA580C","#F97316","#2563EB","#FFF7ED","Appetizing orange + trust blue"), + "Ride Hailing / Transportation":("#1E293B","#334155","#2563EB","#0F172A","Map dark + route blue"), + "Recipe & Cooking App": ("#9A3412","#C2410C","#059669","#FFFBEB","Warm terracotta + fresh green"), + "Meditation & Mindfulness": ("#7C3AED","#8B5CF6","#059669","#FAF5FF","Calm lavender + mindful green"), + "Weather App": ("#0284C7","#0EA5E9","#F59E0B","#F0F9FF","Sky blue + sun amber"), + "Diary & Journal App": ("#92400E","#A16207","#6366F1","#FFFBEB","Warm journal brown + ink violet"), + "CRM & Client Management": ("#2563EB","#3B82F6","#059669","#F8FAFC","Professional blue + deal green"), + "Inventory & Stock Management":("#334155","#475569","#059669","#F8FAFC","Industrial slate + stock green"), + "Flashcard & Study Tool": ("#7C3AED","#8B5CF6","#059669","#FAF5FF","Study purple + correct green"), + "Booking & Appointment App": ("#0284C7","#0EA5E9","#059669","#F0F9FF","Calendar blue + available green"), + "Invoice & Billing Tool": ("#1E3A5F","#2563EB","#059669","#F8FAFC","Navy professional + paid green"), + "Grocery & Shopping List": ("#059669","#10B981","#D97706","#ECFDF5","Fresh green + food amber"), + "Timer & Pomodoro": ("#DC2626","#EF4444","#059669","#0F172A","Focus red on dark + break green"), + "Parenting & Baby Tracker": ("#EC4899","#F472B6","#0284C7","#FDF2F8","Soft pink + trust blue"), + "Scanner & Document Manager": ("#1E293B","#334155","#2563EB","#F8FAFC","Document grey + scan blue"), + # ── A. Utility / Productivity ── + "Calendar & Scheduling App": ("#2563EB","#3B82F6","#059669","#F8FAFC","Calendar blue + event green"), + "Password Manager": ("#1E3A5F","#334155","#059669","#0F172A","Vault dark blue + secure green"), + "Expense Splitter / Bill Split":("#059669","#10B981","#DC2626","#F8FAFC","Balance green + owe red"), + "Voice Recorder & Memo": ("#DC2626","#EF4444","#2563EB","#FFFFFF","Recording red + waveform blue"), + "Bookmark & Read-Later": ("#D97706","#F59E0B","#2563EB","#FFFBEB","Warm amber + link blue"), + "Translator App": ("#2563EB","#0891B2","#EA580C","#F8FAFC","Global blue + teal + accent orange"), + "Calculator & Unit Converter": ("#EA580C","#F97316","#2563EB","#1C1917","Operation orange on dark"), + "Alarm & World Clock": ("#D97706","#F59E0B","#6366F1","#0F172A","Time amber + night indigo on dark"), + "File Manager & Transfer": ("#2563EB","#3B82F6","#D97706","#F8FAFC","Folder blue + file amber"), + "Email Client": ("#2563EB","#3B82F6","#DC2626","#FFFFFF","Inbox blue + priority red"), + # ── B. Games ── + "Casual Puzzle Game": ("#EC4899","#8B5CF6","#F59E0B","#FDF2F8","Cheerful pink + reward gold"), + "Trivia & Quiz Game": ("#2563EB","#7C3AED","#F59E0B","#EFF6FF","Quiz blue + gold leaderboard"), + "Card & Board Game": ("#15803D","#166534","#D97706","#0F172A","Felt green + gold on dark"), + "Idle & Clicker Game": ("#D97706","#F59E0B","#7C3AED","#FFFBEB","Coin gold + prestige purple"), + "Word & Crossword Game": ("#15803D","#059669","#D97706","#FFFFFF","Word green + letter amber"), + "Arcade & Retro Game": ("#DC2626","#2563EB","#22C55E","#0F172A","Neon red+blue on dark + score green"), + # ── C. Creator Tools ── + "Photo Editor & Filters": ("#7C3AED","#6366F1","#0891B2","#0F172A","Editor violet + filter cyan on dark"), + "Short Video Editor": ("#EC4899","#DB2777","#2563EB","#0F172A","Video pink on dark + timeline blue"), + "Drawing & Sketching Canvas": ("#7C3AED","#8B5CF6","#0891B2","#1C1917","Canvas purple + tool teal on dark"), + "Music Creation & Beat Maker": ("#7C3AED","#6366F1","#22C55E","#0F172A","Studio purple + waveform green on dark"), + "Meme & Sticker Maker": ("#EC4899","#F59E0B","#2563EB","#FFFFFF","Viral pink + comedy yellow + share blue"), + "AI Photo & Avatar Generator": ("#7C3AED","#6366F1","#EC4899","#FAF5FF","AI purple + generation pink"), + "Link-in-Bio Page Builder": ("#2563EB","#7C3AED","#EC4899","#FFFFFF","Brand blue + creator purple"), + # ── D. Personal Life ── + "Wardrobe & Outfit Planner": ("#BE185D","#EC4899","#D97706","#FDF2F8","Fashion rose + gold accent"), + "Plant Care Tracker": ("#15803D","#059669","#D97706","#F0FDF4","Nature green + sun yellow"), + "Book & Reading Tracker": ("#78716C","#92400E","#D97706","#FFFBEB","Book brown + page amber"), + "Couple & Relationship App": ("#BE185D","#EC4899","#DC2626","#FDF2F8","Romance rose + love red"), + "Family Calendar & Chores": ("#2563EB","#059669","#D97706","#F8FAFC","Family blue + chore green"), + "Mood Tracker": ("#7C3AED","#6366F1","#D97706","#FAF5FF","Mood purple + insight amber"), + "Gift & Wishlist": ("#DC2626","#D97706","#EC4899","#FFF1F2","Gift red + gold + surprise pink"), + # ── E. Health ── + "Running & Cycling GPS": ("#EA580C","#F97316","#059669","#0F172A","Energetic orange + pace green on dark"), + "Yoga & Stretching Guide": ("#6B7280","#78716C","#0891B2","#F5F5F0","Sage neutral + calm teal"), + "Sleep Tracker": ("#4338CA","#6366F1","#7C3AED","#0F172A","Night indigo + dream violet on dark"), + "Calorie & Nutrition Counter": ("#059669","#10B981","#EA580C","#ECFDF5","Healthy green + macro orange"), + "Period & Cycle Tracker": ("#BE185D","#EC4899","#7C3AED","#FDF2F8","Blush rose + fertility lavender"), + "Medication & Pill Reminder": ("#0284C7","#0891B2","#DC2626","#F0F9FF","Medical blue + alert red"), + "Water & Hydration Reminder": ("#0284C7","#06B6D4","#0891B2","#F0F9FF","Refreshing blue + water cyan"), + "Fasting & Intermittent Timer":("#6366F1","#4338CA","#059669","#0F172A","Fasting indigo on dark + eating green"), + # ── F. Social ── + "Anonymous Community / Confession":("#475569","#334155","#0891B2","#0F172A","Protective grey + subtle teal on dark"), + "Local Events & Discovery": ("#EA580C","#F97316","#2563EB","#FFF7ED","Event orange + map blue"), + "Study Together / Virtual Coworking":("#2563EB","#3B82F6","#059669","#F8FAFC","Focus blue + session green"), + # ── G. Education ── + "Coding Challenge & Practice": ("#22C55E","#059669","#D97706","#0F172A","Code green + difficulty amber on dark"), + "Kids Learning (ABC & Math)": ("#2563EB","#F59E0B","#EC4899","#EFF6FF","Learning blue + play yellow + fun pink"), + "Music Instrument Learning": ("#DC2626","#9A3412","#D97706","#FFFBEB","Musical red + warm amber"), + # ── H. Transport ── + "Parking Finder": ("#2563EB","#059669","#DC2626","#F0F9FF","Available blue/green + occupied red"), + "Public Transit Guide": ("#2563EB","#0891B2","#EA580C","#F8FAFC","Transit blue + line colors"), + "Road Trip Planner": ("#EA580C","#0891B2","#D97706","#FFF7ED","Adventure orange + map teal"), + # ── I. Safety & Lifestyle ── + "VPN & Privacy Tool": ("#1E3A5F","#334155","#22C55E","#0F172A","Shield dark + connected green"), + "Emergency SOS & Safety": ("#DC2626","#EF4444","#2563EB","#FFF1F2","Alert red + safety blue"), + "Wallpaper & Theme App": ("#7C3AED","#EC4899","#2563EB","#FAF5FF","Aesthetic purple + trending pink"), + "White Noise & Ambient Sound": ("#475569","#334155","#4338CA","#0F172A","Ambient grey + deep indigo on dark"), + "Home Decoration & Interior Design":("#78716C","#A8A29E","#D97706","#FAF5F2","Interior warm grey + gold accent"), +} + +# ─── 1. REBUILD colors.csv ─────────────────────────────────────────────────── +def rebuild_colors(): + src = os.path.join(BASE, "colors.csv") + with open(src, newline="", encoding="utf-8") as f: + reader = csv.DictReader(f) + headers = reader.fieldnames + existing = list(reader) + + # Build lookup: Product Type -> row data + color_map = {} + for row in existing: + pt = row.get("Product Type", "").strip() + if not pt: + continue + # Remove deleted types + if pt in REMOVE_TYPES: + print(f" [colors] REMOVE: {pt}") + continue + # Rename mismatched types + if pt in COLOR_RENAMES: + new_name = COLOR_RENAMES[pt] + print(f" [colors] RENAME: {pt} → {new_name}") + row["Product Type"] = new_name + pt = new_name + color_map[pt] = row + + # Read products.csv to get the correct order + with open(os.path.join(BASE, "products.csv"), newline="", encoding="utf-8") as f: + products = list(csv.DictReader(f)) + + # Build final rows in products.csv order + final_rows = [] + added = 0 + for i, prod in enumerate(products, 1): + pt = prod["Product Type"] + if pt in color_map: + row = color_map[pt] + row["No"] = str(i) + final_rows.append(row) + elif pt in NEW_COLORS: + pri, sec, acc, bg, notes = NEW_COLORS[pt] + new_row = derive_row(pt, pri, sec, acc, bg, notes) + d = dict(zip(headers, [str(i)] + new_row)) + final_rows.append(d) + added += 1 + else: + print(f" [colors] WARNING: No color data for '{pt}' - using defaults") + new_row = derive_row(pt, "#2563EB", "#3B82F6", "#059669", "#F8FAFC", "Auto-generated default") + d = dict(zip(headers, [str(i)] + new_row)) + final_rows.append(d) + added += 1 + + # Write + with open(src, "w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter(f, fieldnames=headers) + writer.writeheader() + writer.writerows(final_rows) + + product_count = len(products) + print(f"\n ✅ colors.csv: {len(final_rows)} rows ({product_count} products)") + print(f" Added: {added} new color rows") + +# ─── 2. REBUILD ui-reasoning.csv ───────────────────────────────────────────── +def derive_ui_reasoning(prod): + """Generate ui-reasoning row from products.csv row.""" + pt = prod["Product Type"] + style = prod.get("Primary Style Recommendation", "") + landing = prod.get("Landing Page Pattern", "") + color_focus = prod.get("Color Palette Focus", "") + considerations = prod.get("Key Considerations", "") + keywords = prod.get("Keywords", "") + + # Typography mood derived from style + typo_map = { + "Minimalism": "Professional + Clean hierarchy", + "Glassmorphism": "Modern + Clear hierarchy", + "Brutalism": "Bold + Oversized + Monospace", + "Claymorphism": "Playful + Rounded + Friendly", + "Dark Mode": "High contrast + Light on dark", + "Neumorphism": "Subtle + Soft + Monochromatic", + "Flat Design": "Bold + Clean + Sans-serif", + "Vibrant": "Energetic + Bold + Large", + "Aurora": "Elegant + Gradient-friendly", + "AI-Native": "Conversational + Minimal chrome", + "Organic": "Warm + Humanist + Natural", + "Motion": "Dynamic + Hierarchy-shifting", + "Accessible": "Large + High contrast + Clear", + "Soft UI": "Modern + Accessible + Balanced", + "Trust": "Professional + Serif accents", + "Swiss": "Grid-based + Mathematical + Helvetica", + "3D": "Immersive + Spatial + Variable", + "Retro": "Nostalgic + Monospace + Neon", + "Cyberpunk": "Terminal + Monospace + Neon", + "Pixel": "Retro + Blocky + 8-bit", + } + typo_mood = "Professional + Clear hierarchy" + for key, val in typo_map.items(): + if key.lower() in style.lower(): + typo_mood = val + break + + # Key effects from style + eff_map = { + "Glassmorphism": "Backdrop blur (10-20px) + Translucent overlays", + "Neumorphism": "Dual shadows (light+dark) + Soft press 150ms", + "Claymorphism": "Multi-layer shadows + Spring bounce + Soft press 200ms", + "Brutalism": "No transitions + Hard borders + Instant feedback", + "Dark Mode": "Subtle glow + Neon accents + High contrast", + "Flat Design": "Color shift hover + Fast 150ms transitions + No shadows", + "Minimalism": "Subtle hover 200ms + Smooth transitions + Clean", + "Motion-Driven": "Scroll animations + Parallax + Page transitions", + "Micro-interactions": "Haptic feedback + Small 50-100ms animations", + "Vibrant": "Large section gaps 48px+ + Color shift hover + Scroll-snap", + "Aurora": "Flowing gradients 8-12s + Color morphing", + "AI-Native": "Typing indicator + Streaming text + Context reveal", + "Organic": "Rounded 16-24px + Natural shadows + Flowing SVG", + "Soft UI": "Improved shadows + Modern 200-300ms + Focus visible", + "3D": "WebGL/Three.js + Parallax 3-5 layers + Physics 300-400ms", + "Trust": "Clear focus rings + Badge hover + Metric pulse", + "Accessible": "Focus rings 3-4px + ARIA + Reduced motion", + } + key_effects = "Subtle hover (200ms) + Smooth transitions" + for key, val in eff_map.items(): + if key.lower() in style.lower(): + key_effects = val + break + + # Decision rules + rules = {} + if "dark" in style.lower() or "oled" in style.lower(): + rules["if_light_mode_needed"] = "provide-theme-toggle" + if "glass" in style.lower(): + rules["if_low_performance"] = "fallback-to-flat" + if "conversion" in landing.lower(): + rules["if_conversion_focused"] = "add-urgency-colors" + if "social" in landing.lower(): + rules["if_trust_needed"] = "add-testimonials" + if "data" in keywords.lower() or "dashboard" in keywords.lower(): + rules["if_data_heavy"] = "prioritize-data-density" + if not rules: + rules["if_ux_focused"] = "prioritize-clarity" + rules["if_mobile"] = "optimize-touch-targets" + + # Anti-patterns + anti_patterns = [] + if "minimalism" in style.lower() or "minimal" in style.lower(): + anti_patterns.append("Excessive decoration") + if "dark" in style.lower(): + anti_patterns.append("Pure white backgrounds") + if "flat" in style.lower(): + anti_patterns.append("Complex shadows + 3D effects") + if "vibrant" in style.lower(): + anti_patterns.append("Muted colors + Low energy") + if "accessible" in style.lower(): + anti_patterns.append("Color-only indicators") + if not anti_patterns: + anti_patterns = ["Inconsistent styling", "Poor contrast ratios"] + anti_str = " + ".join(anti_patterns[:2]) + + return { + "UI_Category": pt, + "Recommended_Pattern": landing, + "Style_Priority": style, + "Color_Mood": color_focus, + "Typography_Mood": typo_mood, + "Key_Effects": key_effects, + "Decision_Rules": json.dumps(rules), + "Anti_Patterns": anti_str, + "Severity": "HIGH" + } + + +def rebuild_ui_reasoning(): + src = os.path.join(BASE, "ui-reasoning.csv") + with open(src, newline="", encoding="utf-8") as f: + reader = csv.DictReader(f) + headers = reader.fieldnames + existing = list(reader) + + # Build lookup + ui_map = {} + for row in existing: + cat = row.get("UI_Category", "").strip() + if not cat: + continue + if cat in REMOVE_TYPES: + print(f" [ui-reason] REMOVE: {cat}") + continue + if cat in UI_RENAMES: + new_name = UI_RENAMES[cat] + print(f" [ui-reason] RENAME: {cat} → {new_name}") + row["UI_Category"] = new_name + cat = new_name + ui_map[cat] = row + + with open(os.path.join(BASE, "products.csv"), newline="", encoding="utf-8") as f: + products = list(csv.DictReader(f)) + + final_rows = [] + added = 0 + for i, prod in enumerate(products, 1): + pt = prod["Product Type"] + if pt in ui_map: + row = ui_map[pt] + row["No"] = str(i) + final_rows.append(row) + else: + row = derive_ui_reasoning(prod) + row["No"] = str(i) + final_rows.append(row) + added += 1 + + with open(src, "w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter(f, fieldnames=headers) + writer.writeheader() + writer.writerows(final_rows) + + print(f"\n ✅ ui-reasoning.csv: {len(final_rows)} rows") + print(f" Added: {added} new reasoning rows") + + +# ─── MAIN ──────────────────────────────────────────────────────────────────── +if __name__ == "__main__": + print("=== Rebuilding colors.csv ===") + rebuild_colors() + print("\n=== Rebuilding ui-reasoning.csv ===") + rebuild_ui_reasoning() + print("\n🎉 Done!") diff --git a/.claude/skills/ui-ux-pro-max/data/app-interface.csv b/.claude/skills/ui-ux-pro-max/data/app-interface.csv new file mode 100644 index 000000000..f34c3cd0d --- /dev/null +++ b/.claude/skills/ui-ux-pro-max/data/app-interface.csv @@ -0,0 +1,31 @@ +No,Category,Issue,Keywords,Platform,Description,Do,Don't,Code Example Good,Code Example Bad,Severity +1,Accessibility,Icon Button Labels,icon button accessibilityLabel,iOS/Android/React Native,Icon-only buttons must expose an accessible label,Set accessibilityLabel or label prop on icon buttons,Icon buttons without accessible names,"","",Critical +2,Accessibility,Form Control Labels,form input label accessibilityLabel,iOS/Android/React Native,All inputs must have a visible label and an accessibility label,Pair Text label with input and set accessibilityLabel,Inputs with placeholder only,"Email","",Critical +3,Accessibility,Role & Traits,accessibilityRole accessibilityTraits,iOS/Android/React Native,Interactive elements must expose correct roles/traits,Use accessibilityRole/button/link/checkbox etc.,Rely on generic views with no roles,"Submit","Submit",High +4,Accessibility,Dynamic Updates,accessibilityLiveRegion announce,iOS/Android/React Native,Async status updates should be announced to screen readers,Use accessibilityLiveRegion or announceForAccessibility,Update text silently with no announcement,"{status}","{status}",Medium +5,Accessibility,Decorative Icons,accessible={false} importantForAccessibility,iOS/Android/React Native,Decorative icons should be hidden from screen readers,Mark decorative icons as not accessible,Have screen reader read every icon,"","",Medium +6,Touch,Touch Target Size,touch 44x44 hitSlop,iOS/Android/React Native,Primary touch targets must be at least 44x44pt,Increase hitSlop or padding to meet minimum,Small icons with tiny touch area,"","",Critical +7,Touch,Touch Spacing,touch spacing gap 8px,iOS/Android/React Native,Adjacent touch targets need enough spacing,Keep at least 8dp spacing between touchables,Cluster many buttons with no gap,"