14 KiB
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
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.
<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
open src-tauri/icons/icon.svg
Open the SVG in a browser. Verify:
- Dark background fills the square
- Film strip is visible as a horizontal band with a diagonal gap
- Sprocket holes are visible as darker rectangles along the strip edges
- Scissors blades cross through the gap at a diagonal angle
- Blade gradient goes from lighter (tips) to darker (pivot)
- Handles taper from the pivot to the rings
- Rings are visible as circular outlines
- Pivot screw is centered
- 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
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:
#!/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
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
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
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
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, plusstatic/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):
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
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:
sips -g pixelHeight -g pixelWidth src-tauri/icons/app-icon-1024.png
Expected:
pixelHeight: 1024
pixelWidth: 1024
- Step 4: Run Tauri icon generator
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
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:
"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
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
rm src-tauri/icons/app-icon-1024.png
- Step 8: Commit
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 byfavicon.svg) - Modify:
index.html(add favicon link)
Interfaces:
-
Consumes:
static/favicon.svgandstatic/favicon-32.pngfrom Task 3 -
Produces: the app loads the new icon at all touchpoints (window icon, favicon, dock icon)
-
Step 1: Remove the default Tauri SVG
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:
<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:
<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
npm run build
Build should succeed with no missing asset errors.
- Step 4: Commit
git add -A
git commit -m "chore: replace default Tauri branding with app icon"