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/fetch-background.py
1#!/usr/bin/env python32"""3Background Image Fetcher4Fetches real images from Pexels for slide backgrounds.5Uses web scraping (no API key required) or WebFetch tool integration.6"""78import json9import csv10import re11import sys12from pathlib import Path1314# Project root relative to this script15PROJECT_ROOT = Path(__file__).parent.parent.parent.parent.parent16TOKENS_PATH = PROJECT_ROOT / 'assets' / 'design-tokens.json'17BACKGROUNDS_CSV = Path(__file__).parent.parent / 'data' / 'slide-backgrounds.csv'181920def resolve_token_reference(ref: str, tokens: dict) -> str:21"""Resolve token reference like {primitive.color.ocean-blue.500} to hex value."""22if not ref or not ref.startswith('{') or not ref.endswith('}'):23return ref # Already a value, not a reference2425# Parse reference: {primitive.color.ocean-blue.500}26path = ref[1:-1].split('.') # ['primitive', 'color', 'ocean-blue', '500']27current = tokens28for key in path:29if isinstance(current, dict):30current = current.get(key)31else:32return None # Invalid path33# Return $value if it's a token object34if isinstance(current, dict) and '$value' in current:35return current['$value']36return current373839def load_brand_colors():40"""Load colors from assets/design-tokens.json for overlay gradients.4142Resolves semantic token references to actual hex values.43"""44try:45with open(TOKENS_PATH) as f:46tokens = json.load(f)4748colors = tokens.get('primitive', {}).get('color', {})49semantic = tokens.get('semantic', {}).get('color', {})5051# Try semantic tokens first (preferred) - resolve references52if semantic:53primary_ref = semantic.get('primary', {}).get('$value')54secondary_ref = semantic.get('secondary', {}).get('$value')55accent_ref = semantic.get('accent', {}).get('$value')56background_ref = semantic.get('background', {}).get('$value')5758primary = resolve_token_reference(primary_ref, tokens)59secondary = resolve_token_reference(secondary_ref, tokens)60accent = resolve_token_reference(accent_ref, tokens)61background = resolve_token_reference(background_ref, tokens)6263if primary and secondary:64return {65'primary': primary,66'secondary': secondary,67'accent': accent or primary,68'background': background or '#0D0D0D',69}7071# Fallback: find first color palette with 500 value (primary)72primary_keys = ['ocean-blue', 'coral', 'blue', 'primary']73secondary_keys = ['golden-amber', 'purple', 'amber', 'secondary']74accent_keys = ['emerald', 'mint', 'green', 'accent']7576primary_color = None77secondary_color = None78accent_color = None7980for key in primary_keys:81if key in colors and isinstance(colors[key], dict):82primary_color = colors[key].get('500', {}).get('$value')83if primary_color:84break8586for key in secondary_keys:87if key in colors and isinstance(colors[key], dict):88secondary_color = colors[key].get('500', {}).get('$value')89if secondary_color:90break9192for key in accent_keys:93if key in colors and isinstance(colors[key], dict):94accent_color = colors[key].get('500', {}).get('$value')95if accent_color:96break9798background = colors.get('dark', {}).get('800', {}).get('$value', '#0D0D0D')99100return {101'primary': primary_color or '#3B82F6',102'secondary': secondary_color or '#F59E0B',103'accent': accent_color or '#10B981',104'background': background,105}106except (FileNotFoundError, KeyError, TypeError):107# Fallback defaults108return {109'primary': '#3B82F6',110'secondary': '#F59E0B',111'accent': '#10B981',112'background': '#0D0D0D',113}114115116def load_backgrounds_config():117"""Load background configuration from CSV."""118config = {}119try:120with open(BACKGROUNDS_CSV, newline='') as f:121reader = csv.DictReader(f)122for row in reader:123config[row['slide_type']] = row124except FileNotFoundError:125print(f"Warning: {BACKGROUNDS_CSV} not found")126return config127128129def get_overlay_css(style: str, brand_colors: dict) -> str:130"""Generate overlay CSS using brand colors from design-tokens.json."""131overlays = {132'gradient-dark': f"linear-gradient(135deg, {brand_colors['background']}E6, {brand_colors['background']}B3)",133'gradient-brand': f"linear-gradient(135deg, {brand_colors['primary']}CC, {brand_colors['secondary']}99)",134'gradient-accent': f"linear-gradient(135deg, {brand_colors['accent']}99, transparent)",135'blur-dark': f"rgba(13,13,13,0.8)",136'desaturate-dark': f"rgba(13,13,13,0.7)",137}138return overlays.get(style, overlays['gradient-dark'])139140141# Curated high-quality images from Pexels (free to use, pre-selected for brand aesthetic)142CURATED_IMAGES = {143'hero': [144'https://images.pexels.com/photos/3861969/pexels-photo-3861969.jpeg?auto=compress&cs=tinysrgb&w=1920',145'https://images.pexels.com/photos/2582937/pexels-photo-2582937.jpeg?auto=compress&cs=tinysrgb&w=1920',146'https://images.pexels.com/photos/1089438/pexels-photo-1089438.jpeg?auto=compress&cs=tinysrgb&w=1920',147],148'vision': [149'https://images.pexels.com/photos/3183150/pexels-photo-3183150.jpeg?auto=compress&cs=tinysrgb&w=1920',150'https://images.pexels.com/photos/3182812/pexels-photo-3182812.jpeg?auto=compress&cs=tinysrgb&w=1920',151'https://images.pexels.com/photos/3184291/pexels-photo-3184291.jpeg?auto=compress&cs=tinysrgb&w=1920',152],153'team': [154'https://images.pexels.com/photos/3184418/pexels-photo-3184418.jpeg?auto=compress&cs=tinysrgb&w=1920',155'https://images.pexels.com/photos/3184338/pexels-photo-3184338.jpeg?auto=compress&cs=tinysrgb&w=1920',156'https://images.pexels.com/photos/3182773/pexels-photo-3182773.jpeg?auto=compress&cs=tinysrgb&w=1920',157],158'testimonial': [159'https://images.pexels.com/photos/3184465/pexels-photo-3184465.jpeg?auto=compress&cs=tinysrgb&w=1920',160'https://images.pexels.com/photos/1181622/pexels-photo-1181622.jpeg?auto=compress&cs=tinysrgb&w=1920',161],162'cta': [163'https://images.pexels.com/photos/3184339/pexels-photo-3184339.jpeg?auto=compress&cs=tinysrgb&w=1920',164'https://images.pexels.com/photos/3184298/pexels-photo-3184298.jpeg?auto=compress&cs=tinysrgb&w=1920',165],166'problem': [167'https://images.pexels.com/photos/3760529/pexels-photo-3760529.jpeg?auto=compress&cs=tinysrgb&w=1920',168'https://images.pexels.com/photos/897817/pexels-photo-897817.jpeg?auto=compress&cs=tinysrgb&w=1920',169],170'solution': [171'https://images.pexels.com/photos/3184292/pexels-photo-3184292.jpeg?auto=compress&cs=tinysrgb&w=1920',172'https://images.pexels.com/photos/3184644/pexels-photo-3184644.jpeg?auto=compress&cs=tinysrgb&w=1920',173],174'hook': [175'https://images.pexels.com/photos/2582937/pexels-photo-2582937.jpeg?auto=compress&cs=tinysrgb&w=1920',176'https://images.pexels.com/photos/1089438/pexels-photo-1089438.jpeg?auto=compress&cs=tinysrgb&w=1920',177],178'social': [179'https://images.pexels.com/photos/3184360/pexels-photo-3184360.jpeg?auto=compress&cs=tinysrgb&w=1920',180'https://images.pexels.com/photos/3184287/pexels-photo-3184287.jpeg?auto=compress&cs=tinysrgb&w=1920',181],182'demo': [183'https://images.pexels.com/photos/1181675/pexels-photo-1181675.jpeg?auto=compress&cs=tinysrgb&w=1920',184'https://images.pexels.com/photos/3861958/pexels-photo-3861958.jpeg?auto=compress&cs=tinysrgb&w=1920',185],186}187188189def get_curated_images(slide_type: str) -> list:190"""Get curated images for slide type."""191return CURATED_IMAGES.get(slide_type, CURATED_IMAGES.get('hero', []))192193194def get_pexels_search_url(keywords: str) -> str:195"""Generate Pexels search URL for manual lookup."""196import urllib.parse197return f"https://www.pexels.com/search/{urllib.parse.quote(keywords)}/"198199200def get_background_image(slide_type: str) -> dict:201"""202Get curated image matching slide type and brand aesthetic.203Uses pre-selected Pexels images (no API/scraping needed).204"""205brand_colors = load_brand_colors()206config = load_backgrounds_config()207208slide_config = config.get(slide_type)209overlay_style = 'gradient-dark'210keywords = slide_type211212if slide_config:213keywords = slide_config.get('search_keywords', slide_config.get('image_category', slide_type))214overlay_style = slide_config.get('overlay_style', 'gradient-dark')215216# Get curated images217urls = get_curated_images(slide_type)218if urls:219return {220'url': urls[0],221'all_urls': urls,222'overlay': get_overlay_css(overlay_style, brand_colors),223'attribution': 'Photo from Pexels (free to use)',224'source': 'pexels-curated',225'search_url': get_pexels_search_url(keywords),226}227228# Fallback: provide search URL for manual selection229return {230'url': None,231'overlay': get_overlay_css(overlay_style, brand_colors),232'keywords': keywords,233'search_url': get_pexels_search_url(keywords),234'available_types': list(CURATED_IMAGES.keys()),235}236237238def generate_css_for_background(result: dict, slide_class: str = '.slide-with-bg') -> str:239"""Generate CSS for a background slide."""240if not result.get('url'):241search_url = result.get('search_url', '')242return f"""/* No image scraped. Search manually: {search_url} */243/* Overlay ready: {result.get('overlay', 'gradient-dark')} */244"""245246return f"""{slide_class} {{247background-image: url('{result['url']}');248background-size: cover;249background-position: center;250position: relative;251}}252253{slide_class}::before {{254content: '';255position: absolute;256inset: 0;257background: {result['overlay']};258}}259260{slide_class} .content {{261position: relative;262z-index: 1;263}}264265/* {result.get('attribution', 'Pexels')} - {result.get('search_url', '')} */266"""267268269def main():270"""CLI entry point."""271import argparse272273parser = argparse.ArgumentParser(description='Get background images for slides')274parser.add_argument('slide_type', nargs='?', help='Slide type (hero, vision, team, etc.)')275parser.add_argument('--list', action='store_true', help='List available slide types')276parser.add_argument('--css', action='store_true', help='Output CSS for the background')277parser.add_argument('--json', action='store_true', help='Output JSON')278parser.add_argument('--colors', action='store_true', help='Show brand colors')279parser.add_argument('--all', action='store_true', help='Show all curated URLs')280281args = parser.parse_args()282283if args.colors:284colors = load_brand_colors()285print("\nBrand Colors (from design-tokens.json):")286for name, value in colors.items():287print(f" {name}: {value}")288return289290if args.list:291print("\nAvailable slide types (curated images):")292for slide_type, urls in CURATED_IMAGES.items():293print(f" {slide_type}: {len(urls)} images")294return295296if not args.slide_type:297parser.print_help()298return299300result = get_background_image(args.slide_type)301302if args.json:303print(json.dumps(result, indent=2))304elif args.css:305print(generate_css_for_background(result))306elif args.all:307print(f"\nAll images for '{args.slide_type}':")308for i, url in enumerate(result.get('all_urls', []), 1):309print(f" {i}. {url}")310else:311print(f"\nImage URL: {result['url']}")312print(f"Alternatives: {len(result.get('all_urls', []))} available (use --all)")313print(f"Overlay: {result['overlay']}")314315316if __name__ == '__main__':317main()318