107 lines
4.9 KiB
JavaScript
107 lines
4.9 KiB
JavaScript
/**
|
||
* تولید تصویر پیشفرض مقالهها — `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`);
|