This commit is contained in:
2026-04-12 01:06:31 +07:00
commit 10d660cbcb
1066 changed files with 228596 additions and 0 deletions

View File

@@ -0,0 +1,215 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CIP Design Core - BM25 search engine for Corporate Identity Program design guidelines
"""
import csv
import re
from pathlib import Path
from math import log
from collections import defaultdict
# ============ CONFIGURATION ============
DATA_DIR = Path(__file__).parent.parent.parent / "data" / "cip"
MAX_RESULTS = 3
CSV_CONFIG = {
"deliverable": {
"file": "deliverables.csv",
"search_cols": ["Deliverable", "Category", "Keywords", "Description", "Mockup Context"],
"output_cols": ["Deliverable", "Category", "Keywords", "Description", "Dimensions", "File Format", "Logo Placement", "Color Usage", "Typography Notes", "Mockup Context", "Best Practices", "Avoid"]
},
"style": {
"file": "styles.csv",
"search_cols": ["Style Name", "Category", "Keywords", "Description", "Mood"],
"output_cols": ["Style Name", "Category", "Keywords", "Description", "Primary Colors", "Secondary Colors", "Typography", "Materials", "Finishes", "Mood", "Best For", "Avoid For"]
},
"industry": {
"file": "industries.csv",
"search_cols": ["Industry", "Keywords", "CIP Style", "Mood"],
"output_cols": ["Industry", "Keywords", "CIP Style", "Primary Colors", "Secondary Colors", "Typography", "Key Deliverables", "Mood", "Best Practices", "Avoid"]
},
"mockup": {
"file": "mockup-contexts.csv",
"search_cols": ["Context Name", "Category", "Keywords", "Scene Description"],
"output_cols": ["Context Name", "Category", "Keywords", "Scene Description", "Lighting", "Environment", "Props", "Camera Angle", "Background", "Style Notes", "Best For", "Prompt Modifiers"]
}
}
# ============ BM25 IMPLEMENTATION ============
class BM25:
"""BM25 ranking algorithm for text search"""
def __init__(self, k1=1.5, b=0.75):
self.k1 = k1
self.b = b
self.corpus = []
self.doc_lengths = []
self.avgdl = 0
self.idf = {}
self.doc_freqs = defaultdict(int)
self.N = 0
def tokenize(self, text):
"""Lowercase, split, remove punctuation, filter short words"""
text = re.sub(r'[^\w\s]', ' ', str(text).lower())
return [w for w in text.split() if len(w) > 2]
def fit(self, documents):
"""Build BM25 index from documents"""
self.corpus = [self.tokenize(doc) for doc in documents]
self.N = len(self.corpus)
if self.N == 0:
return
self.doc_lengths = [len(doc) for doc in self.corpus]
self.avgdl = sum(self.doc_lengths) / self.N
for doc in self.corpus:
seen = set()
for word in doc:
if word not in seen:
self.doc_freqs[word] += 1
seen.add(word)
for word, freq in self.doc_freqs.items():
self.idf[word] = log((self.N - freq + 0.5) / (freq + 0.5) + 1)
def score(self, query):
"""Score all documents against query"""
query_tokens = self.tokenize(query)
scores = []
for idx, doc in enumerate(self.corpus):
score = 0
doc_len = self.doc_lengths[idx]
term_freqs = defaultdict(int)
for word in doc:
term_freqs[word] += 1
for token in query_tokens:
if token in self.idf:
tf = term_freqs[token]
idf = self.idf[token]
numerator = tf * (self.k1 + 1)
denominator = tf + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)
score += idf * numerator / denominator
scores.append((idx, score))
return sorted(scores, key=lambda x: x[1], reverse=True)
# ============ SEARCH FUNCTIONS ============
def _load_csv(filepath):
"""Load CSV and return list of dicts"""
with open(filepath, 'r', encoding='utf-8') as f:
return list(csv.DictReader(f))
def _search_csv(filepath, search_cols, output_cols, query, max_results):
"""Core search function using BM25"""
if not filepath.exists():
return []
data = _load_csv(filepath)
# Build documents from search columns
documents = [" ".join(str(row.get(col, "")) for col in search_cols) for row in data]
# BM25 search
bm25 = BM25()
bm25.fit(documents)
ranked = bm25.score(query)
# Get top results with score > 0
results = []
for idx, score in ranked[:max_results]:
if score > 0:
row = data[idx]
results.append({col: row.get(col, "") for col in output_cols if col in row})
return results
def detect_domain(query):
"""Auto-detect the most relevant domain from query"""
query_lower = query.lower()
domain_keywords = {
"deliverable": ["card", "letterhead", "envelope", "folder", "shirt", "cap", "badge", "signage", "vehicle", "car", "van", "stationery", "uniform", "merchandise", "packaging", "banner", "booth"],
"style": ["style", "minimal", "modern", "luxury", "vintage", "industrial", "elegant", "bold", "corporate", "organic", "playful"],
"industry": ["tech", "finance", "legal", "healthcare", "hospitality", "food", "fashion", "retail", "construction", "logistics"],
"mockup": ["mockup", "scene", "context", "photo", "shot", "lighting", "background", "studio", "lifestyle"]
}
scores = {domain: sum(1 for kw in keywords if kw in query_lower) for domain, keywords in domain_keywords.items()}
best = max(scores, key=scores.get)
return best if scores[best] > 0 else "deliverable"
def search(query, domain=None, max_results=MAX_RESULTS):
"""Main search function with auto-domain detection"""
if domain is None:
domain = detect_domain(query)
config = CSV_CONFIG.get(domain, CSV_CONFIG["deliverable"])
filepath = DATA_DIR / config["file"]
if not filepath.exists():
return {"error": f"File not found: {filepath}", "domain": domain}
results = _search_csv(filepath, config["search_cols"], config["output_cols"], query, max_results)
return {
"domain": domain,
"query": query,
"file": config["file"],
"count": len(results),
"results": results
}
def search_all(query, max_results=2):
"""Search across all domains and combine results"""
all_results = {}
for domain in CSV_CONFIG.keys():
result = search(query, domain, max_results)
if result.get("results"):
all_results[domain] = result["results"]
return all_results
def get_cip_brief(brand_name, industry_query, style_query=None):
"""Generate a comprehensive CIP brief for a brand"""
# Search industry
industry_results = search(industry_query, "industry", 1)
industry = industry_results.get("results", [{}])[0] if industry_results.get("results") else {}
# Search style (use industry style if not specified)
style_query = style_query or industry.get("CIP Style", "corporate minimal")
style_results = search(style_query, "style", 1)
style = style_results.get("results", [{}])[0] if style_results.get("results") else {}
# Get recommended deliverables for the industry
key_deliverables = industry.get("Key Deliverables", "").split()
deliverable_results = []
for d in key_deliverables[:5]:
result = search(d, "deliverable", 1)
if result.get("results"):
deliverable_results.append(result["results"][0])
return {
"brand_name": brand_name,
"industry": industry,
"style": style,
"recommended_deliverables": deliverable_results,
"color_system": {
"primary": style.get("Primary Colors", industry.get("Primary Colors", "")),
"secondary": style.get("Secondary Colors", industry.get("Secondary Colors", ""))
},
"typography": style.get("Typography", industry.get("Typography", "")),
"materials": style.get("Materials", ""),
"finishes": style.get("Finishes", "")
}

View File

@@ -0,0 +1,484 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CIP Design Generator - Generate corporate identity mockups using Gemini Nano Banana
Uses Gemini's native image generation (Nano Banana 2/Pro) for high-quality mockups.
Supports text-and-image-to-image generation for using actual brand logos.
- gemini-3.1-flash-image-preview: Nano Banana 2, fastest, 95% Pro quality (default)
- gemini-3-pro-image-preview: Pro quality, 4K text rendering
Image Editing (text-and-image-to-image):
When --logo is provided, the script uses Gemini's image editing capability
to incorporate the actual logo into CIP mockups instead of generating one.
"""
import argparse
import json
import os
import sys
from pathlib import Path
from datetime import datetime
# Add parent directory for imports
sys.path.insert(0, str(Path(__file__).parent))
from core import search, get_cip_brief
# Model options
MODELS = {
"flash": "gemini-3.1-flash-image-preview", # Nano Banana 2 - fastest, 95% Pro quality (default)
"pro": "gemini-3-pro-image-preview" # Nano Banana Pro - quality, 4K text
}
DEFAULT_MODEL = "flash"
def load_logo_image(logo_path):
"""Load logo image using PIL for Gemini image editing"""
try:
from PIL import Image
except ImportError:
print("Error: pillow package not installed.")
print("Install with: pip install pillow")
return None
logo_path = Path(logo_path)
if not logo_path.exists():
print(f"Error: Logo file not found: {logo_path}")
return None
try:
img = Image.open(logo_path)
# Convert to RGB if necessary (Gemini works best with RGB)
if img.mode in ('RGBA', 'P'):
# Create white background for transparent images
background = Image.new('RGB', img.size, (255, 255, 255))
if img.mode == 'RGBA':
background.paste(img, mask=img.split()[3]) # Use alpha channel as mask
else:
background.paste(img)
img = background
elif img.mode != 'RGB':
img = img.convert('RGB')
return img
except Exception as e:
print(f"Error loading logo: {e}")
return None
# Load environment variables
def load_env():
"""Load environment variables from .env files"""
env_paths = [
Path(__file__).parent.parent.parent / ".env",
Path.home() / ".claude" / "skills" / ".env",
Path.home() / ".claude" / ".env"
]
for env_path in env_paths:
if env_path.exists():
with open(env_path) as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
if key not in os.environ:
os.environ[key] = value.strip('"\'')
load_env()
def build_cip_prompt(deliverable, brand_name, style=None, industry=None, mockup=None, use_logo_image=False):
"""Build an optimized prompt for CIP mockup generation
Args:
deliverable: Type of deliverable (business card, letterhead, etc.)
brand_name: Name of the brand
style: Design style preference
industry: Industry for style recommendations
mockup: Mockup context override
use_logo_image: If True, prompt is optimized for image editing with logo
"""
# Get deliverable details
deliverable_info = search(deliverable, "deliverable", 1)
deliverable_data = deliverable_info.get("results", [{}])[0] if deliverable_info.get("results") else {}
# Get style details
style_info = search(style or "corporate minimal", "style", 1) if style else {}
style_data = style_info.get("results", [{}])[0] if style_info.get("results") else {}
# Get industry details
industry_info = search(industry or "technology", "industry", 1) if industry else {}
industry_data = industry_info.get("results", [{}])[0] if industry_info.get("results") else {}
# Get mockup context
mockup_context = deliverable_data.get("Mockup Context", "clean professional")
if mockup:
mockup_info = search(mockup, "mockup", 1)
if mockup_info.get("results"):
mockup_data = mockup_info["results"][0]
mockup_context = mockup_data.get("Scene Description", mockup_context)
# Build prompt components
deliverable_name = deliverable_data.get("Deliverable", deliverable)
description = deliverable_data.get("Description", "")
dimensions = deliverable_data.get("Dimensions", "")
logo_placement = deliverable_data.get("Logo Placement", "center")
style_name = style_data.get("Style Name", style or "corporate")
primary_colors = style_data.get("Primary Colors", industry_data.get("Primary Colors", "#0F172A #FFFFFF"))
typography = style_data.get("Typography", industry_data.get("Typography", "clean sans-serif"))
materials = style_data.get("Materials", "premium quality")
finishes = style_data.get("Finishes", "professional")
mood = style_data.get("Mood", industry_data.get("Mood", "professional"))
# Construct the prompt - different for image editing vs pure generation
if use_logo_image:
# Image editing prompt: instructs to USE the provided logo image
prompt_parts = [
f"Create a professional corporate identity mockup photograph of a {deliverable_name}",
f"Use the EXACT logo from the provided image - do NOT modify or recreate the logo",
f"The logo MUST appear exactly as shown in the input image",
f"Place the logo on the {deliverable_name} at: {logo_placement}",
f"Brand name: '{brand_name}'",
f"{description}" if description else "",
f"Design style: {style_name}",
f"Color scheme matching the logo colors",
f"Materials: {materials} with {finishes} finish",
f"Setting: {mockup_context}",
f"Mood: {mood}",
"Photorealistic product photography",
"Soft natural lighting, professional studio quality",
"8K resolution, sharp details"
]
else:
# Pure text-to-image prompt
prompt_parts = [
f"Professional corporate identity mockup photograph",
f"showing {deliverable_name} for brand '{brand_name}'",
f"{description}" if description else "",
f"{style_name} design style",
f"using colors {primary_colors}",
f"{typography} typography",
f"logo placement: {logo_placement}",
f"{materials} materials with {finishes} finish",
f"{mockup_context} setting",
f"{mood} mood",
"photorealistic product photography",
"soft natural lighting",
"high quality professional shot",
"8k resolution detailed"
]
prompt = ", ".join([p for p in prompt_parts if p])
return {
"prompt": prompt,
"deliverable": deliverable_name,
"style": style_name,
"brand": brand_name,
"colors": primary_colors,
"mockup_context": mockup_context,
"logo_placement": logo_placement
}
def generate_with_nano_banana(prompt_data, output_dir=None, model_key="flash", aspect_ratio="1:1", logo_image=None):
"""Generate image using Gemini Nano Banana (native image generation)
Supports two modes:
1. Text-to-image: Pure prompt-based generation (logo_image=None)
2. Image editing: Text-and-image-to-image using provided logo (logo_image=PIL.Image)
Models:
- flash: gemini-3.1-flash-image-preview (fast, cost-effective) - DEFAULT
- pro: gemini-3-pro-image-preview (quality, 4K text rendering)
Args:
prompt_data: Dict with prompt, deliverable, brand, etc.
output_dir: Output directory for generated images
model_key: 'flash' or 'pro'
aspect_ratio: Output aspect ratio (1:1, 16:9, etc.)
logo_image: PIL.Image object of the brand logo for image editing mode
"""
try:
from google import genai
from google.genai import types
except ImportError:
print("Error: google-genai package not installed.")
print("Install with: pip install google-genai")
return None
api_key = os.environ.get("GEMINI_API_KEY") or os.environ.get("GOOGLE_API_KEY")
if not api_key:
print("Error: GEMINI_API_KEY or GOOGLE_API_KEY not set")
return None
client = genai.Client(api_key=api_key)
prompt = prompt_data["prompt"]
model_name = MODELS.get(model_key, MODELS[DEFAULT_MODEL])
# Determine mode
mode = "image-editing" if logo_image else "text-to-image"
print(f"\n🎨 Generating CIP mockup...")
print(f" Mode: {mode}")
print(f" Deliverable: {prompt_data['deliverable']}")
print(f" Brand: {prompt_data['brand']}")
print(f" Style: {prompt_data['style']}")
print(f" Model: {model_name}")
print(f" Context: {prompt_data['mockup_context']}")
if logo_image:
print(f" Logo: Using provided image ({logo_image.size[0]}x{logo_image.size[1]})")
try:
# Build contents: either just prompt or [prompt, image] for image editing
if logo_image:
# Image editing mode: pass both prompt and logo image
contents = [prompt, logo_image]
else:
# Text-to-image mode: just the prompt
contents = prompt
# Use generate_content with response_modalities=['IMAGE'] for Nano Banana
response = client.models.generate_content(
model=model_name,
contents=contents,
config=types.GenerateContentConfig(
response_modalities=['IMAGE'], # Uppercase required
image_config=types.ImageConfig(
aspect_ratio=aspect_ratio
)
)
)
# Extract image from response
if response.candidates and response.candidates[0].content.parts:
for part in response.candidates[0].content.parts:
if hasattr(part, 'inline_data') and part.inline_data:
# Save image
output_dir = output_dir or Path.cwd()
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
brand_slug = prompt_data["brand"].lower().replace(" ", "-")
deliverable_slug = prompt_data["deliverable"].lower().replace(" ", "-")
filename = f"{brand_slug}-{deliverable_slug}-{timestamp}.png"
filepath = output_dir / filename
image_data = part.inline_data.data
with open(filepath, "wb") as f:
f.write(image_data)
print(f"\n✅ Generated: {filepath}")
return str(filepath)
print("No image generated in response")
return None
except Exception as e:
print(f"Error generating image: {e}")
return None
def generate_cip_set(brand_name, industry, style=None, deliverables=None, output_dir=None, model_key="flash", logo_path=None, aspect_ratio="1:1"):
"""Generate a complete CIP set for a brand
Args:
brand_name: Brand name to generate for
industry: Industry type for style recommendations
style: Optional specific style override
deliverables: List of deliverables to generate (default: core set)
output_dir: Output directory for images
model_key: 'flash' (fast) or 'pro' (quality)
logo_path: Path to brand logo image for image editing mode
aspect_ratio: Output aspect ratio
"""
# Load logo image if provided
logo_image = None
if logo_path:
logo_image = load_logo_image(logo_path)
if not logo_image:
print("Warning: Could not load logo, falling back to text-to-image mode")
# Get CIP brief for the brand
brief = get_cip_brief(brand_name, industry, style)
# Default deliverables if not specified
if not deliverables:
deliverables = ["business card", "letterhead", "office signage", "vehicle", "polo shirt"]
results = []
for deliverable in deliverables:
prompt_data = build_cip_prompt(
deliverable=deliverable,
brand_name=brand_name,
style=brief.get("style", {}).get("Style Name"),
industry=industry,
use_logo_image=(logo_image is not None)
)
filepath = generate_with_nano_banana(
prompt_data,
output_dir,
model_key=model_key,
aspect_ratio=aspect_ratio,
logo_image=logo_image
)
if filepath:
results.append({
"deliverable": deliverable,
"filepath": filepath,
"prompt": prompt_data["prompt"]
})
return results
def check_logo_required(brand_name, skip_prompt=False):
"""Check if logo is required and suggest logo-design skill if not provided
Returns:
str: 'continue' to proceed without logo, 'generate' to use logo-design skill, 'exit' to abort
"""
if skip_prompt:
return 'continue'
print(f"\n⚠️ No logo image provided for '{brand_name}'")
print(" Without a logo, AI will generate its own interpretation of the brand logo.")
print("")
print(" Options:")
print(" 1. Continue without logo (AI-generated logo interpretation)")
print(" 2. Generate a logo first using 'logo-design' skill")
print(" 3. Exit and provide a logo path with --logo")
print("")
try:
choice = input(" Enter choice [1/2/3] (default: 1): ").strip()
if choice == '2':
return 'generate'
elif choice == '3':
return 'exit'
return 'continue'
except (EOFError, KeyboardInterrupt):
return 'continue'
def main():
parser = argparse.ArgumentParser(
description="Generate CIP mockups using Gemini Nano Banana",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Generate with brand logo (RECOMMENDED)
python generate.py --brand "TopGroup" --logo /path/to/logo.png --deliverable "business card"
# Generate CIP set with logo
python generate.py --brand "TopGroup" --logo /path/to/logo.png --industry "consulting" --set
# Generate without logo (AI interprets brand)
python generate.py --brand "TechFlow" --deliverable "business card" --no-logo-prompt
# Generate with Pro model (higher quality, 4K text)
python generate.py --brand "TechFlow" --logo logo.png --deliverable "business card" --model pro
# Specify output directory and aspect ratio
python generate.py --brand "MyBrand" --logo logo.png --deliverable "vehicle" --output ./mockups --ratio 16:9
Models:
flash (default): gemini-3.1-flash-image-preview - Fast, cost-effective
pro: gemini-3-pro-image-preview - Quality, 4K text rendering
Image Editing Mode:
When --logo is provided, uses Gemini's text-and-image-to-image capability
to incorporate your ACTUAL logo into the CIP mockups.
"""
)
parser.add_argument("--brand", "-b", required=True, help="Brand name")
parser.add_argument("--logo", "-l", help="Path to brand logo image (enables image editing mode)")
parser.add_argument("--deliverable", "-d", help="Single deliverable to generate")
parser.add_argument("--deliverables", help="Comma-separated list of deliverables")
parser.add_argument("--industry", "-i", default="technology", help="Industry type")
parser.add_argument("--style", "-s", help="Design style")
parser.add_argument("--mockup", "-m", help="Mockup context")
parser.add_argument("--set", action="store_true", help="Generate full CIP set")
parser.add_argument("--output", "-o", help="Output directory")
parser.add_argument("--model", default="flash", choices=["flash", "pro"], help="Model: flash (fast) or pro (quality)")
parser.add_argument("--ratio", default="1:1", help="Aspect ratio (1:1, 16:9, 4:3, etc.)")
parser.add_argument("--prompt-only", action="store_true", help="Only show prompt, don't generate")
parser.add_argument("--json", "-j", action="store_true", help="Output as JSON")
parser.add_argument("--no-logo-prompt", action="store_true", help="Skip logo prompt, proceed without logo")
args = parser.parse_args()
# Check if logo is provided, prompt user if not
logo_image = None
if args.logo:
logo_image = load_logo_image(args.logo)
if not logo_image:
print("Error: Could not load logo image")
sys.exit(1)
elif not args.prompt_only:
# No logo provided - ask user what to do
action = check_logo_required(args.brand, skip_prompt=args.no_logo_prompt)
if action == 'generate':
print("\n💡 To generate a logo, use the logo-design skill:")
print(f" python ~/.opencode/skills/design/scripts/logo/generate.py --brand \"{args.brand}\" --industry \"{args.industry}\"")
print("\n Then re-run this command with --logo <generated_logo.png>")
sys.exit(0)
elif action == 'exit':
print("\n Provide logo with: --logo /path/to/your/logo.png")
sys.exit(0)
# else: continue without logo
use_logo = logo_image is not None
if args.set or args.deliverables:
# Generate multiple deliverables
deliverables = args.deliverables.split(",") if args.deliverables else None
if args.prompt_only:
results = []
deliverables = deliverables or ["business card", "letterhead", "office signage", "vehicle", "polo shirt"]
for d in deliverables:
prompt_data = build_cip_prompt(d, args.brand, args.style, args.industry, args.mockup, use_logo_image=use_logo)
results.append(prompt_data)
if args.json:
print(json.dumps(results, indent=2))
else:
for r in results:
print(f"\n{r['deliverable']}:\n{r['prompt']}\n")
else:
results = generate_cip_set(
args.brand, args.industry, args.style, deliverables, args.output,
model_key=args.model, logo_path=args.logo, aspect_ratio=args.ratio
)
if args.json:
print(json.dumps(results, indent=2))
else:
print(f"\n✅ Generated {len(results)} CIP mockups")
else:
# Generate single deliverable
deliverable = args.deliverable or "business card"
prompt_data = build_cip_prompt(deliverable, args.brand, args.style, args.industry, args.mockup, use_logo_image=use_logo)
if args.prompt_only:
if args.json:
print(json.dumps(prompt_data, indent=2))
else:
print(f"\nPrompt:\n{prompt_data['prompt']}")
else:
filepath = generate_with_nano_banana(
prompt_data, args.output, model_key=args.model,
aspect_ratio=args.ratio, logo_image=logo_image
)
if args.json:
print(json.dumps({"filepath": filepath, **prompt_data}, indent=2))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,424 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CIP HTML Presentation Renderer
Generates a professional HTML presentation from CIP mockup images
with detailed descriptions, concepts, and brand guidelines.
"""
import argparse
import json
import os
import sys
import base64
from pathlib import Path
from datetime import datetime
# Add parent directory for imports
sys.path.insert(0, str(Path(__file__).parent))
from core import search, get_cip_brief
# Deliverable descriptions for presentation
DELIVERABLE_INFO = {
"business card": {
"title": "Business Card",
"concept": "First impression touchpoint for professional networking",
"purpose": "Creates memorable brand recall during business exchanges",
"specs": "Standard 3.5 x 2 inches, premium paper stock"
},
"letterhead": {
"title": "Letterhead",
"concept": "Official correspondence identity",
"purpose": "Establishes credibility and professionalism in written communications",
"specs": "A4/Letter size, digital and print versions"
},
"document template": {
"title": "Document Template",
"concept": "Branded document system for internal and external use",
"purpose": "Ensures consistent brand representation across all documents",
"specs": "Multiple formats: Word, PDF, Google Docs compatible"
},
"reception signage": {
"title": "Reception Signage",
"concept": "Brand presence in physical office environment",
"purpose": "Creates strong first impression for visitors and reinforces brand identity",
"specs": "3D dimensional letters, backlit LED options, premium materials"
},
"office signage": {
"title": "Office Signage",
"concept": "Wayfinding and brand presence system",
"purpose": "Guides visitors while maintaining consistent brand experience",
"specs": "Modular system with directional and informational signs"
},
"polo shirt": {
"title": "Polo Shirt",
"concept": "Professional team apparel",
"purpose": "Creates unified team identity and brand ambassadorship",
"specs": "Premium pique cotton, embroidered logo on left chest"
},
"t-shirt": {
"title": "T-Shirt",
"concept": "Casual brand apparel",
"purpose": "Extends brand reach through everyday wear and promotional events",
"specs": "High-quality cotton, screen print or embroidery options"
},
"vehicle": {
"title": "Vehicle Branding",
"concept": "Mobile brand advertising",
"purpose": "Transforms fleet into moving billboards for maximum visibility",
"specs": "Partial or full wrap, vinyl graphics, weather-resistant"
},
"van": {
"title": "Van Branding",
"concept": "Commercial vehicle identity",
"purpose": "Professional fleet presence for service and delivery operations",
"specs": "Full wrap design, high-visibility contact information"
},
"car": {
"title": "Car Branding",
"concept": "Executive vehicle identity",
"purpose": "Professional presence for corporate and sales teams",
"specs": "Subtle branding, door panels and rear window"
},
"envelope": {
"title": "Envelope",
"concept": "Branded mail correspondence",
"purpose": "Extends brand identity to all outgoing mail",
"specs": "DL, C4, C5 sizes with logo placement"
},
"folder": {
"title": "Presentation Folder",
"concept": "Document organization with brand identity",
"purpose": "Professional presentation of proposals and materials",
"specs": "A4/Letter pocket folder with die-cut design"
}
}
def get_image_base64(image_path):
"""Convert image to base64 for embedding in HTML"""
try:
with open(image_path, "rb") as f:
return base64.b64encode(f.read()).decode('utf-8')
except Exception as e:
print(f"Warning: Could not load image {image_path}: {e}")
return None
def get_deliverable_info(filename):
"""Extract deliverable type from filename and get info"""
filename_lower = filename.lower()
for key, info in DELIVERABLE_INFO.items():
if key.replace(" ", "-") in filename_lower or key.replace(" ", "_") in filename_lower:
return info
# Default info
return {
"title": filename.replace("-", " ").replace("_", " ").title(),
"concept": "Brand identity application",
"purpose": "Extends brand presence across touchpoints",
"specs": "Custom specifications"
}
def generate_html(brand_name, industry, images_dir, output_path=None, style=None):
"""Generate HTML presentation from CIP images"""
images_dir = Path(images_dir)
if not images_dir.exists():
print(f"Error: Directory not found: {images_dir}")
return None
# Get all PNG images
images = sorted(images_dir.glob("*.png"))
if not images:
print(f"Error: No PNG images found in {images_dir}")
return None
# Get CIP brief for brand info
brief = get_cip_brief(brand_name, industry, style)
style_info = brief.get("style", {})
industry_info = brief.get("industry", {})
# 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>
<style>
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
}}
body {{
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: #0a0a0a;
color: #ffffff;
line-height: 1.6;
}}
.hero {{
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 4rem 2rem;
background: linear-gradient(135deg, #1a1a2e 0%, #0a0a0a 100%);
}}
.hero h1 {{
font-size: 4rem;
font-weight: 700;
letter-spacing: -0.02em;
margin-bottom: 1rem;
background: linear-gradient(135deg, #ffffff 0%, #888888 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}}
.hero .subtitle {{
font-size: 1.5rem;
color: #888;
margin-bottom: 3rem;
}}
.hero .meta {{
display: flex;
gap: 3rem;
flex-wrap: wrap;
justify-content: center;
}}
.hero .meta-item {{
text-align: center;
}}
.hero .meta-label {{
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #666;
margin-bottom: 0.5rem;
}}
.hero .meta-value {{
font-size: 1rem;
color: #ccc;
}}
.section {{
padding: 6rem 2rem;
max-width: 1400px;
margin: 0 auto;
}}
.section-title {{
font-size: 2.5rem;
font-weight: 600;
margin-bottom: 1rem;
color: #fff;
}}
.section-subtitle {{
font-size: 1.1rem;
color: #888;
margin-bottom: 4rem;
max-width: 600px;
}}
.deliverable {{
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
margin-bottom: 8rem;
align-items: center;
}}
.deliverable:nth-child(even) {{
direction: rtl;
}}
.deliverable:nth-child(even) > * {{
direction: ltr;
}}
.deliverable-image {{
position: relative;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
}}
.deliverable-image img {{
width: 100%;
height: auto;
display: block;
}}
.deliverable-content {{
padding: 2rem 0;
}}
.deliverable-title {{
font-size: 2rem;
font-weight: 600;
margin-bottom: 1rem;
color: #fff;
}}
.deliverable-concept {{
font-size: 1.1rem;
color: #aaa;
margin-bottom: 1.5rem;
font-style: italic;
}}
.deliverable-purpose {{
font-size: 1rem;
color: #888;
margin-bottom: 1.5rem;
line-height: 1.8;
}}
.deliverable-specs {{
display: inline-block;
padding: 0.5rem 1rem;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
font-size: 0.85rem;
color: #666;
}}
.color-palette {{
display: flex;
gap: 1rem;
margin-top: 2rem;
}}
.color-swatch {{
width: 60px;
height: 60px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}}
.footer {{
text-align: center;
padding: 4rem 2rem;
border-top: 1px solid #222;
color: #666;
}}
.footer p {{
margin-bottom: 0.5rem;
}}
@media (max-width: 900px) {{
.hero h1 {{
font-size: 2.5rem;
}}
.deliverable {{
grid-template-columns: 1fr;
gap: 2rem;
}}
.deliverable:nth-child(even) {{
direction: ltr;
}}
}}
</style>
</head>
<body>
<section class="hero">
<h1>{brand_name}</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>
<div class="meta-item">
<div class="meta-label">Style</div>
<div class="meta-value">{style_info.get("Style Name", "Corporate")}</div>
</div>
<div class="meta-item">
<div class="meta-label">Mood</div>
<div class="meta-value">{style_info.get("Mood", "Professional")}</div>
</div>
<div class="meta-item">
<div class="meta-label">Deliverables</div>
<div class="meta-value">{len(images)} Items</div>
</div>
</div>
</section>
<section class="section">
<h2 class="section-title">Brand Applications</h2>
<p class="section-subtitle">
Comprehensive identity system designed to maintain consistency
across all brand touchpoints and communications.
</p>
''']
# Add each deliverable
for i, image_path in enumerate(images):
info = get_deliverable_info(image_path.stem)
img_base64 = get_image_base64(image_path)
if img_base64:
img_src = f"data:image/png;base64,{img_base64}"
else:
img_src = str(image_path)
html_parts.append(f'''
<div class="deliverable">
<div class="deliverable-image">
<img src="{img_src}" alt="{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>
</div>
</div>
''')
# Close HTML
html_parts.append(f'''
</section>
<footer class="footer">
<p><strong>{brand_name}</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>
</body>
</html>
''')
html_content = "".join(html_parts)
# Save HTML
output_path = output_path or images_dir / f"{brand_name.lower().replace(' ', '-')}-cip-presentation.html"
output_path = Path(output_path)
with open(output_path, "w", encoding="utf-8") as f:
f.write(html_content)
print(f"✅ HTML presentation generated: {output_path}")
return str(output_path)
def main():
parser = argparse.ArgumentParser(
description="Generate HTML presentation from CIP mockups",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Generate HTML from CIP images directory
python render-html.py --brand "TopGroup" --industry "consulting" --images ./topgroup-cip
# Specify output path
python render-html.py --brand "TopGroup" --industry "consulting" --images ./cip --output presentation.html
"""
)
parser.add_argument("--brand", "-b", required=True, help="Brand name")
parser.add_argument("--industry", "-i", default="technology", help="Industry type")
parser.add_argument("--style", "-s", help="Design style")
parser.add_argument("--images", required=True, help="Directory containing CIP mockup images")
parser.add_argument("--output", "-o", help="Output HTML file path")
args = parser.parse_args()
generate_html(
brand_name=args.brand,
industry=args.industry,
images_dir=args.images,
output_path=args.output,
style=args.style
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,127 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CIP Design Search CLI - Search corporate identity design guidelines
"""
import argparse
import json
import sys
from pathlib import Path
# Add parent directory for imports
sys.path.insert(0, str(Path(__file__).parent))
from core import search, search_all, get_cip_brief, CSV_CONFIG
def format_results(results, domain):
"""Format search results for display"""
if not results:
return "No results found."
output = []
for i, item in enumerate(results, 1):
output.append(f"\n{'='*60}")
output.append(f"Result {i}:")
for key, value in item.items():
if value:
output.append(f" {key}: {value}")
return "\n".join(output)
def format_brief(brief):
"""Format CIP brief for display"""
output = []
output.append(f"\n{'='*60}")
output.append(f"CIP DESIGN BRIEF: {brief['brand_name']}")
output.append(f"{'='*60}")
if brief.get("industry"):
output.append(f"\n📊 INDUSTRY: {brief['industry'].get('Industry', 'N/A')}")
output.append(f" Style: {brief['industry'].get('CIP Style', 'N/A')}")
output.append(f" Mood: {brief['industry'].get('Mood', 'N/A')}")
if brief.get("style"):
output.append(f"\n🎨 DESIGN STYLE: {brief['style'].get('Style Name', 'N/A')}")
output.append(f" Description: {brief['style'].get('Description', 'N/A')}")
output.append(f" Materials: {brief['style'].get('Materials', 'N/A')}")
output.append(f" Finishes: {brief['style'].get('Finishes', 'N/A')}")
if brief.get("color_system"):
output.append(f"\n🎯 COLOR SYSTEM:")
output.append(f" Primary: {brief['color_system'].get('primary', 'N/A')}")
output.append(f" Secondary: {brief['color_system'].get('secondary', 'N/A')}")
output.append(f"\n✏️ TYPOGRAPHY: {brief.get('typography', 'N/A')}")
if brief.get("recommended_deliverables"):
output.append(f"\n📦 RECOMMENDED DELIVERABLES:")
for d in brief["recommended_deliverables"]:
output.append(f"{d.get('Deliverable', 'N/A')}: {d.get('Description', '')[:60]}...")
return "\n".join(output)
def main():
parser = argparse.ArgumentParser(
description="Search CIP design guidelines",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Search deliverables
python search.py "business card"
# Search specific domain
python search.py "luxury elegant" --domain style
# Generate CIP brief
python search.py "tech startup" --cip-brief -b "TechFlow"
# Search all domains
python search.py "corporate professional" --all
# JSON output
python search.py "vehicle branding" --json
"""
)
parser.add_argument("query", help="Search query")
parser.add_argument("--domain", "-d", choices=list(CSV_CONFIG.keys()),
help="Search domain (auto-detected if not specified)")
parser.add_argument("--max", "-m", type=int, default=3, help="Max results (default: 3)")
parser.add_argument("--all", "-a", action="store_true", help="Search all domains")
parser.add_argument("--cip-brief", "-c", action="store_true", help="Generate CIP brief")
parser.add_argument("--brand", "-b", default="BrandName", help="Brand name for CIP brief")
parser.add_argument("--style", "-s", help="Style override for CIP brief")
parser.add_argument("--json", "-j", action="store_true", help="Output as JSON")
args = parser.parse_args()
if args.cip_brief:
brief = get_cip_brief(args.brand, args.query, args.style)
if args.json:
print(json.dumps(brief, indent=2))
else:
print(format_brief(brief))
elif args.all:
results = search_all(args.query, args.max)
if args.json:
print(json.dumps(results, indent=2))
else:
for domain, items in results.items():
print(f"\n{'#'*60}")
print(f"# {domain.upper()}")
print(format_results(items, domain))
else:
result = search(args.query, args.domain, args.max)
if args.json:
print(json.dumps(result, indent=2))
else:
print(f"\nDomain: {result['domain']}")
print(f"Query: {result['query']}")
print(f"Results: {result['count']}")
print(format_results(result.get("results", []), result["domain"]))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,487 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Icon Generation Script using Gemini 3.1 Pro Preview API
Generates SVG icons via text generation (SVG is XML text format)
Model: gemini-3.1-pro-preview - best thinking, token efficiency, factual consistency
Usage:
python generate.py --prompt "settings gear icon" --style outlined
python generate.py --prompt "shopping cart" --style filled --color "#6366F1"
python generate.py --name "dashboard" --category navigation --style duotone
python generate.py --prompt "cloud upload" --batch 4 --output-dir ./icons
python generate.py --prompt "user profile" --sizes "16,24,32,48"
"""
import argparse
import json
import os
import re
import sys
import time
from pathlib import Path
from datetime import datetime
def load_env():
"""Load .env files in priority order"""
env_paths = [
Path(__file__).parent.parent.parent / ".env",
Path.home() / ".claude" / "skills" / ".env",
Path.home() / ".claude" / ".env"
]
for env_path in env_paths:
if env_path.exists():
with open(env_path) as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
if key not in os.environ:
os.environ[key] = value.strip('"\'')
load_env()
try:
from google import genai
from google.genai import types
except ImportError:
print("Error: google-genai package not installed.")
print("Install with: pip install google-genai")
sys.exit(1)
# ============ CONFIGURATION ============
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
MODEL = "gemini-3.1-pro-preview"
# Icon styles with SVG-specific instructions
ICON_STYLES = {
"outlined": "outlined stroke icons, 2px stroke width, no fill, clean open paths",
"filled": "solid filled icons, no stroke, flat color fills, bold shapes",
"duotone": "duotone style with primary color at full opacity and secondary color at 30% opacity, layered shapes",
"thin": "thin line icons, 1px or 1.5px stroke width, delicate minimalist lines",
"bold": "bold thick line icons, 3px stroke width, heavy weight, impactful",
"rounded": "rounded icons with round line caps and joins, soft corners, friendly feel",
"sharp": "sharp angular icons, square line caps and mitered joins, precise edges",
"flat": "flat design icons, solid fills, no gradients or shadows, geometric simplicity",
"gradient": "linear or radial gradient fills, modern vibrant color transitions",
"glassmorphism": "glassmorphism style with semi-transparent fills, blur backdrop effect simulation, frosted glass",
"pixel": "pixel art style icons on a grid, retro 8-bit aesthetic, crisp edges",
"hand-drawn": "hand-drawn sketch style, slightly irregular strokes, organic feel, imperfect lines",
"isometric": "isometric 3D projection, 30-degree angles, dimensional depth",
"glyph": "simple glyph style, single solid shape, minimal detail, pictogram",
"animated-ready": "animated-ready SVG with named groups and IDs for CSS/JS animation targets",
}
ICON_CATEGORIES = {
"navigation": "arrows, menus, hamburger, chevrons, home, back, forward, breadcrumb",
"action": "edit, delete, save, download, upload, share, copy, paste, print, search",
"communication": "email, chat, phone, video call, notification, bell, message bubble",
"media": "play, pause, stop, skip, volume, microphone, camera, image, gallery",
"file": "document, folder, archive, attachment, cloud, database, storage",
"user": "person, group, avatar, profile, settings, lock, key, shield",
"commerce": "cart, bag, wallet, credit card, receipt, tag, gift, store",
"data": "chart, graph, analytics, dashboard, table, filter, sort, calendar",
"development": "code, terminal, bug, git, API, server, database, deploy",
"social": "heart, star, thumbs up, bookmark, flag, trophy, badge, crown",
"weather": "sun, moon, cloud, rain, snow, wind, thunder, temperature",
"map": "pin, location, compass, globe, route, directions, map marker",
}
# SVG generation prompt template
SVG_PROMPT_TEMPLATE = """Generate a clean, production-ready SVG icon.
Requirements:
- Output ONLY valid SVG code, nothing else
- ViewBox: "0 0 {viewbox} {viewbox}"
- Use currentColor for strokes/fills (inherits CSS color)
- No embedded fonts or text elements unless specifically requested
- No raster images or external references
- Optimized paths with minimal nodes
- Accessible: include <title> element with icon description
{style_instructions}
{color_instructions}
{size_instructions}
Icon to generate: {prompt}
Output the SVG code only, wrapped in ```svg``` code block."""
SVG_BATCH_PROMPT_TEMPLATE = """Generate {count} distinct SVG icon variations for: {prompt}
Requirements for EACH icon:
- Output ONLY valid SVG code
- ViewBox: "0 0 {viewbox} {viewbox}"
- Use currentColor for strokes/fills (inherits CSS color)
- No embedded fonts, raster images, or external references
- Optimized paths with minimal nodes
- Include <title> element with icon description
{style_instructions}
{color_instructions}
Generate {count} different visual interpretations. Output each SVG in a separate ```svg``` code block.
Label each variation (e.g., "Variation 1: [brief description]")."""
def extract_svgs(text):
"""Extract SVG code blocks from model response"""
svgs = []
# Try ```svg code blocks first
pattern = r'```svg\s*\n(.*?)```'
matches = re.findall(pattern, text, re.DOTALL)
if matches:
svgs.extend(matches)
# Fallback: try ```xml code blocks
if not svgs:
pattern = r'```xml\s*\n(.*?)```'
matches = re.findall(pattern, text, re.DOTALL)
svgs.extend(matches)
# Fallback: try bare <svg> tags
if not svgs:
pattern = r'(<svg[^>]*>.*?</svg>)'
matches = re.findall(pattern, text, re.DOTALL)
svgs.extend(matches)
# Clean up extracted SVGs
cleaned = []
for svg in svgs:
svg = svg.strip()
if not svg.startswith('<svg'):
# Try to find <svg> within the extracted text
match = re.search(r'(<svg[^>]*>.*?</svg>)', svg, re.DOTALL)
if match:
svg = match.group(1)
else:
continue
cleaned.append(svg)
return cleaned
def apply_color(svg_code, color):
"""Replace currentColor with specific color if provided"""
if color:
# Replace currentColor with the specified color
svg_code = svg_code.replace('currentColor', color)
# If no currentColor was present, add fill/stroke color
if color not in svg_code:
svg_code = svg_code.replace('<svg', f'<svg color="{color}"', 1)
return svg_code
def apply_viewbox_size(svg_code, size):
"""Adjust SVG viewBox to target size"""
if size:
# Update width/height attributes if present
svg_code = re.sub(r'width="[^"]*"', f'width="{size}"', svg_code)
svg_code = re.sub(r'height="[^"]*"', f'height="{size}"', svg_code)
# Add width/height if not present
if 'width=' not in svg_code:
svg_code = svg_code.replace('<svg', f'<svg width="{size}" height="{size}"', 1)
return svg_code
def generate_icon(prompt, style=None, category=None, name=None,
color=None, size=24, output_path=None, viewbox=24):
"""Generate a single SVG icon using Gemini 3.1 Pro Preview"""
if not GEMINI_API_KEY:
print("Error: GEMINI_API_KEY not set")
print("Set it with: export GEMINI_API_KEY='your-key'")
return None
client = genai.Client(api_key=GEMINI_API_KEY)
# Build style instructions
style_instructions = ""
if style and style in ICON_STYLES:
style_instructions = f"- Style: {ICON_STYLES[style]}"
# Build color instructions
color_instructions = "- Use currentColor for all strokes and fills"
if color:
color_instructions = f"- Use color: {color} for primary elements, currentColor for secondary"
# Build size instructions
size_instructions = f"- Design for {size}px display size, optimize detail level accordingly"
# Build final prompt
icon_prompt = prompt
if category and category in ICON_CATEGORIES:
icon_prompt = f"{prompt} (category: {ICON_CATEGORIES[category]})"
if name:
icon_prompt = f"'{name}' icon: {icon_prompt}"
full_prompt = SVG_PROMPT_TEMPLATE.format(
prompt=icon_prompt,
viewbox=viewbox,
style_instructions=style_instructions,
color_instructions=color_instructions,
size_instructions=size_instructions
)
print(f"Generating icon with {MODEL}...")
print(f"Prompt: {prompt}")
if style:
print(f"Style: {style}")
print()
try:
response = client.models.generate_content(
model=MODEL,
contents=full_prompt,
config=types.GenerateContentConfig(
temperature=0.7,
max_output_tokens=4096,
)
)
# Extract SVG from response
response_text = response.text if hasattr(response, 'text') else ""
if not response_text:
for part in response.candidates[0].content.parts:
if hasattr(part, 'text') and part.text:
response_text += part.text
svgs = extract_svgs(response_text)
if not svgs:
print("No valid SVG generated. Model response:")
print(response_text[:500])
return None
svg_code = svgs[0]
# Apply color if specified
svg_code = apply_color(svg_code, color)
# Apply size
svg_code = apply_viewbox_size(svg_code, size)
# Determine output path
if output_path is None:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
slug = name or prompt.split()[0] if prompt else "icon"
slug = re.sub(r'[^a-zA-Z0-9_-]', '_', slug.lower())
style_suffix = f"_{style}" if style else ""
output_path = f"{slug}{style_suffix}_{timestamp}.svg"
# Save SVG
with open(output_path, "w", encoding="utf-8") as f:
f.write(svg_code)
print(f"Icon saved to: {output_path}")
return output_path
except Exception as e:
print(f"Error generating icon: {e}")
return None
def generate_batch(prompt, count, output_dir, style=None, color=None,
viewbox=24, name=None):
"""Generate multiple icon variations"""
if not GEMINI_API_KEY:
print("Error: GEMINI_API_KEY not set")
return []
client = genai.Client(api_key=GEMINI_API_KEY)
os.makedirs(output_dir, exist_ok=True)
# Build instructions
style_instructions = ""
if style and style in ICON_STYLES:
style_instructions = f"- Style: {ICON_STYLES[style]}"
color_instructions = "- Use currentColor for all strokes and fills"
if color:
color_instructions = f"- Use color: {color} for primary elements"
full_prompt = SVG_BATCH_PROMPT_TEMPLATE.format(
prompt=prompt,
count=count,
viewbox=viewbox,
style_instructions=style_instructions,
color_instructions=color_instructions
)
print(f"\n{'='*60}")
print(f" BATCH ICON GENERATION")
print(f" Model: {MODEL}")
print(f" Prompt: {prompt}")
print(f" Variants: {count}")
print(f" Output: {output_dir}")
print(f"{'='*60}\n")
try:
response = client.models.generate_content(
model=MODEL,
contents=full_prompt,
config=types.GenerateContentConfig(
temperature=0.9,
max_output_tokens=16384,
)
)
response_text = response.text if hasattr(response, 'text') else ""
if not response_text:
for part in response.candidates[0].content.parts:
if hasattr(part, 'text') and part.text:
response_text += part.text
svgs = extract_svgs(response_text)
if not svgs:
print("No valid SVGs generated.")
print(response_text[:500])
return []
results = []
slug = name or re.sub(r'[^a-zA-Z0-9_-]', '_', prompt.split()[0].lower())
style_suffix = f"_{style}" if style else ""
for i, svg_code in enumerate(svgs[:count]):
svg_code = apply_color(svg_code, color)
filename = f"{slug}{style_suffix}_{i+1:02d}.svg"
filepath = os.path.join(output_dir, filename)
with open(filepath, "w", encoding="utf-8") as f:
f.write(svg_code)
results.append(filepath)
print(f" [{i+1}/{len(svgs[:count])}] Saved: {filename}")
print(f"\n{'='*60}")
print(f" BATCH COMPLETE: {len(results)}/{count} icons generated")
print(f"{'='*60}\n")
return results
except Exception as e:
print(f"Error generating icons: {e}")
return []
def generate_sizes(prompt, sizes, style=None, color=None, output_dir=None, name=None):
"""Generate same icon at multiple sizes"""
if output_dir is None:
output_dir = "."
os.makedirs(output_dir, exist_ok=True)
results = []
slug = name or re.sub(r'[^a-zA-Z0-9_-]', '_', prompt.split()[0].lower())
style_suffix = f"_{style}" if style else ""
for size in sizes:
print(f"Generating {size}px variant...")
filename = f"{slug}{style_suffix}_{size}px.svg"
filepath = os.path.join(output_dir, filename)
result = generate_icon(
prompt=prompt,
style=style,
color=color,
size=size,
output_path=filepath,
viewbox=size
)
if result:
results.append(result)
time.sleep(1)
return results
def main():
parser = argparse.ArgumentParser(
description="Generate SVG icons using Gemini 3.1 Pro Preview"
)
parser.add_argument("--prompt", "-p", type=str, help="Icon description")
parser.add_argument("--name", "-n", type=str, help="Icon name (for filename)")
parser.add_argument("--style", "-s", choices=list(ICON_STYLES.keys()),
help="Icon style")
parser.add_argument("--category", "-c", choices=list(ICON_CATEGORIES.keys()),
help="Icon category for context")
parser.add_argument("--color", type=str,
help="Primary color (hex, e.g. #6366F1). Default: currentColor")
parser.add_argument("--size", type=int, default=24,
help="Icon size in px (default: 24)")
parser.add_argument("--viewbox", type=int, default=24,
help="SVG viewBox size (default: 24)")
parser.add_argument("--output", "-o", type=str, help="Output file path")
parser.add_argument("--output-dir", type=str, help="Output directory for batch")
parser.add_argument("--batch", type=int,
help="Number of icon variants to generate")
parser.add_argument("--sizes", type=str,
help="Comma-separated sizes (e.g. '16,24,32,48')")
parser.add_argument("--list-styles", action="store_true",
help="List available icon styles")
parser.add_argument("--list-categories", action="store_true",
help="List available icon categories")
args = parser.parse_args()
if args.list_styles:
print("Available icon styles:")
for style, desc in ICON_STYLES.items():
print(f" {style}: {desc[:70]}...")
return
if args.list_categories:
print("Available icon categories:")
for cat, desc in ICON_CATEGORIES.items():
print(f" {cat}: {desc}")
return
if not args.prompt and not args.name:
parser.error("Either --prompt or --name is required")
prompt = args.prompt or args.name
# Multi-size mode
if args.sizes:
sizes = [int(s.strip()) for s in args.sizes.split(",")]
generate_sizes(
prompt=prompt,
sizes=sizes,
style=args.style,
color=args.color,
output_dir=args.output_dir or "./icons",
name=args.name
)
# Batch mode
elif args.batch:
output_dir = args.output_dir or "./icons"
generate_batch(
prompt=prompt,
count=args.batch,
output_dir=output_dir,
style=args.style,
color=args.color,
viewbox=args.viewbox,
name=args.name
)
# Single icon
else:
generate_icon(
prompt=prompt,
style=args.style,
category=args.category,
name=args.name,
color=args.color,
size=args.size,
output_path=args.output,
viewbox=args.viewbox
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,175 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Logo Design Core - BM25 search engine for logo design guidelines
"""
import csv
import re
from pathlib import Path
from math import log
from collections import defaultdict
# ============ CONFIGURATION ============
DATA_DIR = Path(__file__).parent.parent.parent / "data" / "logo"
MAX_RESULTS = 3
CSV_CONFIG = {
"style": {
"file": "styles.csv",
"search_cols": ["Style Name", "Category", "Keywords", "Best For"],
"output_cols": ["Style Name", "Category", "Keywords", "Primary Colors", "Secondary Colors", "Typography", "Effects", "Best For", "Avoid For", "Complexity", "Era"]
},
"color": {
"file": "colors.csv",
"search_cols": ["Palette Name", "Category", "Keywords", "Psychology", "Best For"],
"output_cols": ["Palette Name", "Category", "Keywords", "Primary Hex", "Secondary Hex", "Accent Hex", "Background Hex", "Text Hex", "Psychology", "Best For", "Avoid For"]
},
"industry": {
"file": "industries.csv",
"search_cols": ["Industry", "Keywords", "Recommended Styles", "Mood"],
"output_cols": ["Industry", "Keywords", "Recommended Styles", "Primary Colors", "Typography", "Common Symbols", "Mood", "Best Practices", "Avoid"]
}
}
# ============ BM25 IMPLEMENTATION ============
class BM25:
"""BM25 ranking algorithm for text search"""
def __init__(self, k1=1.5, b=0.75):
self.k1 = k1
self.b = b
self.corpus = []
self.doc_lengths = []
self.avgdl = 0
self.idf = {}
self.doc_freqs = defaultdict(int)
self.N = 0
def tokenize(self, text):
"""Lowercase, split, remove punctuation, filter short words"""
text = re.sub(r'[^\w\s]', ' ', str(text).lower())
return [w for w in text.split() if len(w) > 2]
def fit(self, documents):
"""Build BM25 index from documents"""
self.corpus = [self.tokenize(doc) for doc in documents]
self.N = len(self.corpus)
if self.N == 0:
return
self.doc_lengths = [len(doc) for doc in self.corpus]
self.avgdl = sum(self.doc_lengths) / self.N
for doc in self.corpus:
seen = set()
for word in doc:
if word not in seen:
self.doc_freqs[word] += 1
seen.add(word)
for word, freq in self.doc_freqs.items():
self.idf[word] = log((self.N - freq + 0.5) / (freq + 0.5) + 1)
def score(self, query):
"""Score all documents against query"""
query_tokens = self.tokenize(query)
scores = []
for idx, doc in enumerate(self.corpus):
score = 0
doc_len = self.doc_lengths[idx]
term_freqs = defaultdict(int)
for word in doc:
term_freqs[word] += 1
for token in query_tokens:
if token in self.idf:
tf = term_freqs[token]
idf = self.idf[token]
numerator = tf * (self.k1 + 1)
denominator = tf + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)
score += idf * numerator / denominator
scores.append((idx, score))
return sorted(scores, key=lambda x: x[1], reverse=True)
# ============ SEARCH FUNCTIONS ============
def _load_csv(filepath):
"""Load CSV and return list of dicts"""
with open(filepath, 'r', encoding='utf-8') as f:
return list(csv.DictReader(f))
def _search_csv(filepath, search_cols, output_cols, query, max_results):
"""Core search function using BM25"""
if not filepath.exists():
return []
data = _load_csv(filepath)
# Build documents from search columns
documents = [" ".join(str(row.get(col, "")) for col in search_cols) for row in data]
# BM25 search
bm25 = BM25()
bm25.fit(documents)
ranked = bm25.score(query)
# Get top results with score > 0
results = []
for idx, score in ranked[:max_results]:
if score > 0:
row = data[idx]
results.append({col: row.get(col, "") for col in output_cols if col in row})
return results
def detect_domain(query):
"""Auto-detect the most relevant domain from query"""
query_lower = query.lower()
domain_keywords = {
"style": ["style", "minimalist", "vintage", "modern", "retro", "geometric", "abstract", "emblem", "badge", "wordmark", "mascot", "luxury", "playful", "corporate"],
"color": ["color", "palette", "hex", "#", "rgb", "blue", "red", "green", "gold", "warm", "cool", "vibrant", "pastel"],
"industry": ["tech", "healthcare", "finance", "legal", "restaurant", "food", "fashion", "beauty", "education", "sports", "fitness", "real estate", "crypto", "gaming"]
}
scores = {domain: sum(1 for kw in keywords if kw in query_lower) for domain, keywords in domain_keywords.items()}
best = max(scores, key=scores.get)
return best if scores[best] > 0 else "style"
def search(query, domain=None, max_results=MAX_RESULTS):
"""Main search function with auto-domain detection"""
if domain is None:
domain = detect_domain(query)
config = CSV_CONFIG.get(domain, CSV_CONFIG["style"])
filepath = DATA_DIR / config["file"]
if not filepath.exists():
return {"error": f"File not found: {filepath}", "domain": domain}
results = _search_csv(filepath, config["search_cols"], config["output_cols"], query, max_results)
return {
"domain": domain,
"query": query,
"file": config["file"],
"count": len(results),
"results": results
}
def search_all(query, max_results=2):
"""Search across all domains and combine results"""
all_results = {}
for domain in CSV_CONFIG.keys():
result = search(query, domain, max_results)
if result.get("results"):
all_results[domain] = result["results"]
return all_results

View File

@@ -0,0 +1,362 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Logo Generation Script using Gemini Nano Banana API
Uses Gemini 3.1 Flash Image Preview and Gemini 3 Pro Image Preview models
Models:
- Nano Banana 2 (default): gemini-3.1-flash-image-preview - fastest, 95% Pro quality, web grounding
- Nano Banana Pro (--pro): gemini-3-pro-image-preview - professional quality, advanced reasoning
Usage:
python generate.py --prompt "tech startup logo minimalist blue"
python generate.py --prompt "coffee shop vintage badge" --style vintage --output logo.png
python generate.py --brand "TechFlow" --industry tech --style minimalist
python generate.py --brand "TechFlow" --pro # Use Nano Banana Pro model
Batch mode (generates multiple variants):
python generate.py --brand "Unikorn" --batch 9 --output-dir ./logos --pro
"""
import argparse
import os
import sys
import time
from pathlib import Path
from datetime import datetime
# Load environment variables
def load_env():
"""Load .env files in priority order"""
env_paths = [
Path(__file__).parent.parent.parent / ".env",
Path.home() / ".claude" / "skills" / ".env",
Path.home() / ".claude" / ".env"
]
for env_path in env_paths:
if env_path.exists():
with open(env_path) as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
if key not in os.environ:
os.environ[key] = value.strip('"\'')
load_env()
try:
from google import genai
from google.genai import types
except ImportError:
print("Error: google-genai package not installed.")
print("Install with: pip install google-genai")
sys.exit(1)
# ============ CONFIGURATION ============
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
# Gemini "Nano Banana" model configurations for image generation
GEMINI_FLASH = "gemini-3.1-flash-image-preview" # Nano Banana 2: fastest, 95% Pro quality, web grounding
GEMINI_PRO = "gemini-3-pro-image-preview" # Nano Banana Pro: professional quality, advanced reasoning
# Supported aspect ratios
ASPECT_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4"]
DEFAULT_ASPECT_RATIO = "1:1" # Square is ideal for logos
# Logo-specific prompt templates
LOGO_PROMPT_TEMPLATE = """Generate a professional logo image: {prompt}
Style requirements:
- Clean vector-style illustration suitable for a logo
- Simple, scalable design that works at any size
- Clear silhouette and recognizable shape
- Professional quality suitable for business use
- Centered composition on plain white or transparent background
- No text unless specifically requested
- High contrast and clear edges
- Square format, perfectly centered
- Output as a clean, high-quality logo image
"""
STYLE_MODIFIERS = {
"minimalist": "minimalist, simple geometric shapes, clean lines, lots of white space, single color or limited palette",
"vintage": "vintage, retro, badge style, distressed texture, heritage feel, warm earth tones",
"modern": "modern, sleek, gradient colors, tech-forward, innovative feel",
"luxury": "luxury, elegant, gold accents, refined, premium feel, serif typography",
"playful": "playful, fun, colorful, friendly, approachable, rounded shapes",
"corporate": "corporate, professional, trustworthy, stable, conservative colors",
"organic": "organic, natural, flowing lines, earth tones, sustainable feel",
"geometric": "geometric, abstract, mathematical precision, symmetrical",
"hand-drawn": "hand-drawn, artisan, sketch-like, authentic, imperfect lines",
"3d": "3D, dimensional, depth, shadows, isometric perspective",
"abstract": "abstract mark, conceptual, symbolic, non-literal representation, artistic interpretation",
"lettermark": "lettermark, single letter or initials, typographic, monogram style, distinctive character",
"wordmark": "wordmark, logotype, custom typography, brand name as logo, distinctive lettering",
"emblem": "emblem, badge, crest style, enclosed design, traditional, authoritative feel",
"mascot": "mascot, character, friendly face, personified, memorable figure",
"gradient": "gradient, color transition, vibrant, modern digital feel, smooth color flow",
"lineart": "line art, single stroke, continuous line, elegant simplicity, wire-frame style",
"negative-space": "negative space, clever use of white space, hidden meaning, dual imagery, optical illusion"
}
INDUSTRY_PROMPTS = {
"tech": "technology company, digital, innovative, modern, circuit-like elements",
"healthcare": "healthcare, medical, caring, trust, cross or heart symbol",
"finance": "financial services, stable, trustworthy, growth, upward elements",
"food": "food and beverage, appetizing, warm colors, welcoming",
"fashion": "fashion brand, elegant, stylish, refined, artistic",
"fitness": "fitness and sports, dynamic, energetic, powerful, movement",
"eco": "eco-friendly, sustainable, natural, green, leaf or earth elements",
"education": "education, knowledge, growth, learning, book or cap symbol",
"real-estate": "real estate, property, home, roof or building silhouette",
"creative": "creative agency, artistic, unique, expressive, colorful"
}
def enhance_prompt(base_prompt, style=None, industry=None, brand_name=None):
"""Enhance the logo prompt with style and industry modifiers"""
prompt_parts = [base_prompt]
if style and style in STYLE_MODIFIERS:
prompt_parts.append(STYLE_MODIFIERS[style])
if industry and industry in INDUSTRY_PROMPTS:
prompt_parts.append(INDUSTRY_PROMPTS[industry])
if brand_name:
prompt_parts.insert(0, f"Logo for '{brand_name}':")
combined = ", ".join(prompt_parts)
return LOGO_PROMPT_TEMPLATE.format(prompt=combined)
def generate_logo(prompt, style=None, industry=None, brand_name=None,
output_path=None, use_pro=False, aspect_ratio=None):
"""Generate a logo using Gemini models with image generation
Args:
aspect_ratio: Image aspect ratio. Options: "1:1", "16:9", "9:16", "4:3", "3:4"
Default is "1:1" (square) for logos.
"""
if not GEMINI_API_KEY:
print("Error: GEMINI_API_KEY not set")
print("Set it with: export GEMINI_API_KEY='your-key'")
return None
# Initialize client
client = genai.Client(api_key=GEMINI_API_KEY)
# Enhance the prompt
full_prompt = enhance_prompt(prompt, style, industry, brand_name)
# Select model
model = GEMINI_PRO if use_pro else GEMINI_FLASH
model_label = "Nano Banana Pro (gemini-3-pro-image-preview)" if use_pro else "Nano Banana 2 (gemini-3.1-flash-image-preview)"
# Set aspect ratio (default to 1:1 for logos)
ratio = aspect_ratio if aspect_ratio in ASPECT_RATIOS else DEFAULT_ASPECT_RATIO
print(f"Generating logo with {model_label}...")
print(f"Aspect ratio: {ratio}")
print(f"Prompt: {full_prompt[:150]}...")
print()
try:
# Generate image using Gemini with image generation capability
response = client.models.generate_content(
model=model,
contents=full_prompt,
config=types.GenerateContentConfig(
response_modalities=["IMAGE", "TEXT"],
image_config=types.ImageConfig(
aspect_ratio=ratio
),
safety_settings=[
types.SafetySetting(
category="HARM_CATEGORY_HATE_SPEECH",
threshold="BLOCK_LOW_AND_ABOVE"
),
types.SafetySetting(
category="HARM_CATEGORY_DANGEROUS_CONTENT",
threshold="BLOCK_LOW_AND_ABOVE"
),
types.SafetySetting(
category="HARM_CATEGORY_SEXUALLY_EXPLICIT",
threshold="BLOCK_LOW_AND_ABOVE"
),
types.SafetySetting(
category="HARM_CATEGORY_HARASSMENT",
threshold="BLOCK_LOW_AND_ABOVE"
),
]
)
)
# Extract image from response
image_data = None
for part in response.candidates[0].content.parts:
if hasattr(part, 'inline_data') and part.inline_data:
if part.inline_data.mime_type.startswith('image/'):
image_data = part.inline_data.data
break
if not image_data:
print("No image generated. The model may not have produced an image.")
print("Try a different prompt or check if the model supports image generation.")
return None
# Determine output path
if output_path is None:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
brand_slug = brand_name.lower().replace(" ", "_") if brand_name else "logo"
output_path = f"{brand_slug}_{timestamp}.png"
# Save image
with open(output_path, "wb") as f:
f.write(image_data)
print(f"Logo saved to: {output_path}")
return output_path
except Exception as e:
print(f"Error generating logo: {e}")
return None
def generate_batch(prompt, brand_name, count, output_dir, use_pro=False, brand_context=None, aspect_ratio=None):
"""Generate multiple logo variants with different styles"""
# Select appropriate styles for batch generation
batch_styles = [
("minimalist", "Clean, simple geometric shape with minimal details"),
("modern", "Sleek gradient with tech-forward aesthetic"),
("geometric", "Abstract geometric patterns, mathematical precision"),
("gradient", "Vibrant color transitions, modern digital feel"),
("abstract", "Conceptual symbolic representation"),
("lettermark", "Stylized letter 'U' as monogram"),
("negative-space", "Clever use of negative space, hidden meaning"),
("lineart", "Single stroke continuous line design"),
("3d", "Dimensional design with depth and shadows"),
]
# Ensure output directory exists
os.makedirs(output_dir, exist_ok=True)
results = []
model_label = "Pro" if use_pro else "Flash"
ratio = aspect_ratio if aspect_ratio in ASPECT_RATIOS else DEFAULT_ASPECT_RATIO
print(f"\n{'='*60}")
print(f" BATCH LOGO GENERATION: {brand_name}")
print(f" Model: Nano Banana {model_label}")
print(f" Aspect Ratio: {ratio}")
print(f" Variants: {count}")
print(f" Output: {output_dir}")
print(f"{'='*60}\n")
for i in range(min(count, len(batch_styles))):
style_key, style_desc = batch_styles[i]
# Build enhanced prompt with brand context
enhanced_prompt = f"{prompt}, {style_desc}"
if brand_context:
enhanced_prompt = f"{brand_context}, {enhanced_prompt}"
# Generate filename
filename = f"{brand_name.lower().replace(' ', '_')}_{style_key}_{i+1:02d}.png"
output_path = os.path.join(output_dir, filename)
print(f"[{i+1}/{count}] Generating {style_key} variant...")
result = generate_logo(
prompt=enhanced_prompt,
style=style_key,
industry="tech",
brand_name=brand_name,
output_path=output_path,
use_pro=use_pro,
aspect_ratio=aspect_ratio
)
if result:
results.append(result)
print(f" ✓ Saved: {filename}\n")
else:
print(f" ✗ Failed: {style_key}\n")
# Rate limiting between requests
if i < count - 1:
time.sleep(2)
print(f"\n{'='*60}")
print(f" BATCH COMPLETE: {len(results)}/{count} logos generated")
print(f"{'='*60}\n")
return results
def main():
parser = argparse.ArgumentParser(description="Generate logos using Gemini Nano Banana models")
parser.add_argument("--prompt", "-p", type=str, help="Logo description prompt")
parser.add_argument("--brand", "-b", type=str, help="Brand name")
parser.add_argument("--style", "-s", choices=list(STYLE_MODIFIERS.keys()), help="Logo style")
parser.add_argument("--industry", "-i", choices=list(INDUSTRY_PROMPTS.keys()), help="Industry type")
parser.add_argument("--output", "-o", type=str, help="Output file path")
parser.add_argument("--output-dir", type=str, help="Output directory for batch generation")
parser.add_argument("--batch", type=int, help="Number of logo variants to generate (batch mode)")
parser.add_argument("--brand-context", type=str, help="Additional brand context for prompts")
parser.add_argument("--pro", action="store_true", help="Use Nano Banana Pro (gemini-3-pro-image-preview) for professional quality")
parser.add_argument("--aspect-ratio", "-r", choices=ASPECT_RATIOS, default=DEFAULT_ASPECT_RATIO,
help=f"Image aspect ratio (default: {DEFAULT_ASPECT_RATIO} for logos)")
parser.add_argument("--list-styles", action="store_true", help="List available styles")
parser.add_argument("--list-industries", action="store_true", help="List available industries")
args = parser.parse_args()
if args.list_styles:
print("Available styles:")
for style, desc in STYLE_MODIFIERS.items():
print(f" {style}: {desc[:60]}...")
return
if args.list_industries:
print("Available industries:")
for industry, desc in INDUSTRY_PROMPTS.items():
print(f" {industry}: {desc[:60]}...")
return
if not args.prompt and not args.brand:
parser.error("Either --prompt or --brand is required")
prompt = args.prompt or "professional logo"
# Batch mode
if args.batch:
output_dir = args.output_dir or f"./{args.brand.lower().replace(' ', '_')}_logos"
generate_batch(
prompt=prompt,
brand_name=args.brand or "Logo",
count=args.batch,
output_dir=output_dir,
use_pro=args.pro,
brand_context=args.brand_context,
aspect_ratio=args.aspect_ratio
)
else:
generate_logo(
prompt=prompt,
style=args.style,
industry=args.industry,
brand_name=args.brand,
output_path=args.output,
use_pro=args.pro,
aspect_ratio=args.aspect_ratio
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,114 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Logo Design Search - CLI for searching logo design guidelines
Usage: python search.py "<query>" [--domain <domain>] [--max-results 3]
python search.py "<query>" --design-brief [-p "Brand Name"]
Domains: style, color, industry
"""
import argparse
from core import CSV_CONFIG, MAX_RESULTS, search, search_all
def format_output(result):
"""Format results for Claude consumption (token-optimized)"""
if "error" in result:
return f"Error: {result['error']}"
output = []
output.append(f"## Logo Design Search Results")
output.append(f"**Domain:** {result['domain']} | **Query:** {result['query']}")
output.append(f"**Source:** {result['file']} | **Found:** {result['count']} results\n")
for i, row in enumerate(result['results'], 1):
output.append(f"### Result {i}")
for key, value in row.items():
value_str = str(value)
if len(value_str) > 300:
value_str = value_str[:300] + "..."
output.append(f"- **{key}:** {value_str}")
output.append("")
return "\n".join(output)
def generate_design_brief(query, brand_name=None):
"""Generate a comprehensive logo design brief based on query"""
results = search_all(query, max_results=2)
output = []
output.append("=" * 60)
if brand_name:
output.append(f" LOGO DESIGN BRIEF: {brand_name.upper()}")
else:
output.append(" LOGO DESIGN BRIEF")
output.append("=" * 60)
output.append(f" Query: {query}")
output.append("=" * 60)
output.append("")
# Industry recommendations
if "industry" in results:
output.append("## INDUSTRY ANALYSIS")
for r in results["industry"]:
output.append(f"**Industry:** {r.get('Industry', 'N/A')}")
output.append(f"- Recommended Styles: {r.get('Recommended Styles', 'N/A')}")
output.append(f"- Colors: {r.get('Primary Colors', 'N/A')}")
output.append(f"- Typography: {r.get('Typography', 'N/A')}")
output.append(f"- Symbols: {r.get('Common Symbols', 'N/A')}")
output.append(f"- Mood: {r.get('Mood', 'N/A')}")
output.append(f"- Best Practices: {r.get('Best Practices', 'N/A')}")
output.append(f"- Avoid: {r.get('Avoid', 'N/A')}")
output.append("")
# Style recommendations
if "style" in results:
output.append("## STYLE RECOMMENDATIONS")
for r in results["style"]:
output.append(f"**{r.get('Style Name', 'N/A')}** ({r.get('Category', 'N/A')})")
output.append(f"- Colors: {r.get('Primary Colors', 'N/A')} | {r.get('Secondary Colors', 'N/A')}")
output.append(f"- Typography: {r.get('Typography', 'N/A')}")
output.append(f"- Effects: {r.get('Effects', 'N/A')}")
output.append(f"- Best For: {r.get('Best For', 'N/A')}")
output.append(f"- Complexity: {r.get('Complexity', 'N/A')}")
output.append("")
# Color recommendations
if "color" in results:
output.append("## COLOR PALETTE OPTIONS")
for r in results["color"]:
output.append(f"**{r.get('Palette Name', 'N/A')}**")
output.append(f"- Primary: {r.get('Primary Hex', 'N/A')}")
output.append(f"- Secondary: {r.get('Secondary Hex', 'N/A')}")
output.append(f"- Accent: {r.get('Accent Hex', 'N/A')}")
output.append(f"- Background: {r.get('Background Hex', 'N/A')}")
output.append(f"- Psychology: {r.get('Psychology', 'N/A')}")
output.append("")
output.append("=" * 60)
return "\n".join(output)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Logo Design Search")
parser.add_argument("query", help="Search query")
parser.add_argument("--domain", "-d", choices=list(CSV_CONFIG.keys()), help="Search domain")
parser.add_argument("--max-results", "-n", type=int, default=MAX_RESULTS, help="Max results (default: 3)")
parser.add_argument("--json", action="store_true", help="Output as JSON")
parser.add_argument("--design-brief", "-db", action="store_true", help="Generate comprehensive design brief")
parser.add_argument("--brand-name", "-p", type=str, default=None, help="Brand name for design brief")
args = parser.parse_args()
if args.design_brief:
result = generate_design_brief(args.query, args.brand_name)
print(result)
else:
result = search(args.query, args.domain, args.max_results)
if args.json:
import json
print(json.dumps(result, indent=2, ensure_ascii=False))
else:
print(format_output(result))