commit 904d09e3f673b78a522aa21f1a0715c094385850 Author: Zhongwei Li Date: Sun Nov 30 08:36:56 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..7f6ca32 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "pwa-assets-generator", + "description": "Generate all required PWA assets from a single 1024x1024px image", + "version": "1.0.0", + "author": { + "name": "Laststance", + "email": "ryota.murakami@laststance.io" + }, + "skills": [ + "./skills" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..510f97d --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# pwa-assets-generator + +Generate all required PWA assets from a single 1024x1024px image diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..525710f --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,49 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:laststance/claude-code-marketplace:pwa-assets-generator", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "9cbda3b3c0a10faed9ac8e931d57f8c19b379565", + "treeHash": "0d259dd5f8bc9f0ce73f2bb07b230f3844ae3d4a328fd2368634961038cf9ed6", + "generatedAt": "2025-11-28T10:20:04.008729Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "pwa-assets-generator", + "description": "Generate all required PWA assets from a single 1024x1024px image", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "4829bcf1fcb10aa00853d708a571ceb42d789563b01677aecb6eb93a640c32a0" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "0d3dcd463b0766186c5ad88b48a68e51f6b8e6331bc15b41d6c0a96a2179917d" + }, + { + "path": "skills/pwa-assets-generator/SKILL.md", + "sha256": "2e2e300ba6a316b9992223a4f6992b321e5d8eb65ca13599ee97542df0471e3e" + }, + { + "path": "skills/pwa-assets-generator/scripts/generate-pwa-assets.js", + "sha256": "24893395c10bae08ccbf0ca6adb95df0b9218bfd6d3ea7b697465ccc2abd809e" + } + ], + "dirSha256": "0d259dd5f8bc9f0ce73f2bb07b230f3844ae3d4a328fd2368634961038cf9ed6" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/pwa-assets-generator/SKILL.md b/skills/pwa-assets-generator/SKILL.md new file mode 100644 index 0000000..798a6bb --- /dev/null +++ b/skills/pwa-assets-generator/SKILL.md @@ -0,0 +1,104 @@ +--- +name: pwa-assets-generator +description: Generate all required PWA assets from a single 1024x1024px image. This skill creates app icons, favicons, Apple touch icons, maskable icons, badge icons, and placeholder screenshots for PWAs. Use when developers need to prepare PWA manifest assets, generate multiple icon sizes from a source image, or create a complete set of PWA-compliant images. +allowed-tools: Bash, Read, Write +--- + +# PWA Assets Generator + +Generate complete set of PWA assets from a single 1024x1024px source image. + +## Prerequisites + +Ensure Node.js and npm are installed. The script will automatically install required dependencies. + +## Quick Start + +1. Place your 1024x1024px source image in the project directory +2. Run the generation script: + ```bash + node scripts/generate-pwa-assets.js + ``` + +## Generated Assets + +The script creates the following PWA-compliant assets: + +### App Icons (Standard) +- `icon-144x144.png` - Standard PWA icon +- `icon-192x192.png` - Android Chrome icon +- `icon-512x512.png` - High-resolution PWA icon + +### Maskable Icons +- `icon-192x192-safe.png` - Maskable variant with safe area padding (20% padding) + +### iOS Support +- `apple-touch-icon.png` - 180×180px for iOS devices + +### Favicon +- `favicon.ico` - Multi-resolution icon (16×16, 32×32, 48×48) + +### Badge Icon +- `badge.png` - 96×96px monochrome white badge + +### Screenshot Placeholders +- `screenshots/desktop-wide.png` - 1280×720px desktop placeholder +- `screenshots/mobile-narrow.png` - 375×812px mobile placeholder + +### Shortcut Icons +- `shortcuts/start.png` - 96×96px with play overlay +- `shortcuts/settings.png` - 96×96px with gear overlay + +## Script Features + +- **Automatic dependency installation**: Installs sharp and png-to-ico if not present +- **Smart resizing**: Uses sharp for high-quality image processing +- **Maskable icon generation**: Adds proper padding for maskable icons +- **Multi-resolution favicon**: Creates proper .ico file with multiple sizes +- **Badge creation**: Converts to monochrome white for notification badges +- **Placeholder screenshots**: Generates branded placeholders with instructions +- **Overlay icons**: Adds symbolic overlays for shortcut icons +- **Progress tracking**: Shows real-time generation progress + +## Customization + +### Maskable Icons +The script adds 20% padding to maskable icons by default. Adjust the `MASKABLE_PADDING` constant in the script if needed. + +### Screenshot Placeholders +The generated screenshots include instructional text. Replace these with actual app screenshots before deployment. + +### Badge Color +The badge is converted to white monochrome. Edit the script's badge generation section for different color schemes. + +## Usage Example + +```bash +# Generate all assets from logo.png into public/ directory +node scripts/generate-pwa-assets.js ./logo.png ./public +``` + +## Output Structure + +``` +output-directory/ +├── icon-144x144.png +├── icon-192x192.png +├── icon-512x512.png +├── icon-192x192-safe.png +├── apple-touch-icon.png +├── favicon.ico +├── badge.png +├── screenshots/ +│ ├── desktop-wide.png +│ └── mobile-narrow.png +└── shortcuts/ + ├── start.png + └── settings.png +``` + +## Troubleshooting + +- **Image too small**: Source image must be at least 1024×1024px +- **Transparency issues**: PNG with alpha channel recommended for best results +- **Favicon not showing**: Clear browser cache after replacing favicon.ico diff --git a/skills/pwa-assets-generator/scripts/generate-pwa-assets.js b/skills/pwa-assets-generator/scripts/generate-pwa-assets.js new file mode 100755 index 0000000..bdf6366 --- /dev/null +++ b/skills/pwa-assets-generator/scripts/generate-pwa-assets.js @@ -0,0 +1,257 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +// Configuration +const MASKABLE_PADDING = 0.2; // 20% padding for maskable icons + +// Asset definitions +const ASSETS = [ + { name: 'icon-144x144.png', size: 144 }, + { name: 'icon-192x192.png', size: 192 }, + { name: 'icon-512x512.png', size: 512 }, + { name: 'icon-192x192-safe.png', size: 192, maskable: true }, + { name: 'apple-touch-icon.png', size: 180 }, + { name: 'badge.png', size: 96, monochrome: true }, +]; + +const SCREENSHOTS = [ + { name: 'screenshots/desktop-wide.png', width: 1280, height: 720 }, + { name: 'screenshots/mobile-narrow.png', width: 375, height: 812 }, +]; + +const SHORTCUTS = [ + { name: 'shortcuts/start.png', size: 96, overlay: 'play' }, + { name: 'shortcuts/settings.png', size: 96, overlay: 'gear' }, +]; + +// Check and install dependencies +function ensureDependencies() { + const requiredPackages = ['sharp', 'png-to-ico']; + const missingPackages = []; + + requiredPackages.forEach(pkg => { + try { + require.resolve(pkg); + } catch { + missingPackages.push(pkg); + } + }); + + if (missingPackages.length > 0) { + console.log('📦 Installing required dependencies...'); + try { + execSync(`npm install ${missingPackages.join(' ')}`, { stdio: 'inherit' }); + console.log('✅ Dependencies installed successfully!\n'); + } catch (error) { + console.error('❌ Failed to install dependencies. Please run:'); + console.error(` npm install ${missingPackages.join(' ')}`); + process.exit(1); + } + } +} + +// Main function +async function generatePWAAssets(sourcePath, outputDir) { + ensureDependencies(); + + const sharp = require('sharp'); + const pngToIco = require('png-to-ico'); + + // Validate source image + if (!fs.existsSync(sourcePath)) { + throw new Error(`Source image not found: ${sourcePath}`); + } + + // Get image metadata + const metadata = await sharp(sourcePath).metadata(); + if (metadata.width < 1024 || metadata.height < 1024) { + throw new Error(`Source image must be at least 1024x1024px. Current size: ${metadata.width}x${metadata.height}`); + } + + // Create output directories + const dirs = [ + outputDir, + path.join(outputDir, 'screenshots'), + path.join(outputDir, 'shortcuts'), + ]; + + dirs.forEach(dir => { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + }); + + console.log('🎨 Generating PWA Assets...\n'); + + // Generate standard icons + for (const asset of ASSETS) { + const outputPath = path.join(outputDir, asset.name); + console.log(`📱 Creating ${asset.name}...`); + + let pipeline = sharp(sourcePath); + + if (asset.maskable) { + // Add padding for maskable icon + const paddedSize = Math.round(asset.size * (1 + MASKABLE_PADDING * 2)); + pipeline = pipeline + .resize(asset.size, asset.size) + .extend({ + top: Math.round(asset.size * MASKABLE_PADDING), + bottom: Math.round(asset.size * MASKABLE_PADDING), + left: Math.round(asset.size * MASKABLE_PADDING), + right: Math.round(asset.size * MASKABLE_PADDING), + background: { r: 255, g: 255, b: 255, alpha: 0 } + }) + .resize(asset.size, asset.size); + } else if (asset.monochrome) { + // Create monochrome white badge + pipeline = pipeline + .resize(asset.size, asset.size) + .grayscale() + .negate() + .threshold(128); + } else { + // Standard resize + pipeline = pipeline.resize(asset.size, asset.size); + } + + await pipeline.png().toFile(outputPath); + } + + // Generate favicon.ico + console.log('🌐 Creating favicon.ico...'); + const faviconSizes = [16, 32, 48]; + const faviconBuffers = []; + + for (const size of faviconSizes) { + const buffer = await sharp(sourcePath) + .resize(size, size) + .png() + .toBuffer(); + faviconBuffers.push(buffer); + } + + const icoBuffer = await pngToIco(faviconBuffers); + fs.writeFileSync(path.join(outputDir, 'favicon.ico'), icoBuffer); + + // Generate screenshot placeholders + for (const screenshot of SCREENSHOTS) { + const outputPath = path.join(outputDir, screenshot.name); + console.log(`📸 Creating ${screenshot.name}...`); + + // Create a placeholder with the source image centered + const maxDimension = Math.min(screenshot.width, screenshot.height) * 0.4; + + await sharp(sourcePath) + .resize(Math.round(maxDimension), Math.round(maxDimension), { fit: 'inside' }) + .toBuffer() + .then(async (logoBuffer) => { + // Create background with gradient + const svg = ` + + + + + + + + + + Replace with actual app screenshot + + + `; + + const background = await sharp(Buffer.from(svg)) + .png() + .toBuffer(); + + await sharp(background) + .composite([{ + input: logoBuffer, + gravity: 'center' + }]) + .toFile(outputPath); + }); + } + + // Generate shortcut icons with overlays + for (const shortcut of SHORTCUTS) { + const outputPath = path.join(outputDir, shortcut.name); + console.log(`⚡ Creating ${shortcut.name}...`); + + // Resize base icon + const resizedIcon = await sharp(sourcePath) + .resize(shortcut.size, shortcut.size) + .toBuffer(); + + // Create overlay SVG based on type + let overlaySvg; + if (shortcut.overlay === 'play') { + overlaySvg = ` + + + + + + + `; + } else if (shortcut.overlay === 'gear') { + overlaySvg = ` + + + + + + + `; + } + + const overlay = await sharp(Buffer.from(overlaySvg)) + .png() + .toBuffer(); + + await sharp(resizedIcon) + .composite([{ input: overlay }]) + .toFile(outputPath); + } + + console.log('\n✅ All PWA assets generated successfully!'); + console.log(`📂 Output directory: ${outputDir}`); + console.log('\n📋 Generated files:'); + console.log(' ✓ App icons (144x144, 192x192, 512x512)'); + console.log(' ✓ Maskable icon (192x192-safe)'); + console.log(' ✓ Apple touch icon (180x180)'); + console.log(' ✓ Favicon.ico (multi-resolution)'); + console.log(' ✓ Badge icon (96x96, monochrome)'); + console.log(' ✓ Screenshot placeholders (desktop & mobile)'); + console.log(' ✓ Shortcut icons (start & settings)'); + console.log('\n💡 Remember to replace screenshot placeholders with actual app screenshots!'); +} + +// CLI handling +if (require.main === module) { + const args = process.argv.slice(2); + + if (args.length < 2) { + console.log('Usage: node generate-pwa-assets.js '); + console.log('\nExample:'); + console.log(' node generate-pwa-assets.js ./logo.png ./public'); + process.exit(1); + } + + const [sourcePath, outputDir] = args; + + generatePWAAssets(sourcePath, outputDir) + .catch(error => { + console.error('\n❌ Error:', error.message); + process.exit(1); + }); +} + +module.exports = { generatePWAAssets };