Files
cv-site/prompts/done/002-sprite-generation-system.md
T
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

12 KiB
Raw Blame History

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
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
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 ## 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:

{
  "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.

    // 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.

    // 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):

// 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

  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>
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)

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:

<!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 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>