80 lines
3.8 KiB
JavaScript
80 lines
3.8 KiB
JavaScript
/* Cover generator — run: npm run covers
|
|
Produces 12 JPGs (1600x900) in public/covers/ plus per-category defaults. */
|
|
import sharp from 'sharp'
|
|
import { mkdirSync } from 'node:fs'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
const outDir = path.resolve(dirname, '../public/covers')
|
|
mkdirSync(outDir, { recursive: true })
|
|
|
|
const W = 1600
|
|
const H = 900
|
|
|
|
const POSTS = [
|
|
// [file, title, category, kind: free|vip]
|
|
['agents-free', 'بناء أول وكيل بالعربية', 'وكلاء'],
|
|
['agents-vip', 'حلقة الوكيل: تخطيط وتنفيذ وتحقق', 'وكلاء'],
|
|
['models-free', 'كيف تختار نموذجك', 'نماذج'],
|
|
['models-vip', 'تقييم النماذج منهجياً', 'نماذج'],
|
|
['retrieval-free', 'الاسترجاع بلا تعقيد', 'استرجاع'],
|
|
['retrieval-vip', 'قواعد بيانات المتجهات عملياً', 'استرجاع'],
|
|
['safety-free', 'حدود الوكيل الآمنة', 'سلامة'],
|
|
['safety-vip', 'مراجعة مخرجات الوكلاء', 'سلامة'],
|
|
['tools-free', 'أدوات لا غنى عنها', 'أدوات'],
|
|
['tools-vip', 'أنابيب الأدوات والصلاحيات', 'أدوات'],
|
|
['business-free', 'الذكاء الاصطناعي في عملك', 'أعمال'],
|
|
['business-vip', 'من نموذج أولي إلى إنتاج', 'أعمال'],
|
|
// fallback default cover for the agent API
|
|
['cover-default', 'أنور بي آي', 'وكلاء'],
|
|
// per-category default covers for the agent API
|
|
['cover-agents', 'وكلاء', 'وكلاء'],
|
|
['cover-models', 'نماذج', 'نماذج'],
|
|
['cover-retrieval', 'استرجاع', 'استرجاع'],
|
|
['cover-safety', 'سلامة', 'سلامة'],
|
|
['cover-tools', 'أدوات', 'أدوات'],
|
|
['cover-business', 'أعمال', 'أعمال'],
|
|
['cover-default', 'أنور بي آي', 'وكلاء'],
|
|
]
|
|
|
|
function svgCover(title, category) {
|
|
const titleEsc = title.replace(/&/g, '&').replace(/</g, '<')
|
|
const catEsc = category.replace(/&/g, '&').replace(/</g, '<')
|
|
return Buffer.from(`
|
|
<svg width="${W}" height="${H}" xmlns="http://www.w3.org/2000/svg" dir="rtl">
|
|
<defs>
|
|
<radialGradient id="g" cx="20%" cy="15%" r="90%">
|
|
<stop offset="0%" stop-color="#16202a"/>
|
|
<stop offset="55%" stop-color="#0c1016"/>
|
|
<stop offset="100%" stop-color="#050708"/>
|
|
</radialGradient>
|
|
<linearGradient id="beam" x1="0" y1="0" x2="1" y2="1">
|
|
<stop offset="0%" stop-color="#7fd8cc"/>
|
|
<stop offset="100%" stop-color="#3a6a8a"/>
|
|
</linearGradient>
|
|
</defs>
|
|
<rect width="${W}" height="${H}" fill="url(#g)"/>
|
|
<g opacity="0.5" stroke="#31414f" stroke-width="1">
|
|
${Array.from({ length: 9 }, (_, i) => `<line x1="0" y1="${(i + 1) * 90}" x2="${W}" y2="${(i + 1) * 90}"/>`).join('')}
|
|
</g>
|
|
<g transform="translate(${W - 140}, 110)">
|
|
<rect x="-26" y="-26" width="52" height="52" transform="rotate(45)" fill="none" stroke="url(#beam)" stroke-width="5"/>
|
|
<rect x="-9" y="-9" width="18" height="18" transform="rotate(45)" fill="#e8f6f3"/>
|
|
</g>
|
|
<text x="${W - 80}" y="480" text-anchor="end" fill="#f2f7f6"
|
|
font-family="SF Arabic, Noto Sans Arabic, sans-serif" font-weight="700" font-size="72">${titleEsc}</text>
|
|
<text x="${W - 80}" y="560" text-anchor="end" fill="#8fa3ad"
|
|
font-family="SF Mono, monospace" font-size="30" letter-spacing="4">${catEsc} · أنور بي آي</text>
|
|
<rect x="${W - 80 - 180}" y="620" width="180" height="3" fill="url(#beam)"/>
|
|
</svg>`)
|
|
}
|
|
|
|
for (const [name, title, category] of POSTS) {
|
|
await sharp(svgCover(title, category))
|
|
.jpeg({ quality: 82 })
|
|
.toFile(path.join(outDir, `${name}.jpg`))
|
|
console.log(`✓ ${name}.jpg`)
|
|
}
|
|
console.log(`Done: ${POSTS.length} covers → ${outDir}`)
|