--- name: Asset Manager description: Organize design assets, optimize images and fonts, maintain brand asset libraries, implement version control for assets, and enforce naming conventions. Keep design assets organized and production-ready. version: 1.0.0 tags: [assets, images, fonts, optimization, organization] --- # Asset Manager Keep design assets organized, optimized, and accessible. ## Core Principle **Organized assets = faster development.** Assets should be: - Easy to find - Properly named - Optimized for production - Version controlled - Consistently formatted --- ## Phase 1: Asset Organization ### Directory Structure ``` assets/ ├── images/ │ ├── products/ │ ├── team/ │ ├── marketing/ │ └── ui/ ├── icons/ │ ├── svg/ │ └── png/ ├── fonts/ │ ├── primary/ │ └── secondary/ ├── videos/ ├── logos/ │ ├── svg/ │ ├── png/ │ └── variants/ └── brand/ ├── colors.json ├── typography.json └── guidelines.pdf ``` ### Naming Conventions **Images:** ``` {category}-{description}-{size}.{format} Examples: product-hero-1920x1080.jpg team-sarah-400x400.jpg ui-background-pattern.png ``` **Icons:** ``` {icon-name}-{variant}.svg Examples: home-outline.svg home-filled.svg user-circle.svg arrow-right.svg ``` **Fonts:** ``` {font-family}-{weight}.{format} Examples: Inter-Regular.woff2 Inter-Bold.woff2 Poppins-SemiBold.woff2 ``` ### Automated Organization Script ```typescript // scripts/organize-assets.ts import fs from 'fs/promises' import path from 'path' interface AssetRule { pattern: RegExp destination: string } const rules: AssetRule[] = [ { pattern: /product-/i, destination: 'images/products' }, { pattern: /team-/i, destination: 'images/team' }, { pattern: /icon-/i, destination: 'icons/svg' }, { pattern: /logo-/i, destination: 'logos' } ] async function organizeAssets(sourceDir: string) { const files = await fs.readdir(sourceDir) for (const file of files) { const sourcePath = path.join(sourceDir, file) const stat = await fs.stat(sourcePath) if (stat.isDirectory()) continue // Find matching rule const rule = rules.find(r => r.pattern.test(file)) if (rule) { const destDir = path.join('assets', rule.destination) await fs.mkdir(destDir, { recursive: true }) const destPath = path.join(destDir, file) await fs.rename(sourcePath, destPath) console.log(`Moved: ${file} → ${rule.destination}`) } } console.log('Assets organized!') } organizeAssets('./unsorted-assets') ``` --- ## Phase 2: Image Optimization ### Install Optimization Tools ```bash npm install sharp imagemin imagemin-mozjpeg imagemin-pngquant imagemin-svgo ``` ### Optimize Images Script ```typescript // scripts/optimize-images.ts import sharp from 'sharp' import imagemin from 'imagemin' import imageminMozjpeg from 'imagemin-mozjpeg' import imageminPngquant from 'imagemin-pngquant' import imageminSvgo from 'imagemin-svgo' import fs from 'fs/promises' import path from 'path' interface OptimizeOptions { quality?: number maxWidth?: number formats?: ('jpg' | 'png' | 'webp' | 'avif')[] } async function optimizeImages(inputDir: string, outputDir: string, options: OptimizeOptions = {}) { const { quality = 80, maxWidth = 2000, formats = ['jpg', 'png', 'webp'] } = options const files = await fs.readdir(inputDir) for (const file of files) { const inputPath = path.join(inputDir, file) const stat = await fs.stat(inputPath) if (stat.isDirectory()) continue const ext = path.extname(file).toLowerCase() const name = path.basename(file, ext) console.log(`Processing: ${file}`) // Skip SVGs (handle separately) if (ext === '.svg') { await optimizeSVG(inputPath, outputDir) continue } // Skip non-images if (!['.jpg', '.jpeg', '.png'].includes(ext)) continue // Read image const image = sharp(inputPath) const metadata = await image.metadata() // Resize if too large if (metadata.width && metadata.width > maxWidth) { image.resize(maxWidth, null, { withoutEnlargement: true, fit: 'inside' }) } // Generate formats for (const format of formats) { const outputPath = path.join(outputDir, `${name}.${format}`) if (format === 'jpg') { await image.jpeg({ quality, mozjpeg: true }).toFile(outputPath) } else if (format === 'png') { await image.png({ quality, compressionLevel: 9 }).toFile(outputPath) } else if (format === 'webp') { await image.webp({ quality }).toFile(outputPath) } else if (format === 'avif') { await image.avif({ quality }).toFile(outputPath) } console.log(` ✓ Generated ${format}`) } } console.log('Images optimized!') } async function optimizeSVG(inputPath: string, outputDir: string) { const fileName = path.basename(inputPath) const outputPath = path.join(outputDir, fileName) await imagemin([inputPath], { destination: outputDir, plugins: [ imageminSvgo({ plugins: [ { name: 'removeViewBox', active: false }, { name: 'removeDimensions', active: true }, { name: 'removeUselessStrokeAndFill', active: true } ] }) ] }) console.log(` ✓ Optimized SVG`) } // Usage optimizeImages('./assets/images/raw', './assets/images/optimized', { quality: 85, maxWidth: 1920, formats: ['jpg', 'webp', 'avif'] }) ``` ### Responsive Images Generator ```typescript // scripts/generate-responsive-images.ts import sharp from 'sharp' import fs from 'fs/promises' import path from 'path' const breakpoints = [ { name: 'mobile', width: 640 }, { name: 'tablet', width: 768 }, { name: 'desktop', width: 1920 } ] async function generateResponsiveImages(inputPath: string) { const ext = path.extname(inputPath) const name = path.basename(inputPath, ext) const dir = path.dirname(inputPath) for (const bp of breakpoints) { const image = sharp(inputPath) // Resize image.resize(bp.width, null, { withoutEnlargement: true, fit: 'inside' }) // Generate WebP const webpPath = path.join(dir, `${name}-${bp.name}.webp`) await image.webp({ quality: 80 }).toFile(webpPath) console.log(` ✓ ${name}-${bp.name}.webp`) // Generate AVIF const avifPath = path.join(dir, `${name}-${bp.name}.avif`) await image.avif({ quality: 80 }).toFile(avifPath) console.log(` ✓ ${name}-${bp.name}.avif`) } console.log(`Generated responsive images for: ${name}`) } // Usage generateResponsiveImages('./assets/images/hero.jpg') ``` --- ## Phase 3: Font Management ### Font Optimization ```typescript // scripts/optimize-fonts.ts import { exec } from 'child_process' import { promisify } from 'util' import fs from 'fs/promises' import path from 'path' const execAsync = promisify(exec) async function optimizeFonts(inputDir: string, outputDir: string) { const files = await fs.readdir(inputDir) for (const file of files) { const inputPath = path.join(inputDir, file) const ext = path.extname(file).toLowerCase() if (ext !== '.ttf' && ext !== '.otf') continue const name = path.basename(file, ext) console.log(`Processing: ${file}`) // Convert to WOFF2 (best compression) const woff2Path = path.join(outputDir, `${name}.woff2`) await convertToWOFF2(inputPath, woff2Path) console.log(` ✓ ${name}.woff2`) // Convert to WOFF (fallback) const woffPath = path.join(outputDir, `${name}.woff`) await convertToWOFF(inputPath, woffPath) console.log(` ✓ ${name}.woff`) } console.log('Fonts optimized!') } async function convertToWOFF2(input: string, output: string) { // Requires woff2_compress tool // Install: brew install woff2 await execAsync(`woff2_compress ${input}`) const woff2File = input.replace(/\.(ttf|otf)$/, '.woff2') await fs.rename(woff2File, output) } async function convertToWOFF(input: string, output: string) { // Requires sfnt2woff tool await execAsync(`sfnt2woff ${input}`) const woffFile = input.replace(/\.(ttf|otf)$/, '.woff') await fs.rename(woffFile, output) } optimizeFonts('./assets/fonts/raw', './assets/fonts/optimized') ``` ### Font Loading Strategy ```css /* fonts.css */ /* Preload critical fonts */ @font-face { font-family: 'Inter'; src: url('/fonts/Inter-Regular.woff2') format('woff2'), url('/fonts/Inter-Regular.woff') format('woff'); font-weight: 400; font-style: normal; font-display: swap; /* Show fallback first */ } @font-face { font-family: 'Inter'; src: url('/fonts/Inter-Bold.woff2') format('woff2'), url('/fonts/Inter-Bold.woff') format('woff'); font-weight: 700; font-style: normal; font-display: swap; } ``` **Preload in HTML:** ```html
``` --- ## Phase 4: Asset Version Control ### Git LFS Setup ```bash # Install Git LFS brew install git-lfs git lfs install # Track large files git lfs track "*.psd" git lfs track "*.ai" git lfs track "*.sketch" git lfs track "*.fig" git lfs track "*.mp4" git lfs track "*.mov" git lfs track "assets/images/**/*.jpg" git lfs track "assets/images/**/*.png" # Add .gitattributes git add .gitattributes git commit -m "Configure Git LFS" ``` ### Asset Versioning System ```typescript // scripts/version-assets.ts import fs from 'fs/promises' import path from 'path' import crypto from 'crypto' interface AssetVersion { path: string hash: string size: number modified: string version: number } class AssetVersionManager { private versionFile = 'asset-versions.json' private versions: Map
// Or use Next.js Image
import Image from 'next/image'
;
```
### 4. Automated Asset Pipeline
```typescript
// scripts/asset-pipeline.ts
async function runAssetPipeline() {
console.log('Starting asset pipeline...')
// 1. Organize assets
await organizeAssets('./unsorted')
// 2. Optimize images
await optimizeImages('./assets/images/raw', './assets/images/optimized')
// 3. Generate responsive images
await generateResponsiveImages('./assets/images/optimized')
// 4. Optimize fonts
await optimizeFonts('./assets/fonts/raw', './assets/fonts/optimized')
// 5. Version assets
const manager = new AssetVersionManager()
await manager.load()
await manager.trackDirectory('./assets')
await manager.save()
console.log('Asset pipeline complete!')
}
runAssetPipeline()
```
---
## Tools & Resources
**Optimization Tools:**
- [Sharp](https://sharp.pixelplumbing.com/) - Image processing
- [ImageOptim](https://imageoptim.com/) - Image compression
- [SVGO](https://github.com/svg/svgo) - SVG optimization
- [Squoosh](https://squoosh.app/) - Online image compression
**Font Tools:**
- [Glyphhanger](https://github.com/zachleat/glyphhanger) - Font subsetting
- [Transfonter](https://transfonter.org/) - Font conversion
- [FontForge](https://fontforge.org/) - Font editor
**Related Skills:**
- `visual-designer` - Design principles
- `figma-developer` - Export from Figma
- `brand-designer` - Brand asset creation
---
**Organized assets, optimized performance.** 📦