thumbnails are encoded as thumbnails

Quality 82 went on every rung, so a picture 173 CSS pixels wide in a gallery grid carried the same
fidelity as the copy somebody opens full screen. Compression artefacts scale with display size: at
thumbnail size 74 is indistinguishable from 82 and 15% smaller — 108,388 bytes to 92,568 on the worst
image in this gallery, measured rather than assumed.

640 is the line because it is the widest rung a phone picks for a grid slot. Above it something is
showing the picture large, and 82 is the right answer again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-30 00:01:57 +01:00
co-authored by Claude Opus 5
parent f98f8f0771
commit f3dda78ed0
+21 -2
View File
@@ -62,8 +62,27 @@ import { mediaPath } from './media';
*/ */
export const LADDER = [320, 480, 640, 960, 1280, 1920, 2560] as const; export const LADDER = [320, 480, 640, 960, 1280, 1920, 2560] as const;
/** WebP quality. 82 is the usual knee: visually clean on photographs, well off the top of the curve. */ /**
* WebP quality, by rung.
*
* 82 is the usual knee for a photograph being LOOKED at — the lightbox, a detail page, anything at or
* near its real size. It was applied to every rung, which meant a thumbnail 173 CSS pixels wide in a
* gallery grid carried the same fidelity as the copy someone opens full screen.
*
* Artefacts scale with display size. At thumbnail size 74 is indistinguishable from 82 and 15% smaller,
* measured on the worst image in this gallery: 108,388 bytes to 92,568. Nine of those on a page is
* most of what an audit calls "increasing the compression factor".
*
* 640 is the line because it is the widest rung a phone ever picks for a grid slot. Anything above it
* is being chosen because something is showing the picture large.
*/
const WEBP_QUALITY_SMALL = 74;
const WEBP_QUALITY = 82; const WEBP_QUALITY = 82;
const THUMB_MAX_WIDTH = 640;
/** The quality a rung is worth. `null` is `full.webp`, which is the one people open. */
const qualityFor = (rung: number | null): number =>
rung !== null && rung <= THUMB_MAX_WIDTH ? WEBP_QUALITY_SMALL : WEBP_QUALITY;
/** /**
* The placeholder: a picture small enough to travel INSIDE the manifest. * The placeholder: a picture small enough to travel INSIDE the manifest.
@@ -454,7 +473,7 @@ async function encodeWebp(
{ rotate: {} }, { rotate: {} },
...(rung === null ? [] : [{ resize: { width: rung, fit: 'inside', withoutEnlargement: true } }]), ...(rung === null ? [] : [{ resize: { width: rung, fit: 'inside', withoutEnlargement: true } }]),
], ],
output: { format: 'webp', options: { quality: WEBP_QUALITY } }, output: { format: 'webp', options: { quality: qualityFor(rung) } },
}); });
const width = rung ?? sourceWidth; const width = rung ?? sourceWidth;