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)
12 KiB
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:
- Automated normalization: Users throw ANY size image into source folders → system automatically normalizes to icon size
- Go implementation: Use Go with native/standard libraries (prefer stdlib, avoid heavy dependencies)
- One command:
make spriteshandles everything (normalize + generate + update registry) - Documentation required: Create doc/XX-SPRITES.md following existing doc/ patterns
- Tests required: Create tests/mjs/XX-sprites.test.mjs following existing test patterns
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
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/drawfor 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:
{
"company": "Olympic Broadcasting Services",
"companyLogo": "olympic-broadcasting.png",
"logoIndex": 0
}
⚠️ DO NOT TOUCH - Preserve these existing patterns:
-
Empty logo fields - Some projects have
"projectLogo": ""(no image). Leave as-is, no logoIndex.// KEEP AS-IS - no sprite integration { "projectLogo": "", ... } -
Iconify icons in HTML - Course/project items use inline
<iconify-icon>for individual entries (e.g., Go courses from Udemy). These are in theresponsibilitiesarray, NOT the logo field. Do not modify.// 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
logoIndexfield - nothing else - NO offset in JSON (calculated as
index × 48pxin CSS) - The CV JSON is an INFO document, minimize pollution
The Go tool generates a SEPARATE mapping file (not in CV 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:
.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:
{{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}}
<implementation_flow> PHASE 1: Go Tool Creation
- Research best Go approach for image processing
- Create sprite generator tool
- Implement: scan → normalize → combine → output
- Generate sprite-map.json for reference
PHASE 2: Integration
- Create _sprites.css with positioning classes
- Update main.css to import sprites
- Update JSON files with logoIndex values
- Modify templates to render sprites
PHASE 3: Makefile & Workflow
- Add
make spritestarget - Add
make sprites-cleantarget - Document workflow in doc/XX-SPRITES.md
PHASE 4: Testing & Documentation
- Create tests/mjs/XX-sprites.test.mjs
- Create doc/XX-SPRITES.md
- Update PROJECT-MEMORY.md with sprite system rules </implementation_flow>
Files to MODIFY:
Makefile- Add sprites targetsstatic/css/main.css- Import sprites CSSdata/cv-en.json- Add logoIndex to all entriesdata/cv-es.json- Add logoIndex to all entriestemplates/partials/sections/experience.html- Use sprite renderingtemplates/partials/sections/projects.html- Use sprite renderingtemplates/partials/sections/courses.html- Use sprite renderingPROJECT-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)
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-DPISHOWCASE 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:
- Visual QA during development
- Documentation for icon positions
- Test fixture for E2E tests
- Reference for future icon additions
Example structure:
<!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:
# 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
<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 spritesis 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.htmlauto-generated with all icons visible and labeled </success_criteria>