Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
AI-powered design system generator that produces complete, tailored design systems from project requirements.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/generate-slide.py
1#!/usr/bin/env python32# -*- coding: utf-8 -*-3"""4Slide Generator - Generates HTML slides using design tokens5ALL styles MUST use CSS variables from design-tokens.css6NO hardcoded colors, fonts, or spacing allowed7"""89import argparse10import json11from pathlib import Path12from datetime import datetime1314# Paths15SCRIPT_DIR = Path(__file__).parent16DATA_DIR = SCRIPT_DIR.parent / "data"17TOKENS_CSS = Path(__file__).resolve().parents[4] / "assets" / "design-tokens.css"18TOKENS_JSON = Path(__file__).resolve().parents[4] / "assets" / "design-tokens.json"19OUTPUT_DIR = Path(__file__).resolve().parents[4] / "assets" / "designs" / "slides"2021# ============ BRAND-COMPLIANT SLIDE TEMPLATE ============22# ALL values reference CSS variables from design-tokens.css2324SLIDE_TEMPLATE = '''<!DOCTYPE html>25<html lang="en" data-theme="dark">26<head>27<meta charset="UTF-8">28<meta name="viewport" content="width=device-width, initial-scale=1.0">29<title>{title}</title>3031<!-- Brand Fonts -->32<link rel="preconnect" href="https://fonts.googleapis.com">33<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet">3435<!-- Design Tokens - SINGLE SOURCE OF TRUTH -->36<link rel="stylesheet" href="{tokens_css_path}">3738<style>39/* ============================================40STRICT TOKEN USAGE - NO HARDCODED VALUES41All styles MUST use var(--token-name)42============================================ */4344* {{45margin: 0;46padding: 0;47box-sizing: border-box;48}}4950html, body {{51width: 100%;52height: 100%;53}}5455body {{56font-family: var(--typography-font-body);57background: var(--color-background);58color: var(--color-foreground);59line-height: var(--primitive-lineHeight-relaxed);60}}6162/* Slide Container - 16:9 aspect ratio */63.slide-deck {{64width: 100%;65max-width: 1920px;66margin: 0 auto;67}}6869.slide {{70width: 100%;71aspect-ratio: 16 / 9;72padding: var(--slide-padding);73background: var(--slide-bg);74display: flex;75flex-direction: column;76position: relative;77overflow: hidden;78}}7980.slide + .slide {{81margin-top: var(--primitive-spacing-8);82}}8384/* Background Variants */85.slide--surface {{86background: var(--slide-bg-surface);87}}8889.slide--gradient {{90background: var(--slide-bg-gradient);91}}9293.slide--glow::before {{94content: '';95position: absolute;96top: 50%;97left: 50%;98transform: translate(-50%, -50%);99width: 150%;100height: 150%;101background: var(--primitive-gradient-glow);102pointer-events: none;103}}104105/* Typography - MUST use token fonts and sizes */106h1, h2, h3, h4, h5, h6 {{107font-family: var(--typography-font-heading);108font-weight: var(--primitive-fontWeight-bold);109line-height: var(--primitive-lineHeight-tight);110}}111112.slide-title {{113font-size: var(--slide-title-size);114background: var(--primitive-gradient-primary);115-webkit-background-clip: text;116-webkit-text-fill-color: transparent;117background-clip: text;118}}119120.slide-heading {{121font-size: var(--slide-heading-size);122color: var(--color-foreground);123}}124125.slide-subheading {{126font-size: var(--primitive-fontSize-3xl);127color: var(--color-foreground-secondary);128font-weight: var(--primitive-fontWeight-medium);129}}130131.slide-body {{132font-size: var(--slide-body-size);133color: var(--color-foreground-secondary);134max-width: 80ch;135}}136137/* Brand Colors - Primary/Secondary/Accent */138.text-primary {{ color: var(--color-primary); }}139.text-secondary {{ color: var(--color-secondary); }}140.text-accent {{ color: var(--color-accent); }}141.text-muted {{ color: var(--color-foreground-muted); }}142143.bg-primary {{ background: var(--color-primary); }}144.bg-secondary {{ background: var(--color-secondary); }}145.bg-accent {{ background: var(--color-accent); }}146.bg-surface {{ background: var(--color-surface); }}147148/* Cards - Using component tokens */149.card {{150background: var(--card-bg);151border: 1px solid var(--card-border);152border-radius: var(--card-radius);153padding: var(--card-padding);154box-shadow: var(--card-shadow);155transition: border-color var(--primitive-duration-base) var(--primitive-easing-out);156}}157158.card:hover {{159border-color: var(--card-border-hover);160}}161162/* Buttons - Using component tokens */163.btn {{164display: inline-flex;165align-items: center;166justify-content: center;167padding: var(--button-primary-padding-y) var(--button-primary-padding-x);168border-radius: var(--button-primary-radius);169font-size: var(--button-primary-font-size);170font-weight: var(--button-primary-font-weight);171font-family: var(--typography-font-body);172text-decoration: none;173cursor: pointer;174border: none;175transition: all var(--primitive-duration-base) var(--primitive-easing-out);176}}177178.btn-primary {{179background: var(--button-primary-bg);180color: var(--button-primary-fg);181box-shadow: var(--button-primary-shadow);182}}183184.btn-primary:hover {{185background: var(--button-primary-bg-hover);186}}187188.btn-secondary {{189background: transparent;190color: var(--color-primary);191border: 2px solid var(--color-primary);192}}193194/* Layout Utilities */195.flex {{ display: flex; }}196.flex-col {{ flex-direction: column; }}197.items-center {{ align-items: center; }}198.justify-center {{ justify-content: center; }}199.justify-between {{ justify-content: space-between; }}200.gap-4 {{ gap: var(--primitive-spacing-4); }}201.gap-6 {{ gap: var(--primitive-spacing-6); }}202.gap-8 {{ gap: var(--primitive-spacing-8); }}203204.grid {{ display: grid; }}205.grid-2 {{ grid-template-columns: repeat(2, 1fr); }}206.grid-3 {{ grid-template-columns: repeat(3, 1fr); }}207.grid-4 {{ grid-template-columns: repeat(4, 1fr); }}208209.text-center {{ text-align: center; }}210.mt-auto {{ margin-top: auto; }}211.mb-4 {{ margin-bottom: var(--primitive-spacing-4); }}212.mb-6 {{ margin-bottom: var(--primitive-spacing-6); }}213.mb-8 {{ margin-bottom: var(--primitive-spacing-8); }}214215/* Metric Cards */216.metric {{217text-align: center;218padding: var(--primitive-spacing-6);219}}220221.metric-value {{222font-family: var(--typography-font-heading);223font-size: var(--primitive-fontSize-6xl);224font-weight: var(--primitive-fontWeight-bold);225background: var(--primitive-gradient-primary);226-webkit-background-clip: text;227-webkit-text-fill-color: transparent;228background-clip: text;229}}230231.metric-label {{232font-size: var(--primitive-fontSize-lg);233color: var(--color-foreground-secondary);234margin-top: var(--primitive-spacing-2);235}}236237/* Feature List */238.feature-item {{239display: flex;240align-items: flex-start;241gap: var(--primitive-spacing-4);242padding: var(--primitive-spacing-4) 0;243}}244245.feature-icon {{246width: 48px;247height: 48px;248border-radius: var(--primitive-radius-lg);249background: var(--color-surface-elevated);250display: flex;251align-items: center;252justify-content: center;253color: var(--color-primary);254font-size: var(--primitive-fontSize-xl);255flex-shrink: 0;256}}257258.feature-content h4 {{259font-size: var(--primitive-fontSize-xl);260color: var(--color-foreground);261margin-bottom: var(--primitive-spacing-2);262}}263264.feature-content p {{265color: var(--color-foreground-secondary);266font-size: var(--primitive-fontSize-base);267}}268269/* Testimonial */270.testimonial {{271background: var(--color-surface);272border-radius: var(--primitive-radius-xl);273padding: var(--primitive-spacing-8);274border-left: 4px solid var(--color-primary);275}}276277.testimonial-quote {{278font-size: var(--primitive-fontSize-2xl);279color: var(--color-foreground);280font-style: italic;281margin-bottom: var(--primitive-spacing-6);282}}283284.testimonial-author {{285font-size: var(--primitive-fontSize-lg);286color: var(--color-primary);287font-weight: var(--primitive-fontWeight-semibold);288}}289290.testimonial-role {{291font-size: var(--primitive-fontSize-base);292color: var(--color-foreground-muted);293}}294295/* Badge/Tag */296.badge {{297display: inline-block;298padding: var(--primitive-spacing-2) var(--primitive-spacing-4);299background: var(--color-surface-elevated);300border-radius: var(--primitive-radius-full);301font-size: var(--primitive-fontSize-sm);302color: var(--color-accent);303font-weight: var(--primitive-fontWeight-medium);304}}305306/* Chart Container */307.chart-container {{308background: var(--color-surface);309border-radius: var(--primitive-radius-xl);310padding: var(--primitive-spacing-6);311height: 100%;312display: flex;313flex-direction: column;314}}315316.chart-title {{317font-family: var(--typography-font-heading);318font-size: var(--primitive-fontSize-xl);319color: var(--color-foreground);320margin-bottom: var(--primitive-spacing-4);321}}322323/* CSS-only Bar Chart */324.bar-chart {{325display: flex;326align-items: flex-end;327gap: var(--primitive-spacing-4);328height: 200px;329padding-top: var(--primitive-spacing-4);330}}331332.bar {{333flex: 1;334background: var(--primitive-gradient-primary);335border-radius: var(--primitive-radius-md) var(--primitive-radius-md) 0 0;336position: relative;337min-width: 40px;338}}339340.bar-label {{341position: absolute;342bottom: -30px;343left: 50%;344transform: translateX(-50%);345font-size: var(--primitive-fontSize-sm);346color: var(--color-foreground-muted);347white-space: nowrap;348}}349350.bar-value {{351position: absolute;352top: -25px;353left: 50%;354transform: translateX(-50%);355font-size: var(--primitive-fontSize-sm);356color: var(--color-foreground);357font-weight: var(--primitive-fontWeight-semibold);358}}359360/* Progress Bar */361.progress {{362height: 12px;363background: var(--color-surface-elevated);364border-radius: var(--primitive-radius-full);365overflow: hidden;366}}367368.progress-fill {{369height: 100%;370background: var(--primitive-gradient-primary);371border-radius: var(--primitive-radius-full);372}}373374/* Footer */375.slide-footer {{376margin-top: auto;377display: flex;378justify-content: space-between;379align-items: center;380padding-top: var(--primitive-spacing-6);381border-top: 1px solid var(--color-border);382color: var(--color-foreground-muted);383font-size: var(--primitive-fontSize-sm);384}}385386/* Glow Effects */387.glow-coral {{388box-shadow: var(--primitive-shadow-glow-coral);389}}390391.glow-purple {{392box-shadow: var(--primitive-shadow-glow-purple);393}}394395.glow-mint {{396box-shadow: var(--primitive-shadow-glow-mint);397}}398</style>399</head>400<body>401<div class="slide-deck">402{slides_content}403</div>404</body>405</html>406'''407408409# ============ SLIDE GENERATORS ============410411def generate_title_slide(data):412"""Title slide with gradient headline"""413return f'''414<section class="slide slide--glow flex flex-col items-center justify-center text-center">415<div class="badge mb-6">{data.get('badge', 'Pitch Deck')}</div>416<h1 class="slide-title mb-6">{data.get('title', 'Your Title Here')}</h1>417<p class="slide-subheading mb-8">{data.get('subtitle', 'Your compelling subtitle')}</p>418<div class="flex gap-4">419<a href="#" class="btn btn-primary">{data.get('cta', 'Get Started')}</a>420<a href="#" class="btn btn-secondary">{data.get('secondary_cta', 'Learn More')}</a>421</div>422<div class="slide-footer">423<span>{data.get('company', 'Company Name')}</span>424<span>{data.get('date', datetime.now().strftime('%B %Y'))}</span>425</div>426</section>427'''428429430def generate_problem_slide(data):431"""Problem statement slide using PAS formula"""432return f'''433<section class="slide slide--surface">434<div class="badge mb-6">The Problem</div>435<h2 class="slide-heading mb-8">{data.get('headline', 'The problem your audience faces')}</h2>436<div class="grid grid-3 gap-8">437<div class="card">438<div class="text-primary" style="font-size: var(--primitive-fontSize-4xl); margin-bottom: var(--primitive-spacing-4);">01</div>439<h4 style="margin-bottom: var(--primitive-spacing-2); font-size: var(--primitive-fontSize-xl);">{data.get('pain_1_title', 'Pain Point 1')}</h4>440<p class="text-muted">{data.get('pain_1_desc', 'Description of the first pain point')}</p>441</div>442<div class="card">443<div class="text-secondary" style="font-size: var(--primitive-fontSize-4xl); margin-bottom: var(--primitive-spacing-4);">02</div>444<h4 style="margin-bottom: var(--primitive-spacing-2); font-size: var(--primitive-fontSize-xl);">{data.get('pain_2_title', 'Pain Point 2')}</h4>445<p class="text-muted">{data.get('pain_2_desc', 'Description of the second pain point')}</p>446</div>447<div class="card">448<div class="text-accent" style="font-size: var(--primitive-fontSize-4xl); margin-bottom: var(--primitive-spacing-4);">03</div>449<h4 style="margin-bottom: var(--primitive-spacing-2); font-size: var(--primitive-fontSize-xl);">{data.get('pain_3_title', 'Pain Point 3')}</h4>450<p class="text-muted">{data.get('pain_3_desc', 'Description of the third pain point')}</p>451</div>452</div>453<div class="slide-footer">454<span>{data.get('company', 'Company Name')}</span>455<span>{data.get('page', '2')}</span>456</div>457</section>458'''459460461def generate_solution_slide(data):462"""Solution slide with feature highlights"""463return f'''464<section class="slide">465<div class="badge mb-6">The Solution</div>466<h2 class="slide-heading mb-8">{data.get('headline', 'How we solve this')}</h2>467<div class="flex gap-8" style="flex: 1;">468<div style="flex: 1;">469<div class="feature-item">470<div class="feature-icon">✓</div>471<div class="feature-content">472<h4>{data.get('feature_1_title', 'Feature 1')}</h4>473<p>{data.get('feature_1_desc', 'Description of feature 1')}</p>474</div>475</div>476<div class="feature-item">477<div class="feature-icon">✓</div>478<div class="feature-content">479<h4>{data.get('feature_2_title', 'Feature 2')}</h4>480<p>{data.get('feature_2_desc', 'Description of feature 2')}</p>481</div>482</div>483<div class="feature-item">484<div class="feature-icon">✓</div>485<div class="feature-content">486<h4>{data.get('feature_3_title', 'Feature 3')}</h4>487<p>{data.get('feature_3_desc', 'Description of feature 3')}</p>488</div>489</div>490</div>491<div style="flex: 1;" class="card flex items-center justify-center">492<div class="text-center">493<div class="text-accent" style="font-size: 80px; margin-bottom: var(--primitive-spacing-4);">◆</div>494<p class="text-muted">Product screenshot or demo</p>495</div>496</div>497</div>498<div class="slide-footer">499<span>{data.get('company', 'Company Name')}</span>500<span>{data.get('page', '3')}</span>501</div>502</section>503'''504505506def generate_metrics_slide(data):507"""Traction/metrics slide with large numbers"""508metrics = data.get('metrics', [509{'value': '10K+', 'label': 'Active Users'},510{'value': '95%', 'label': 'Retention Rate'},511{'value': '3x', 'label': 'Revenue Growth'},512{'value': '$2M', 'label': 'ARR'}513])514515metrics_html = ''.join([f'''516<div class="card metric">517<div class="metric-value">{m['value']}</div>518<div class="metric-label">{m['label']}</div>519</div>520''' for m in metrics[:4]])521522return f'''523<section class="slide slide--surface slide--glow">524<div class="badge mb-6">Traction</div>525<h2 class="slide-heading mb-8 text-center">{data.get('headline', 'Our Growth')}</h2>526<div class="grid grid-4 gap-6" style="flex: 1; align-items: center;">527{metrics_html}528</div>529<div class="slide-footer">530<span>{data.get('company', 'Company Name')}</span>531<span>{data.get('page', '4')}</span>532</div>533</section>534'''535536537def generate_chart_slide(data):538"""Chart slide with CSS bar chart"""539bars = data.get('bars', [540{'label': 'Q1', 'value': 40},541{'label': 'Q2', 'value': 60},542{'label': 'Q3', 'value': 80},543{'label': 'Q4', 'value': 100}544])545546bars_html = ''.join([f'''547<div class="bar" style="height: {b['value']}%;">548<span class="bar-value">{b.get('display', str(b['value']) + '%')}</span>549<span class="bar-label">{b['label']}</span>550</div>551''' for b in bars])552553return f'''554<section class="slide">555<div class="badge mb-6">{data.get('badge', 'Growth')}</div>556<h2 class="slide-heading mb-8">{data.get('headline', 'Revenue Growth')}</h2>557<div class="chart-container" style="flex: 1;">558<div class="chart-title">{data.get('chart_title', 'Quarterly Revenue')}</div>559<div class="bar-chart" style="flex: 1; padding-bottom: 40px;">560{bars_html}561</div>562</div>563<div class="slide-footer">564<span>{data.get('company', 'Company Name')}</span>565<span>{data.get('page', '5')}</span>566</div>567</section>568'''569570571def generate_testimonial_slide(data):572"""Social proof slide"""573return f'''574<section class="slide slide--surface flex flex-col justify-center">575<div class="badge mb-6">What They Say</div>576<div class="testimonial" style="max-width: 900px;">577<p class="testimonial-quote">"{data.get('quote', 'This product changed how we work. Incredible results.')}"</p>578<p class="testimonial-author">{data.get('author', 'Jane Doe')}</p>579<p class="testimonial-role">{data.get('role', 'CEO, Example Company')}</p>580</div>581<div class="slide-footer">582<span>{data.get('company', 'Company Name')}</span>583<span>{data.get('page', '6')}</span>584</div>585</section>586'''587588589def generate_cta_slide(data):590"""Closing CTA slide"""591return f'''592<section class="slide slide--gradient flex flex-col items-center justify-center text-center">593<h2 class="slide-heading mb-6" style="color: var(--color-foreground);">{data.get('headline', 'Ready to get started?')}</h2>594<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>595<div class="flex gap-4">596<a href="{data.get('cta_url', '#')}" class="btn" style="background: var(--color-foreground); color: var(--color-primary);">{data.get('cta', 'Start Free Trial')}</a>597</div>598<div class="slide-footer" style="border-color: rgba(255,255,255,0.2); color: rgba(255,255,255,0.6);">599<span>{data.get('contact', '[email protected]')}</span>600<span>{data.get('website', 'www.example.com')}</span>601</div>602</section>603'''604605606# Slide type mapping607SLIDE_GENERATORS = {608'title': generate_title_slide,609'problem': generate_problem_slide,610'solution': generate_solution_slide,611'metrics': generate_metrics_slide,612'traction': generate_metrics_slide,613'chart': generate_chart_slide,614'testimonial': generate_testimonial_slide,615'cta': generate_cta_slide,616'closing': generate_cta_slide617}618619620def generate_deck(slides_data, title="Pitch Deck"):621"""Generate complete deck from slide data list"""622slides_html = ""623for slide in slides_data:624slide_type = slide.get('type', 'title')625generator = SLIDE_GENERATORS.get(slide_type)626if generator:627slides_html += generator(slide)628else:629print(f"Warning: Unknown slide type '{slide_type}'")630631# Calculate relative path to tokens CSS632tokens_rel_path = "../../../assets/design-tokens.css"633634return SLIDE_TEMPLATE.format(635title=title,636tokens_css_path=tokens_rel_path,637slides_content=slides_html638)639640641def main():642parser = argparse.ArgumentParser(description="Generate brand-compliant slides")643parser.add_argument("--json", "-j", help="JSON file with slide data")644parser.add_argument("--output", "-o", help="Output HTML file path")645parser.add_argument("--demo", action="store_true", help="Generate demo deck")646647args = parser.parse_args()648649if args.demo:650# Demo deck showcasing all slide types651demo_slides = [652{653'type': 'title',654'badge': 'Investor Deck 2024',655'title': 'ClaudeKit Marketing',656'subtitle': 'Your AI marketing team. Always on.',657'cta': 'Join Waitlist',658'secondary_cta': 'See Demo',659'company': 'ClaudeKit',660'date': 'December 2024'661},662{663'type': 'problem',664'headline': 'Marketing teams are drowning',665'pain_1_title': 'Content Overload',666'pain_1_desc': 'Need to produce 10x content with same headcount',667'pain_2_title': 'Tool Fatigue',668'pain_2_desc': '15+ tools that don\'t talk to each other',669'pain_3_title': 'No Time to Think',670'pain_3_desc': 'Strategy suffers when execution consumes all hours',671'company': 'ClaudeKit',672'page': '2'673},674{675'type': 'solution',676'headline': 'AI agents that actually get marketing',677'feature_1_title': 'Content Creation',678'feature_1_desc': 'Blog posts, social, email - all on brand, all on time',679'feature_2_title': 'Campaign Management',680'feature_2_desc': 'Multi-channel orchestration with one command',681'feature_3_title': 'Analytics & Insights',682'feature_3_desc': 'Real-time optimization without the spreadsheets',683'company': 'ClaudeKit',684'page': '3'685},686{687'type': 'metrics',688'headline': 'Early traction speaks volumes',689'metrics': [690{'value': '500+', 'label': 'Beta Users'},691{'value': '85%', 'label': 'Weekly Active'},692{'value': '4.9', 'label': 'NPS Score'},693{'value': '50hrs', 'label': 'Saved/Week'}694],695'company': 'ClaudeKit',696'page': '4'697},698{699'type': 'chart',700'badge': 'Revenue',701'headline': 'Growing month over month',702'chart_title': 'MRR Growth ($K)',703'bars': [704{'label': 'Sep', 'value': 20, 'display': '$5K'},705{'label': 'Oct', 'value': 40, 'display': '$12K'},706{'label': 'Nov', 'value': 70, 'display': '$28K'},707{'label': 'Dec', 'value': 100, 'display': '$45K'}708],709'company': 'ClaudeKit',710'page': '5'711},712{713'type': 'testimonial',714'quote': 'ClaudeKit replaced 3 tools and 2 contractors. Our content output tripled while costs dropped 60%.',715'author': 'Sarah Chen',716'role': 'Head of Marketing, TechStartup',717'company': 'ClaudeKit',718'page': '6'719},720{721'type': 'cta',722'headline': 'Ship campaigns while you sleep',723'subheadline': 'Early access available. Limited spots.',724'cta': 'Join the Waitlist',725'contact': '[email protected]',726'website': 'claudekit.ai'727}728]729730html = generate_deck(demo_slides, "ClaudeKit Marketing - Pitch Deck")731732OUTPUT_DIR.mkdir(parents=True, exist_ok=True)733output_path = OUTPUT_DIR / f"demo-pitch-{datetime.now().strftime('%y%m%d')}.html"734output_path.write_text(html, encoding='utf-8')735print(f"Demo deck generated: {output_path}")736737elif args.json:738with open(args.json, 'r') as f:739data = json.load(f)740741html = generate_deck(data.get('slides', []), data.get('title', 'Presentation'))742743output_path = Path(args.output) if args.output else OUTPUT_DIR / f"deck-{datetime.now().strftime('%y%m%d-%H%M')}.html"744output_path.parent.mkdir(parents=True, exist_ok=True)745output_path.write_text(html, encoding='utf-8')746print(f"Deck generated: {output_path}")747748else:749parser.print_help()750751752if __name__ == "__main__":753main()754