404 lines
14 KiB
Markdown
404 lines
14 KiB
Markdown
|
|
# App Icon Implementation Plan
|
|||
|
|
|
|||
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|||
|
|
|
|||
|
|
**Goal:** Create an SVG app icon (scissors cutting a film strip) in five color variants, let the user pick one, and generate all platform icon files.
|
|||
|
|
|
|||
|
|
**Architecture:** Hand-craft a single base SVG icon, generate color variants via shell script, use `npx tauri icon` to produce all platform-specific icon files (PNG, ICNS, ICO). Replace the default Tauri branding.
|
|||
|
|
|
|||
|
|
**Tech Stack:** SVG (hand-authored), `rsvg-convert` (SVG→PNG), `npx tauri icon` (multi-platform icon generation), bash (variant generation script)
|
|||
|
|
|
|||
|
|
## Global Constraints
|
|||
|
|
|
|||
|
|
- Canvas: 512×512px SVG viewBox, full-square (no rounded corners, no shadows — Google Play adds dynamically)
|
|||
|
|
- Safe zone: inner 66% (338×338px centered, 87px inset) — all critical artwork within this area
|
|||
|
|
- Color space: sRGB
|
|||
|
|
- Background: solid `#18181b` (zinc-900)
|
|||
|
|
- All five color variants must be generated and previewed before selecting final
|
|||
|
|
- Spec: `docs/superpowers/specs/2026-09-22-app-icon-design.md`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Task 1: Create the Base SVG Icon (Blue Variant)
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Create: `src-tauri/icons/icon.svg`
|
|||
|
|
|
|||
|
|
**Interfaces:**
|
|||
|
|
- Consumes: nothing
|
|||
|
|
- Produces: `src-tauri/icons/icon.svg` — the blue accent variant, used as the template for all other variants. Contains three accent hex values that the variant script substitutes: `#60a5fa` (blade tip), `#2563eb` (blade pivot), `#3b82f6` (cut accent).
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: Create the icons directory**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mkdir -p src-tauri/icons
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: Create the base SVG file**
|
|||
|
|
|
|||
|
|
Create `src-tauri/icons/icon.svg` with the following content. This is the blue accent variant — scissors cutting diagonally across a film strip on a dark background.
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
|||
|
|
<defs>
|
|||
|
|
<linearGradient id="blade-grad" gradientUnits="userSpaceOnUse" x1="256" y1="130" x2="256" y2="290">
|
|||
|
|
<stop offset="0%" stop-color="#60a5fa"/>
|
|||
|
|
<stop offset="100%" stop-color="#2563eb"/>
|
|||
|
|
</linearGradient>
|
|||
|
|
</defs>
|
|||
|
|
|
|||
|
|
<!-- Background: solid zinc-900, full square -->
|
|||
|
|
<rect width="512" height="512" fill="#18181b"/>
|
|||
|
|
|
|||
|
|
<!-- Film strip - left half (offset down-left for separation effect) -->
|
|||
|
|
<g transform="translate(-3, 3)">
|
|||
|
|
<polygon points="72,220 265,220 239,292 72,292" fill="#3f3f46"/>
|
|||
|
|
<!-- Sprocket holes: top row -->
|
|||
|
|
<rect x="92" y="226" width="11" height="13" rx="3" fill="#27272a"/>
|
|||
|
|
<rect x="130" y="226" width="11" height="13" rx="3" fill="#27272a"/>
|
|||
|
|
<rect x="168" y="226" width="11" height="13" rx="3" fill="#27272a"/>
|
|||
|
|
<!-- Sprocket holes: bottom row -->
|
|||
|
|
<rect x="92" y="273" width="11" height="13" rx="3" fill="#27272a"/>
|
|||
|
|
<rect x="130" y="273" width="11" height="13" rx="3" fill="#27272a"/>
|
|||
|
|
<rect x="168" y="273" width="11" height="13" rx="3" fill="#27272a"/>
|
|||
|
|
</g>
|
|||
|
|
|
|||
|
|
<!-- Film strip - right half (offset up-right for separation effect) -->
|
|||
|
|
<g transform="translate(3, -3)">
|
|||
|
|
<polygon points="273,220 440,220 440,292 247,292" fill="#3f3f46"/>
|
|||
|
|
<!-- Sprocket holes: top row -->
|
|||
|
|
<rect x="298" y="226" width="11" height="13" rx="3" fill="#27272a"/>
|
|||
|
|
<rect x="345" y="226" width="11" height="13" rx="3" fill="#27272a"/>
|
|||
|
|
<rect x="392" y="226" width="11" height="13" rx="3" fill="#27272a"/>
|
|||
|
|
<!-- Sprocket holes: bottom row -->
|
|||
|
|
<rect x="278" y="273" width="11" height="13" rx="3" fill="#27272a"/>
|
|||
|
|
<rect x="330" y="273" width="11" height="13" rx="3" fill="#27272a"/>
|
|||
|
|
<rect x="382" y="273" width="11" height="13" rx="3" fill="#27272a"/>
|
|||
|
|
</g>
|
|||
|
|
|
|||
|
|
<!-- Cut accent line along the diagonal gap -->
|
|||
|
|
<line x1="269" y1="216" x2="243" y2="296" stroke="#3b82f6" stroke-width="2" opacity="0.6"/>
|
|||
|
|
|
|||
|
|
<!-- Scissors (symmetric, rotated 20° clockwise for diagonal cut alignment) -->
|
|||
|
|
<g transform="rotate(20, 256, 256)">
|
|||
|
|
<!-- Blade B cutting portion (rendered first = behind) -->
|
|||
|
|
<polygon points="264,250 172,155 248,262" fill="url(#blade-grad)"/>
|
|||
|
|
<!-- Blade A cutting portion (rendered second = in front, blades cross) -->
|
|||
|
|
<polygon points="248,250 340,155 264,262" fill="url(#blade-grad)"/>
|
|||
|
|
|
|||
|
|
<!-- Handle A (behind at handle end) -->
|
|||
|
|
<polygon points="248,250 194,330 202,336 264,262" fill="#52525b"/>
|
|||
|
|
<!-- Handle B (in front at handle end, crossing) -->
|
|||
|
|
<polygon points="264,250 318,330 310,336 248,262" fill="#52525b"/>
|
|||
|
|
|
|||
|
|
<!-- Ring A (lower-left) -->
|
|||
|
|
<circle cx="192" cy="348" r="20" stroke="#a1a1aa" stroke-width="4" fill="#18181b"/>
|
|||
|
|
<!-- Ring B (lower-right) -->
|
|||
|
|
<circle cx="320" cy="348" r="20" stroke="#a1a1aa" stroke-width="4" fill="#18181b"/>
|
|||
|
|
|
|||
|
|
<!-- Pivot screw -->
|
|||
|
|
<circle cx="256" cy="256" r="7" fill="#a1a1aa"/>
|
|||
|
|
</g>
|
|||
|
|
</svg>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Key geometry decisions:
|
|||
|
|
- Film strip spans x=72–440 (extends beyond safe zone for graceful clipping on round masks), y=220–292 (72px tall)
|
|||
|
|
- Cut line from (269, 216) to (243, 296) — gentle diagonal matching the 20° scissors rotation
|
|||
|
|
- Left half polygon: (72,220)→(265,220)→(239,292)→(72,292) — the right edge is the diagonal cut
|
|||
|
|
- Right half polygon: (273,220)→(440,220)→(440,292)→(247,292) — the left edge is the diagonal cut
|
|||
|
|
- Gap between halves: ~8px perpendicular to the cut line
|
|||
|
|
- Scissors: symmetric blades (triangular tapers, 20px wide at pivot → 0 at tip, 130px long), rotated 20° as a group
|
|||
|
|
- Handles: tapered quadrilaterals, ~90px from pivot to ring
|
|||
|
|
- Rings: 20px radius circles with zinc-400 stroke, zinc-900 fill (transparent to background)
|
|||
|
|
- Blade gradient: light blue (`#60a5fa`) at tips, darker (`#2563eb`) near pivot
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: Verify visually in browser**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
open src-tauri/icons/icon.svg
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Open the SVG in a browser. Verify:
|
|||
|
|
1. Dark background fills the square
|
|||
|
|
2. Film strip is visible as a horizontal band with a diagonal gap
|
|||
|
|
3. Sprocket holes are visible as darker rectangles along the strip edges
|
|||
|
|
4. Scissors blades cross through the gap at a diagonal angle
|
|||
|
|
5. Blade gradient goes from lighter (tips) to darker (pivot)
|
|||
|
|
6. Handles taper from the pivot to the rings
|
|||
|
|
7. Rings are visible as circular outlines
|
|||
|
|
8. Pivot screw is centered
|
|||
|
|
9. All critical elements (scissors, film strip center) are within the inner 66% safe zone
|
|||
|
|
|
|||
|
|
If any element needs adjustment (position, size, proportions), edit the coordinates in `icon.svg` and refresh. This visual tuning is expected — hand-authored SVG coordinates are approximate starting points.
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add src-tauri/icons/icon.svg
|
|||
|
|
git commit -m "feat: add base SVG app icon (blue variant)"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Task 2: Generate Color Variant SVGs
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Create: `scripts/generate-icon-variants.sh`
|
|||
|
|
- Create: `src-tauri/icons/variants/icon-blue.svg`
|
|||
|
|
- Create: `src-tauri/icons/variants/icon-purple.svg`
|
|||
|
|
- Create: `src-tauri/icons/variants/icon-green.svg`
|
|||
|
|
- Create: `src-tauri/icons/variants/icon-orange.svg`
|
|||
|
|
- Create: `src-tauri/icons/variants/icon-mono.svg`
|
|||
|
|
|
|||
|
|
**Interfaces:**
|
|||
|
|
- Consumes: `src-tauri/icons/icon.svg` — the blue variant template containing hex values `#60a5fa`, `#2563eb`, `#3b82f6`
|
|||
|
|
- Produces: five SVG files in `src-tauri/icons/variants/`, one per color accent. User picks their preferred variant in the next task.
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: Create the variant generation script**
|
|||
|
|
|
|||
|
|
Create `scripts/generate-icon-variants.sh`:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
set -euo pipefail
|
|||
|
|
|
|||
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|||
|
|
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|||
|
|
SOURCE="$ROOT_DIR/src-tauri/icons/icon.svg"
|
|||
|
|
OUT_DIR="$ROOT_DIR/src-tauri/icons/variants"
|
|||
|
|
|
|||
|
|
mkdir -p "$OUT_DIR"
|
|||
|
|
|
|||
|
|
# Blue is the base template — copy as-is
|
|||
|
|
cp "$SOURCE" "$OUT_DIR/icon-blue.svg"
|
|||
|
|
|
|||
|
|
# Each variant substitutes three accent hex values:
|
|||
|
|
# #60a5fa = blade tip (lighter)
|
|||
|
|
# #2563eb = blade pivot (darker)
|
|||
|
|
# #3b82f6 = cut edge accent
|
|||
|
|
create_variant() {
|
|||
|
|
local name="$1" tip="$2" pivot="$3" accent="$4"
|
|||
|
|
sed \
|
|||
|
|
-e "s/#60a5fa/$tip/g" \
|
|||
|
|
-e "s/#2563eb/$pivot/g" \
|
|||
|
|
-e "s/#3b82f6/$accent/g" \
|
|||
|
|
"$SOURCE" > "$OUT_DIR/icon-${name}.svg"
|
|||
|
|
echo "Created icon-${name}.svg"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
create_variant "purple" "#c084fc" "#7c3aed" "#a855f7"
|
|||
|
|
create_variant "green" "#34d399" "#059669" "#10b981"
|
|||
|
|
create_variant "orange" "#fb923c" "#ea580c" "#f97316"
|
|||
|
|
create_variant "mono" "#d4d4d8" "#71717a" "#a1a1aa"
|
|||
|
|
|
|||
|
|
echo "All variants generated in $OUT_DIR"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: Run the script**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
chmod +x scripts/generate-icon-variants.sh
|
|||
|
|
./scripts/generate-icon-variants.sh
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected output:
|
|||
|
|
```
|
|||
|
|
Created icon-purple.svg
|
|||
|
|
Created icon-green.svg
|
|||
|
|
Created icon-orange.svg
|
|||
|
|
Created icon-mono.svg
|
|||
|
|
All variants generated in /path/to/src-tauri/icons/variants
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: Verify all five variants exist and differ only in accent colors**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
ls -la src-tauri/icons/variants/
|
|||
|
|
diff <(grep -v '#60a5fa\|#2563eb\|#3b82f6' src-tauri/icons/variants/icon-blue.svg) \
|
|||
|
|
<(grep -v '#c084fc\|#7c3aed\|#a855f7' src-tauri/icons/variants/icon-purple.svg)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected: `diff` produces no output (files are identical once accent colors are removed).
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: Open all variants for visual comparison**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
open src-tauri/icons/variants/icon-blue.svg \
|
|||
|
|
src-tauri/icons/variants/icon-purple.svg \
|
|||
|
|
src-tauri/icons/variants/icon-green.svg \
|
|||
|
|
src-tauri/icons/variants/icon-orange.svg \
|
|||
|
|
src-tauri/icons/variants/icon-mono.svg
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Visually confirm each variant renders with the correct accent color on the scissor blades and cut line.
|
|||
|
|
|
|||
|
|
- [ ] **Step 5: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add scripts/generate-icon-variants.sh src-tauri/icons/variants/
|
|||
|
|
git commit -m "feat: add icon color variant generation script and 5 variants"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Task 3: User Selects Variant and Generate Platform Icons
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `src-tauri/icons/icon.svg` (replaced with chosen variant if not blue)
|
|||
|
|
- Create: `src-tauri/icons/32x32.png` (generated)
|
|||
|
|
- Create: `src-tauri/icons/128x128.png` (generated)
|
|||
|
|
- Create: `src-tauri/icons/128x128@2x.png` (generated)
|
|||
|
|
- Create: `src-tauri/icons/icon.icns` (generated)
|
|||
|
|
- Create: `src-tauri/icons/icon.ico` (generated)
|
|||
|
|
- Create: `static/favicon.svg`
|
|||
|
|
|
|||
|
|
**Interfaces:**
|
|||
|
|
- Consumes: the user's chosen variant from `src-tauri/icons/variants/icon-{color}.svg`
|
|||
|
|
- Produces: all platform icon files referenced by `tauri.conf.json`, plus `static/favicon.svg`
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: Present variants to the user and get their choice**
|
|||
|
|
|
|||
|
|
Ask the user which color variant they prefer: blue, purple, green, orange, or mono.
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: Copy the chosen variant to primary locations**
|
|||
|
|
|
|||
|
|
Replace `CHOSEN` with the selected variant name (e.g., `purple`):
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cp src-tauri/icons/variants/icon-CHOSEN.svg src-tauri/icons/icon.svg
|
|||
|
|
cp src-tauri/icons/variants/icon-CHOSEN.svg static/favicon.svg
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: Generate a high-res PNG from the SVG for Tauri icon generation**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
rsvg-convert -w 1024 -h 1024 src-tauri/icons/icon.svg -o src-tauri/icons/app-icon-1024.png
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Verify the PNG was created and is 1024×1024:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
sips -g pixelHeight -g pixelWidth src-tauri/icons/app-icon-1024.png
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected:
|
|||
|
|
```
|
|||
|
|
pixelHeight: 1024
|
|||
|
|
pixelWidth: 1024
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: Run Tauri icon generator**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
npx tauri icon src-tauri/icons/app-icon-1024.png -o src-tauri/icons
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
This generates all platform icon files into `src-tauri/icons/`:
|
|||
|
|
- `32x32.png`
|
|||
|
|
- `128x128.png`
|
|||
|
|
- `128x128@2x.png`
|
|||
|
|
- `icon.icns`
|
|||
|
|
- `icon.ico`
|
|||
|
|
- Plus iOS and Android variants if applicable
|
|||
|
|
|
|||
|
|
- [ ] **Step 5: Verify generated icon files exist and match tauri.conf.json references**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
ls -la src-tauri/icons/32x32.png \
|
|||
|
|
src-tauri/icons/128x128.png \
|
|||
|
|
src-tauri/icons/128x128@2x.png \
|
|||
|
|
src-tauri/icons/icon.icns \
|
|||
|
|
src-tauri/icons/icon.ico
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
All five files should exist and be non-empty.
|
|||
|
|
|
|||
|
|
Cross-check with the icon paths in `tauri.conf.json`:
|
|||
|
|
```json
|
|||
|
|
"icon": [
|
|||
|
|
"icons/32x32.png",
|
|||
|
|
"icons/128x128.png",
|
|||
|
|
"icons/128x128@2x.png",
|
|||
|
|
"icons/icon.icns",
|
|||
|
|
"icons/icon.ico"
|
|||
|
|
]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
All referenced paths must have corresponding generated files.
|
|||
|
|
|
|||
|
|
- [ ] **Step 6: Generate a 32px favicon PNG fallback**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
rsvg-convert -w 32 -h 32 src-tauri/icons/icon.svg -o static/favicon-32.png
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 7: Clean up the intermediate 1024px source PNG**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
rm src-tauri/icons/app-icon-1024.png
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 8: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add src-tauri/icons/ static/favicon.svg static/favicon-32.png
|
|||
|
|
git commit -m "feat: generate platform icons from chosen variant"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Task 4: Update Project References
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Delete: `static/tauri.svg` (replaced by `favicon.svg`)
|
|||
|
|
- Modify: `index.html` (add favicon link)
|
|||
|
|
|
|||
|
|
**Interfaces:**
|
|||
|
|
- Consumes: `static/favicon.svg` and `static/favicon-32.png` from Task 3
|
|||
|
|
- Produces: the app loads the new icon at all touchpoints (window icon, favicon, dock icon)
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: Remove the default Tauri SVG**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
rm static/tauri.svg
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
No source files reference `tauri.svg` — it is only a static asset. No further cleanup needed.
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: Add a favicon link to `index.html`**
|
|||
|
|
|
|||
|
|
The current `index.html` has no favicon `<link>`. Add one inside the `<head>` tag, after the `<title>` element:
|
|||
|
|
|
|||
|
|
```html
|
|||
|
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|||
|
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
The full `<head>` section should look like:
|
|||
|
|
|
|||
|
|
```html
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8" />
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|||
|
|
<title>Video Clipper</title>
|
|||
|
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|||
|
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
|
|||
|
|
</head>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: Verify the app builds**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
npm run build
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Build should succeed with no missing asset errors.
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add -A
|
|||
|
|
git commit -m "chore: replace default Tauri branding with app icon"
|
|||
|
|
```
|