Files
juanatsap b5a50ca3ef feat: implement CSS sprite system for image optimization
Reduces HTTP requests from 44+ individual images to 3 sprite sheets
(~93% reduction). Includes Go sprite generator tool, CSS classes,
template integration, and E2E tests.

- Add cmd/sprites/main.go for sprite generation (60x60px + 120x120px @2x)
- Add _sprites.css with responsive sizing and retina support
- Update templates to use sprites with logoIndex fallback
- Add Makefile targets: sprites, sprites-clean
- Add 9-test E2E suite for sprite functionality
- Add doc/22-SPRITES.md with usage documentation
2025-12-04 11:38:36 +00:00

5.6 KiB

CSS Sprites - Image Request Optimization

Overview

The CV website uses CSS sprites to dramatically reduce HTTP requests for company, project, and course logos. Instead of loading 44+ individual image files, we load only 3 sprite sheets (6 files total including retina versions).

Performance Impact

Metric Before After Improvement
Image Requests 44+ 3-6 ~93% reduction
Cache Invalidation Per image Per sprite Simplified
HTTP Overhead 44 round-trips 3-6 round-trips Dramatic reduction

Architecture

File Structure

static/
├── images/
│   ├── companies/          # Source images (any size)
│   ├── projects/           # Source images (any size)
│   ├── courses/            # Source images (any size)
│   └── sprites/            # Generated sprites
│       ├── sprite-companies.png
│       ├── sprite-companies@2x.png
│       ├── sprite-projects.png
│       ├── sprite-projects@2x.png
│       ├── sprite-courses.png
│       ├── sprite-courses@2x.png
│       └── sprite-map.json
├── sprite-showcase.html    # Visual QA page
└── css/
    └── 04-interactive/
        └── _sprites.css    # Sprite CSS classes

Go Sprite Generator Tool

Located at cmd/sprites/main.go, this tool:

  1. Scans source directories for PNG images
  2. Normalizes images to standard sizes (60x60px for 1x, 120x120px for 2x)
  3. Maintains aspect ratio and centers on transparent background
  4. Combines into horizontal strips for each category
  5. Generates sprite-map.json for documentation
  6. Creates sprite-showcase.html for visual QA

Image Size Standards

  • Base size: 60x60px (optimal for 80px display box with 10px padding)
  • Retina size: 120x120px (@2x for high-DPI displays)
  • Section display: 80x80px box (60px icon + 10px padding each side)

Usage

Makefile Targets

# Generate sprites from source images
make sprites

# Clean generated sprite files
make sprites-clean

JSON Data Structure

Add logoIndex to entries in cv-en.json and cv-es.json:

{
  "company": "Olympic Broadcasting Services",
  "companyLogo": "olympic-broadcasting.png",
  "logoIndex": 15
}

Important: Only add logoIndex when there's an actual PNG file. Entries without a logo file should not have logoIndex.

Template Integration

Templates automatically use sprites when logoIndex is present:

{{if .LogoIndex}}
<span class="icon-sprite icon-section 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">
{{else}}
<iconify-icon icon="mdi:office-building" width="80" height="80"></iconify-icon>
{{end}}

CSS Classes

/* Base sprite class */
.icon-sprite {
  display: inline-block;
  width: 50px;
  height: 50px;
  background-repeat: no-repeat;
  background-size: auto 50px;
}

/* Category-specific classes */
.icon-company { background-image: url('/static/images/sprites/sprite-companies.png'); }
.icon-project { background-image: url('/static/images/sprites/sprite-projects.png'); }
.icon-course { background-image: url('/static/images/sprites/sprite-courses.png'); }

/* Size variants */
.icon-sprite.icon-section {
  width: 80px;
  height: 80px;
  padding: 10px;
  background-size: auto 60px;
  background-origin: content-box;
  background-clip: content-box;
}
.icon-sprite.icon-small { width: 32px; height: 32px; }
.icon-sprite.icon-large { width: 64px; height: 64px; }

Adding New Icons

  1. Drop source image into appropriate directory:

    • static/images/companies/ for company logos
    • static/images/projects/ for project logos
    • static/images/courses/ for course logos
  2. Run sprite generation:

    make sprites
    
  3. Update JSON files with new logoIndex based on sprite-map.json

  4. Verify in showcase page at /static/sprite-showcase.html

Retina Display Support

The CSS automatically loads @2x sprites on retina displays:

@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 60px; /* Display at 1x size */
  }
}

Sprite Map JSON

The sprite-map.json file documents icon positions:

{
  "companies": [
    {"index": 0, "name": "accenture.png"},
    {"index": 1, "name": "aena-long.png"},
    ...
  ],
  "projects": [...],
  "courses": [...]
}

This file is for documentation/debugging only - CSS calculates offset from index using calc(var(--icon-index) * -60px).

Verification

Showcase Page

Visit /static/sprite-showcase.html to:

  • View full sprite sheets
  • See all individual icons with index labels
  • Test zoom levels (100%, 200%, 300%)
  • Verify retina rendering

Network Verification

In browser DevTools (Network tab, filter Images):

  • Should see: sprite-companies.png, sprite-projects.png, sprite-courses.png
  • Should NOT see: individual logo files (unless fallback triggers)

Troubleshooting

Invalid PNG Warning

If you see "png: invalid format: not a PNG file", the source file is not a valid PNG. Check the file with file <filename> to verify format.

Icon Not Displaying

  1. Verify logoIndex is present in JSON
  2. Check sprite-map.json for correct index
  3. Verify CSS is loaded
  4. Check browser console for errors

Wrong Icon Displayed

Verify the logoIndex value matches the icon's position in sprite-map.json (0-indexed).