- Hero: T-Rex (hero-bg1) and family moved to z-[20/30], rendered after text so dinosaur visually overlaps the title text on desktop - WhyParents: auto-cycle accordion items every 4s; manual click resets timer - VideoSection: detect YouTube URLs → render iframe; mp4 uses video tag - Webp filename cleanup across section components Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
78 lines
2.5 KiB
JavaScript
78 lines
2.5 KiB
JavaScript
import sharp from 'sharp'
|
||
import { readdir, stat } from 'fs/promises'
|
||
import path from 'path'
|
||
import { fileURLToPath } from 'url'
|
||
|
||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||
const imgDir = path.resolve(__dirname, '../public/images/figma')
|
||
|
||
const SKIP = /\.(svg|webp|gif)$/i
|
||
|
||
const SIZE_LIMITS = {
|
||
// hero / footer / video-preview — full bleed, keep wide
|
||
'hero-bg1.png': { width: 1920 },
|
||
'hero-bg2.png': { width: 1920 },
|
||
'hero-bg-family.png': { width: 1920 },
|
||
'footer-bg.png': { width: 1920 },
|
||
'video-preview.png': { width: 1920 },
|
||
// location cards — displayed at ~560px, 2× retina = 1120
|
||
'loc-dinopark.jpg': { width: 1200 },
|
||
'loc-divo-lis.png': { width: 1200 },
|
||
// gallery tiles — displayed at ~380px, 2× = 760
|
||
'gallery-1.png': { width: 800 },
|
||
'gallery-2.png': { width: 800 },
|
||
'gallery-3.png': { width: 800 },
|
||
'gallery-4.png': { width: 800 },
|
||
'gallery-6.png': { width: 800 },
|
||
'gallery-7.png': { width: 800 },
|
||
'gallery-8.png': { width: 800 },
|
||
// why-parents side tiles
|
||
'why-parents-1.png': { width: 800 },
|
||
'why-parents-2.png': { width: 800 },
|
||
'why-parents-3.png': { width: 800 },
|
||
'why-parents-4.png': { width: 800 },
|
||
// news / blog thumbnails
|
||
'news-bg1.jpg': { width: 800 },
|
||
'news-bg2.png': { width: 800 },
|
||
'news-bg3.jpg': { width: 800 },
|
||
// misc
|
||
'review-avatar-bg.jpg': { width: 160 },
|
||
'check-mark.png': { width: 48 },
|
||
}
|
||
|
||
const files = await readdir(imgDir)
|
||
|
||
let totalBefore = 0
|
||
let totalAfter = 0
|
||
|
||
for (const file of files) {
|
||
if (SKIP.test(file)) continue
|
||
if (file.endsWith('.webp')) continue
|
||
|
||
const src = path.join(imgDir, file)
|
||
const dst = path.join(imgDir, file.replace(/\.(png|jpg|jpeg)$/i, '.webp'))
|
||
|
||
const srcStat = await stat(src)
|
||
totalBefore += srcStat.size
|
||
|
||
const limit = SIZE_LIMITS[file] ?? { width: 1920 }
|
||
|
||
try {
|
||
const info = await sharp(src)
|
||
.resize({ width: limit.width, withoutEnlargement: true })
|
||
.webp({ quality: 82, effort: 4 })
|
||
.toFile(dst)
|
||
|
||
totalAfter += info.size
|
||
const pct = ((1 - info.size / srcStat.size) * 100).toFixed(1)
|
||
console.log(
|
||
`✓ ${file} → ${file.replace(/\.(png|jpg|jpeg)$/i, '.webp')} ${(srcStat.size / 1e6).toFixed(1)}MB → ${(info.size / 1e6).toFixed(1)}MB (${pct}% smaller)`
|
||
)
|
||
} catch (err) {
|
||
console.error(`✗ ${file}: ${err.message}`)
|
||
}
|
||
}
|
||
|
||
console.log(
|
||
`\nTotal: ${(totalBefore / 1e6).toFixed(1)}MB → ${(totalAfter / 1e6).toFixed(1)}MB (${((1 - totalAfter / totalBefore) * 100).toFixed(1)}% reduction)`
|
||
)
|