logo-creator
PublicRepository: resciencelab/opc-skills
Low Risk
No security issues found
Skill manifest does not include a 'license' field. Specifying a license helps users understand usage terms.
Remediation Add 'license' field to SKILL.md frontmatter (e.g., MIT, Apache-2.0)
Description
Create logos using AI image generation. Discuss style/ratio, generate variations, iterate with user feedback, crop, remove background, and export as SVG. Use when user wants to create a logo, icon, favicon, brand mark, mascot, emblem, or design a logo.
Skill Files
# Logo Creator Skill
Create professional logos through AI image generation with an iterative design process.
## Prerequisites
**Required API Keys (set in environment):**
- `GEMINI_API_KEY` - Get from [Google AI Studio](https://aistudio.google.com/apikey)
- `REMOVE_BG_API_KEY` - Get from [remove.bg](https://www.remove.bg/api)
- `RECRAFT_API_KEY` - Get from [recraft.ai](https://www.recraft.ai/)
**Required Skills:**
- `nanobanana` - AI image generation (Gemini 3 Pro Image)
## File Output Location
All generated files should be saved to the `.skill-archive` directory:
```
.skill-archive/logo-creator/<yyyy-mm-dd-summaryname>/
```
**Example:**
```
.skill-archive/logo-creator/2026-01-18-opc-logo/
logo-01.png
logo-02.png
...
logo-09-cropped.png
logo-09-nobg.png
logo-09.svg
preview.html
```
**Guidelines:**
- Use current date in format `yyyy-mm-dd`
- Add short summary name (project/brand name, kebab-case)
- Create directory before generating first logo
- Keep all variations and iterations in same folder
- Final approved logo should be copied to user's desired location
## Workflow
### Step 1: Discovery & Requirements
Before generating, gather requirements from user:
**Ask about:**
1. **Project/Brand name** - What is the logo for?
2. **Style preference** - See [references/styles.md](./references/styles.md) for options:
- Pixel art / 8-bit retro
- Minimalist / flat design
- 3D / isometric
- Hand-drawn / sketch
- Mascot / character
- Monogram / lettermark
- Abstract / geometric
3. **Aspect ratio** - Default is 1:1 (square), options:
- `1:1` - Square (favicons, app icons)
- `16:9` - Wide (headers, banners)
- `4:3` - Standard
- `2:3` - Portrait
4. **Color preferences**:
- Monochrome (black & white)
- Specific brand colors
- Let AI decide
5. **Reference images** - Any existing logos or styles to reference?
**Wait for user confirmation before proceeding!**
### Step 2: Generate Logo Variations
Generate 20 logo variations (default) using the `nanobanana` skill:
```bash
# Generate single logo
python3 <nanobanana_skill_dir>/scripts/generate.py "{style} logo for {brand}, {description}, {colors}" \
--ratio 1:1 -o .skill-archive/logo-creator/<date-name>/logo-01.png
# Batch generate 20 logos
python3 <nanobanana_skill_dir>/scripts/batch_generate.py "{style} logo for {brand}, {description}, {colors}" \
-n 20 --ratio 1:1 -d .skill-archive/logo-creator/<date-name> -p logo
```
**Guidelines:**
- Use batch_generate.py for multiple variations (includes auto-delay)
- Save to `.skill-archive/logo-creator/<yyyy-mm-dd-summaryname>/` directory
- Use sequential naming: `logo-01.png`, `logo-02.png`, etc.
**Prompt Tips:**
- Include style keywords: "pixel art", "minimalist", "8-bit", "flat design"
- Specify colors: "black on white", "monochrome", "blue gradient"
- Add context: "tech startup", "food brand", "gaming company"
- Request format: "icon", "emblem", "mascot", "lettermark"
### Step 3: Create HTML Preview
Copy the preview template and open in browser:
```bash
cp <skill_dir>/templates/preview.html .skill-archive/logo-creator/<yyyy-mm-dd-summaryname>/preview.html
```
Then open in default browser:
```bash
open .skill-archive/logo-creator/<yyyy-mm-dd-summaryname>/preview.html
```
**IMPORTANT:** Update the HTML to include the correct number of logos generated.
### Step 4: Iterate with User
Ask user which logos they prefer:
- "Which logos do you like? (e.g., #5, #12, #18)"
- "What do you like about them?"
- "Any changes you'd want?"
Based on feedback:
1. Generate 10-20 more variations of favorite styles
2. Use naming: `logo-{original}-v{n}.png` (e.g., `logo-05-v1.png`)
3. Update HTML preview
4. Repeat until user selects final logo
### Step 5: Finalize Logo
Once user approves a logo, process it:
**5a. Crop whitespace (make 1:1 with no margins):**
```bash
python3 <skill_dir>/scripts/crop_logo.py {input.png} {output-cropped.png}
```
**5b. Remove background:**
```bash
python3 <skill_dir>/scripts/remove_bg.py {input.png} {output-nobg.png}
```
**5c. Convert to SVG:**
```bash
python3 <skill_dir>/scripts/vectorize.py {input.png} {output.svg}
```
### Step 6: Deliver Final Assets
Present final deliverables:
```
## Final Logo Assets
| File | Description | Size |
|------|-------------|------|
| logo.png | Original | 1024x1024 |
| logo-cropped.png | No margins, 1:1 | ~800x800 |
| logo-nobg.png | Transparent background | ~800x800 |
| logo.svg | Vector (scalable) | ~20KB |
All files saved to: `.skill-archive/logo-creator/<yyyy-mm-dd-summaryname>/`
Copy final logo to user's desired location.
```
## Quick Reference
### Common Prompt Patterns
**Pixel Art:**
```
Pixel art {subject} logo, 8-bit retro style, black pixels on white background, {size}x{size} grid, minimalist icon
```
**Minimalist:**
```
Minimalist {subject} logo, flat design, clean lines, {color} on white, simple geometric shapes
```
**Mascot:**
```
Cute {animal/character} mascot logo, friendly expression, {style} style, {colors}, suitable for brand icon
```
**Lettermark:**
```
Letter "{letter}" logo, modern typography, {style} design, {colors}, clean professional look
```
### Supported Aspect Ratios
- `1:1` - Square (default for logos)
- `2:3`, `3:2` - Portrait/Landscape
- `3:4`, `4:3` - Standard
- `4:5`, `5:4` - Photo
- `9:16`, `16:9` - Wide
- `21:9` - Ultra-wide
## References
- [references/styles.md](./references/styles.md) - Logo style guide with prompt examples
- [examples/opc-logo-creation.md](./examples/opc-logo-creation.md) - Full example conversation
{
"name": "logo-creator",
"version": "1.0.0",
"description": "Create logos using AI image generation. Discuss style/ratio, generate variations, iterate with user feedback, crop, remove background, and export as SVG.",
"author": {
"name": "ReScienceLab"
},
"homepage": "https://github.com/ReScienceLab/opc-skills/tree/main/skills/logo-creator",
"repository": "https://github.com/ReScienceLab/opc-skills",
"license": "MIT",
"keywords": [
"logo",
"brand",
"icon",
"favicon",
"mascot",
"emblem",
"create logo",
"design logo"
],
"skills": [
"./SKILL.md"
],
"commands": [
"./scripts/"
]
}
# Example: Creating OPC Skills Logo **User:** Create a logo for opc.dev - it's a website for agent skills for one person companies. **Agent:** I'll help you create a logo for OPC Skills. Let me ask a few questions first: 1. Style preference? (Pixel art, minimalist, 3D, hand-drawn, etc.) 2. Aspect ratio? 1:1 square for favicon/icons? 3. Color preferences? Monochrome or specific colors? 4. Any reference images to draw inspiration from? **User:** Please refer to this pixel art mule logo - I like the 8-bit retro style with black pixels on white background. **Agent:** Got it! The reference is a classic pixel art style: - Black pixels on white background - 8-bit retro aesthetic - Simple, recognizable silhouette I'll generate 20 pixel art logo variations for OPC Skills. Generating with prompts like: - "Pixel art robot head logo, 8-bit retro style, black pixels on white background" - "Pixel art crowned person, 8-bit style, solopreneur king" - "Pixel art lightning bolt, 8-bit, black on white" Here are the generated logos:          **User:** I like #09 with the crowned person - it represents "king of your own company". Please create 20 more variations of that concept. **Agent:** Generating 20 variations of the "Crowned Person" concept... Here are the variations:       **User:** The original #09 looks best. Please crop it to fit a 1:1 square, remove background, and convert to SVG. **Agent:** Processing the logo through finalization steps: 1. Cropping to 1:1 square (removing margins) 2. Removing background for transparency 3. Converting to SVG for scalability    The SVG is now ready for web use, favicons, and print!
# Logo Styles Reference
Common logo styles and prompt patterns for AI image generation.
## Style Categories
### 1. Pixel Art / 8-bit Retro
Classic video game aesthetic with blocky pixels.
**Best for:** Gaming, indie apps, nostalgic brands, tech startups
**Prompt patterns:**
```
Pixel art {subject} logo, 8-bit retro style, black pixels on white background, 32x32 grid, minimalist icon, sharp edges, no anti-aliasing
Pixel art {subject}, 8-bit style, {color} pixels on white, clean geometric, retro arcade game sprite
```
**Keywords:** pixel art, 8-bit, retro, blocky, sharp edges, grid, arcade
---
### 2. Minimalist / Flat Design
Clean, simple shapes with no gradients or shadows.
**Best for:** Modern brands, tech companies, professional services
**Prompt patterns:**
```
Minimalist {subject} logo, flat design, clean lines, {color} on white background, simple geometric shapes, modern professional
Minimalist icon of {subject}, single color, ultra simple, negative space, clean vector style
```
**Keywords:** minimalist, flat, clean, simple, geometric, modern, negative space
---
### 3. 3D / Isometric
Three-dimensional appearance with depth and perspective.
**Best for:** Tech products, games, modern apps
**Prompt patterns:**
```
3D isometric {subject} logo, modern tech style, {colors}, clean render, glossy finish, white background
Isometric icon of {subject}, 3D cube style, geometric, {colors}, soft shadows, clean design
```
**Keywords:** 3D, isometric, depth, perspective, glossy, render, dimensional
---
### 4. Hand-drawn / Sketch
Organic, artistic feel with visible strokes.
**Best for:** Creative agencies, artisan brands, personal projects
**Prompt patterns:**
```
Hand-drawn {subject} logo, sketch style, black ink on white paper, artistic strokes, organic lines
Doodle style {subject} icon, hand-sketched, playful, loose lines, {color} ink
```
**Keywords:** hand-drawn, sketch, doodle, ink, artistic, organic, strokes
---
### 5. Mascot / Character
Friendly character representing the brand.
**Best for:** Food brands, children's products, gaming, community apps
**Prompt patterns:**
```
Cute {animal/character} mascot logo, friendly expression, {style} style, {colors}, suitable for brand icon
Adorable {character} mascot, cartoon style, big eyes, happy expression, {colors}, clean simple design
```
**Keywords:** mascot, character, cute, friendly, cartoon, adorable, expressive
---
### 6. Monogram / Lettermark
Stylized letter(s) as the logo.
**Best for:** Personal brands, luxury, corporate, professional services
**Prompt patterns:**
```
Letter "{letter}" logo, modern typography, {style} design, {colors}, clean professional look
Monogram logo combining letters "{letters}", elegant design, {style}, minimalist, {colors}
```
**Keywords:** monogram, lettermark, typography, initial, elegant, professional
---
### 7. Abstract / Geometric
Non-representational shapes and patterns.
**Best for:** Tech companies, startups, modern brands
**Prompt patterns:**
```
Abstract geometric logo, {shapes} pattern, {colors}, modern minimal, clean vector design
Abstract symbol logo, interconnected {shapes}, flowing design, {colors}, contemporary style
```
**Keywords:** abstract, geometric, shapes, pattern, modern, symbol, contemporary
---
### 8. Emblem / Badge
Logo contained within a shape (circle, shield, etc.).
**Best for:** Sports teams, organizations, traditional brands, craft products
**Prompt patterns:**
```
{Subject} emblem logo in circular badge, vintage style, {colors}, detailed border, traditional design
Shield emblem with {subject}, {style} style, banner ribbon, {colors}, heraldic look
```
**Keywords:** emblem, badge, shield, seal, crest, vintage, traditional
---
### 9. Wordmark / Typography
The brand name itself as the logo with custom typography.
**Best for:** Established brands, media companies, fashion
**Prompt patterns:**
```
"{brand name}" wordmark logo, custom typography, {style} font style, {colors}, clean design
Typography logo spelling "{name}", creative letterforms, {style}, modern minimal
```
**Keywords:** wordmark, typography, lettering, font, text-based
---
### 10. Combination Mark
Icon combined with text.
**Best for:** Most brands, especially new ones needing recognition
**Prompt patterns:**
```
{Subject} icon with "{name}" text below, {style} style, {colors}, balanced composition
Logo combining {symbol} icon and "{name}" wordmark, {style}, professional, {colors}
```
**Keywords:** combination, icon + text, symbol + name, integrated
---
## Color Guidelines
### Monochrome
- Black on white (most versatile)
- White on black (dark mode)
- Single brand color
### Limited Palette
- 2-3 colors maximum
- High contrast combinations
- Consider accessibility
### Common Color Meanings
- **Blue:** Trust, professional, tech
- **Green:** Growth, nature, health
- **Red:** Energy, passion, urgency
- **Orange:** Creative, friendly, youthful
- **Purple:** Luxury, creative, wisdom
- **Yellow:** Optimism, warmth, clarity
- **Black:** Premium, elegant, powerful
---
## Aspect Ratios
| Ratio | Use Case |
|-------|----------|
| 1:1 | Favicons, app icons, social avatars |
| 16:9 | Website headers, presentations |
| 4:3 | Traditional formats |
| 2:1 | Horizontal banners |
| 1:2 | Vertical banners, mobile |
---
## File Format Guidelines
| Format | Use Case |
|--------|----------|
| SVG | Web, print, scaling |
| PNG | Web with transparency |
| JPG | Web photos, no transparency |
| ICO | Favicons |
| PDF | Print, documents |
---
## Tips for Better Prompts
1. **Be specific about style:** "minimalist flat design" vs just "simple"
2. **Specify colors:** "black on white" or "navy blue and gold"
3. **Mention use case:** "suitable for app icon" or "for website header"
4. **Include constraints:** "no text" or "must work at small sizes"
5. **Reference known styles:** "like Apple's design" or "retro 80s arcade"
#!/usr/bin/env python3
"""
Crop whitespace from logo and center in 1:1 square.
Usage:
python3 crop_logo.py input.png output.png [--padding 5]
"""
import sys
import os
try:
from PIL import Image
import numpy as np
except ImportError:
print("Error: PIL and numpy required. Install with: pip install Pillow numpy")
sys.exit(1)
def crop_to_content(image_path, output_path, padding=5, threshold=240):
"""
Crop whitespace from image and center in 1:1 square.
Args:
image_path: Path to input image
output_path: Path to save cropped image
padding: Pixels of padding around content (default: 5)
threshold: Pixel value threshold for "white" (default: 240)
"""
img = Image.open(image_path).convert('RGB')
data = np.array(img)
# Find non-white pixels (where any channel is below threshold)
mask = (data[:,:,0] < threshold) | (data[:,:,1] < threshold) | (data[:,:,2] < threshold)
rows = np.any(mask, axis=1)
cols = np.any(mask, axis=0)
if not np.any(rows) or not np.any(cols):
print(f"Warning: No content found in {image_path}")
img.save(output_path)
return output_path
rmin, rmax = np.where(rows)[0][[0, -1]]
cmin, cmax = np.where(cols)[0][[0, -1]]
# Add padding
top = max(0, rmin - padding)
bottom = min(img.size[1], rmax + padding + 1)
left = max(0, cmin - padding)
right = min(img.size[0], cmax + padding + 1)
# Crop to content
cropped = img.crop((left, top, right, bottom))
# Make square
w, h = cropped.size
size = max(w, h)
# Create new square image with white background
square = Image.new('RGB', (size, size), (255, 255, 255))
# Center the cropped image
x = (size - w) // 2
y = (size - h) // 2
square.paste(cropped, (x, y))
square.save(output_path, quality=95)
print(f"Original: {img.size[0]}x{img.size[1]}")
print(f"Content: {cmax-cmin+1}x{rmax-rmin+1}")
print(f"Output: {size}x{size}")
print(f"Saved to: {output_path}")
return output_path
def main():
if len(sys.argv) < 3:
print(__doc__)
sys.exit(1)
input_path = sys.argv[1]
output_path = sys.argv[2]
padding = int(sys.argv[3]) if len(sys.argv) > 3 else 5
if not os.path.exists(input_path):
print(f"Error: File not found: {input_path}")
sys.exit(1)
crop_to_content(input_path, output_path, padding)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Remove background from image using remove.bg API.
Requires REMOVE_BG_API_KEY environment variable.
Usage:
python3 remove_bg.py input.png output.png
"""
import sys
import os
import subprocess
def remove_background(input_path, output_path):
"""
Remove background from image using remove.bg API.
Args:
input_path: Path to input image
output_path: Path to save output image (transparent PNG)
"""
api_key = os.environ.get('REMOVE_BG_API_KEY')
if not api_key:
# Try to get from ~/.zshrc
try:
result = subprocess.run(
['grep', 'REMOVE_BG_API_KEY', os.path.expanduser('~/.zshrc')],
capture_output=True, text=True
)
if result.stdout:
api_key = result.stdout.split('"')[1]
except:
pass
if not api_key:
print("Error: REMOVE_BG_API_KEY not found in environment or ~/.zshrc")
print("Get your API key from: https://www.remove.bg/api")
sys.exit(1)
if not os.path.exists(input_path):
print(f"Error: File not found: {input_path}")
sys.exit(1)
# Use curl to call the API
cmd = [
'curl', '-s',
'-H', f'X-Api-Key: {api_key}',
'-F', f'image_file=@{input_path}',
'-F', 'size=auto',
'-F', 'format=png',
'-o', output_path,
'https://api.remove.bg/v1.0/removebg'
]
print(f"Removing background from: {input_path}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"Error: {result.stderr}")
sys.exit(1)
# Check if output is valid
if os.path.exists(output_path) and os.path.getsize(output_path) > 100:
print(f"Success! Saved to: {output_path}")
print(f"File size: {os.path.getsize(output_path) / 1024:.1f} KB")
else:
print("Error: API call failed. Check your API key and quota.")
if os.path.exists(output_path):
with open(output_path, 'r') as f:
print(f.read())
sys.exit(1)
return output_path
def main():
if len(sys.argv) < 3:
print(__doc__)
sys.exit(1)
input_path = sys.argv[1]
output_path = sys.argv[2]
remove_background(input_path, output_path)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Convert raster image to SVG using Recraft API.
Requires RECRAFT_API_KEY environment variable.
Usage:
python3 vectorize.py input.png output.svg
"""
import sys
import os
import subprocess
import json
def vectorize_image(input_path, output_path):
"""
Convert raster image to SVG using Recraft API.
Args:
input_path: Path to input image (PNG, JPG, or WEBP)
output_path: Path to save SVG output
"""
api_key = os.environ.get('RECRAFT_API_KEY')
if not api_key:
# Try to get from ~/.zshrc
try:
result = subprocess.run(
['grep', 'RECRAFT_API_KEY', os.path.expanduser('~/.zshrc')],
capture_output=True, text=True
)
if result.stdout:
api_key = result.stdout.split('"')[1]
except:
pass
if not api_key:
print("Error: RECRAFT_API_KEY not found in environment or ~/.zshrc")
print("Get your API key from: https://www.recraft.ai/")
sys.exit(1)
if not os.path.exists(input_path):
print(f"Error: File not found: {input_path}")
sys.exit(1)
# Step 1: Call Recraft API to vectorize
print(f"Vectorizing: {input_path}")
cmd = [
'curl', '-s', '-X', 'POST',
'https://external.api.recraft.ai/v1/images/vectorize',
'-H', f'Authorization: Bearer {api_key}',
'-H', 'Content-Type: multipart/form-data',
'-F', f'file=@{input_path}'
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"Error: {result.stderr}")
sys.exit(1)
try:
response = json.loads(result.stdout)
except json.JSONDecodeError:
print(f"Error: Invalid API response: {result.stdout}")
sys.exit(1)
if 'error' in response:
print(f"API Error: {response['error']}")
sys.exit(1)
svg_url = response.get('image', {}).get('url')
if not svg_url:
print(f"Error: No SVG URL in response: {response}")
sys.exit(1)
print(f"SVG URL: {svg_url}")
# Step 2: Download the SVG
download_cmd = ['curl', '-s', svg_url, '-o', output_path]
result = subprocess.run(download_cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"Error downloading SVG: {result.stderr}")
sys.exit(1)
# Verify output
if os.path.exists(output_path) and os.path.getsize(output_path) > 100:
# Check if it's actually an SVG
with open(output_path, 'r') as f:
content = f.read(100)
if '<svg' in content.lower() or '<?xml' in content.lower():
print(f"Success! Saved to: {output_path}")
print(f"File size: {os.path.getsize(output_path) / 1024:.1f} KB")
else:
print(f"Warning: Output may not be a valid SVG")
print(f"First 100 chars: {content}")
else:
print("Error: Failed to download SVG")
sys.exit(1)
return output_path
def main():
if len(sys.argv) < 3:
print(__doc__)
sys.exit(1)
input_path = sys.argv[1]
output_path = sys.argv[2]
vectorize_image(input_path, output_path)
if __name__ == "__main__":
main()
<!DOCTYPE html>
<html>
<head>
<title>Logo Preview Gallery</title>
<style>
* { box-sizing: border-box; }
body {
font-family: system-ui, -apple-system, sans-serif;
background: #1a1a1a;
padding: 20px;
margin: 0;
color: white;
}
h1 { text-align: center; margin-bottom: 10px; }
.subtitle { text-align: center; color: #888; margin-bottom: 30px; }
.controls {
text-align: center;
margin-bottom: 20px;
}
.controls input {
padding: 8px 16px;
font-size: 14px;
border: 1px solid #444;
border-radius: 6px;
background: #2a2a2a;
color: white;
width: 300px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 16px;
max-width: 1400px;
margin: 0 auto;
}
.logo-card {
background: white;
border-radius: 10px;
padding: 16px;
text-align: center;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.logo-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(0,0,0,0.4);
}
.logo-card img {
width: 100%;
height: 120px;
object-fit: contain;
margin-bottom: 8px;
}
.logo-card p {
margin: 0;
color: #333;
font-size: 13px;
font-weight: 600;
}
.logo-card span {
display: block;
color: #888;
font-size: 11px;
margin-top: 4px;
}
.logo-card.favorite {
box-shadow: 0 0 0 3px #4CAF50;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
z-index: 1000;
align-items: center;
justify-content: center;
flex-direction: column;
}
.modal.active { display: flex; }
.modal img {
max-width: 500px;
max-height: 500px;
background: white;
padding: 30px;
border-radius: 12px;
}
.modal-info {
margin-top: 20px;
color: white;
text-align: center;
}
.modal-info p { margin: 5px 0; }
.modal-close {
position: absolute;
top: 20px;
right: 30px;
color: white;
font-size: 40px;
cursor: pointer;
}
.modal-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
color: white;
font-size: 60px;
cursor: pointer;
user-select: none;
padding: 20px;
}
.modal-nav:hover { color: #4CAF50; }
.modal-nav.prev { left: 20px; }
.modal-nav.next { right: 20px; }
.favorites-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #2a2a2a;
padding: 15px 20px;
display: flex;
align-items: center;
gap: 15px;
border-top: 1px solid #444;
}
.favorites-bar span { color: #888; }
.favorites-list {
flex: 1;
color: #4CAF50;
font-weight: 600;
}
.section-divider {
grid-column: 1 / -1;
text-align: center;
padding: 20px 0;
color: #666;
font-size: 14px;
border-top: 1px solid #333;
margin-top: 10px;
}
body { padding-bottom: 80px; }
</style>
</head>
<body>
<h1>Logo Preview Gallery</h1>
<p class="subtitle">Click any logo to enlarge • Click again to toggle favorite</p>
<div class="controls">
<input type="text" id="search" placeholder="Filter logos... (e.g., 'v1' or '05')">
</div>
<div class="grid" id="grid">
<!-- LOGO_CARDS_PLACEHOLDER -->
<!-- Example cards - replace with actual logos -->
<div class="logo-card" data-src="logo-01.png" onclick="showModal(this)">
<img src="logo-01.png" onerror="this.parentElement.style.display='none'">
<p>#01</p>
</div>
<div class="logo-card" data-src="logo-02.png" onclick="showModal(this)">
<img src="logo-02.png" onerror="this.parentElement.style.display='none'">
<p>#02</p>
</div>
<div class="logo-card" data-src="logo-03.png" onclick="showModal(this)">
<img src="logo-03.png" onerror="this.parentElement.style.display='none'">
<p>#03</p>
</div>
<div class="logo-card" data-src="logo-04.png" onclick="showModal(this)">
<img src="logo-04.png" onerror="this.parentElement.style.display='none'">
<p>#04</p>
</div>
<div class="logo-card" data-src="logo-05.png" onclick="showModal(this)">
<img src="logo-05.png" onerror="this.parentElement.style.display='none'">
<p>#05</p>
</div>
<div class="logo-card" data-src="logo-06.png" onclick="showModal(this)">
<img src="logo-06.png" onerror="this.parentElement.style.display='none'">
<p>#06</p>
</div>
<div class="logo-card" data-src="logo-07.png" onclick="showModal(this)">
<img src="logo-07.png" onerror="this.parentElement.style.display='none'">
<p>#07</p>
</div>
<div class="logo-card" data-src="logo-08.png" onclick="showModal(this)">
<img src="logo-08.png" onerror="this.parentElement.style.display='none'">
<p>#08</p>
</div>
<div class="logo-card" data-src="logo-09.png" onclick="showModal(this)">
<img src="logo-09.png" onerror="this.parentElement.style.display='none'">
<p>#09</p>
</div>
<div class="logo-card" data-src="logo-10.png" onclick="showModal(this)">
<img src="logo-10.png" onerror="this.parentElement.style.display='none'">
<p>#10</p>
</div>
<div class="logo-card" data-src="logo-11.png" onclick="showModal(this)">
<img src="logo-11.png" onerror="this.parentElement.style.display='none'">
<p>#11</p>
</div>
<div class="logo-card" data-src="logo-12.png" onclick="showModal(this)">
<img src="logo-12.png" onerror="this.parentElement.style.display='none'">
<p>#12</p>
</div>
<div class="logo-card" data-src="logo-13.png" onclick="showModal(this)">
<img src="logo-13.png" onerror="this.parentElement.style.display='none'">
<p>#13</p>
</div>
<div class="logo-card" data-src="logo-14.png" onclick="showModal(this)">
<img src="logo-14.png" onerror="this.parentElement.style.display='none'">
<p>#14</p>
</div>
<div class="logo-card" data-src="logo-15.png" onclick="showModal(this)">
<img src="logo-15.png" onerror="this.parentElement.style.display='none'">
<p>#15</p>
</div>
<div class="logo-card" data-src="logo-16.png" onclick="showModal(this)">
<img src="logo-16.png" onerror="this.parentElement.style.display='none'">
<p>#16</p>
</div>
<div class="logo-card" data-src="logo-17.png" onclick="showModal(this)">
<img src="logo-17.png" onerror="this.parentElement.style.display='none'">
<p>#17</p>
</div>
<div class="logo-card" data-src="logo-18.png" onclick="showModal(this)">
<img src="logo-18.png" onerror="this.parentElement.style.display='none'">
<p>#18</p>
</div>
<div class="logo-card" data-src="logo-19.png" onclick="showModal(this)">
<img src="logo-19.png" onerror="this.parentElement.style.display='none'">
<p>#19</p>
</div>
<div class="logo-card" data-src="logo-20.png" onclick="showModal(this)">
<img src="logo-20.png" onerror="this.parentElement.style.display='none'">
<p>#20</p>
</div>
</div>
<div class="favorites-bar">
<span>Favorites:</span>
<div class="favorites-list" id="favorites-list">None selected (click logos to favorite)</div>
</div>
<div class="modal" id="modal" onclick="if(event.target === this) hideModal()">
<span class="modal-close" onclick="hideModal()">×</span>
<span class="modal-nav prev" onclick="navModal(-1)">‹</span>
<span class="modal-nav next" onclick="navModal(1)">›</span>
<img id="modal-img" src="">
<div class="modal-info">
<p id="modal-name"></p>
<p style="color:#888;font-size:12px">Press F to favorite • Arrow keys to navigate • Escape to close</p>
</div>
</div>
<script>
let currentCard = null;
let favorites = new Set();
let visibleCards = [];
function updateVisibleCards() {
visibleCards = Array.from(document.querySelectorAll('.logo-card')).filter(c => c.style.display !== 'none');
}
function showModal(card) {
currentCard = card;
const src = card.dataset.src;
document.getElementById('modal-img').src = src;
document.getElementById('modal-name').textContent = card.querySelector('p').textContent;
document.getElementById('modal').classList.add('active');
updateVisibleCards();
}
function hideModal() {
document.getElementById('modal').classList.remove('active');
currentCard = null;
}
function navModal(dir) {
if (!currentCard) return;
updateVisibleCards();
const idx = visibleCards.indexOf(currentCard);
const newIdx = (idx + dir + visibleCards.length) % visibleCards.length;
showModal(visibleCards[newIdx]);
}
function toggleFavorite(card) {
const name = card.querySelector('p').textContent;
if (favorites.has(name)) {
favorites.delete(name);
card.classList.remove('favorite');
} else {
favorites.add(name);
card.classList.add('favorite');
}
updateFavoritesList();
}
function updateFavoritesList() {
const list = document.getElementById('favorites-list');
if (favorites.size === 0) {
list.textContent = 'None selected (click logos to favorite)';
} else {
list.textContent = Array.from(favorites).sort().join(', ');
}
}
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') hideModal();
if (e.key === 'ArrowLeft') navModal(-1);
if (e.key === 'ArrowRight') navModal(1);
if ((e.key === 'f' || e.key === 'F') && currentCard) {
toggleFavorite(currentCard);
}
});
document.querySelectorAll('.logo-card').forEach(card => {
card.addEventListener('dblclick', () => toggleFavorite(card));
});
document.getElementById('search').addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
document.querySelectorAll('.logo-card').forEach(card => {
const name = card.querySelector('p').textContent.toLowerCase();
const src = card.dataset.src.toLowerCase();
card.style.display = (name.includes(query) || src.includes(query)) ? '' : 'none';
});
});
</script>
</body>
</html>