- SSR-пререндер через Vite + react-dom/server (entry-server.jsx, scripts/prerender.mjs) - Секция партнёров: RU-CENTER, REG.RU, МТВ, КОНТУР - Реквизиты компании в футере (ИНН, ОГРН, банк) - Копирайт с 2011 года - Логотип увеличен (h-8→h-12 навбар, h-7→h-10 футер) - favicon.ico пересобран с 16×16, 32×32, 48×48 из logo.png - og:image и twitter:image переключены на .png - apple-touch-icon.png и og-image.png сгенерированы через sharp - SSR-safe: localStorage и window.location проверяются на typeof Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
68 lines
2.2 KiB
JavaScript
68 lines
2.2 KiB
JavaScript
import sharp from 'sharp'
|
||
import { readFileSync } from 'fs'
|
||
import { resolve, dirname } from 'path'
|
||
import { fileURLToPath } from 'url'
|
||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||
const publicDir = resolve(__dirname, '../public')
|
||
|
||
// og-image: SVG → PNG 1200×630
|
||
const svgBuffer = readFileSync(resolve(publicDir, 'og-image.svg'))
|
||
await sharp(svgBuffer)
|
||
.resize(1200, 630)
|
||
.png({ quality: 90 })
|
||
.toFile(resolve(publicDir, 'og-image.png'))
|
||
console.log('✓ og-image.png (1200×630)')
|
||
|
||
// apple-touch-icon: logo.png → 180×180
|
||
await sharp(resolve(publicDir, 'logo.png'))
|
||
.resize(180, 180, { fit: 'contain', background: { r: 255, g: 255, b: 255, alpha: 1 } })
|
||
.png()
|
||
.toFile(resolve(publicDir, 'apple-touch-icon.png'))
|
||
console.log('✓ apple-touch-icon.png (180×180)')
|
||
|
||
// favicon.ico: logo.png → multi-size ICO (16, 32, 48)
|
||
import { writeFileSync } from 'fs'
|
||
|
||
const icoSizes = [16, 32, 48]
|
||
const pngBuffers = await Promise.all(
|
||
icoSizes.map(size =>
|
||
sharp(resolve(publicDir, 'logo.png'))
|
||
.resize(size, size, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
|
||
.png()
|
||
.toBuffer()
|
||
)
|
||
)
|
||
|
||
// Build ICO: header + directory + image data
|
||
const count = icoSizes.length
|
||
const headerSize = 6
|
||
const dirEntrySize = 16
|
||
const dirSize = count * dirEntrySize
|
||
let offset = headerSize + dirSize
|
||
|
||
const header = Buffer.alloc(headerSize)
|
||
header.writeUInt16LE(0, 0) // reserved
|
||
header.writeUInt16LE(1, 2) // type: 1 = ICO
|
||
header.writeUInt16LE(count, 4)
|
||
|
||
const dirs = []
|
||
for (let i = 0; i < count; i++) {
|
||
const dir = Buffer.alloc(dirEntrySize)
|
||
const size = icoSizes[i]
|
||
dir.writeUInt8(size === 256 ? 0 : size, 0) // width
|
||
dir.writeUInt8(size === 256 ? 0 : size, 1) // height
|
||
dir.writeUInt8(0, 2) // color count
|
||
dir.writeUInt8(0, 3) // reserved
|
||
dir.writeUInt16LE(1, 4) // color planes
|
||
dir.writeUInt16LE(32, 6) // bits per pixel
|
||
dir.writeUInt32LE(pngBuffers[i].length, 8)
|
||
dir.writeUInt32LE(offset, 12)
|
||
offset += pngBuffers[i].length
|
||
dirs.push(dir)
|
||
}
|
||
|
||
const ico = Buffer.concat([header, ...dirs, ...pngBuffers])
|
||
writeFileSync(resolve(publicDir, 'favicon.ico'), ico)
|
||
console.log('✓ favicon.ico (16×16, 32×32, 48×48)')
|