Files
suphicakir 7d8fcce2e1 feat: add OG images, favicon set, manifest and performance tuning
- Build-time Open Graph cards per post and a default card (Satori + Sharp)
- Favicon/ICO/apple-touch/manifest icons generated from one SVG (npm run icons)
- robots.txt and web manifest generated from site.json
- Subset self-hosted fonts to Latin + Turkish (188 KB -> 66 KB) with preload
- Inline the stylesheet to remove the render-blocking request
- Post-build site audit (npm run check:site): h1, alt, SEO tags, links
- Omit canonical on noindex pages

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 02:36:56 +03:00

77 lines
2.8 KiB
JavaScript

// Regenerates the favicon set in public/ from the Tabler antenna-tower icon.
// Usage: npm run icons
import { writeFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import sharp from 'sharp';
const require = createRequire(import.meta.url);
const { icons } = require('@iconify-json/tabler/icons.json');
const RED = '#c95748';
const INK = '#111012';
const tower = (stroke) =>
icons['building-broadcast-tower'].body
.replaceAll('currentColor', INK)
.replaceAll('stroke-width="2"', `stroke-width="${stroke}"`);
/**
* Square icon: red background, ink tower.
* `pad` = icon inset (0..1), `radius` in viewBox units, `stroke` = Tabler stroke width.
*/
const svg = ({ pad = 0.14, radius = 6, stroke = 2.4 } = {}) => {
const size = 32;
const inner = size * (1 - pad * 2);
const offset = size * pad;
const scale = inner / 24;
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}"><rect width="${size}" height="${size}" rx="${radius}" fill="${RED}"/><g transform="translate(${offset} ${offset}) scale(${scale})">${tower(stroke)}</g></svg>`;
};
const png = (source, px) =>
sharp(Buffer.from(source), { density: 72 * (px / 32) * 4 })
.resize(px, px)
.png()
.toBuffer();
/** ICO container with embedded PNG images. */
function ico(images) {
const header = Buffer.alloc(6 + images.length * 16);
header.writeUInt16LE(0, 0);
header.writeUInt16LE(1, 2);
header.writeUInt16LE(images.length, 4);
let offset = header.length;
images.forEach(({ size, data }, i) => {
const e = 6 + i * 16;
header.writeUInt8(size >= 256 ? 0 : size, e);
header.writeUInt8(size >= 256 ? 0 : size, e + 1);
header.writeUInt16LE(1, e + 4); // colour planes
header.writeUInt16LE(32, e + 6); // bits per pixel
header.writeUInt32LE(data.length, e + 8);
header.writeUInt32LE(offset, e + 12);
offset += data.length;
});
return Buffer.concat([header, ...images.map((i) => i.data)]);
}
const rounded = svg();
const tiny = svg({ pad: 0.06, radius: 4, stroke: 3.2 }); // 16 px tab icon: bigger, bolder
const square = svg({ radius: 0 }); // platforms apply their own mask
const maskable = svg({ pad: 0.24, radius: 0 }); // keep the icon inside the 80% safe zone
writeFileSync('public/favicon.svg', rounded);
writeFileSync(
'public/favicon.ico',
ico(
await Promise.all(
[16, 32, 48].map(async (size) => ({
size,
data: await png(size === 16 ? tiny : rounded, size),
})),
),
),
);
writeFileSync('public/apple-touch-icon.png', await png(svg({ pad: 0.18, radius: 0 }), 180));
writeFileSync('public/icon-192.png', await png(square, 192));
writeFileSync('public/icon-512.png', await png(square, 512));
writeFileSync('public/icon-maskable-512.png', await png(maskable, 512));
console.log('Icons written to public/');