@hegemonart/get-design-done 1.16.0 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.claude-plugin/marketplace.json +12 -4
  2. package/.claude-plugin/plugin.json +22 -4
  3. package/CHANGELOG.md +111 -0
  4. package/README.md +27 -2
  5. package/agents/design-auditor.md +65 -1
  6. package/agents/design-context-builder.md +6 -1
  7. package/agents/design-doc-writer.md +21 -0
  8. package/agents/design-executor.md +22 -4
  9. package/agents/design-pattern-mapper.md +62 -0
  10. package/agents/design-phase-researcher.md +1 -1
  11. package/agents/motion-mapper.md +74 -9
  12. package/agents/token-mapper.md +8 -0
  13. package/package.json +16 -2
  14. package/reference/components/README.md +27 -23
  15. package/reference/components/alert.md +198 -0
  16. package/reference/components/badge.md +202 -0
  17. package/reference/components/breadcrumbs.md +198 -0
  18. package/reference/components/chip.md +209 -0
  19. package/reference/components/command-palette.md +228 -0
  20. package/reference/components/date-picker.md +227 -0
  21. package/reference/components/file-upload.md +219 -0
  22. package/reference/components/list.md +217 -0
  23. package/reference/components/menu.md +212 -0
  24. package/reference/components/navbar.md +211 -0
  25. package/reference/components/pagination.md +205 -0
  26. package/reference/components/progress.md +210 -0
  27. package/reference/components/rich-text-editor.md +226 -0
  28. package/reference/components/sidebar.md +211 -0
  29. package/reference/components/skeleton.md +197 -0
  30. package/reference/components/slider.md +208 -0
  31. package/reference/components/stepper.md +220 -0
  32. package/reference/components/table.md +229 -0
  33. package/reference/components/toast.md +200 -0
  34. package/reference/components/tree.md +225 -0
  35. package/reference/css-grid-layout.md +835 -0
  36. package/reference/data-visualization.md +333 -0
  37. package/reference/external/NOTICE.hyperframes +28 -0
  38. package/reference/form-patterns.md +245 -0
  39. package/reference/image-optimization.md +582 -0
  40. package/reference/information-architecture.md +255 -0
  41. package/reference/motion-advanced.md +754 -0
  42. package/reference/motion-easings.md +381 -0
  43. package/reference/motion-interpolate.md +282 -0
  44. package/reference/motion-spring.md +234 -0
  45. package/reference/motion-transition-taxonomy.md +155 -0
  46. package/reference/motion.md +20 -0
  47. package/reference/onboarding-progressive-disclosure.md +250 -0
  48. package/reference/output-contracts/motion-map.schema.json +135 -0
  49. package/reference/platforms.md +346 -0
  50. package/reference/registry.json +445 -220
  51. package/reference/registry.schema.json +4 -0
  52. package/reference/rtl-cjk-cultural.md +353 -0
  53. package/reference/user-research.md +360 -0
  54. package/reference/variable-fonts-loading.md +532 -0
  55. package/scripts/lib/easings.cjs +280 -0
  56. package/scripts/lib/parse-contract.cjs +220 -0
  57. package/scripts/lib/spring.cjs +160 -0
  58. package/scripts/tests/test-motion-provenance.sh +64 -0
