Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/skills/brand/scripts/sync-brand-to-tokens.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -250,7 +250,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'
});
Expand Down
111 changes: 63 additions & 48 deletions .claude/skills/design-system/scripts/generate-slide.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@
"""

import argparse
import html as html_mod
import json
from pathlib import Path
from datetime import datetime


def esc(value):
"""Escape user-provided values for safe HTML embedding."""
return html_mod.escape(str(value))

# Paths
SCRIPT_DIR = Path(__file__).parent
DATA_DIR = SCRIPT_DIR.parent / "data"
Expand Down Expand Up @@ -412,16 +418,16 @@ def generate_title_slide(data):
"""Title slide with gradient headline"""
return f'''
<section class="slide slide--glow flex flex-col items-center justify-center text-center">
<div class="badge mb-6">{data.get('badge', 'Pitch Deck')}</div>
<h1 class="slide-title mb-6">{data.get('title', 'Your Title Here')}</h1>
<p class="slide-subheading mb-8">{data.get('subtitle', 'Your compelling subtitle')}</p>
<div class="badge mb-6">{esc(data.get('badge', 'Pitch Deck'))}</div>
<h1 class="slide-title mb-6">{esc(data.get('title', 'Your Title Here'))}</h1>
<p class="slide-subheading mb-8">{esc(data.get('subtitle', 'Your compelling subtitle'))}</p>
<div class="flex gap-4">
<a href="#" class="btn btn-primary">{data.get('cta', 'Get Started')}</a>
<a href="#" class="btn btn-secondary">{data.get('secondary_cta', 'Learn More')}</a>
<a href="#" class="btn btn-primary">{esc(data.get('cta', 'Get Started'))}</a>
<a href="#" class="btn btn-secondary">{esc(data.get('secondary_cta', 'Learn More'))}</a>
</div>
<div class="slide-footer">
<span>{data.get('company', 'Company Name')}</span>
<span>{data.get('date', datetime.now().strftime('%B %Y'))}</span>
<span>{esc(data.get('company', 'Company Name'))}</span>
<span>{esc(data.get('date', datetime.now().strftime('%B %Y')))}</span>
</div>
</section>
'''
Expand All @@ -432,27 +438,27 @@ def generate_problem_slide(data):
return f'''
<section class="slide slide--surface">
<div class="badge mb-6">The Problem</div>
<h2 class="slide-heading mb-8">{data.get('headline', 'The problem your audience faces')}</h2>
<h2 class="slide-heading mb-8">{esc(data.get('headline', 'The problem your audience faces'))}</h2>
<div class="grid grid-3 gap-8">
<div class="card">
<div class="text-primary" style="font-size: var(--primitive-fontSize-4xl); margin-bottom: var(--primitive-spacing-4);">01</div>
<h4 style="margin-bottom: var(--primitive-spacing-2); font-size: var(--primitive-fontSize-xl);">{data.get('pain_1_title', 'Pain Point 1')}</h4>
<p class="text-muted">{data.get('pain_1_desc', 'Description of the first pain point')}</p>
<h4 style="margin-bottom: var(--primitive-spacing-2); font-size: var(--primitive-fontSize-xl);">{esc(data.get('pain_1_title', 'Pain Point 1'))}</h4>
<p class="text-muted">{esc(data.get('pain_1_desc', 'Description of the first pain point'))}</p>
</div>
<div class="card">
<div class="text-secondary" style="font-size: var(--primitive-fontSize-4xl); margin-bottom: var(--primitive-spacing-4);">02</div>
<h4 style="margin-bottom: var(--primitive-spacing-2); font-size: var(--primitive-fontSize-xl);">{data.get('pain_2_title', 'Pain Point 2')}</h4>
<p class="text-muted">{data.get('pain_2_desc', 'Description of the second pain point')}</p>
<h4 style="margin-bottom: var(--primitive-spacing-2); font-size: var(--primitive-fontSize-xl);">{esc(data.get('pain_2_title', 'Pain Point 2'))}</h4>
<p class="text-muted">{esc(data.get('pain_2_desc', 'Description of the second pain point'))}</p>
</div>
<div class="card">
<div class="text-accent" style="font-size: var(--primitive-fontSize-4xl); margin-bottom: var(--primitive-spacing-4);">03</div>
<h4 style="margin-bottom: var(--primitive-spacing-2); font-size: var(--primitive-fontSize-xl);">{data.get('pain_3_title', 'Pain Point 3')}</h4>
<p class="text-muted">{data.get('pain_3_desc', 'Description of the third pain point')}</p>
<h4 style="margin-bottom: var(--primitive-spacing-2); font-size: var(--primitive-fontSize-xl);">{esc(data.get('pain_3_title', 'Pain Point 3'))}</h4>
<p class="text-muted">{esc(data.get('pain_3_desc', 'Description of the third pain point'))}</p>
</div>
</div>
<div class="slide-footer">
<span>{data.get('company', 'Company Name')}</span>
<span>{data.get('page', '2')}</span>
<span>{esc(data.get('company', 'Company Name'))}</span>
<span>{esc(data.get('page', '2'))}</span>
</div>
</section>
'''
Expand All @@ -463,28 +469,28 @@ def generate_solution_slide(data):
return f'''
<section class="slide">
<div class="badge mb-6">The Solution</div>
<h2 class="slide-heading mb-8">{data.get('headline', 'How we solve this')}</h2>
<h2 class="slide-heading mb-8">{esc(data.get('headline', 'How we solve this'))}</h2>
<div class="flex gap-8" style="flex: 1;">
<div style="flex: 1;">
<div class="feature-item">
<div class="feature-icon">&#10003;</div>
<div class="feature-content">
<h4>{data.get('feature_1_title', 'Feature 1')}</h4>
<p>{data.get('feature_1_desc', 'Description of feature 1')}</p>
<h4>{esc(data.get('feature_1_title', 'Feature 1'))}</h4>
<p>{esc(data.get('feature_1_desc', 'Description of feature 1'))}</p>
</div>
</div>
<div class="feature-item">
<div class="feature-icon">&#10003;</div>
<div class="feature-content">
<h4>{data.get('feature_2_title', 'Feature 2')}</h4>
<p>{data.get('feature_2_desc', 'Description of feature 2')}</p>
<h4>{esc(data.get('feature_2_title', 'Feature 2'))}</h4>
<p>{esc(data.get('feature_2_desc', 'Description of feature 2'))}</p>
</div>
</div>
<div class="feature-item">
<div class="feature-icon">&#10003;</div>
<div class="feature-content">
<h4>{data.get('feature_3_title', 'Feature 3')}</h4>
<p>{data.get('feature_3_desc', 'Description of feature 3')}</p>
<h4>{esc(data.get('feature_3_title', 'Feature 3'))}</h4>
<p>{esc(data.get('feature_3_desc', 'Description of feature 3'))}</p>
</div>
</div>
</div>
Expand All @@ -496,8 +502,8 @@ def generate_solution_slide(data):
</div>
</div>
<div class="slide-footer">
<span>{data.get('company', 'Company Name')}</span>
<span>{data.get('page', '3')}</span>
<span>{esc(data.get('company', 'Company Name'))}</span>
<span>{esc(data.get('page', '3'))}</span>
</div>
</section>
'''
Expand All @@ -514,21 +520,21 @@ def generate_metrics_slide(data):

metrics_html = ''.join([f'''
<div class="card metric">
<div class="metric-value">{m['value']}</div>
<div class="metric-label">{m['label']}</div>
<div class="metric-value">{esc(m['value'])}</div>
<div class="metric-label">{esc(m['label'])}</div>
</div>
''' for m in metrics[:4]])

return f'''
<section class="slide slide--surface slide--glow">
<div class="badge mb-6">Traction</div>
<h2 class="slide-heading mb-8 text-center">{data.get('headline', 'Our Growth')}</h2>
<h2 class="slide-heading mb-8 text-center">{esc(data.get('headline', 'Our Growth'))}</h2>
<div class="grid grid-4 gap-6" style="flex: 1; align-items: center;">
{metrics_html}
</div>
<div class="slide-footer">
<span>{data.get('company', 'Company Name')}</span>
<span>{data.get('page', '4')}</span>
<span>{esc(data.get('company', 'Company Name'))}</span>
<span>{esc(data.get('page', '4'))}</span>
</div>
</section>
'''
Expand All @@ -544,25 +550,25 @@ def generate_chart_slide(data):
])

bars_html = ''.join([f'''
<div class="bar" style="height: {b['value']}%;">
<span class="bar-value">{b.get('display', str(b['value']) + '%')}</span>
<span class="bar-label">{b['label']}</span>
<div class="bar" style="height: {int(b['value']) if isinstance(b['value'], (int, float)) else 0}%;">
<span class="bar-value">{esc(b.get('display', str(b['value']) + '%'))}</span>
<span class="bar-label">{esc(b['label'])}</span>
</div>
''' for b in bars])

return f'''
<section class="slide">
<div class="badge mb-6">{data.get('badge', 'Growth')}</div>
<h2 class="slide-heading mb-8">{data.get('headline', 'Revenue Growth')}</h2>
<div class="badge mb-6">{esc(data.get('badge', 'Growth'))}</div>
<h2 class="slide-heading mb-8">{esc(data.get('headline', 'Revenue Growth'))}</h2>
<div class="chart-container" style="flex: 1;">
<div class="chart-title">{data.get('chart_title', 'Quarterly Revenue')}</div>
<div class="chart-title">{esc(data.get('chart_title', 'Quarterly Revenue'))}</div>
<div class="bar-chart" style="flex: 1; padding-bottom: 40px;">
{bars_html}
</div>
</div>
<div class="slide-footer">
<span>{data.get('company', 'Company Name')}</span>
<span>{data.get('page', '5')}</span>
<span>{esc(data.get('company', 'Company Name'))}</span>
<span>{esc(data.get('page', '5'))}</span>
</div>
</section>
'''
Expand All @@ -574,30 +580,39 @@ def generate_testimonial_slide(data):
<section class="slide slide--surface flex flex-col justify-center">
<div class="badge mb-6">What They Say</div>
<div class="testimonial" style="max-width: 900px;">
<p class="testimonial-quote">"{data.get('quote', 'This product changed how we work. Incredible results.')}"</p>
<p class="testimonial-author">{data.get('author', 'Jane Doe')}</p>
<p class="testimonial-role">{data.get('role', 'CEO, Example Company')}</p>
<p class="testimonial-quote">"{esc(data.get('quote', 'This product changed how we work. Incredible results.'))}"</p>
<p class="testimonial-author">{esc(data.get('author', 'Jane Doe'))}</p>
<p class="testimonial-role">{esc(data.get('role', 'CEO, Example Company'))}</p>
</div>
<div class="slide-footer">
<span>{data.get('company', 'Company Name')}</span>
<span>{data.get('page', '6')}</span>
<span>{esc(data.get('company', 'Company Name'))}</span>
<span>{esc(data.get('page', '6'))}</span>
</div>
</section>
'''


def _sanitize_url(url):
"""Ensure URL is safe for href attribute (no javascript: or data: URIs)."""
url = str(url).strip()
if url.startswith(('http://', 'https://', '/', '#', 'mailto:')):
return html_mod.escape(url, quote=True)
return '#'


def generate_cta_slide(data):
"""Closing CTA slide"""
cta_url = _sanitize_url(data.get('cta_url', '#'))
return f'''
<section class="slide slide--gradient flex flex-col items-center justify-center text-center">
<h2 class="slide-heading mb-6" style="color: var(--color-foreground);">{data.get('headline', 'Ready to get started?')}</h2>
<p class="slide-body mb-8" style="color: rgba(255,255,255,0.8);">{data.get('subheadline', 'Join thousands of teams already using our solution.')}</p>
<h2 class="slide-heading mb-6" style="color: var(--color-foreground);">{esc(data.get('headline', 'Ready to get started?'))}</h2>
<p class="slide-body mb-8" style="color: rgba(255,255,255,0.8);">{esc(data.get('subheadline', 'Join thousands of teams already using our solution.'))}</p>
<div class="flex gap-4">
<a href="{data.get('cta_url', '#')}" class="btn" style="background: var(--color-foreground); color: var(--color-primary);">{data.get('cta', 'Start Free Trial')}</a>
<a href="{cta_url}" class="btn" style="background: var(--color-foreground); color: var(--color-primary);">{esc(data.get('cta', 'Start Free Trial'))}</a>
</div>
<div class="slide-footer" style="border-color: rgba(255,255,255,0.2); color: rgba(255,255,255,0.6);">
<span>{data.get('contact', 'contact@example.com')}</span>
<span>{data.get('website', 'www.example.com')}</span>
<span>{esc(data.get('contact', 'contact@example.com'))}</span>
<span>{esc(data.get('website', 'www.example.com'))}</span>
</div>
</section>
'''
Expand Down
29 changes: 18 additions & 11 deletions .claude/skills/design/scripts/cip/render-html.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""

import argparse
import html
import json
import os
import sys
Expand Down Expand Up @@ -140,13 +141,19 @@ def generate_html(brand_name, industry, images_dir, output_path=None, style=None
style_info = brief.get("style", {})
industry_info = brief.get("industry", {})

# Sanitize user-provided values for safe HTML embedding
safe_brand = html.escape(brand_name)
safe_industry = html.escape(industry_info.get("Industry", industry.title()))
safe_style = html.escape(style_info.get("Style Name", "Corporate"))
safe_mood = html.escape(style_info.get("Mood", "Professional"))

# Build HTML
html_parts = [f'''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{brand_name} - Corporate Identity Program</title>
<title>{safe_brand} - Corporate Identity Program</title>
<style>
* {{
margin: 0;
Expand Down Expand Up @@ -309,20 +316,20 @@ def generate_html(brand_name, industry, images_dir, output_path=None, style=None
</head>
<body>
<section class="hero">
<h1>{brand_name}</h1>
<h1>{safe_brand}</h1>
<p class="subtitle">Corporate Identity Program</p>
<div class="meta">
<div class="meta-item">
<div class="meta-label">Industry</div>
<div class="meta-value">{industry_info.get("Industry", industry.title())}</div>
<div class="meta-value">{safe_industry}</div>
</div>
<div class="meta-item">
<div class="meta-label">Style</div>
<div class="meta-value">{style_info.get("Style Name", "Corporate")}</div>
<div class="meta-value">{safe_style}</div>
</div>
<div class="meta-item">
<div class="meta-label">Mood</div>
<div class="meta-value">{style_info.get("Mood", "Professional")}</div>
<div class="meta-value">{safe_mood}</div>
</div>
<div class="meta-item">
<div class="meta-label">Deliverables</div>
Expand Down Expand Up @@ -352,13 +359,13 @@ def generate_html(brand_name, industry, images_dir, output_path=None, style=None
html_parts.append(f'''
<div class="deliverable">
<div class="deliverable-image">
<img src="{img_src}" alt="{info['title']}" loading="lazy">
<img src="{img_src}" alt="{html.escape(info['title'])}" loading="lazy">
</div>
<div class="deliverable-content">
<h3 class="deliverable-title">{info['title']}</h3>
<p class="deliverable-concept">{info['concept']}</p>
<p class="deliverable-purpose">{info['purpose']}</p>
<span class="deliverable-specs">{info['specs']}</span>
<h3 class="deliverable-title">{html.escape(info['title'])}</h3>
<p class="deliverable-concept">{html.escape(info['concept'])}</p>
<p class="deliverable-purpose">{html.escape(info['purpose'])}</p>
<span class="deliverable-specs">{html.escape(info['specs'])}</span>
</div>
</div>
''')
Expand All @@ -368,7 +375,7 @@ def generate_html(brand_name, industry, images_dir, output_path=None, style=None
</section>

<footer class="footer">
<p><strong>{brand_name}</strong> Corporate Identity Program</p>
<p><strong>{safe_brand}</strong> Corporate Identity Program</p>
<p>Generated on {datetime.now().strftime("%B %d, %Y")}</p>
<p style="margin-top: 1rem; font-size: 0.8rem;">Powered by CIP Design Skill</p>
</footer>
Expand Down
Binary file removed .claude/skills/ui-styling/scripts/.coverage
Binary file not shown.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ out/
.env.local
.env.*.local

# Coverage
.coverage

# Local settings
.claude/settings.local.json
.claude/session-state/
Expand Down
Loading