diff --git a/docs/MINIFY.md b/docs/MINIFY.md
new file mode 100644
index 0000000..0ddfa98
--- /dev/null
+++ b/docs/MINIFY.md
@@ -0,0 +1,398 @@
+# CSS/JS Minification for ATCR
+
+## Overview
+
+ATCR embeds static assets (CSS, JavaScript) directly into the binary using Go's `embed` directive. Currently:
+
+- **CSS Size:** 40KB (`pkg/appview/static/css/style.css`, 2,210 lines)
+- **Embedded:** All static files compiled into binary at build time
+- **No Minification:** Source files embedded as-is
+
+**Problem:** Embedded assets increase binary size and network transfer time.
+
+**Solution:** Minify CSS/JS before embedding to reduce both binary size and network transfer.
+
+## Recommended Approach: `tdewolff/minify`
+
+Use the pure Go `tdewolff/minify` library with `go:generate` to minify assets at build time.
+
+**Benefits:**
+- Pure Go, no external dependencies (Node.js, npm)
+- Integrates with existing `go:generate` workflow
+- ~30-40% CSS size reduction (40KB → ~28KB)
+- Minifies CSS, JS, HTML, JSON, SVG, XML
+
+## Implementation
+
+### Step 1: Add Dependency
+
+```bash
+go get github.com/tdewolff/minify/v2
+```
+
+This will update `go.mod`:
+```go
+require github.com/tdewolff/minify/v2 v2.20.37
+```
+
+### Step 2: Create Minification Script
+
+Create `pkg/appview/static/minify_assets.go`:
+
+```go
+//go:build ignore
+
+package main
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+
+ "github.com/tdewolff/minify/v2"
+ "github.com/tdewolff/minify/v2/css"
+ "github.com/tdewolff/minify/v2/js"
+)
+
+func main() {
+ m := minify.New()
+ m.AddFunc("text/css", css.Minify)
+ m.AddFunc("text/javascript", js.Minify)
+
+ // Get the directory of this script
+ dir, err := os.Getwd()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Minify CSS
+ if err := minifyFile(m, "text/css",
+ filepath.Join(dir, "pkg/appview/static/css/style.css"),
+ filepath.Join(dir, "pkg/appview/static/css/style.min.css"),
+ ); err != nil {
+ log.Fatalf("Failed to minify CSS: %v", err)
+ }
+
+ // Minify JavaScript
+ if err := minifyFile(m, "text/javascript",
+ filepath.Join(dir, "pkg/appview/static/js/app.js"),
+ filepath.Join(dir, "pkg/appview/static/js/app.min.js"),
+ ); err != nil {
+ log.Fatalf("Failed to minify JS: %v", err)
+ }
+
+ fmt.Println("✓ Assets minified successfully")
+}
+
+func minifyFile(m *minify.M, mediatype, src, dst string) error {
+ // Read source file
+ input, err := os.ReadFile(src)
+ if err != nil {
+ return fmt.Errorf("read %s: %w", src, err)
+ }
+
+ // Minify
+ output, err := m.Bytes(mediatype, input)
+ if err != nil {
+ return fmt.Errorf("minify %s: %w", src, err)
+ }
+
+ // Write minified output
+ if err := os.WriteFile(dst, output, 0644); err != nil {
+ return fmt.Errorf("write %s: %w", dst, err)
+ }
+
+ // Print size reduction
+ originalSize := len(input)
+ minifiedSize := len(output)
+ reduction := float64(originalSize-minifiedSize) / float64(originalSize) * 100
+
+ fmt.Printf(" %s: %d bytes → %d bytes (%.1f%% reduction)\n",
+ filepath.Base(src), originalSize, minifiedSize, reduction)
+
+ return nil
+}
+```
+
+### Step 3: Add `go:generate` Directive
+
+Add to `pkg/appview/ui.go` (before the `//go:embed` directive):
+
+```go
+//go:generate go run ./static/minify_assets.go
+
+//go:embed static
+var staticFS embed.FS
+```
+
+### Step 4: Update HTML Templates
+
+Update all template files to reference minified assets:
+
+**Before:**
+```html
+
+
+```
+
+**After:**
+```html
+
+
+```
+
+**Files to update:**
+- `pkg/appview/templates/components/head.html`
+- Any other templates that reference CSS/JS directly
+
+### Step 5: Build Workflow
+
+```bash
+# Generate minified assets
+go generate ./pkg/appview
+
+# Build binary (embeds minified assets)
+go build -o bin/atcr-appview ./cmd/appview
+
+# Or build all
+go generate ./...
+go build -o bin/atcr-appview ./cmd/appview
+go build -o bin/atcr-hold ./cmd/hold
+```
+
+### Step 6: Add to .gitignore
+
+Add minified files to `.gitignore` since they're generated:
+
+```
+# Generated minified assets
+pkg/appview/static/css/*.min.css
+pkg/appview/static/js/*.min.js
+```
+
+**Alternative:** Commit minified files if you want reproducible builds without running `go generate`.
+
+## Build Modes (Optional Enhancement)
+
+Use build tags to serve unminified assets in development:
+
+**Development (default):**
+- Edit `style.css` directly
+- No minification, easier debugging
+- Faster build times
+
+**Production (with `-tags production`):**
+- Use minified assets
+- Smaller binary size
+- Optimized for deployment
+
+### Implementation with Build Tags
+
+**pkg/appview/ui.go** (development):
+```go
+//go:build !production
+
+//go:embed static
+var staticFS embed.FS
+
+func StylePath() string { return "/static/css/style.css" }
+func ScriptPath() string { return "/static/js/app.js" }
+```
+
+**pkg/appview/ui_production.go** (production):
+```go
+//go:build production
+
+//go:generate go run ./static/minify_assets.go
+
+//go:embed static
+var staticFS embed.FS
+
+func StylePath() string { return "/static/css/style.min.css" }
+func ScriptPath() string { return "/static/js/app.min.js" }
+```
+
+**Usage:**
+```bash
+# Development build (unminified)
+go build ./cmd/appview
+
+# Production build (minified)
+go generate ./pkg/appview
+go build -tags production ./cmd/appview
+```
+
+## Alternative Approaches
+
+### Option 2: External Minifier (cssnano, esbuild)
+
+Use Node.js-based minifiers via `go:generate`:
+
+```go
+//go:generate sh -c "npx cssnano static/css/style.css static/css/style.min.css"
+//go:generate sh -c "npx esbuild static/js/app.js --minify --outfile=static/js/app.min.js"
+```
+
+**Pros:**
+- Best-in-class minification (potentially better than tdewolff)
+- Wide ecosystem of tools
+
+**Cons:**
+- Requires Node.js/npm in build environment
+- Cross-platform compatibility issues (Windows vs Unix)
+- External dependency management
+
+### Option 3: Runtime Gzip Compression
+
+Compress assets at runtime (complementary to minification):
+
+```go
+import "github.com/NYTimes/gziphandler"
+
+// Wrap static handler
+mux.Handle("/static/", gziphandler.GzipHandler(appview.StaticHandler()))
+```
+
+**Pros:**
+- Works for all static files (images, fonts)
+- ~70-80% size reduction over network
+- No build changes needed
+
+**Cons:**
+- Doesn't reduce binary size
+- Adds runtime CPU cost
+- Should be combined with minification for best results
+
+### Option 4: Brotli Compression (Better than Gzip)
+
+```go
+import "github.com/andybalholm/brotli"
+
+// Custom handler with brotli
+func BrotliHandler(h http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if !strings.Contains(r.Header.Get("Accept-Encoding"), "br") {
+ h.ServeHTTP(w, r)
+ return
+ }
+ w.Header().Set("Content-Encoding", "br")
+ bw := brotli.NewWriterLevel(w, brotli.DefaultCompression)
+ defer bw.Close()
+ h.ServeHTTP(&brotliResponseWriter{Writer: bw, ResponseWriter: w}, r)
+ })
+}
+```
+
+## Expected Benefits
+
+### File Size Reduction
+
+**Current (unminified):**
+- CSS: 40KB
+- JS: ~5KB (estimated)
+- **Total embedded:** ~45KB
+
+**With Minification:**
+- CSS: ~28KB (30% reduction)
+- JS: ~3KB (40% reduction)
+- **Total embedded:** ~31KB
+- **Binary size savings:** ~14KB
+
+**With Minification + Gzip (network transfer):**
+- CSS: ~8KB (80% reduction from original)
+- JS: ~1.5KB (70% reduction from original)
+- **Total transferred:** ~9.5KB
+
+### Performance Impact
+
+- **Build time:** +1-2 seconds (running minifier)
+- **Runtime:** No impact (files pre-minified)
+- **Network:** 75% less data transferred (with gzip)
+- **Browser parsing:** Slightly faster (smaller files)
+
+## Maintenance
+
+### Development Workflow
+
+1. **Edit source files:**
+ - Modify `pkg/appview/static/css/style.css`
+ - Modify `pkg/appview/static/js/app.js`
+
+2. **Test locally:**
+ ```bash
+ # Development build (unminified)
+ go run ./cmd/appview serve
+ ```
+
+3. **Build for production:**
+ ```bash
+ # Generate minified assets
+ go generate ./pkg/appview
+
+ # Build binary
+ go build -o bin/atcr-appview ./cmd/appview
+ ```
+
+4. **CI/CD:**
+ ```bash
+ # In GitHub Actions / CI
+ go generate ./...
+ go build ./...
+ ```
+
+### Troubleshooting
+
+**Q: Minified assets not updating?**
+- Delete `*.min.css` and `*.min.js` files
+- Run `go generate ./pkg/appview` again
+
+**Q: Build fails with "package not found"?**
+- Run `go mod tidy` to download dependencies
+
+**Q: CSS broken after minification?**
+- Check for syntax errors in source CSS
+- Minifier is strict about valid CSS
+
+## Integration with Existing Build
+
+ATCR already uses `go:generate` for:
+- CBOR generation (`pkg/atproto/lexicon.go`)
+- License downloads (`pkg/appview/licenses/licenses.go`)
+
+Minification follows the same pattern:
+```bash
+# Generate all (CBOR, licenses, minified assets)
+go generate ./...
+
+# Build all binaries
+go build -o bin/atcr-appview ./cmd/appview
+go build -o bin/atcr-hold ./cmd/hold
+go build -o bin/docker-credential-atcr ./cmd/credential-helper
+```
+
+## Recommendation
+
+**For ATCR:**
+
+1. **Immediate:** Implement Option 1 (`tdewolff/minify`)
+ - Pure Go, no external dependencies
+ - Integrates with existing `go:generate` workflow
+ - ~30% size reduction
+
+2. **Future:** Add runtime gzip/brotli compression
+ - Wrap static handler with compression middleware
+ - Benefits all static assets
+ - Standard practice for web servers
+
+3. **Long-term:** Consider build modes (development vs production)
+ - Use unminified assets in development
+ - Use minified assets in production builds
+ - Best developer experience
+
+## References
+
+- [tdewolff/minify](https://github.com/tdewolff/minify) - Go minifier library
+- [NYTimes/gziphandler](https://github.com/NYTimes/gziphandler) - Gzip middleware
+- [Go embed directive](https://pkg.go.dev/embed) - Embedding static files
+- [Go generate](https://go.dev/blog/generate) - Code generation tool