@@ -0,0 +1,582 @@
1
+ <!-- Source: Phase 18 — get-design-done -->
2
+
3
+ # Image Optimization Reference
4
+
5
+ Practical reference for responsive images, modern formats, loading strategies, and CDN transform patterns. Written for AI agents and developers working on web performance.
6
+
7
+ ---
8
+
9
+ ## 1. Format Choice Matrix
10
+
11
+ ### When to Use Each Format
12
+
13
+ | Format | Browser Support | Compression vs JPEG | Encode Time | Alpha | Animation | Best Use Case |
14
+ |--------|----------------|---------------------|-------------|-------|-----------|---------------|
15
+ | **JPEG** | Universal (100%) | Baseline | Fast | No | No | Legacy fallback, wide compatibility required |
16
+ | **WebP** | 97%+ (all modern) | 25–35% smaller | Fast | Yes | Yes | Default for photos, general web images today |
17
+ | **AVIF** | 90%+ (Chrome 85+, Firefox 93+, Safari 16+) | 40–55% smaller than JPEG | Slow (CPU-heavy) | Yes | Yes | Photos where quality-per-byte is critical; static assets |
18
+ | **JPEG XL** | Limited (Chrome 91–109 behind flag; Safari 17+) | 20–60% smaller | Medium | Yes | No | Future-proofing; not yet viable as primary format |
19
+ | **PNG** | Universal (100%) | Lossless only | Fast | Yes | No | Logos, UI icons needing lossless or transparency |
20
+ | **SVG** | Universal (100%) | Vector = tiny | N/A | Yes | Yes | Icons, logos, illustrations with clean paths |
21
+ | **GIF** | Universal (100%) | Poor | Fast | 1-bit | Yes | Avoid — use WebP or video instead |
22
+
23
+ ### Decision Rules
24
+
25
+ - **Default for photos**: serve AVIF with WebP fallback, JPEG last resort.
26
+ - **Default for UI assets with transparency**: WebP with PNG fallback.
27
+ - **Icons/logos**: SVG always; PNG fallback only for email.
28
+ - **Animated content**: WebP animated or `<video autoplay muted loop>` — never GIF.
29
+ - **JPEG XL**: Do not use as primary format until Safari + Chrome stable support lands.
30
+
31
+ ```html
32
+ <!-- Standard format stack via <picture> -->
33
+ <picture>
34
+ <source srcset="hero.avif" type="image/avif">
35
+ <source srcset="hero.webp" type="image/webp">
36
+ <img src="hero.jpg" alt="Hero image" width="1200" height="630">
37
+ </picture>
38
+ ```
39
+
40
+ ---
41
+
42
+ ## 2. `srcset` and `sizes` Math
43
+
44
+ ### Width Descriptors (w descriptors)
45
+
46
+ Width descriptors tell the browser the intrinsic pixel width of each candidate. The browser picks the best one based on the `sizes` attribute and device pixel ratio.
47
+
48
+ ```html
49
+ <img
50
+ srcset="
51
+ image-480w.jpg 480w,
52
+ image-960w.jpg 960w,
53
+ image-1440w.jpg 1440w,
54
+ image-1920w.jpg 1920w
55
+ "
56
+ sizes="
57
+ (max-width: 480px) 480px,
58
+ (max-width: 960px) 960px,
59
+ (max-width: 1440px) 1440px,
60
+ 1920px
61
+ "
62
+ src="image-1920w.jpg"
63
+ alt="Responsive photo"
64
+ width="1920"
65
+ height="1080"
66
+ >
67
+ ```
68
+
69
+ ### How to Calculate Descriptor Values
70
+
71
+ 1. **Identify breakpoints** where layout changes (e.g., 480, 768, 960, 1280, 1920).
72
+ 2. **For each breakpoint**, determine the rendered CSS width of the image.
73
+ 3. **Multiply by the highest expected DPR** (typically 2x) to get the source pixel width.
74
+ 4. **Round to the nearest common size** and generate that asset.
75
+
76
+ Example: image renders at 480px on mobile at 2x DPR → need a 960px source → label it `960w`.
77
+
78
+ ### Sizes Attribute Math
79
+
80
+ `sizes` is evaluated left-to-right; first matching condition wins. Express as CSS `calc()` when the image is a fraction of the viewport:
81
+
82
+ ```html
83
+ <!-- Image is full-width on mobile, 50vw on tablet, 33vw on desktop -->
84
+ <img
85
+ srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w, photo-1600.jpg 1600w"
86
+ sizes="(max-width: 600px) 100vw, (max-width: 1024px) 50vw, 33vw"
87
+ src="photo-1200.jpg"
88
+ alt="Gallery photo"
89
+ >
90
+ ```
91
+
92
+ Breakpoint widths to always generate sources for:
93
+ - `480w` — small mobile
94
+ - `768w` — large mobile / portrait tablet
95
+ - `960w` — landscape tablet / small desktop
96
+ - `1280w` — standard desktop
97
+ - `1920w` — full HD desktop
98
+ - `2560w` — retina desktop / 2x of 1280
99
+
100
+ ### Density Descriptors (x descriptors)
101
+
102
+ Use `x` descriptors only when the image always renders at a fixed CSS size (e.g., a fixed-width avatar or logo). Do not mix `w` and `x` in the same `srcset`.
103
+
104
+ ```html
105
+ <!-- Fixed 80×80px avatar — use density descriptors -->
106
+ <img
107
+ srcset="avatar.jpg 1x, avatar@2x.jpg 2x, avatar@3x.jpg 3x"
108
+ src="avatar.jpg"
109
+ alt="User avatar"
110
+ width="80"
111
+ height="80"
112
+ >
113
+ ```
114
+
115
+ **Rule of thumb**: use `w` descriptors + `sizes` for fluid/responsive images; use `x` descriptors for fixed-size images.
116
+
117
+ ---
118
+
119
+ ## 3. Responsive Art Direction
120
+
121
+ Art direction changes the image content (crop, composition, subject) at different viewport sizes — not just resolution. Use `<picture>` with `media` attributes.
122
+
123
+ ### `<picture>` + `<source>` Pattern
124
+
125
+ ```html
126
+ <picture>
127
+ <!-- Portrait crop for mobile -->
128
+ <source
129
+ media="(max-width: 600px)"
130
+ srcset="hero-portrait-600.webp 600w, hero-portrait-1200.webp 1200w"
131
+ sizes="100vw"
132
+ type="image/webp"
133
+ >
134
+ <source
135
+ media="(max-width: 600px)"
136
+ srcset="hero-portrait-600.jpg 600w, hero-portrait-1200.jpg 1200w"
137
+ sizes="100vw"
138
+ >
139
+
140
+ <!-- Landscape crop for desktop -->
141
+ <source
142
+ media="(min-width: 601px)"
143
+ srcset="hero-landscape-1200.webp 1200w, hero-landscape-1920.webp 1920w"
144
+ sizes="100vw"
145
+ type="image/webp"
146
+ >
147
+ <source
148
+ media="(min-width: 601px)"
149
+ srcset="hero-landscape-1200.jpg 1200w, hero-landscape-1920.jpg 1920w"
150
+ sizes="100vw"
151
+ >
152
+
153
+ <!-- Fallback <img> — always required, used when no <source> matches -->
154
+ <img
155
+ src="hero-landscape-1200.jpg"
156
+ alt="Team working in studio"
157
+ width="1200"
158
+ height="630"
159
+ >
160
+ </picture>
161
+ ```
162
+
163
+ ### Source Order Rules
164
+
165
+ 1. `<source>` elements are evaluated top-to-bottom; first match wins.
166
+ 2. Place mobile/narrow sources first when using `max-width` media queries.
167
+ 3. Always end with a plain `<img>` — it is the fallback AND the element browsers use for accessibility attributes.
168
+ 4. The `type` attribute on `<source>` enables format negotiation without JS.
169
+
170
+ ### Combining Art Direction + Format Negotiation
171
+
172
+ ```html
173
+ <picture>
174
+ <!-- Mobile: square crop, AVIF -->
175
+ <source media="(max-width: 480px)" srcset="product-sq.avif" type="image/avif">
176
+ <source media="(max-width: 480px)" srcset="product-sq.webp" type="image/webp">
177
+ <source media="(max-width: 480px)" srcset="product-sq.jpg">
178
+
179
+ <!-- Desktop: wide crop, AVIF -->
180
+ <source srcset="product-wide.avif" type="image/avif">
181
+ <source srcset="product-wide.webp" type="image/webp">
182
+
183
+ <img src="product-wide.jpg" alt="Product on white background" width="1200" height="600">
184
+ </picture>
185
+ ```
186
+
187
+ ---
188
+
189
+ ## 4. Placeholder Strategies (LQIP / BlurHash / ThumbHash)
190
+
191
+ Placeholders prevent layout shift and give perceived performance improvement during image load.
192
+
193
+ ### LQIP (Low-Quality Image Placeholder)
194
+
195
+ A tiny JPEG (typically 20–40px wide) embedded as a base64 data URI, displayed blurred via CSS until the full image loads.
196
+
197
+ **Implementation:**
198
+
199
+ ```html
200
+ <div class="img-wrapper">
201
+ <img
202
+ class="lqip"
203
+ src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBD..."
204
+ aria-hidden="true"
205
+ alt=""
206
+ >
207
+ <img
208
+ class="full"
209
+ src="photo.jpg"
210
+ srcset="photo-480.jpg 480w, photo-960.jpg 960w"
211
+ sizes="(max-width: 600px) 100vw, 50vw"
212
+ alt="Studio workspace"
213
+ loading="lazy"
214
+ onload="this.previousElementSibling.style.opacity=0"
215
+ >
216
+ </div>
217
+ ```
218
+
219
+ ```css
220
+ .img-wrapper { position: relative; overflow: hidden; }
221
+ .lqip { position: absolute; inset: 0; width: 100%; height: 100%;
222
+ object-fit: cover; filter: blur(20px); transition: opacity 0.3s; }
223
+ .full { display: block; width: 100%; }
224
+ ```
225
+
226
+ **Trade-offs**: Simple, no JS library needed. Base64 adds ~33% size overhead. LQIP JPEG should be under 300 bytes (20–40px wide, quality 20).
227
+
228
+ ### BlurHash
229
+
230
+ A compact string (20–30 chars) encoding a blurred preview. Decoded client-side via the `blurhash` library into a canvas or CSS gradient.
231
+
232
+ **Implementation:**
233
+
234
+ ```js
235
+ import { decode } from 'blurhash';
236
+
237
+ function renderBlurHash(hash, width = 32, height = 32) {
238
+ const pixels = decode(hash, width, height);
239
+ const canvas = document.createElement('canvas');
240
+ canvas.width = width;
241
+ canvas.height = height;
242
+ const ctx = canvas.getContext('2d');
243
+ const imageData = ctx.createImageData(width, height);
244
+ imageData.data.set(pixels);
245
+ ctx.putImageData(imageData, 0, 0);
246
+ return canvas.toDataURL();
247
+ }
248
+ ```
249
+
250
+ ```html
251
+ <!-- Store hash in data attribute; render via JS -->
252
+ <img
253
+ data-blurhash="LEHV6nWB2yk8pyo0adR*.7kCMdnj"
254
+ src="photo.jpg"
255
+ alt="Photo"
256
+ loading="lazy"
257
+ >
258
+ ```
259
+
260
+ **Trade-offs**: Smaller than LQIP (string vs base64 blob). Requires JS to decode. Good for CMS-driven images where hashes are stored in metadata. Generate server-side with `sharp` + `blurhash` npm package.
261
+
262
+ ### ThumbHash
263
+
264
+ Newer algorithm (2023). Preserves aspect ratio in the hash, better color accuracy for fine details and alpha channels. Drop-in replacement for BlurHash.
265
+
266
+ ```js
267
+ import { thumbHashToDataURL } from 'thumbhash';
268
+
269
+ // hash is a Uint8Array stored alongside image metadata
270
+ const placeholderURL = thumbHashToDataURL(hash);
271
+ img.src = placeholderURL; // set before lazy-load fires
272
+ ```
273
+
274
+ **Trade-offs**: Slightly larger hash (~28 bytes binary vs BlurHash string) but visually superior. Choose ThumbHash for new projects; BlurHash for existing integrations.
275
+
276
+ ### Comparison Table
277
+
278
+ | Strategy | Size overhead | JS required | Alpha support | Aspect ratio encoded | Best for |
279
+ |----------|---------------|-------------|---------------|----------------------|----------|
280
+ | LQIP | ~200–400 bytes base64 | No | No | No | Simple static sites |
281
+ | BlurHash | ~20–30 char string | Yes | No | No | CMS/dynamic images |
282
+ | ThumbHash | ~28 bytes binary | Yes | Yes | Yes | New projects, transparent images |
283
+
284
+ ---
285
+
286
+ ## 5. Lazy Loading
287
+
288
+ ### `loading="lazy"` Attribute
289
+
290
+ Native browser lazy loading. Defers fetching images outside the viewport.
291
+
292
+ ```html
293
+ <!-- Apply to all non-critical images -->
294
+ <img src="photo.jpg" alt="Gallery item" loading="lazy" width="800" height="600">
295
+ ```
296
+
297
+ **Critical rule**: Never apply `loading="lazy"` to the LCP (Largest Contentful Paint) image. The LCP image must be fetched immediately. Identify it as the largest above-the-fold image — hero, product shot, or article lead image.
298
+
299
+ ### Intersection Observer Pattern
300
+
301
+ For fine-grained control or browsers that need older support:
302
+
303
+ ```js
304
+ const lazyImages = document.querySelectorAll('img[data-src]');
305
+
306
+ const observer = new IntersectionObserver((entries, obs) => {
307
+ entries.forEach(entry => {
308
+ if (!entry.isIntersecting) return;
309
+ const img = entry.target;
310
+ img.src = img.dataset.src;
311
+ if (img.dataset.srcset) img.srcset = img.dataset.srcset;
312
+ img.removeAttribute('data-src');
313
+ img.removeAttribute('data-srcset');
314
+ obs.unobserve(img);
315
+ });
316
+ }, {
317
+ rootMargin: '200px 0px', // Start loading 200px before entering viewport
318
+ threshold: 0
319
+ });
320
+
321
+ lazyImages.forEach(img => observer.observe(img));
322
+ ```
323
+
324
+ ```html
325
+ <!-- Use data-src / data-srcset to prevent immediate fetch -->
326
+ <img
327
+ data-src="photo.jpg"
328
+ data-srcset="photo-480.jpg 480w, photo-960.jpg 960w"
329
+ sizes="(max-width: 600px) 100vw, 50vw"
330
+ alt="Gallery photo"
331
+ width="960"
332
+ height="640"
333
+ >
334
+ ```
335
+
336
+ ### Lazy Loading Rules Summary
337
+
338
+ - `loading="lazy"` on all below-the-fold images.
339
+ - Never on LCP image — use `fetchpriority="high"` instead.
340
+ - Set explicit `width` and `height` on every `<img>` to prevent layout shift (CLS).
341
+ - `rootMargin: '200px'` is a safe default for IntersectionObserver to preload slightly early.
342
+
343
+ ---
344
+
345
+ ## 6. `decoding="async"`
346
+
347
+ ### What It Does
348
+
349
+ `decoding="async"` tells the browser it can decode the image off the main thread, allowing other rendering work to proceed in parallel. Without it, browsers may block rendering while decoding a large image.
350
+
351
+ ```html
352
+ <img src="photo.jpg" alt="Decoded async" decoding="async" width="1200" height="800">
353
+ ```
354
+
355
+ ### When to Apply
356
+
357
+ - Apply to all large images that are not LCP-critical.
358
+ - **Do not** apply to the LCP image — synchronous decoding ensures the image renders as soon as it loads, which improves LCP score.
359
+ - Combine with `loading="lazy"` on below-the-fold images.
360
+
361
+ ```html
362
+ <!-- LCP image: no async decoding, no lazy loading -->
363
+ <img
364
+ src="hero.jpg"
365
+ alt="Hero"
366
+ fetchpriority="high"
367
+ width="1920"
368
+ height="1080"
369
+ >
370
+
371
+ <!-- Below-fold images: async decoding + lazy loading -->
372
+ <img
373
+ src="card.jpg"
374
+ alt="Card"
375
+ decoding="async"
376
+ loading="lazy"
377
+ width="400"
378
+ height="300"
379
+ >
380
+ ```
381
+
382
+ ---
383
+
384
+ ## 7. `fetchpriority="high"`
385
+
386
+ ### What It Does
387
+
388
+ `fetchpriority="high"` elevates the image's network priority in the browser's resource scheduler. Applied to the LCP image, it reduces the time the browser spends waiting for it, directly improving LCP Core Web Vital.
389
+
390
+ ```html
391
+ <!-- Boost the LCP image -->
392
+ <img
393
+ src="hero.jpg"
394
+ srcset="hero-960.jpg 960w, hero-1920.jpg 1920w"
395
+ sizes="100vw"
396
+ alt="Hero"
397
+ fetchpriority="high"
398
+ width="1920"
399
+ height="1080"
400
+ >
401
+ ```
402
+
403
+ ### Rules
404
+
405
+ - Apply to **exactly one** image per page — the LCP candidate.
406
+ - Do not use on multiple images; it degrades priority scheduling.
407
+ - Use `fetchpriority="low"` on decorative or off-screen images you know will not be needed soon.
408
+ - Works on `<img>`, `<link rel="preload">`, and `fetch()` calls.
409
+
410
+ ```html
411
+ <!-- Preload LCP image in <head> for maximum early fetch -->
412
+ <link
413
+ rel="preload"
414
+ as="image"
415
+ href="hero.jpg"
416
+ imagesrcset="hero-960.jpg 960w, hero-1920.jpg 1920w"
417
+ imagesizes="100vw"
418
+ fetchpriority="high"
419
+ >
420
+ ```
421
+
422
+ ---
423
+
424
+ ## 8. CDN Transform Patterns
425
+
426
+ ### Cloudinary
427
+
428
+ Uses URL-based transformation parameters. `f_auto` selects the best format; `q_auto` selects optimal quality.
429
+
430
+ ```html
431
+ <!-- Base URL structure: https://res.cloudinary.com/{cloud_name}/image/upload/{transformations}/{public_id} -->
432
+
433
+ <!-- Auto format + auto quality -->
434
+ <img
435
+ src="https://res.cloudinary.com/demo/image/upload/f_auto,q_auto/sample.jpg"
436
+ alt="Auto-optimized"
437
+ >
438
+
439
+ <!-- Responsive srcset with Cloudinary transforms -->
440
+ <img
441
+ srcset="
442
+ https://res.cloudinary.com/demo/image/upload/f_auto,q_auto,w_480/sample.jpg 480w,
443
+ https://res.cloudinary.com/demo/image/upload/f_auto,q_auto,w_960/sample.jpg 960w,
444
+ https://res.cloudinary.com/demo/image/upload/f_auto,q_auto,w_1920/sample.jpg 1920w
445
+ "
446
+ sizes="(max-width: 600px) 100vw, 50vw"
447
+ src="https://res.cloudinary.com/demo/image/upload/f_auto,q_auto,w_1200/sample.jpg"
448
+ alt="Cloudinary responsive"
449
+ >
450
+ ```
451
+
452
+ Key Cloudinary params: `f_auto` (format), `q_auto` (quality), `w_N` (width), `h_N` (height), `c_fill` (crop mode), `g_auto` (smart gravity/focus).
453
+
454
+ ### imgix
455
+
456
+ Similar URL-based API. `auto=format` selects best format; `auto=compress` applies smart compression.
457
+
458
+ ```html
459
+ <!-- Auto format + compress -->
460
+ <img
461
+ src="https://yoursite.imgix.net/photo.jpg?auto=format,compress"
462
+ alt="imgix optimized"
463
+ >
464
+
465
+ <!-- Responsive srcset with imgix -->
466
+ <img
467
+ srcset="
468
+ https://yoursite.imgix.net/photo.jpg?auto=format,compress&w=480 480w,
469
+ https://yoursite.imgix.net/photo.jpg?auto=format,compress&w=960 960w,
470
+ https://yoursite.imgix.net/photo.jpg?auto=format,compress&w=1920 1920w
471
+ "
472
+ sizes="(max-width: 600px) 100vw, 50vw"
473
+ src="https://yoursite.imgix.net/photo.jpg?auto=format,compress&w=1200"
474
+ alt="imgix responsive"
475
+ >
476
+ ```
477
+
478
+ Key imgix params: `auto=format` (WebP/AVIF negotiation), `auto=compress`, `w`, `h`, `fit=crop`, `fp-x`/`fp-y` (focal point).
479
+
480
+ ### Vercel / Next.js Image (`next/image`)
481
+
482
+ The `<Image>` component handles `srcset`, lazy loading, format negotiation, and the `/_next/image` optimization endpoint automatically.
483
+
484
+ ```jsx
485
+ import Image from 'next/image';
486
+
487
+ // Responsive image — fill container
488
+ <div style={{ position: 'relative', width: '100%', aspectRatio: '16/9' }}>
489
+ <Image
490
+ src="/photos/hero.jpg"
491
+ alt="Hero"
492
+ fill
493
+ sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 33vw"
494
+ priority // equivalent to fetchpriority="high" — use for LCP image
495
+ quality={85} // default is 75; 80–90 for hero images
496
+ />
497
+ </div>
498
+
499
+ // Fixed-size image
500
+ <Image
501
+ src="/avatars/user.jpg"
502
+ alt="User avatar"
503
+ width={80}
504
+ height={80}
505
+ quality={80}
506
+ />
507
+ ```
508
+
509
+ Next.js generates transform URLs like: `/_next/image?url=%2Fphotos%2Fhero.jpg&w=1920&q=85`
510
+
511
+ ### CDN Comparison
512
+
513
+ | Feature | Cloudinary | imgix | Vercel (next/image) |
514
+ |---------|-----------|-------|---------------------|
515
+ | Auto format (WebP/AVIF) | `f_auto` | `auto=format` | Automatic |
516
+ | Auto quality | `q_auto` | `auto=compress` | `quality` prop (default 75) |
517
+ | Art direction / crop | `c_fill`, `g_auto` | `fit=crop`, `fp-x` | `object-fit` via CSS |
518
+ | Responsive srcset | Manual URL params | Manual URL params | Automatic via `sizes` |
519
+ | Setup required | Cloudinary account | imgix source | Next.js project |
520
+
521
+ ---
522
+
523
+ ## 9. Image Budget Enforcement
524
+
525
+ ### Maximum File Sizes by Image Type
526
+
527
+ | Image Type | Max Compressed Size | Max Dimensions | Notes |
528
+ |------------|--------------------|--------------------|-------|
529
+ | Hero / full-width banner | 200 KB | 1920 × 1080px | WebP/AVIF; 1x size, rely on srcset for 2x |
530
+ | Article / blog lead image | 100 KB | 1200 × 630px | Open Graph dimensions; dual purpose |
531
+ | Card / thumbnail | 40 KB | 600 × 400px | At displayed size, not source size |
532
+ | Avatar / profile photo | 15 KB | 200 × 200px | Square; serve WebP |
533
+ | Product image | 150 KB | 1000 × 1000px | White background; support zoom |
534
+ | Icon (raster fallback) | 5 KB | 64 × 64px | Prefer SVG; PNG only for emoji/photo icons |
535
+ | LQIP placeholder | 0.5 KB | 20–40px wide | Base64-embedded; blur via CSS |
536
+ | Logo (raster) | 20 KB | 400 × 200px | SVG strongly preferred |
537
+
538
+ ### Tooling
539
+
540
+ **Build-time optimization:**
541
+
542
+ ```bash
543
+ # sharp (Node.js) — resize + convert
544
+ npx sharp-cli --input "src/**/*.{jpg,png}" --output dist/images \
545
+ --format webp --quality 82 --width 1920
546
+
547
+ # squoosh-cli — AVIF + WebP batch encoding
548
+ npx @squoosh/cli --avif '{"quality":60}' --webp '{"quality":82}' src/*.jpg
549
+
550
+ # imagemin — plugin-based pipeline
551
+ npx imagemin src/images/* --out-dir=dist/images \
552
+ --plugin=imagemin-webp --plugin=imagemin-mozjpeg
553
+ ```
554
+
555
+ **CI enforcement (example GitHub Actions step):**
556
+
557
+ ```yaml
558
+ - name: Check image sizes
559
+ run: |
560
+ find dist/images -name "*.jpg" -size +200k -print | while read f; do
561
+ echo "FAIL: $f exceeds 200KB hero budget"
562
+ exit 1
563
+ done
564
+ ```
565
+
566
+ **Audit tools:**
567
+
568
+ - Lighthouse `uses-optimized-images` and `uses-webp-images` audits
569
+ - `next/image` built-in warnings for missing `width`/`height`
570
+ - `imagesize` npm package for build-time dimension checks
571
+ - Squoosh web app for manual before/after quality comparison
572
+
573
+ ### Enforcement Checklist
574
+
575
+ - [ ] All hero images: AVIF + WebP + JPEG stack via `<picture>`
576
+ - [ ] Every `<img>` has explicit `width` and `height` attributes (prevents CLS)
577
+ - [ ] LCP image: `fetchpriority="high"`, no `loading="lazy"`, preload in `<head>`
578
+ - [ ] All below-fold images: `loading="lazy"` + `decoding="async"`
579
+ - [ ] No image exceeds budget for its type (see table above)
580
+ - [ ] CDN delivers `Content-Type: image/avif` or `image/webp` where supported
581
+ - [ ] Placeholders (LQIP or hash) on images with visible load time
582
+ - [ ] `sizes` attribute present on all images using `w` descriptors in `srcset`