// Generate WebP variants of source PNGs in public/product/. // Run after replacing/adding a source PNG; outputs are committed. // // For each .png we emit: // -1280.webp (q=82, used as inline srcset for desktop ≤ ~1280 px) // -1920.webp (q=80, used as inline srcset for retina/wide viewports // and as the lightbox-modal full-size source) // // The original .png is kept as a fallback for the rare browser // without WebP support (~2 % globally). import { readdir, stat } from 'node:fs/promises'; import { join, parse } from 'node:path'; import { fileURLToPath } from 'node:url'; import sharp from 'sharp'; const SRC_DIR = fileURLToPath(new URL('../public/product/', import.meta.url)); const VARIANTS = [ { width: 1280, quality: 82, suffix: '-1280' }, { width: 1920, quality: 80, suffix: '-1920' }, ]; const entries = await readdir(SRC_DIR); const pngs = entries.filter((f) => f.toLowerCase().endsWith('.png')); if (pngs.length === 0) { console.error(`No PNGs found in ${SRC_DIR}`); process.exit(1); } for (const file of pngs) { const { name } = parse(file); const inputPath = join(SRC_DIR, file); const inputBytes = (await stat(inputPath)).size; console.log(`\n${file} (${(inputBytes / 1024).toFixed(0)} KiB)`); for (const v of VARIANTS) { const outName = `${name}${v.suffix}.webp`; const outPath = join(SRC_DIR, outName); const info = await sharp(inputPath) .resize({ width: v.width, withoutEnlargement: true }) .webp({ quality: v.quality, effort: 6 }) .toFile(outPath); const pct = ((1 - info.size / inputBytes) * 100).toFixed(0); console.log(` → ${outName} ${(info.size / 1024).toFixed(0)} KiB (-${pct}%)`); } }