This repository has been archived on 2026-08-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maple-brief-aggregator/scripts/make-covers.mjs
T

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);
}