logo-creator

Public
0

Repository: resciencelab/opc-skills

Log in or sign up to clone this skill.

R
resciencelab
Imported Feb 28, 2026

Low Risk

No security issues found

INFO

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)

Scanned in 0.005s

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

Download .zip
SKILL.md
# 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
.claude-plugin/plugin.json Reference
{
  "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/"
  ]
}
examples/opc-logo-creation.md Reference
# 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:

![Logo 01](/skills/logo-creator/examples/images/logo-01.png)
![Logo 02](/skills/logo-creator/examples/images/logo-02.png)
![Logo 03](/skills/logo-creator/examples/images/logo-03.png)
![Logo 04](/skills/logo-creator/examples/images/logo-04.png)
![Logo 05](/skills/logo-creator/examples/images/logo-05.png)
![Logo 06](/skills/logo-creator/examples/images/logo-06.png)
![Logo 07](/skills/logo-creator/examples/images/logo-07.png)
![Logo 08](/skills/logo-creator/examples/images/logo-08.png)
![Logo 09](/skills/logo-creator/examples/images/logo-09.png)

**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:

![Variation 1](/skills/logo-creator/examples/images/logo-09-v1.png)
![Variation 2](/skills/logo-creator/examples/images/logo-09-v2.png)
![Variation 3](/skills/logo-creator/examples/images/logo-09-v3.png)
![Variation 4](/skills/logo-creator/examples/images/logo-09-v4.png)
![Variation 5](/skills/logo-creator/examples/images/logo-09-v5.png)
![Variation 6](/skills/logo-creator/examples/images/logo-09-v6.png)

**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

![Original](/skills/logo-creator/examples/images/logo-09.png)
![Selected](/skills/logo-creator/examples/images/opc-logo-selected.png)
![Final SVG](/skills/logo-creator/examples/images/opc-logo-final.svg)

The SVG is now ready for web use, favicons, and print!
references/styles.md Reference
# 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"
scripts/crop_logo.py Script
#!/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()
scripts/remove_bg.py Script
#!/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()
scripts/vectorize.py Script
#!/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()
templates/preview.html Reference
<!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()">&times;</span>
    <span class="modal-nav prev" onclick="navModal(-1)">&#8249;</span>
    <span class="modal-nav next" onclick="navModal(1)">&#8250;</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>

Version History

v1.0.0 Imported from GitHub
1 week ago