Files
cv-site/prompts/done/002-sprite-generation-system.md
juanatsap 71d9258c58 feat: add application-level data caching for CV/UI
Eliminate per-request file I/O by loading CV and UI data once at startup.

## Problem
- LoadCV() and LoadUI() were called on every request
- Each call read from disk and unmarshaled JSON
- 6 locations affected: cv_cmdk, cv_helpers, cv_contact

## Solution
- New `internal/cache` package with language-keyed cache
- Data loaded once at startup via `cache.New(["en", "es"])`
- Handlers use `h.dataCache.GetCV(lang)` / `GetUI(lang)`
- Thread-safe concurrent reads via sync.RWMutex
- Deep copy for mutable slices (Experience, Projects)

## Performance
- Before: ~3ms file I/O per request
- After: <1µs cache lookup (~3000x improvement)

## Files
- internal/cache/data_cache.go (new)
- internal/cache/data_cache_test.go (new)
- internal/cache/README.md (new)
- internal/handlers/cv.go (added dataCache field)
- internal/handlers/cv_*.go (use cache)
- main.go (initialize cache at startup)
2025-12-06 15:57:23 +00:00

337 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<objective>
Create a complete CSS sprite system for company, project, and course icons using Go.
This dramatically improves page load performance by reducing HTTP requests from 44+ individual images to just 3 sprite sheets.
PERFORMANCE IMPACT:
- Current: 23 company + 12 project + 9 course = 44 separate HTTP requests
- Target: 3 sprite images (one per category)
- Result: ~93% reduction in image requests
CRITICAL REQUIREMENTS:
1. **Automated normalization**: Users throw ANY size image into source folders → system automatically normalizes to icon size
2. **Go implementation**: Use Go with native/standard libraries (prefer stdlib, avoid heavy dependencies)
3. **One command**: `make sprites` handles everything (normalize + generate + update registry)
4. **Documentation required**: Create doc/XX-SPRITES.md following existing doc/ patterns
5. **Tests required**: Create tests/mjs/XX-sprites.test.mjs following existing test patterns
</objective>
<context>
Project: CV website (Go + HTMX)
Current image structure:
- static/images/companies/*.png (23 images, various sizes)
- static/images/projects/*.png (12 images, various sizes)
- static/images/courses/*.png (9 images, various sizes)
JSON references (data/cv-en.json, data/cv-es.json):
- experience[].companyLogo: "filename.png"
- projects[].projectLogo: "filename.png"
- courses[].courseLogo: "filename.png"
Existing patterns to follow:
- Tests: tests/mjs/XX-name.test.mjs (Playwright E2E, numbered)
- Docs: doc/XX-NAME.md (numbered markdown)
- Scripts: scripts/*.sh (deployment tools)
- Makefile: existing targets for build, test, css-prod
</context>
<research>
Before implementing, thoroughly research:
1. Go native image processing libraries (image/png, image/draw, golang.org/x/image)
2. Best approach for image resizing with aspect ratio preservation in Go
3. How to composite images into horizontal strips in Go
4. Whether to use cmd/sprites/ pattern or internal/tools/ for Go tooling
</research>
<requirements>
## 1. Go Sprite Generator Tool
Create a Go tool that:
- Scans source folders for images (any size/format)
- Normalizes each to 48x48px (1x) and 96x96px (2x retina)
- Maintains aspect ratio, centers on transparent background
- Combines into horizontal sprite strips
- Generates ICON-REGISTRY.md with positions
Location options to evaluate:
- `cmd/sprites/main.go` (separate binary)
- `internal/tools/sprites/` (internal package)
- Prefer native Go libs: `image`, `image/png`, `image/draw`
- If needed: `golang.org/x/image/draw` for better scaling
## 2. Icon Size Standards
- Base: 48x48px (readable at 100% zoom, works up to 300%)
- Retina: 96x96px (@2x for high-DPI displays)
- WHY 48px: Standard icon size, crisp at all zoom levels, small file size
## 3. Naming Convention
Source images (user drops these - ANY size):
```
static/images/companies/olympic-broadcasting.png (could be 500x300)
static/images/companies/sap.png (could be 100x100)
```
Generated outputs:
```
static/images/sprites/sprite-companies.png (horizontal strip, 48px tall)
static/images/sprites/sprite-companies@2x.png (horizontal strip, 96px tall)
static/images/sprites/sprite-projects.png
static/images/sprites/sprite-projects@2x.png
static/images/sprites/sprite-courses.png
static/images/sprites/sprite-courses@2x.png
```
## 4. JSON Integration
Add `logoIndex` to each entry in cv-en.json and cv-es.json:
```json
{
"company": "Olympic Broadcasting Services",
"companyLogo": "olympic-broadcasting.png",
"logoIndex": 0
}
```
**⚠️ DO NOT TOUCH - Preserve these existing patterns:**
1. **Empty logo fields** - Some projects have `"projectLogo": ""` (no image). Leave as-is, no logoIndex.
```json
// KEEP AS-IS - no sprite integration
{ "projectLogo": "", ... }
```
2. **Iconify icons in HTML** - Course/project items use inline `<iconify-icon>` for individual entries (e.g., Go courses from Udemy). These are in the `responsibilities` array, NOT the logo field. Do not modify.
```json
// KEEP AS-IS - uses Iconify system, not image sprites
"responsibilities": [
"<iconify-icon icon='simple-icons:go' ...></iconify-icon><div>Go - The Complete Guide</div>"
]
```
**RULE: Only add logoIndex when there's an actual PNG file in companyLogo/projectLogo/courseLogo**
**INDEX ORDERING: Chronological (oldest first)**
- Index 0 = oldest/first experience in career
- Last index = most recent/current experience
- This matches natural reading order (career progression)
**KEEP CV JSON CLEAN:**
- Only add `logoIndex` field - nothing else
- NO offset in JSON (calculated as `index × 48px` in CSS)
- The CV JSON is an INFO document, minimize pollution
The Go tool generates a SEPARATE mapping file (not in CV JSON):
```json
// static/images/sprites/sprite-map.json (reference only, not used at runtime)
{
"companies": [
{"index": 0, "name": "oldest-company.png"},
{"index": 1, "name": "next-company.png"},
{"index": 10, "name": "olympic-broadcasting.png"}
],
"projects": [...],
"courses": [...]
}
```
This file is for documentation/debugging only - the CSS calculates offset from index.
## 5. CSS Integration
Create `static/css/04-interactive/_sprites.css`:
```css
.icon-sprite {
display: inline-block;
width: 48px;
height: 48px;
background-repeat: no-repeat;
background-size: auto 48px;
}
.icon-company {
background-image: url('/static/images/sprites/sprite-companies.png');
background-position-x: calc(var(--icon-index, 0) * -48px);
}
/* Retina */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
.icon-company {
background-image: url('/static/images/sprites/sprite-companies@2x.png');
background-size: auto 48px; /* Display at 1x size */
}
}
```
## 6. Template Integration
Update templates to use sprites:
```html
{{if ge .logoIndex 0}}
<span class="icon-sprite icon-company"
style="--icon-index: {{.logoIndex}};"
role="img"
aria-label="{{.company}} logo"></span>
{{else if .companyLogo}}
<img src="/static/images/companies/{{.companyLogo}}" alt="{{.company}} logo">
{{end}}
```
</requirements>
<implementation_flow>
PHASE 1: Go Tool Creation
1. Research best Go approach for image processing
2. Create sprite generator tool
3. Implement: scan → normalize → combine → output
4. Generate sprite-map.json for reference
PHASE 2: Integration
1. Create _sprites.css with positioning classes
2. Update main.css to import sprites
3. Update JSON files with logoIndex values
4. Modify templates to render sprites
PHASE 3: Makefile & Workflow
1. Add `make sprites` target
2. Add `make sprites-clean` target
3. Document workflow in doc/XX-SPRITES.md
PHASE 4: Testing & Documentation
1. Create tests/mjs/XX-sprites.test.mjs
2. Create doc/XX-SPRITES.md
3. Update PROJECT-MEMORY.md with sprite system rules
</implementation_flow>
<output>
Files to CREATE:
- `cmd/sprites/main.go` (or appropriate location) - Go sprite generator
- `static/css/04-interactive/_sprites.css` - Sprite CSS classes
- `static/images/sprites/` - Output directory
- `static/images/sprites/sprite-map.json` - Icon position mapping
- `static/sprite-showcase.html` - **AUTO-GENERATED** visual showcase page
- `doc/XX-SPRITES.md` - Complete documentation
- `tests/mjs/XX-sprites.test.mjs` - E2E tests
Files to MODIFY:
- `Makefile` - Add sprites targets
- `static/css/main.css` - Import sprites CSS
- `data/cv-en.json` - Add logoIndex to all entries
- `data/cv-es.json` - Add logoIndex to all entries
- `templates/partials/sections/experience.html` - Use sprite rendering
- `templates/partials/sections/projects.html` - Use sprite rendering
- `templates/partials/sections/courses.html` - Use sprite rendering
- `PROJECT-MEMORY.md` - Document sprite system rules
**CRITICAL DOCUMENTATION UPDATE:**
- `doc/2-MODERN-WEB-TECHNIQUES.md` - Add new section "12. CSS Sprites - Image Request Optimization"
This document tracks all performance optimizations with metrics. Add a comprehensive section including:
- Problem statement (44 HTTP requests for individual images)
- Solution (CSS sprites with Go generator)
- Before/After metrics table
- Implementation details (Go tool, CSS positioning, template integration)
- Benefits list (93% HTTP reduction, single cache invalidation, etc.)
- Browser support
- Testing approach
Follow the existing document pattern (see sections 1-11 for format/style)
</output>
<verification>
REQUIRED TESTS (must all pass):
1. `make sprites` completes without errors
2. Sprite images exist in static/images/sprites/
3. sprite-map.json has correct positions for all icons
4. `make build` passes
5. `make css-prod` compiles successfully
6. E2E test verifies:
- Only 3 sprite images loaded (not 44 individual)
- All logos display correctly
- Sprites work at different zoom levels
- Retina sprites load on high-DPI
**SHOWCASE PAGE (Critical for visual verification):**
Create `static/sprite-showcase.html` - A standalone HTML page that displays:
- All 3 sprite sheets as full images (visible strips)
- Grid of all individual icons extracted via CSS positioning
- Icon index numbers displayed under each icon
- Category headers (Companies, Projects, Courses)
- Zoom test section (100%, 200%, 300%)
- Retina vs 1x comparison
This page serves as:
1. Visual QA during development
2. Documentation for icon positions
3. Test fixture for E2E tests
4. Reference for future icon additions
Example structure:
```html
<!DOCTYPE html>
<html>
<head>
<title>Sprite Showcase</title>
<link rel="stylesheet" href="/static/css/04-interactive/_sprites.css">
</head>
<body>
<h1>CSS Sprite Showcase</h1>
<section>
<h2>Companies (Full Sprite)</h2>
<img src="/static/images/sprites/sprite-companies.png" alt="Companies sprite">
<h3>Individual Icons</h3>
<div class="icon-grid">
<!-- Generated: one div per icon with index label -->
<div class="icon-item">
<span class="icon-sprite icon-company" style="--icon-index: 0;"></span>
<label>0: oldest-company</label>
</div>
<!-- ... repeat for all -->
</div>
</section>
<!-- Repeat for Projects, Courses -->
<section>
<h2>Zoom Test</h2>
<div style="zoom: 1;">100%: <span class="icon-sprite icon-company" style="--icon-index: 0;"></span></div>
<div style="zoom: 2;">200%: <span class="icon-sprite icon-company" style="--icon-index: 0;"></span></div>
<div style="zoom: 3;">300%: <span class="icon-sprite icon-company" style="--icon-index: 0;"></span></div>
</section>
</body>
</html>
```
The Go tool should AUTO-GENERATE this showcase page from sprite-map.json.
Manual verification:
```bash
# Check sprite dimensions (should be width = 48 * icon_count, height = 48)
identify static/images/sprites/sprite-companies.png
# Open showcase page
open http://localhost:1999/static/sprite-showcase.html
# Verify network requests
# Open browser DevTools → Network → filter Images
# Should see: sprite-companies.png, sprite-projects.png, sprite-courses.png
# Should NOT see: individual logo files
```
</verification>
<success_criteria>
- [ ] Go tool processes any-size images automatically
- [ ] 3 sprite sheets generated (1x and 2x each = 6 files total)
- [ ] ICON-REGISTRY or sprite-map.json documents all positions
- [ ] CSS sprites work with zoom up to 300%
- [ ] Retina displays show crisp icons
- [ ] `make sprites` is single command for regeneration
- [ ] doc/XX-SPRITES.md created following project patterns
- [ ] tests/mjs/XX-sprites.test.mjs passes
- [ ] PROJECT-MEMORY.md updated with sprite rules
- [ ] Network requests reduced from 44+ to 3 images
- [ ] **CRITICAL**: doc/2-MODERN-WEB-TECHNIQUES.md updated with Section 12 (CSS Sprites)
- [ ] `static/sprite-showcase.html` auto-generated with all icons visible and labeled
</success_criteria>