📦 deps(skills): sync thirdparty skills
This commit is contained in:
+193
-21
@@ -40,6 +40,39 @@ SEARCH_CONFIG = {
|
||||
"typography": {"max_results": 2}
|
||||
}
|
||||
|
||||
# ============ DESIGN DIALS (1-10) ============
|
||||
# Inspired by taste-skill's DESIGN_VARIANCE / MOTION_INTENSITY / VISUAL_DENSITY
|
||||
# knobs: three optional 1-10 sliders that bias the existing query-based search
|
||||
# instead of replacing it. Each dial buckets into a low/mid/high tier.
|
||||
DIAL_TIERS = {
|
||||
"variance": [
|
||||
(1, 3, {"label": "Centered / Minimal", "style_keywords": ["Minimalism", "Exaggerated Minimalism", "centered", "symmetric", "grid-based"]}),
|
||||
(4, 7, {"label": "Balanced / Modern", "style_keywords": ["modern", "structured", "balanced"]}),
|
||||
(8, 10, {"label": "Bold / Asymmetric", "style_keywords": ["Brutalism", "Bento Grids", "asymmetric", "experimental"]}),
|
||||
],
|
||||
"motion": [
|
||||
(1, 3, {"label": "Subtle", "tier": "Subtle"}),
|
||||
(4, 7, {"label": "Standard", "tier": "Standard"}),
|
||||
(8, 10, {"label": "Complex", "tier": "Complex"}),
|
||||
],
|
||||
"density": [
|
||||
(1, 3, {"label": "Spacious", "spacing": {"xs": "4px", "sm": "8px", "md": "24px", "lg": "32px", "xl": "48px", "2xl": "64px", "3xl": "96px"}}),
|
||||
(4, 7, {"label": "Standard", "spacing": {"xs": "4px", "sm": "8px", "md": "16px", "lg": "24px", "xl": "32px", "2xl": "48px", "3xl": "64px"}}),
|
||||
(8, 10, {"label": "Dense / Dashboard", "spacing": {"xs": "2px", "sm": "4px", "md": "8px", "lg": "12px", "xl": "16px", "2xl": "24px", "3xl": "32px"}}),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _resolve_dial(dial_name: str, value) -> dict:
|
||||
"""Bucket a 1-10 dial value into its tier config. Returns None if value is None."""
|
||||
if value is None:
|
||||
return None
|
||||
value = max(1, min(10, int(value)))
|
||||
for lo, hi, info in DIAL_TIERS[dial_name]:
|
||||
if lo <= value <= hi:
|
||||
return {**info, "value": value}
|
||||
return None
|
||||
|
||||
|
||||
# ============ DESIGN SYSTEM GENERATOR ============
|
||||
class DesignSystemGenerator:
|
||||
@@ -168,8 +201,18 @@ class DesignSystemGenerator:
|
||||
"""Extract results list from search result dict."""
|
||||
return search_result.get("results", [])
|
||||
|
||||
def generate(self, query: str, project_name: str = None) -> dict:
|
||||
"""Generate complete design system recommendation."""
|
||||
def generate(self, query: str, project_name: str = None,
|
||||
variance: int = None, motion: int = None, density: int = None) -> dict:
|
||||
"""Generate complete design system recommendation.
|
||||
|
||||
variance/motion/density are optional 1-10 dials (see DIAL_TIERS) that bias
|
||||
style selection, pull in a matching motion.csv snippet, and override the
|
||||
spacing scale, without changing behavior when left unset.
|
||||
"""
|
||||
variance_info = _resolve_dial("variance", variance)
|
||||
motion_info = _resolve_dial("motion", motion)
|
||||
density_info = _resolve_dial("density", density)
|
||||
|
||||
# Step 1: First search product to get category
|
||||
product_result = search(query, "product", 1)
|
||||
product_results = product_result.get("results", [])
|
||||
@@ -181,8 +224,14 @@ class DesignSystemGenerator:
|
||||
reasoning = self._apply_reasoning(category, {})
|
||||
style_priority = reasoning.get("style_priority", [])
|
||||
|
||||
# DESIGN_VARIANCE dial: bias style retrieval/selection toward
|
||||
# centered-minimal (low) or bold-asymmetric (high) keywords.
|
||||
effective_style_priority = style_priority
|
||||
if variance_info:
|
||||
effective_style_priority = variance_info["style_keywords"] + style_priority
|
||||
|
||||
# Step 3: Multi-domain search with style priority hints
|
||||
search_results = self._multi_domain_search(query, style_priority)
|
||||
search_results = self._multi_domain_search(query, effective_style_priority)
|
||||
search_results["product"] = product_result # Reuse product search
|
||||
|
||||
# Step 4: Select best matches from each domain using priority
|
||||
@@ -191,11 +240,24 @@ class DesignSystemGenerator:
|
||||
typography_results = self._extract_results(search_results.get("typography", {}))
|
||||
landing_results = self._extract_results(search_results.get("landing", {}))
|
||||
|
||||
best_style = self._select_best_match(style_results, reasoning.get("style_priority", []))
|
||||
best_style = self._select_best_match(style_results, effective_style_priority)
|
||||
best_color = color_results[0] if color_results else {}
|
||||
best_typography = typography_results[0] if typography_results else {}
|
||||
best_landing = landing_results[0] if landing_results else {}
|
||||
|
||||
# MOTION_INTENSITY dial: pull a matching GSAP skeleton from motion.csv
|
||||
# (domain key is "gsap", not "motion" - PR #296 already owns the "motion"
|
||||
# domain for Emil Kowalski's motion-design principles, motion-principles.csv).
|
||||
motion_snippet = {}
|
||||
if motion_info:
|
||||
motion_result = search(f"{query} {motion_info['tier']}", "gsap", 5)
|
||||
motion_matches = motion_result.get("results", [])
|
||||
tiered = [m for m in motion_matches if m.get("Intensity Tier") == motion_info["tier"]]
|
||||
if tiered:
|
||||
motion_snippet = tiered[0]
|
||||
elif motion_matches:
|
||||
motion_snippet = motion_matches[0]
|
||||
|
||||
# Step 5: Build final recommendation
|
||||
# Combine effects from both reasoning and style search
|
||||
style_effects = best_style.get("Effects & Animation", "")
|
||||
@@ -250,7 +312,17 @@ class DesignSystemGenerator:
|
||||
"key_effects": combined_effects,
|
||||
"anti_patterns": reasoning.get("anti_patterns", ""),
|
||||
"decision_rules": reasoning.get("decision_rules", {}),
|
||||
"severity": reasoning.get("severity", "MEDIUM")
|
||||
"severity": reasoning.get("severity", "MEDIUM"),
|
||||
"dials": {
|
||||
"variance": variance_info["value"] if variance_info else None,
|
||||
"variance_label": variance_info["label"] if variance_info else None,
|
||||
"motion": motion_info["value"] if motion_info else None,
|
||||
"motion_label": motion_info["label"] if motion_info else None,
|
||||
"density": density_info["value"] if density_info else None,
|
||||
"density_label": density_info["label"] if density_info else None,
|
||||
},
|
||||
"motion_snippet": motion_snippet,
|
||||
"spacing_scale": density_info["spacing"] if density_info else None,
|
||||
}
|
||||
|
||||
|
||||
@@ -296,6 +368,8 @@ def format_ascii_box(design_system: dict) -> str:
|
||||
typography = design_system.get("typography", {})
|
||||
effects = design_system.get("key_effects", "")
|
||||
anti_patterns = design_system.get("anti_patterns", "")
|
||||
dials = design_system.get("dials", {})
|
||||
motion_snippet = design_system.get("motion_snippet", {})
|
||||
|
||||
def wrap_text(text: str, prefix: str, width: int) -> list:
|
||||
"""Wrap long text into multiple lines."""
|
||||
@@ -329,6 +403,16 @@ def format_ascii_box(design_system: dict) -> str:
|
||||
lines.append("╚" + "═" * w + "╝")
|
||||
lines.append("┌" + "─" * w + "┐")
|
||||
|
||||
# Design Dials section (only if at least one dial was set)
|
||||
if any(dials.get(k) is not None for k in ("variance", "motion", "density")):
|
||||
lines.append(section_header("DESIGN DIALS", BOX_WIDTH + 1))
|
||||
if dials.get("variance") is not None:
|
||||
lines.append(f"│ Variance: {dials['variance']}/10 — {dials['variance_label']}".ljust(BOX_WIDTH) + "│")
|
||||
if dials.get("motion") is not None:
|
||||
lines.append(f"│ Motion: {dials['motion']}/10 — {dials['motion_label']}".ljust(BOX_WIDTH) + "│")
|
||||
if dials.get("density") is not None:
|
||||
lines.append(f"│ Density: {dials['density']}/10 — {dials['density_label']}".ljust(BOX_WIDTH) + "│")
|
||||
|
||||
# Pattern section
|
||||
lines.append(section_header("PATTERN", BOX_WIDTH + 1))
|
||||
lines.append(f"│ Name: {pattern.get('name', '')}".ljust(BOX_WIDTH) + "│")
|
||||
@@ -402,6 +486,17 @@ def format_ascii_box(design_system: dict) -> str:
|
||||
for line in wrap_text(effects, "│ ", BOX_WIDTH):
|
||||
lines.append(line.ljust(BOX_WIDTH) + "│")
|
||||
|
||||
# Motion section (GSAP skeleton, only if --motion dial was set)
|
||||
if motion_snippet:
|
||||
lines.append(section_header("MOTION", BOX_WIDTH + 1))
|
||||
lines.append(f"│ {motion_snippet.get('Category', '')} ({motion_snippet.get('Intensity Tier', '')})".ljust(BOX_WIDTH) + "│")
|
||||
lines.append(f"│ Trigger: {motion_snippet.get('Trigger', '')} | Duration: {motion_snippet.get('Duration', '')} | Easing: {motion_snippet.get('Easing', '')}".ljust(BOX_WIDTH) + "│")
|
||||
for line in wrap_text(f"GSAP: {motion_snippet.get('GSAP Snippet', '')}", "│ ", BOX_WIDTH):
|
||||
lines.append(line.ljust(BOX_WIDTH) + "│")
|
||||
if motion_snippet.get("Framework Notes"):
|
||||
for line in wrap_text(f"Framework: {motion_snippet.get('Framework Notes', '')}", "│ ", BOX_WIDTH):
|
||||
lines.append(line.ljust(BOX_WIDTH) + "│")
|
||||
|
||||
# Anti-patterns section
|
||||
if anti_patterns:
|
||||
lines.append(section_header("AVOID", BOX_WIDTH + 1))
|
||||
@@ -436,11 +531,24 @@ def format_markdown(design_system: dict) -> str:
|
||||
typography = design_system.get("typography", {})
|
||||
effects = design_system.get("key_effects", "")
|
||||
anti_patterns = design_system.get("anti_patterns", "")
|
||||
dials = design_system.get("dials", {})
|
||||
motion_snippet = design_system.get("motion_snippet", {})
|
||||
|
||||
lines = []
|
||||
lines.append(f"## Design System: {project}")
|
||||
lines.append("")
|
||||
|
||||
# Design Dials section (only if at least one dial was set)
|
||||
if any(dials.get(k) is not None for k in ("variance", "motion", "density")):
|
||||
lines.append("### Design Dials")
|
||||
if dials.get("variance") is not None:
|
||||
lines.append(f"- **Variance:** {dials['variance']}/10 — {dials['variance_label']}")
|
||||
if dials.get("motion") is not None:
|
||||
lines.append(f"- **Motion:** {dials['motion']}/10 — {dials['motion_label']}")
|
||||
if dials.get("density") is not None:
|
||||
lines.append(f"- **Density:** {dials['density']}/10 — {dials['density_label']}")
|
||||
lines.append("")
|
||||
|
||||
# Pattern section
|
||||
lines.append("### Pattern")
|
||||
lines.append(f"- **Name:** {pattern.get('name', '')}")
|
||||
@@ -515,6 +623,23 @@ def format_markdown(design_system: dict) -> str:
|
||||
lines.append(f"{effects}")
|
||||
lines.append("")
|
||||
|
||||
# Motion section (GSAP skeleton, only if --motion dial was set)
|
||||
if motion_snippet:
|
||||
lines.append("### Motion")
|
||||
lines.append(f"**{motion_snippet.get('Category', '')}** ({motion_snippet.get('Intensity Tier', '')}) — Trigger: {motion_snippet.get('Trigger', '')} | Duration: {motion_snippet.get('Duration', '')} | Easing: `{motion_snippet.get('Easing', '')}`")
|
||||
lines.append("```js")
|
||||
lines.append(motion_snippet.get("GSAP Snippet", ""))
|
||||
lines.append("```")
|
||||
if motion_snippet.get("Framework Notes"):
|
||||
lines.append(f"*Framework notes: {motion_snippet.get('Framework Notes', '')}*")
|
||||
motion_do = motion_snippet.get("Do", "")
|
||||
motion_dont = motion_snippet.get("Don't", "")
|
||||
if motion_do:
|
||||
lines.append(f"- ✅ {motion_do}")
|
||||
if motion_dont:
|
||||
lines.append(f"- ❌ {motion_dont}")
|
||||
lines.append("")
|
||||
|
||||
# Anti-patterns section
|
||||
if anti_patterns:
|
||||
lines.append("### Avoid (Anti-patterns)")
|
||||
@@ -537,8 +662,9 @@ def format_markdown(design_system: dict) -> str:
|
||||
|
||||
|
||||
# ============ MAIN ENTRY POINT ============
|
||||
def generate_design_system(query: str, project_name: str = None, output_format: str = "ascii",
|
||||
persist: bool = False, page: str = None, output_dir: str = None) -> str:
|
||||
def generate_design_system(query: str, project_name: str = None, output_format: str = "ascii",
|
||||
persist: bool = False, page: str = None, output_dir: str = None,
|
||||
variance: int = None, motion: int = None, density: int = None) -> str:
|
||||
"""
|
||||
Main entry point for design system generation.
|
||||
|
||||
@@ -549,13 +675,16 @@ def generate_design_system(query: str, project_name: str = None, output_format:
|
||||
persist: If True, save design system to design-system/ folder
|
||||
page: Optional page name for page-specific override file
|
||||
output_dir: Optional output directory (defaults to current working directory)
|
||||
variance: Optional 1-10 DESIGN_VARIANCE dial (1=centered/minimal, 10=bold/asymmetric)
|
||||
motion: Optional 1-10 MOTION_INTENSITY dial, pulls a matching GSAP snippet from motion.csv
|
||||
density: Optional 1-10 VISUAL_DENSITY dial, overrides the spacing scale (1=spacious, 10=dense)
|
||||
|
||||
Returns:
|
||||
Formatted design system string
|
||||
"""
|
||||
generator = DesignSystemGenerator()
|
||||
design_system = generator.generate(query, project_name)
|
||||
|
||||
design_system = generator.generate(query, project_name, variance=variance, motion=motion, density=density)
|
||||
|
||||
# Persist to files if requested
|
||||
if persist:
|
||||
persist_design_system(design_system, page, output_dir, query)
|
||||
@@ -627,11 +756,14 @@ def format_master_md(design_system: dict) -> str:
|
||||
typography = design_system.get("typography", {})
|
||||
effects = design_system.get("key_effects", "")
|
||||
anti_patterns = design_system.get("anti_patterns", "")
|
||||
|
||||
dials = design_system.get("dials", {})
|
||||
motion_snippet = design_system.get("motion_snippet", {})
|
||||
spacing_scale = design_system.get("spacing_scale")
|
||||
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
lines = []
|
||||
|
||||
|
||||
# Logic header
|
||||
lines.append("# Design System Master File")
|
||||
lines.append("")
|
||||
@@ -644,6 +776,15 @@ def format_master_md(design_system: dict) -> str:
|
||||
lines.append(f"**Project:** {project}")
|
||||
lines.append(f"**Generated:** {timestamp}")
|
||||
lines.append(f"**Category:** {design_system.get('category', 'General')}")
|
||||
if any(dials.get(k) is not None for k in ("variance", "motion", "density")):
|
||||
dial_parts = []
|
||||
if dials.get("variance") is not None:
|
||||
dial_parts.append(f"Variance {dials['variance']}/10 ({dials['variance_label']})")
|
||||
if dials.get("motion") is not None:
|
||||
dial_parts.append(f"Motion {dials['motion']}/10 ({dials['motion_label']})")
|
||||
if dials.get("density") is not None:
|
||||
dial_parts.append(f"Density {dials['density']}/10 ({dials['density_label']})")
|
||||
lines.append(f"**Design Dials:** {' | '.join(dial_parts)}")
|
||||
lines.append("")
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
@@ -695,18 +836,24 @@ def format_master_md(design_system: dict) -> str:
|
||||
lines.append("```")
|
||||
lines.append("")
|
||||
|
||||
# Spacing Variables
|
||||
# Spacing Variables (overridden by the VISUAL_DENSITY dial when set)
|
||||
default_spacing = DIAL_TIERS["density"][1][2]["spacing"] # mid-tier = the historical defaults
|
||||
scale = spacing_scale or default_spacing
|
||||
spacing_usage = {
|
||||
"xs": "Tight gaps", "sm": "Icon gaps, inline spacing", "md": "Standard padding",
|
||||
"lg": "Section padding", "xl": "Large gaps", "2xl": "Section margins", "3xl": "Hero padding",
|
||||
}
|
||||
lines.append("### Spacing Variables")
|
||||
lines.append("")
|
||||
if spacing_scale:
|
||||
lines.append(f"*Density: {dials.get('density')}/10 — {dials.get('density_label')}*")
|
||||
lines.append("")
|
||||
lines.append("| Token | Value | Usage |")
|
||||
lines.append("|-------|-------|-------|")
|
||||
lines.append("| `--space-xs` | `4px` / `0.25rem` | Tight gaps |")
|
||||
lines.append("| `--space-sm` | `8px` / `0.5rem` | Icon gaps, inline spacing |")
|
||||
lines.append("| `--space-md` | `16px` / `1rem` | Standard padding |")
|
||||
lines.append("| `--space-lg` | `24px` / `1.5rem` | Section padding |")
|
||||
lines.append("| `--space-xl` | `32px` / `2rem` | Large gaps |")
|
||||
lines.append("| `--space-2xl` | `48px` / `3rem` | Section margins |")
|
||||
lines.append("| `--space-3xl` | `64px` / `4rem` | Hero padding |")
|
||||
for token in ("xs", "sm", "md", "lg", "xl", "2xl", "3xl"):
|
||||
px_value = scale[token]
|
||||
rem_value = f"{int(px_value.rstrip('px')) / 16:g}rem"
|
||||
lines.append(f"| `--space-{token}` | `{px_value}` / `{rem_value}` | {spacing_usage[token]} |")
|
||||
lines.append("")
|
||||
|
||||
# Shadow Depths
|
||||
@@ -848,7 +995,32 @@ def format_master_md(design_system: dict) -> str:
|
||||
lines.append(f"- **CTA Placement:** {pattern.get('cta_placement', '')}")
|
||||
lines.append(f"- **Section Order:** {pattern.get('sections', '')}")
|
||||
lines.append("")
|
||||
|
||||
|
||||
# Motion section (GSAP skeleton, only if --motion dial was set)
|
||||
if motion_snippet:
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
lines.append("## Motion")
|
||||
lines.append("")
|
||||
lines.append(f"**{motion_snippet.get('Category', '')}** ({motion_snippet.get('Intensity Tier', '')}) — Trigger: {motion_snippet.get('Trigger', '')} | Duration: {motion_snippet.get('Duration', '')} | Easing: `{motion_snippet.get('Easing', '')}`")
|
||||
lines.append("")
|
||||
lines.append("```js")
|
||||
lines.append(motion_snippet.get("GSAP Snippet", ""))
|
||||
lines.append("```")
|
||||
lines.append("")
|
||||
if motion_snippet.get("Framework Notes"):
|
||||
lines.append(f"**Framework notes:** {motion_snippet.get('Framework Notes', '')}")
|
||||
lines.append("")
|
||||
motion_do = motion_snippet.get("Do", "")
|
||||
motion_dont = motion_snippet.get("Don't", "")
|
||||
if motion_do:
|
||||
lines.append(f"- ✅ {motion_do}")
|
||||
if motion_dont:
|
||||
lines.append(f"- ❌ {motion_dont}")
|
||||
if motion_snippet.get("Performance Notes"):
|
||||
lines.append(f"- ⚡ {motion_snippet.get('Performance Notes', '')}")
|
||||
lines.append("")
|
||||
|
||||
# Anti-Patterns section
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
Reference in New Issue
Block a user