337 lines
12 KiB
Markdown
337 lines
12 KiB
Markdown
|
|
<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>
|