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
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
/* ============================================================================
|
||||
CSS SPRITES - Image Request Optimization
|
||||
============================================================================
|
||||
Reduces HTTP requests from 44+ individual images to just 3 sprite sheets.
|
||||
Each sprite uses CSS custom property --icon-index for positioning.
|
||||
============================================================================ */
|
||||
|
||||
/* Base sprite class */
|
||||
.icon-sprite {
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: auto 50px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Company icons */
|
||||
.icon-company {
|
||||
background-image: url('/static/images/sprites/sprite-companies.png');
|
||||
background-position-x: calc(var(--icon-index, 0) * -50px);
|
||||
}
|
||||
|
||||
/* Project icons */
|
||||
.icon-project {
|
||||
background-image: url('/static/images/sprites/sprite-projects.png');
|
||||
background-position-x: calc(var(--icon-index, 0) * -50px);
|
||||
}
|
||||
|
||||
/* Course icons */
|
||||
.icon-course {
|
||||
background-image: url('/static/images/sprites/sprite-courses.png');
|
||||
background-position-x: calc(var(--icon-index, 0) * -50px);
|
||||
}
|
||||
|
||||
/* Retina displays - use @2x sprites for crisp rendering */
|
||||
@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 50px; /* Display at 1x size */
|
||||
}
|
||||
|
||||
.icon-project {
|
||||
background-image: url('/static/images/sprites/sprite-projects@2x.png');
|
||||
background-size: auto 50px;
|
||||
}
|
||||
|
||||
.icon-course {
|
||||
background-image: url('/static/images/sprites/sprite-courses@2x.png');
|
||||
background-size: auto 50px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Size variants for different contexts */
|
||||
.icon-sprite.icon-small {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-size: auto 32px;
|
||||
}
|
||||
|
||||
.icon-sprite.icon-small.icon-company {
|
||||
background-position-x: calc(var(--icon-index, 0) * -32px);
|
||||
}
|
||||
|
||||
.icon-sprite.icon-small.icon-project {
|
||||
background-position-x: calc(var(--icon-index, 0) * -32px);
|
||||
}
|
||||
|
||||
.icon-sprite.icon-small.icon-course {
|
||||
background-position-x: calc(var(--icon-index, 0) * -32px);
|
||||
}
|
||||
|
||||
.icon-sprite.icon-large {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-size: auto 64px;
|
||||
}
|
||||
|
||||
.icon-sprite.icon-large.icon-company {
|
||||
background-position-x: calc(var(--icon-index, 0) * -64px);
|
||||
}
|
||||
|
||||
.icon-sprite.icon-large.icon-project {
|
||||
background-position-x: calc(var(--icon-index, 0) * -64px);
|
||||
}
|
||||
|
||||
.icon-sprite.icon-large.icon-course {
|
||||
background-position-x: calc(var(--icon-index, 0) * -64px);
|
||||
}
|
||||
|
||||
/* For section logos - match .company-logo img styling */
|
||||
/* Use a wrapper approach: 80px box, 60px icon centered inside */
|
||||
.icon-sprite.icon-section {
|
||||
/* Outer box matches img styling */
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--icon-border, #ddd);
|
||||
background-color: transparent;
|
||||
box-sizing: border-box;
|
||||
/* Inner content area for sprite */
|
||||
padding: 10px;
|
||||
background-size: auto 60px;
|
||||
background-origin: content-box;
|
||||
background-clip: content-box;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
.icon-sprite.icon-section.icon-company {
|
||||
background-position-x: calc(var(--icon-index, 0) * -60px);
|
||||
}
|
||||
|
||||
.icon-sprite.icon-section.icon-project {
|
||||
background-position-x: calc(var(--icon-index, 0) * -60px);
|
||||
}
|
||||
|
||||
.icon-sprite.icon-section.icon-course {
|
||||
background-position-x: calc(var(--icon-index, 0) * -60px);
|
||||
}
|
||||
|
||||
/* Retina overrides for size variants */
|
||||
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
|
||||
.icon-sprite.icon-small {
|
||||
background-size: auto 32px;
|
||||
}
|
||||
|
||||
.icon-sprite.icon-small.icon-company {
|
||||
background-position-x: calc(var(--icon-index, 0) * -32px);
|
||||
}
|
||||
|
||||
.icon-sprite.icon-small.icon-project {
|
||||
background-position-x: calc(var(--icon-index, 0) * -32px);
|
||||
}
|
||||
|
||||
.icon-sprite.icon-small.icon-course {
|
||||
background-position-x: calc(var(--icon-index, 0) * -32px);
|
||||
}
|
||||
|
||||
.icon-sprite.icon-large {
|
||||
background-size: auto 64px;
|
||||
}
|
||||
|
||||
.icon-sprite.icon-large.icon-company {
|
||||
background-position-x: calc(var(--icon-index, 0) * -64px);
|
||||
}
|
||||
|
||||
.icon-sprite.icon-large.icon-project {
|
||||
background-position-x: calc(var(--icon-index, 0) * -64px);
|
||||
}
|
||||
|
||||
.icon-sprite.icon-large.icon-course {
|
||||
background-position-x: calc(var(--icon-index, 0) * -64px);
|
||||
}
|
||||
|
||||
.icon-sprite.icon-section {
|
||||
padding: 10px;
|
||||
background-size: auto 60px;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
.icon-sprite.icon-section.icon-company {
|
||||
background-size: auto 60px;
|
||||
background-position-x: calc(var(--icon-index, 0) * -60px);
|
||||
}
|
||||
|
||||
.icon-sprite.icon-section.icon-project {
|
||||
background-size: auto 60px;
|
||||
background-position-x: calc(var(--icon-index, 0) * -60px);
|
||||
}
|
||||
|
||||
.icon-sprite.icon-section.icon-course {
|
||||
background-size: auto 60px;
|
||||
background-position-x: calc(var(--icon-index, 0) * -60px);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user