Files
nobat724_front/scripts/make-blog-cover.mjs
T

107 lines
4.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* تولید تصویر پیش‌فرض مقاله‌ها — `public/assets/images/blog-default-cover.png`.
*
* یک‌بار اجرا می‌شود و خروجی PNG کامیت می‌گردد؛ اسکریپت می‌ماند تا اگر برند یا
* ابعاد عوض شد، تصویر قابل بازتولید باشد:
*
* node scripts/make-blog-cover.mjs
*
* ابعاد ۱۲۰۰×۶۳۰ استاندارد Open Graph است و با `aspect-video` کارت‌ها هم می‌خواند.
* رنگ‌ها و نشان از `public/nobat724.svg` می‌آیند تا با لوگوی سایت یکی بماند.
*/
import sharp from 'sharp';
import { readFileSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
const W = 1200;
const H = 630;
const BRAND = '#5559CE'; // stroke نشان در public/nobat724.svg
const ACCENT = '#FF8848'; // پلاس نارنجی همان نشان
const OUT = join(ROOT, 'public/assets/images/blog-default-cover.png');
const mark = readFileSync(join(ROOT, 'public/nobat724.svg'), 'utf8')
.replace(/<\?xml[^>]*\?>/, '')
.replace(/width="\d+"\s+height="\d+"/, 'width="200" height="200"');
/**
* چیدمان RTL: لوگو سمت راست، «اسکلتون» یک مقاله سمت چپ — کارت سفید با جای
* تصویر شاخص، یک نوار عنوان و چند خط پاراگراف. همان زبان بصریِ حالت بارگذاری،
* پس «مقالهٔ بدون کاور» به‌جای عکس بی‌ربط، قالبِ خالی مقاله را نشان می‌دهد.
*
* خط‌ها راست‌چین‌اند (RTL) و عرضشان نامنظم است تا شبیه متن واقعی باشد؛ خط آخر
* کوتاه است، همان‌طور که آخرین سطر یک پاراگراف کوتاه می‌افتد.
*/
const CARD = { x: 132, y: 88, w: 628, h: 410, pad: 40 };
const INNER_RIGHT = CARD.x + CARD.w - CARD.pad;
const INNER_W = CARD.w - CARD.pad * 2;
// عرض هر سطر پاراگراف نسبت به عرض داخلی کارت
const PARAGRAPH = [1, 0.94, 0.99, 0.88, 0.52];
const lines = PARAGRAPH.map((ratio, i) => {
const w = Math.round(INNER_W * ratio);
return { x: INNER_RIGHT - w, y: 344 + i * 26, w, h: 14 };
});
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#F7F8FE"/>
<stop offset="100%" stop-color="#ECEDFB"/>
</linearGradient>
<linearGradient id="bar" x1="1" y1="0" x2="0" y2="0">
<stop offset="0%" stop-color="#DEDFF4"/>
<stop offset="100%" stop-color="#EFF0FB"/>
</linearGradient>
<linearGradient id="cover" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#D9DAF2"/>
<stop offset="100%" stop-color="#E9EAF8"/>
</linearGradient>
<filter id="cardShadow" x="-10%" y="-10%" width="130%" height="130%">
<feDropShadow dx="0" dy="10" stdDeviation="16" flood-color="#5559CE" flood-opacity="0.10"/>
</filter>
</defs>
<rect width="${W}" height="${H}" fill="url(#bg)"/>
<rect x="0" y="0" width="${W}" height="8" fill="${BRAND}"/>
<rect x="0" y="${H - 8}" width="${W}" height="8" fill="${ACCENT}"/>
<!-- کارت اسکلتون مقاله (سمت چپ) -->
<rect x="${CARD.x}" y="${CARD.y}" width="${CARD.w}" height="${CARD.h}" rx="20"
fill="#FFFFFF" stroke="#E6E7F5" stroke-width="2" filter="url(#cardShadow)"/>
<!-- جای تصویر شاخص + گلیف عکس -->
<rect x="${CARD.x + CARD.pad}" y="128" width="${INNER_W}" height="118" rx="12" fill="url(#cover)"/>
<g fill="#C6C8E8">
<circle cx="${CARD.x + CARD.pad + 62}" cy="168" r="13"/>
<path d="M${CARD.x + CARD.pad + 34} 232 L${CARD.x + CARD.pad + 96} 178
L${CARD.x + CARD.pad + 150} 232 Z"/>
<path d="M${CARD.x + CARD.pad + 128} 232 L${CARD.x + CARD.pad + 178} 190
L${CARD.x + CARD.pad + 226} 232 Z" opacity="0.75"/>
</g>
<!-- نوار عنوان (راست‌چین، پررنگ‌تر از خطوط متن) -->
<rect x="${INNER_RIGHT - 322}" y="278" width="322" height="26" rx="9" fill="#CBCDEC"/>
<!-- خطوط پاراگراف -->
${lines
.map(
(b) =>
`<rect x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" rx="7" fill="url(#bar)"/>`
)
.join('\n ')}
<!-- لوگو (سمت راست) — مرکزش با مرکز کارت اسکلتون هم‌تراز است -->
<g transform="translate(815, 193)">${mark}</g>
</svg>`;
// density بالا برای متن تیز، سپس resize به ابعاد دقیق OG.
const png = await sharp(Buffer.from(svg), { density: 144 }).resize(W, H).png().toBuffer();
writeFileSync(OUT, png);
const meta = await sharp(png).metadata();
console.log(`✓ ${OUT}${meta.width}×${meta.height}, ${(png.length / 1024).toFixed(0)}KB`);