Skip to main content
Glama

Chuk Design System

by chrishayuk
showcase_visual.py20 kB
""" Visual Design System Showcase Generates PNG images showing colors, gradients, typography, and more. This provides a proper visual representation that terminal output cannot. """ from PIL import Image, ImageDraw, ImageFont import sys from pathlib import Path from chuk_design_system.tokens import ColorTokens, TypographyTokens, GRADIENTS, PALETTE from chuk_design_system.themes import get_theme def hex_to_rgb(hex_color): """Convert hex color to RGB tuple.""" hex_color = hex_color.lstrip("#") return tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4)) def create_color_palette_image(output_path="output/color_palettes.png"): """Generate color palette showcase image.""" # Image dimensions width, height = 1200, 800 img = Image.new("RGB", (width, height), color=(255, 255, 255)) draw = ImageDraw.Draw(img) colors = ColorTokens() hues = ["blue", "green", "purple", "pink", "orange", "zinc"] weights = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] # Title draw.text((20, 20), "Color Palettes", fill=(0, 0, 0), font=None) # Draw color swatches swatch_width = 80 swatch_height = 50 start_y = 60 for i, hue in enumerate(hues): # Draw hue label draw.text((20, start_y + i * (swatch_height + 5)), hue.capitalize(), fill=(0, 0, 0)) scale = colors.palette[hue] for j, weight in enumerate(weights): x = 100 + j * (swatch_width + 2) y = start_y + i * (swatch_height + 5) color = hex_to_rgb(scale[weight]) draw.rectangle([x, y, x + swatch_width, y + swatch_height], fill=color) # Add weight label text_color = (255, 255, 255) if weight >= 500 else (0, 0, 0) draw.text((x + 5, y + 5), str(weight), fill=text_color) img.save(output_path) print(f"✅ Saved color palette to {output_path}") return output_path def create_gradient_image(output_path="output/gradients.png"): """Generate gradient showcase image.""" width, height = 1200, 600 img = Image.new("RGB", (width, height), color=(255, 255, 255)) draw = ImageDraw.Draw(img) # Title draw.text((20, 20), "Gradients", fill=(0, 0, 0), font=None) # Extract colors from gradient strings import re def extract_colors(gradient_str): return re.findall(r"#[0-9a-fA-F]{6}", gradient_str) def draw_gradient(draw, x, y, w, h, colors): """Draw a horizontal gradient.""" if len(colors) < 2: return start_rgb = hex_to_rgb(colors[0]) end_rgb = hex_to_rgb(colors[-1]) for i in range(w): t = i / (w - 1) r = int(start_rgb[0] + (end_rgb[0] - start_rgb[0]) * t) g = int(start_rgb[1] + (end_rgb[1] - start_rgb[1]) * t) b = int(start_rgb[2] + (end_rgb[2] - start_rgb[2]) * t) draw.line([(x + i, y), (x + i, y + h)], fill=(r, g, b)) # Draw each gradient gradient_height = 80 start_y = 60 for i, (name, gradient_str) in enumerate(list(GRADIENTS.items())[:6]): y = start_y + i * (gradient_height + 10) # Draw gradient name draw.text((20, y + 30), name.capitalize(), fill=(0, 0, 0)) # Draw gradient colors = extract_colors(gradient_str) draw_gradient(draw, 150, y, 1000, gradient_height, colors) # Draw color labels for j, color in enumerate(colors[:3]): label_x = 150 + j * 200 draw.text((label_x, y + gradient_height + 5), color, fill=(0, 0, 0)) img.save(output_path) print(f"✅ Saved gradients to {output_path}") return output_path def create_semantic_colors_image(output_path="output/semantic_colors.png"): """Generate semantic colors showcase image.""" width, height = 1000, 600 img = Image.new("RGB", (width, height), color=(240, 240, 240)) draw = ImageDraw.Draw(img) colors = ColorTokens() # Title draw.text((20, 20), "Semantic Colors", fill=(0, 0, 0), font=None) # Dark mode section draw.text((20, 60), "Dark Mode (Blue Primary)", fill=(0, 0, 0)) semantic_dark = colors.get_semantic("blue", "dark") roles = [ ("Primary", semantic_dark.primary.DEFAULT), ("Accent", semantic_dark.accent.DEFAULT), ("Background", semantic_dark.background.DEFAULT), ("Foreground", semantic_dark.foreground.DEFAULT), ("Success", semantic_dark.success.DEFAULT), ("Warning", semantic_dark.warning.DEFAULT), ("Error", semantic_dark.destructive.DEFAULT), ] swatch_size = 120 for i, (role, hex_color) in enumerate(roles): x = 20 + (i % 4) * (swatch_size + 20) y = 100 + (i // 4) * (swatch_size + 40) rgb = hex_to_rgb(hex_color) draw.rectangle([x, y, x + swatch_size, y + swatch_size], fill=rgb, outline=(0, 0, 0)) # Label draw.text((x, y + swatch_size + 5), role, fill=(0, 0, 0)) draw.text((x, y + swatch_size + 20), hex_color, fill=(100, 100, 100)) # Light mode section draw.text((20, 340), "Light Mode (Purple Primary)", fill=(0, 0, 0)) semantic_light = colors.get_semantic("purple", "light") roles_light = [ ("Primary", semantic_light.primary.DEFAULT), ("Accent", semantic_light.accent.DEFAULT), ("Background", semantic_light.background.DEFAULT), ("Foreground", semantic_light.foreground.DEFAULT), ] for i, (role, hex_color) in enumerate(roles_light): x = 20 + i * (swatch_size + 20) y = 380 rgb = hex_to_rgb(hex_color) draw.rectangle([x, y, x + swatch_size, y + swatch_size], fill=rgb, outline=(0, 0, 0)) # Label draw.text((x, y + swatch_size + 5), role, fill=(0, 0, 0)) draw.text((x, y + swatch_size + 20), hex_color, fill=(100, 100, 100)) img.save(output_path) print(f"✅ Saved semantic colors to {output_path}") return output_path def create_theme_comparison_image(output_path="output/theme_comparison.png"): """Generate theme comparison showcase image.""" width, height = 1400, 800 img = Image.new("RGB", (width, height), color=(255, 255, 255)) draw = ImageDraw.Draw(img) # Title draw.text((20, 20), "Theme Comparison", fill=(0, 0, 0), font=None) themes = ["tech", "finance", "education", "lifestyle", "gaming", "business"] card_width = 220 card_height = 300 margin = 20 for i, theme_name in enumerate(themes): theme = get_theme(theme_name) colors = theme["colors"]["semantic"] # Calculate position col = i % 3 row = i // 3 x = margin + col * (card_width + margin) y = 80 + row * (card_height + margin) # Draw theme card background bg_color = hex_to_rgb(colors["background"]["DEFAULT"]) draw.rectangle([x, y, x + card_width, y + card_height], fill=bg_color, outline=(200, 200, 200)) # Theme name fg_color = hex_to_rgb(colors["foreground"]["DEFAULT"]) draw.text((x + 10, y + 10), theme["metadata"]["name"], fill=fg_color) # Color swatches swatch_height = 40 color_roles = [ ("Primary", colors["primary"]["DEFAULT"]), ("Accent", colors["accent"]["DEFAULT"]), ("Success", colors["success"]["DEFAULT"]), ("Warning", colors["warning"]["DEFAULT"]), ] for j, (role, hex_color) in enumerate(color_roles): swatch_y = y + 50 + j * (swatch_height + 10) rgb = hex_to_rgb(hex_color) draw.rectangle([x + 10, swatch_y, x + card_width - 10, swatch_y + swatch_height], fill=rgb) # Role label text_color = (255, 255, 255) if sum(rgb) < 400 else (0, 0, 0) draw.text((x + 15, swatch_y + 10), role, fill=text_color) img.save(output_path) print(f"✅ Saved theme comparison to {output_path}") return output_path def create_typography_image(output_path="output/typography.png"): """Generate typography showcase image.""" width, height = 1400, 1000 img = Image.new("RGB", (width, height), color=(255, 255, 255)) draw = ImageDraw.Draw(img) typography = TypographyTokens() # Title draw.text((20, 20), "Typography System", fill=(0, 0, 0), font=None) # Try to use system fonts for better rendering try: # Try different font sizes for demonstration from PIL import ImageFont # System fonts that are commonly available font_paths = [ "/System/Library/Fonts/Helvetica.ttc", "/System/Library/Fonts/SFNSDisplay.ttf", "/Library/Fonts/Arial.ttf", ] fonts = {} for size in [12, 14, 16, 20, 24, 30, 36, 48]: for font_path in font_paths: try: fonts[size] = ImageFont.truetype(font_path, size) break except: continue if size not in fonts: fonts[size] = None except: fonts = {size: None for size in [12, 14, 16, 20, 24, 30, 36, 48]} # Text style presets showcase y_offset = 80 sample_text = "The quick brown fox jumps over the lazy dog" styles = [ ("hero_title", "Hero Title", 48), ("title", "Title", 36), ("heading", "Heading", 24), ("body", "Body Text", 16), ("caption", "Caption", 14), ] for style_name, label, size in styles: style = typography.get_text_style(style_name, "web") # Draw style label draw.text((20, y_offset), f"{label} ({style['fontSize']}, weight {style['fontWeight']})", fill=(100, 100, 100), font=fonts.get(12)) # Draw sample text with appropriate font font = fonts.get(size) draw.text((20, y_offset + 25), sample_text[:40], fill=(0, 0, 0), font=font) y_offset += 100 # Font size comparison across mediums draw.text((20, 600), "Multi-Medium Font Scales", fill=(0, 0, 0), font=fonts.get(16)) mediums = ["web", "pptx", "video_1080p", "video_4k"] medium_labels = ["Web (16px)", "PPTX (18pt)", "Video 1080p (40px)", "Video 4K (80px)"] y_offset = 640 for i, (medium, label) in enumerate(zip(mediums, medium_labels)): sizes = typography.sizes[medium] x_offset = 20 + (i * 320) draw.text((x_offset, y_offset), label, fill=(100, 100, 100), font=fonts.get(12)) draw.text((x_offset, y_offset + 25), f"Base: {sizes.base}", fill=(0, 0, 0), font=fonts.get(14)) draw.text((x_offset, y_offset + 50), f"Large: {sizes.lg}", fill=(0, 0, 0), font=fonts.get(14)) draw.text((x_offset, y_offset + 75), f"XL: {sizes.xl}", fill=(0, 0, 0), font=fonts.get(14)) # Font weights demonstration draw.text((20, 800), "Font Weights", fill=(0, 0, 0), font=fonts.get(16)) weights_demo = [ ("Light (300)", 300), ("Regular (400)", 400), ("Medium (500)", 500), ("Semibold (600)", 600), ("Bold (700)", 700), ("Black (900)", 900), ] y_offset = 840 for i, (label, weight) in enumerate(weights_demo): x_offset = 20 + (i % 3) * 400 y = y_offset + (i // 3) * 40 draw.text((x_offset, y), f"{label}: Aa Bb 123", fill=(0, 0, 0), font=fonts.get(16)) img.save(output_path) print(f"✅ Saved typography to {output_path}") return output_path def create_motion_image(output_path="output/motion.png"): """Generate motion/animation showcase image.""" width, height = 1400, 1000 img = Image.new("RGB", (width, height), color=(255, 255, 255)) draw = ImageDraw.Draw(img) from chuk_design_system.tokens import MotionTokens, DURATIONS, EASINGS, SPRINGS motion = MotionTokens() # Title draw.text((20, 20), "Motion & Animation System", fill=(0, 0, 0), font=None) # Durations with frame visualization draw.text((20, 60), "Durations (Video-optimized)", fill=(0, 0, 0), font=None) durations_to_show = [ ("instant", DURATIONS["instant"]), ("fast", DURATIONS["fast"]), ("normal", DURATIONS["normal"]), ("slow", DURATIONS["slow"]), ("slowest", DURATIONS["slowest"]), ] y_offset = 90 for name, duration in durations_to_show: # Draw duration info draw.text((20, y_offset), f"{name.capitalize()}: {duration.ms}ms", fill=(0, 0, 0)) draw.text((200, y_offset), f"30fps: {duration.frames_30fps}f", fill=(100, 100, 100)) draw.text((320, y_offset), f"60fps: {duration.frames_60fps}f", fill=(100, 100, 100)) # Draw timeline bar bar_width = min(duration.frames_30fps * 10, 400) draw.rectangle([450, y_offset, 450 + bar_width, y_offset + 20], fill=(59, 130, 246), outline=(0, 0, 0)) y_offset += 35 # Easing curves visualization draw.text((20, 300), "Easing Curves", fill=(0, 0, 0), font=None) easings_to_show = [ ("linear", EASINGS["linear"]), ("ease_out", EASINGS["ease_out"]), ("ease_in_out", EASINGS["ease_in_out"]), ("smooth", EASINGS["smooth"]), ("snappy", EASINGS["snappy"]), ("bouncy", EASINGS["bouncy"]), ] def draw_easing_curve(draw, x, y, width, height, curve_points): """Draw an easing curve visualization.""" # Draw box draw.rectangle([x, y, x + width, y + height], outline=(200, 200, 200)) # Draw curve steps = 50 points = [] for i in range(steps): t = i / (steps - 1) # Approximate bezier curve if curve_points == [0.0, 0.0, 1.0, 1.0]: # linear eased = t elif curve_points == [0.0, 0.0, 0.58, 1.0]: # ease_out eased = 1 - (1 - t) ** 2 elif curve_points == [0.42, 0.0, 0.58, 1.0]: # ease_in_out eased = t ** 2 if t < 0.5 else 1 - (1 - t) ** 2 elif curve_points == [0.4, 0.0, 0.2, 1.0]: # smooth eased = 1 - (1 - t) ** 1.8 elif curve_points == [0.4, 0.0, 0.6, 1.0]: # snappy eased = 1 - (1 - t) ** 1.5 else: # bouncy eased = min(1.2, 1 - (1 - t) ** 2 * (1 + 0.2 * (1 - t))) px = x + t * width py = y + height - eased * height points.append((px, py)) # Draw the curve line for i in range(len(points) - 1): draw.line([points[i], points[i + 1]], fill=(59, 130, 246), width=2) curve_y = 330 for i, (name, easing) in enumerate(easings_to_show): col = i % 3 row = i // 3 x = 20 + col * 450 y = curve_y + row * 180 # Label draw.text((x, y), name.replace("_", " ").title(), fill=(0, 0, 0)) draw.text((x, y + 15), f"{easing.curve}", fill=(100, 100, 100)) # Draw curve draw_easing_curve(draw, x, y + 40, 120, 100, easing.curve) # Spring configurations draw.text((20, 700), "Spring Configurations (Remotion-compatible)", fill=(0, 0, 0), font=None) springs_to_show = [ ("gentle", SPRINGS["gentle"]), ("smooth", SPRINGS["smooth"]), ("bouncy", SPRINGS["bouncy"]), ("snappy", SPRINGS["snappy"]), ("stiff", SPRINGS["stiff"]), ] y_offset = 730 for name, spring in springs_to_show: config = motion.get_spring_config(name) draw.text((20, y_offset), f"{name.capitalize()}:", fill=(0, 0, 0)) draw.text((120, y_offset), f"damping={config['damping']}, stiffness={config['stiffness']}, mass={config['mass']}", fill=(100, 100, 100)) # Draw spring feel indicator stiffness_bar = int(config['stiffness'] / 2) draw.rectangle([600, y_offset, 600 + stiffness_bar, y_offset + 15], fill=(139, 92, 246)) y_offset += 30 img.save(output_path) print(f"✅ Saved motion to {output_path}") return output_path def create_spacing_image(output_path="output/spacing.png"): """Generate spacing system showcase image.""" width, height = 1200, 800 img = Image.new("RGB", (width, height), color=(255, 255, 255)) draw = ImageDraw.Draw(img) from chuk_design_system.tokens import SpacingTokens, SPACING spacing = SpacingTokens() # Title draw.text((20, 20), "Spacing System (8px base unit)", fill=(0, 0, 0), font=None) # Spacing scale visualization draw.text((20, 60), "Spacing Scale", fill=(0, 0, 0), font=None) units = ["0.5u", "1u", "2u", "3u", "4u", "6u", "8u"] y_offset = 90 for unit in units: value = SPACING[unit] px_value = int(value.replace("px", "")) # Draw label draw.text((20, y_offset), f"{unit} ({value})", fill=(0, 0, 0)) # Draw spacing visualization draw.rectangle([150, y_offset, 150 + px_value, y_offset + 20], fill=(59, 130, 246), outline=(0, 0, 0)) y_offset += 40 # Safe areas visualization draw.text((20, 400), "Platform Safe Areas", fill=(0, 0, 0), font=None) platforms = ["youtube_shorts", "tiktok", "instagram_story"] x_offset = 20 for platform in platforms: safe_area = spacing.get_safe_area(platform) # Draw platform card card_width = 180 card_height = 320 draw.text((x_offset, 430), platform.replace("_", " ").title(), fill=(0, 0, 0)) # Draw outer box (viewport) draw.rectangle([x_offset, 460, x_offset + card_width, 460 + card_height], fill=(240, 240, 240), outline=(0, 0, 0)) # Draw safe area (inset) safe_x1 = x_offset + safe_area.left // 4 safe_y1 = 460 + safe_area.top // 4 safe_x2 = x_offset + card_width - safe_area.right // 4 safe_y2 = 460 + card_height - safe_area.bottom // 4 draw.rectangle([safe_x1, safe_y1, safe_x2, safe_y2], fill=(144, 238, 144), outline=(34, 197, 94), width=2) # Labels draw.text((x_offset + 5, 470), f"T:{safe_area.top}", fill=(100, 100, 100)) draw.text((x_offset + 5, 760), f"B:{safe_area.bottom}", fill=(100, 100, 100)) x_offset += card_width + 40 # Grid systems draw.text((700, 400), "Grid Systems", fill=(0, 0, 0), font=None) for i, columns in enumerate([12, 8, 4]): grid = spacing.get_grid(columns) y = 430 + i * 100 draw.text((700, y), f"{columns}-Column Grid", fill=(0, 0, 0)) draw.text((700, y + 20), f"Gap: {grid.gap}, Margin: {grid.margin}", fill=(100, 100, 100)) # Draw simplified grid grid_width = 400 col_width = (grid_width - (columns - 1) * 4) // columns for col in range(columns): x = 700 + col * (col_width + 4) draw.rectangle([x, y + 45, x + col_width, y + 65], fill=(200, 200, 255), outline=(100, 100, 200)) img.save(output_path) print(f"✅ Saved spacing to {output_path}") return output_path def main(): """Generate all visual showcases.""" print("\n" + "=" * 60) print("🎨 GENERATING VISUAL DESIGN SYSTEM SHOWCASE") print("=" * 60 + "\n") # Create output directory output_dir = Path("output") output_dir.mkdir(exist_ok=True) # Generate images files = [] files.append(create_color_palette_image()) files.append(create_gradient_image()) files.append(create_semantic_colors_image()) files.append(create_theme_comparison_image()) files.append(create_typography_image()) files.append(create_motion_image()) files.append(create_spacing_image()) print("\n" + "=" * 60) print("✅ Visual showcase complete!") print("=" * 60) print("\nGenerated files:") for file in files: print(f" - {file}") print("\nOpen these PNG files to see the design system visually!") print() if __name__ == "__main__": main()

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/chrishayuk/chuk-mcp-design-system'

If you have feedback or need assistance with the MCP directory API, please join our Discord server