Archived
93 lines
3.7 KiB
JavaScript
93 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Generate per-category cover art used as the last-resort image fallback
|
|
* (baked into /public/covers/*.svg, served statically).
|
|
*
|
|
* Design language matches the brand system: ink→brazil gradient (#161614 to
|
|
* #45060c), maple-500 line art, Source Serif word label. 1200x630 (16:8).
|
|
*
|
|
* Usage: node scripts/make-covers.mjs (idempotent — overwrites)
|
|
*/
|
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const outDir = join(root, 'public', 'covers');
|
|
|
|
const BG =
|
|
'<defs><linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">' +
|
|
'<stop offset="0" stop-color="#161614"/><stop offset="0.55" stop-color="#2b2b28"/>' +
|
|
'<stop offset="1.3" stop-color="#45060c"/></linearGradient>' +
|
|
'<pattern id="grid" width="48" height="48" patternUnits="userSpaceOnUse">' +
|
|
'<path d="M48 0H0V48" fill="none" stroke="#f8402f" stroke-opacity="0.10" stroke-width="1"/>' +
|
|
'</pattern></defs>';
|
|
|
|
const stroke = 'fill="none" stroke="#f8402f" stroke-opacity="0.38" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"';
|
|
const faint = 'fill="none" stroke="#f8402f" stroke-opacity="0.14" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"';
|
|
|
|
/** Simple geometric motif per category, centred at (880, 300). */
|
|
const MOTIFS = {
|
|
// Parliament Hill: central tower + two flanking towers with connecting wings
|
|
national:
|
|
`<g ${stroke}>` +
|
|
`<path d="M760 440V300M880 440V180M1000 440V300"/>` +
|
|
`<path d="M850 180L880 120L910 180" stroke-width="7"/>` +
|
|
`<path d="M782 300L760 268L738 300M978 300L1000 268L1022 300" stroke-width="7"/>` +
|
|
`<path d="M760 380H1000M820 340H940" stroke-width="7"/>` +
|
|
`</g>` +
|
|
`<g ${faint}>` +
|
|
`<path d="M700 440H1060M700 160H1060"/>` +
|
|
`</g>`,
|
|
// maple leaf: stem + lobe outline
|
|
canada:
|
|
`<g ${stroke}>` +
|
|
`<path d="M880 130L904 196L964 182L934 236L1006 250L948 296L1006 338L934 346L964 402L904 386L880 452L856 386L796 402L826 346L754 338L812 296L754 250L826 236L796 182L856 196Z"/>` +
|
|
`<path d="M880 452V520"/>` +
|
|
`</g>` +
|
|
`<g ${faint}>` +
|
|
`<path d="M700 452H1060"/>` +
|
|
`</g>`,
|
|
// world globe with meridian (top stories)
|
|
'top-stories':
|
|
`<g ${stroke}>` +
|
|
`<circle cx="880" cy="305" r="150"/>` +
|
|
`<ellipse cx="880" cy="305" rx="64" ry="150"/>` +
|
|
`<path d="M736 254H1024M730 356H1030"/>` +
|
|
`</g>` +
|
|
`<g ${faint}>` +
|
|
`<path d="M700 155H1060M700 455H1060"/>` +
|
|
`</g>`,
|
|
};
|
|
|
|
const LABEL = {
|
|
national: 'National',
|
|
canada: 'Canada',
|
|
'top-stories': 'Top Stories',
|
|
};
|
|
|
|
function cover(category, motif, label) {
|
|
void category; // contextual only; filename is the real key
|
|
return (
|
|
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" role="img" aria-label="${label} — MapleBrief">` +
|
|
BG +
|
|
`<rect width="1200" height="630" fill="url(#bg)"/>` +
|
|
`<rect width="1200" height="630" fill="url(#grid)"/>` +
|
|
motif +
|
|
`<text x="80" y="556" font-family="Georgia, 'Times New Roman', serif" font-size="44" font-weight="700" fill="#ffd9d4" fill-opacity="0.92">${label}</text>` +
|
|
`<text x="80" y="592" font-family="Inter, 'Segoe UI', sans-serif" font-size="18" letter-spacing="4" fill="#f8402f" fill-opacity="0.55">MAPLEBRIEF</text>` +
|
|
`</svg>`
|
|
);
|
|
}
|
|
|
|
await mkdir(outDir, { recursive: true });
|
|
const covers = {
|
|
'top-stories.svg': cover('top-stories', MOTIFS['top-stories'], LABEL['top-stories']),
|
|
'national.svg': cover('national', MOTIFS.national, LABEL.national),
|
|
'canada.svg': cover('canada', MOTIFS.canada, LABEL.canada),
|
|
};
|
|
for (const [name, svg] of Object.entries(covers)) {
|
|
await writeFile(join(outDir, name), svg);
|
|
console.log('wrote', name);
|
|
}
|