first commit
ci / build (push) Failing after 23s
deploy / deploy (push) Failing after 10m12s

This commit is contained in:
soroush.asadi
2026-05-31 12:47:02 +03:30
commit add78d8460
100 changed files with 15221 additions and 0 deletions
+117
View File
@@ -0,0 +1,117 @@
// Generates abstract dark "product shot" SVGs for the portfolio gallery.
// Run once: `node scripts/gen-portfolio-art.mjs`. Output -> public/portfolio/<id>/*.svg
// These are tasteful placeholders; the admin panel can upload real screenshots later.
import { mkdirSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
const OUT = join(process.cwd(), 'public', 'portfolio');
// id -> base hue (matches the accent assigned in the dictionary)
const PROJECTS = {
'atlas-rag': 199, // electric
'sentinel-agents': 245, // violet
'vertex-vision': 187, // cyan
'mirage-mobile': 292, // magenta
'flux-stream': 158, // emerald
'oracle-forecast': 205, // electric-2
};
const W = 1600;
const H = 1000;
const defs = (hue, id) => `
<defs>
<radialGradient id="glow${id}" cx="28%" cy="18%" r="90%">
<stop offset="0%" stop-color="hsl(${hue} 90% 62% / 0.55)"/>
<stop offset="45%" stop-color="hsl(${(hue + 28) % 360} 85% 55% / 0.18)"/>
<stop offset="100%" stop-color="hsl(${hue} 60% 8% / 0)"/>
</radialGradient>
<linearGradient id="bg${id}" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#04060f"/>
<stop offset="100%" stop-color="#020308"/>
</linearGradient>
<linearGradient id="line${id}" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="hsl(${hue} 90% 65%)"/>
<stop offset="100%" stop-color="hsl(${(hue + 40) % 360} 90% 68%)"/>
</linearGradient>
</defs>
<rect width="${W}" height="${H}" fill="url(#bg${id})"/>
<rect width="${W}" height="${H}" fill="url(#glow${id})"/>
<g stroke="hsl(${hue} 40% 60% / 0.06)" stroke-width="1">
${Array.from({ length: 16 }, (_, i) => `<line x1="${i * 100}" y1="0" x2="${i * 100}" y2="${H}"/>`).join('')}
${Array.from({ length: 10 }, (_, i) => `<line x1="0" y1="${i * 100}" x2="${W}" y2="${i * 100}"/>`).join('')}
</g>`;
const chrome = (hue, id) => `
<g>
<rect x="80" y="70" width="${W - 160}" height="60" rx="14" fill="hsl(${hue} 30% 18% / 0.4)" stroke="hsl(${hue} 60% 60% / 0.25)"/>
<circle cx="120" cy="100" r="9" fill="hsl(0 70% 60% / .7)"/>
<circle cx="152" cy="100" r="9" fill="hsl(45 80% 60% / .7)"/>
<circle cx="184" cy="100" r="9" fill="hsl(140 60% 55% / .7)"/>
<rect x="240" y="88" width="420" height="24" rx="12" fill="hsl(${hue} 30% 40% / 0.25)"/>
</g>`;
function dashboard(hue, id) {
const bars = Array.from({ length: 9 }, (_, i) => {
const bh = 80 + ((i * 53) % 260);
return `<rect x="${180 + i * 96}" y="${760 - bh}" width="54" height="${bh}" rx="8" fill="url(#line${id})" opacity="${0.5 + (i % 3) * 0.16}"/>`;
}).join('');
const path = `M 1050 620 ${Array.from({ length: 9 }, (_, i) => `L ${1050 + i * 54} ${620 - Math.sin(i / 1.4) * 120 - i * 6}`).join(' ')}`;
return `${defs(hue, id)}${chrome(hue, id)}
<rect x="120" y="200" width="700" height="560" rx="20" fill="hsl(${hue} 30% 12% / 0.5)" stroke="hsl(${hue} 60% 60% / 0.16)"/>
<text x="160" y="270" font-family="monospace" font-size="30" fill="hsl(${hue} 70% 75%)">throughput / day</text>
${bars}
<rect x="880" y="200" width="600" height="560" rx="20" fill="hsl(${hue} 30% 12% / 0.5)" stroke="hsl(${hue} 60% 60% / 0.16)"/>
<path d="${path}" fill="none" stroke="url(#line${id})" stroke-width="6" stroke-linecap="round"/>
<circle cx="1482" cy="${620 - Math.sin(8 / 1.4) * 120 - 48}" r="12" fill="hsl(${hue} 90% 70%)"/>
<text x="920" y="270" font-family="monospace" font-size="30" fill="hsl(${hue} 70% 75%)">latency p95</text>`;
}
function flow(hue, id) {
const node = (x, y, label) =>
`<g><rect x="${x}" y="${y}" width="230" height="110" rx="18" fill="hsl(${hue} 35% 14% / 0.7)" stroke="hsl(${hue} 70% 62% / 0.5)"/><text x="${x + 115}" y="${y + 62}" text-anchor="middle" font-family="monospace" font-size="26" fill="hsl(${hue} 70% 80%)">${label}</text></g>`;
const edge = (x1, y1, x2, y2) =>
`<path d="M ${x1} ${y1} C ${(x1 + x2) / 2} ${y1}, ${(x1 + x2) / 2} ${y2}, ${x2} ${y2}" fill="none" stroke="url(#line${id})" stroke-width="4" opacity="0.7"/>`;
return `${defs(hue, id)}${chrome(hue, id)}
${edge(350, 305, 590, 215)}${edge(350, 305, 590, 405)}${edge(820, 215, 1060, 305)}${edge(820, 405, 1060, 305)}${edge(1290, 305, 1290, 305)}
${node(120, 250, 'ingest')}
${node(590, 160, 'embed')}
${node(590, 350, 'retrieve')}
${node(1060, 250, 'rerank')}
${node(1300, 250, 'generate')}
<g opacity="0.5">${Array.from({ length: 6 }, (_, i) => `<circle cx="${260 + i * 200}" cy="700" r="6" fill="hsl(${hue} 90% 70%)"/>`).join('')}</g>`;
}
function mobile(hue, id) {
return `${defs(hue, id)}
<g transform="translate(560 120)">
<rect width="480" height="780" rx="56" fill="hsl(${hue} 30% 10% / 0.9)" stroke="hsl(${hue} 60% 60% / 0.35)" stroke-width="3"/>
<rect x="22" y="22" width="436" height="736" rx="40" fill="#04060f"/>
<rect x="180" y="40" width="120" height="22" rx="11" fill="hsl(${hue} 30% 25%)"/>
<circle cx="240" cy="230" r="86" fill="none" stroke="url(#line${id})" stroke-width="10"/>
<circle cx="240" cy="230" r="56" fill="hsl(${hue} 80% 60% / 0.18)"/>
${Array.from({ length: 4 }, (_, i) => `<rect x="70" y="${380 + i * 80}" width="${340 - i * 40}" height="46" rx="14" fill="hsl(${hue} 35% 18% / 0.8)"/>`).join('')}
<rect x="70" y="700" width="340" height="56" rx="18" fill="url(#line${id})"/>
</g>`;
}
const SHOTS = [dashboard, flow, mobile];
mkdirSync(OUT, { recursive: true });
for (const [id, hue] of Object.entries(PROJECTS)) {
const dir = join(OUT, id);
mkdirSync(dir, { recursive: true });
// cover = dashboard variant; gallery = all three variants
const files = {
'cover.svg': dashboard(hue, id.replace(/\W/g, '')),
'01.svg': dashboard(hue, id.replace(/\W/g, '') + 'a'),
'02.svg': flow(hue, id.replace(/\W/g, '') + 'b'),
'03.svg': mobile(hue, id.replace(/\W/g, '') + 'c'),
};
for (const [name, inner] of Object.entries(files)) {
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}">${inner}</svg>`;
writeFileSync(join(dir, name), svg);
}
console.log('wrote', id);
}
console.log('done');