tischlerei-staffenberger/scripts/optimize-images.js
mrwhiski b74dd3744e Fix SEO/a11y issues, add Impressum UID, stop tracking raw photos
- Use absolute og:image URLs and add missing og:description on Impressum
- Load Google Fonts via <link> instead of blocking @import
- Prevent default on shop request button to avoid stray URL hash
- Guard optimize-images.js against silent filename collisions
- Add real Umsatzsteuer-Identifikationsnummer (UID) to Impressum
- Correct README's shop description to match actual empty state
- Stop tracking images/fotowebsite/ (raw, uncompressed source photos)
  since only images/optimized/ is served by the site
2026-08-25 21:48:57 +02:00

100 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Image optimization script for Tischlerei Staffenberger.
* Generates WebP + JPEG variants at thumbnail (800px) and lightbox (1920px) sizes.
* Output: images/optimized/{category}/{name}-{size}w.{ext}
*
* Run: node scripts/optimize-images.js
*/
'use strict';
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');
const PROJECT_ROOT = path.join(__dirname, '..');
const INPUT_ROOT = path.join(PROJECT_ROOT, 'images', 'fotowebsite');
const OUTPUT_ROOT = path.join(PROJECT_ROOT, 'images', 'optimized');
// Gallery categories: [input subdir, output subdir]
const GALLERY_DIRS = [
['kitchen', 'kitchen'],
['outdoor', 'outdoor'],
['custom', 'custom'],
];
const GALLERY_SIZES = [800, 1920];
const BANNER_SIZES = [800, 1920];
function safeName(filename) {
return path.basename(filename, path.extname(filename)).replace(/\s+/g, '-');
}
async function processImage(inputPath, outputDir, baseName, sizes) {
fs.mkdirSync(outputDir, { recursive: true });
for (const width of sizes) {
const jpegOut = path.join(outputDir, `${baseName}-${width}w.jpg`);
const webpOut = path.join(outputDir, `${baseName}-${width}w.webp`);
await sharp(inputPath)
.resize({ width, withoutEnlargement: true })
.jpeg({ quality: 85, mozjpeg: true })
.toFile(jpegOut);
await sharp(inputPath)
.resize({ width, withoutEnlargement: true })
.webp({ quality: 82 })
.toFile(webpOut);
const jpegSize = (fs.statSync(jpegOut).size / 1024).toFixed(0);
const webpSize = (fs.statSync(webpOut).size / 1024).toFixed(0);
console.log(` ${path.basename(jpegOut)}: JPEG ${jpegSize}KB WebP ${webpSize}KB`);
}
}
async function main() {
console.log('=== Tischlerei Staffenberger – Image Optimization ===\n');
// --- Gallery images ---
for (const [inputSubdir, outputSubdir] of GALLERY_DIRS) {
const inputDir = path.join(INPUT_ROOT, inputSubdir);
const outputDir = path.join(OUTPUT_ROOT, outputSubdir);
const files = fs.readdirSync(inputDir)
.filter(f => /\.(jpe?g)$/i.test(f))
.sort();
console.log(`[${inputSubdir}] – ${files.length} images`);
const usedNames = new Map(); // baseName -> source filename, to catch collisions
for (const file of files) {
const inputPath = path.join(inputDir, file);
const baseName = safeName(file);
const collidesWith = usedNames.get(baseName);
if (collidesWith) {
throw new Error(
`Filename collision in ${inputSubdir}: "${file}" and "${collidesWith}" both normalize to "${baseName}" ` +
`and would overwrite each other's optimized output. Rename one of the source files and re-run.`
);
}
usedNames.set(baseName, file);
await processImage(inputPath, outputDir, baseName, GALLERY_SIZES);
}
}
// --- Banner ---
const bannerInput = path.join(INPUT_ROOT, 'banner.jpg');
const bannerOutput = OUTPUT_ROOT;
console.log('\n[banner]');
await processImage(bannerInput, bannerOutput, 'banner', BANNER_SIZES);
console.log('\nDone.');
}
main().catch(err => {
console.error(err);
process.exit(1);
});