Files
gui-video-clipper/docs/superpowers/plans/2026-09-22-app-icon.md
2026-09-22 11:56:04 -04:00

14 KiB
Raw Permalink Blame History

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:

  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
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, 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):

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

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"