From daffy0208-ai-dev-standards
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.
npx claudepluginhub joshuarweaver/cascade-content-creation-misc-1 --plugin daffy0208-ai-dev-standardsThis skill uses the workspace's default tool permissions.
Keep design assets organized, optimized, and accessible.
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Keep design assets organized, optimized, and accessible.
Organized assets = faster development.
Assets should be:
assets/
├── images/
│ ├── products/
│ ├── team/
│ ├── marketing/
│ └── ui/
├── icons/
│ ├── svg/
│ └── png/
├── fonts/
│ ├── primary/
│ └── secondary/
├── videos/
├── logos/
│ ├── svg/
│ ├── png/
│ └── variants/
└── brand/
├── colors.json
├── typography.json
└── guidelines.pdf
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
// 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')
npm install sharp imagemin imagemin-mozjpeg imagemin-pngquant imagemin-svgo
// 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']
})
// 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')
// 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')
/* 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:
<head>
<!-- Preload critical fonts -->
<link rel="preload" href="/fonts/Inter-Regular.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Inter-Bold.woff2" as="font" type="font/woff2" crossorigin />
</head>
# 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"
// 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<string, AssetVersion[]> = new Map()
async load() {
try {
const data = await fs.readFile(this.versionFile, 'utf-8')
const parsed = JSON.parse(data)
for (const [path, versions] of Object.entries(parsed)) {
this.versions.set(path, versions as AssetVersion[])
}
} catch (error) {
// File doesn't exist yet
}
}
async save() {
const data = Object.fromEntries(this.versions)
await fs.writeFile(this.versionFile, JSON.stringify(data, null, 2))
}
async trackAsset(filePath: string) {
const buffer = await fs.readFile(filePath)
const hash = crypto.createHash('sha256').update(buffer).digest('hex')
const stat = await fs.stat(filePath)
const versions = this.versions.get(filePath) || []
const lastVersion = versions[versions.length - 1]
// Check if changed
if (lastVersion && lastVersion.hash === hash) {
console.log(`No changes: ${filePath}`)
return
}
// Add new version
versions.push({
path: filePath,
hash,
size: stat.size,
modified: stat.mtime.toISOString(),
version: versions.length + 1
})
this.versions.set(filePath, versions)
console.log(`Tracked: ${filePath} (v${versions.length})`)
}
async trackDirectory(dirPath: string) {
const files = await fs.readdir(dirPath, { recursive: true })
for (const file of files) {
const filePath = path.join(dirPath, file.toString())
const stat = await fs.stat(filePath)
if (stat.isDirectory()) continue
await this.trackAsset(filePath)
}
}
}
// Usage
const manager = new AssetVersionManager()
await manager.load()
await manager.trackDirectory('./assets')
await manager.save()
brand/
├── logos/
│ ├── primary/
│ │ ├── logo-full.svg
│ │ ├── logo-icon.svg
│ │ └── logo-wordmark.svg
│ ├── variations/
│ │ ├── logo-white.svg
│ │ ├── logo-black.svg
│ │ └── logo-inverted.svg
│ └── exports/
│ ├── png/
│ ├── pdf/
│ └── eps/
├── colors/
│ ├── colors.json
│ ├── colors.css
│ └── colors.scss
├── typography/
│ ├── fonts/
│ └── typography.json
└── guidelines/
├── brand-guidelines.pdf
├── logo-usage.pdf
└── color-usage.pdf
// brand/manifest.ts
export interface BrandAssets {
version: string
lastUpdated: string
logos: LogoAsset[]
colors: ColorAsset[]
typography: TypographyAsset[]
}
export interface LogoAsset {
name: string
variants: {
full: string
icon: string
wordmark: string
}
formats: {
svg: string
png: { [size: string]: string }
pdf: string
}
}
export interface ColorAsset {
name: string
hex: string
rgb: { r: number; g: number; b: number }
usage: string
}
export interface TypographyAsset {
name: string
family: string
weights: number[]
formats: string[]
}
export const brandAssets: BrandAssets = {
version: '2.0.0',
lastUpdated: '2024-01-15',
logos: [
{
name: 'Primary Logo',
variants: {
full: '/brand/logos/logo-full.svg',
icon: '/brand/logos/logo-icon.svg',
wordmark: '/brand/logos/logo-wordmark.svg'
},
formats: {
svg: '/brand/logos/logo-full.svg',
png: {
'1x': '/brand/logos/exports/png/logo-full@1x.png',
'2x': '/brand/logos/exports/png/logo-full@2x.png',
'3x': '/brand/logos/exports/png/logo-full@3x.png'
},
pdf: '/brand/logos/exports/pdf/logo-full.pdf'
}
}
],
colors: [
{
name: 'Primary',
hex: '#0066cc',
rgb: { r: 0, g: 102, b: 204 },
usage: 'Primary actions, links, brand elements'
}
],
typography: [
{
name: 'Inter',
family: 'Inter',
weights: [400, 600, 700],
formats: ['woff2', 'woff']
}
]
}
// Lazy load images
;<img src="placeholder.jpg" data-src="hero.jpg" loading="lazy" alt="Hero" />
// Or use Next.js Image
import Image from 'next/image'
;<Image src="/hero.jpg" width={1920} height={1080} placeholder="blur" alt="Hero" />
// Use CDN for assets
const CDN_URL = process.env.CDN_URL || ''
export function getAssetUrl(path: string): string {
if (CDN_URL) {
return `${CDN_URL}${path}`
}
return path
}
// Usage
<img src={getAssetUrl('/images/hero.jpg')} alt="Hero" />
// 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()
Optimization Tools:
Font Tools:
Related Skills:
visual-designer - Design principlesfigma-developer - Export from Figmabrand-designer - Brand asset creationOrganized assets, optimized performance. 📦