Files
mintel.me/apps/web/src/components/blog/BlogThumbnailSVG.tsx
Marc Mintel 6864903cff
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Failing after 2m24s
Build & Deploy / 🏗️ Build (push) Failing after 3m40s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
fix(web): remove redundant prop-types and unblock lint pipeline
2026-02-24 11:38:43 +01:00

1108 lines
26 KiB
TypeScript

import type { ThumbnailIcon } from "./blogThumbnails";
import { blogThumbnails } from "./blogThumbnails";
interface BlogThumbnailSVGProps {
slug: string;
variant?: "square" | "banner";
className?: string;
}
// Grid pattern used in the background
const GridPattern: React.FC<{ size: number }> = ({ size }) => (
<defs>
<pattern id="grid" width={size} height={size} patternUnits="userSpaceOnUse">
<path
d={`M ${size} 0 L 0 0 0 ${size}`}
fill="none"
stroke="#94a3b8"
strokeWidth="0.5"
opacity="0.12"
/>
</pattern>
</defs>
);
// ─── Icon Renderers ───────────────────────────────────────────────
function renderGauge(cx: number, cy: number, accent: string) {
const r = 52;
// Arc from ~210° to ~330° (lower half open)
const startAngle = (210 * Math.PI) / 180;
const endAngle = (330 * Math.PI) / 180;
const x1 = cx + r * Math.cos(startAngle);
const y1 = cy + r * Math.sin(startAngle);
const x2 = cx + r * Math.cos(endAngle);
const y2 = cy + r * Math.sin(endAngle);
// Needle at ~280° (pointing upper-right = fast/danger zone)
const needleAngle = (280 * Math.PI) / 180;
const nx = cx + (r - 14) * Math.cos(needleAngle);
const ny = cy + (r - 14) * Math.sin(needleAngle);
return (
<g>
<path
d={`M ${x1} ${y1} A ${r} ${r} 0 1 1 ${x2} ${y2}`}
fill="none"
stroke="#cbd5e1"
strokeWidth="6"
strokeLinecap="round"
/>
<path
d={`M ${x1} ${y1} A ${r} ${r} 0 1 1 ${x2} ${y2}`}
fill="none"
stroke={accent}
strokeWidth="6"
strokeLinecap="round"
strokeDasharray="200"
strokeDashoffset="60"
/>
<line
x1={cx}
y1={cy}
x2={nx}
y2={ny}
stroke="#0f172a"
strokeWidth="3"
strokeLinecap="round"
/>
<circle cx={cx} cy={cy} r="5" fill="#0f172a" />
{/* Tick marks */}
{[210, 240, 270, 300, 330].map((deg) => {
const rad = (deg * Math.PI) / 180;
const tx1 = cx + (r + 6) * Math.cos(rad);
const ty1 = cy + (r + 6) * Math.sin(rad);
const tx2 = cx + (r + 12) * Math.cos(rad);
const ty2 = cy + (r + 12) * Math.sin(rad);
return (
<line
key={deg}
x1={tx1}
y1={ty1}
x2={tx2}
y2={ty2}
stroke="#94a3b8"
strokeWidth="1.5"
/>
);
})}
</g>
);
}
function renderBottleneck(cx: number, cy: number, accent: string) {
return (
<g>
{/* Wide top */}
<rect
x={cx - 50}
y={cy - 50}
width="100"
height="30"
rx="4"
fill="#f1f5f9"
stroke="#cbd5e1"
strokeWidth="1.5"
/>
{/* Narrow middle (bottleneck) */}
<path
d={`M ${cx - 50} ${cy - 20} L ${cx - 12} ${cy} L ${cx - 12} ${cy + 10} L ${cx - 50} ${cy + 30} Z`}
fill="none"
stroke="#cbd5e1"
strokeWidth="1.5"
/>
<path
d={`M ${cx + 50} ${cy - 20} L ${cx + 12} ${cy} L ${cx + 12} ${cy + 10} L ${cx + 50} ${cy + 30} Z`}
fill="none"
stroke="#cbd5e1"
strokeWidth="1.5"
/>
{/* Flow lines */}
{[-20, 0, 20].map((offset) => (
<circle
key={offset}
cx={cx + offset}
cy={cy - 38}
r="3"
fill={accent}
opacity="0.7"
/>
))}
<circle cx={cx} cy={cy + 5} r="3" fill={accent} />
{/* Wide bottom */}
<rect
x={cx - 50}
y={cy + 30}
width="100"
height="30"
rx="4"
fill="#f1f5f9"
stroke="#cbd5e1"
strokeWidth="1.5"
/>
{/* Arrow down through throttle */}
<line
x1={cx}
y1={cy - 30}
x2={cx}
y2={cy + 20}
stroke={accent}
strokeWidth="2"
strokeDasharray="4 3"
/>
</g>
);
}
function renderPlugin(cx: number, cy: number, accent: string) {
const s = 28;
return (
<g>
{/* Connected piece 1 */}
<rect
x={cx - s - 8}
y={cy - s}
width={s}
height={s}
rx="4"
fill="#f1f5f9"
stroke="#cbd5e1"
strokeWidth="1.5"
/>
{/* Connected piece 2 */}
<rect
x={cx - 8}
y={cy - s}
width={s}
height={s}
rx="4"
fill="#f1f5f9"
stroke="#cbd5e1"
strokeWidth="1.5"
/>
{/* Connected piece 3 */}
<rect
x={cx - s - 8}
y={cy + 4}
width={s}
height={s}
rx="4"
fill="#f1f5f9"
stroke="#cbd5e1"
strokeWidth="1.5"
/>
{/* Disconnected piece (offset) */}
<rect
x={cx + 6}
y={cy + 12}
width={s}
height={s}
rx="4"
fill="none"
stroke={accent}
strokeWidth="2"
strokeDasharray="4 3"
transform={`rotate(8, ${cx + 6 + s / 2}, ${cy + 12 + s / 2})`}
/>
{/* Connector dots */}
<circle cx={cx - 8} cy={cy - s / 2} r="2.5" fill="#cbd5e1" />
<circle cx={cx - 8} cy={cy + 4 + s / 2} r="2.5" fill="#cbd5e1" />
{/* Warning */}
<text
x={cx + 20 + s / 2}
y={cy + 26 + s / 2}
fontSize="18"
fontWeight="bold"
fill={accent}
textAnchor="middle"
>
!
</text>
</g>
);
}
function renderShield(cx: number, cy: number, accent: string) {
return (
<g>
<path
d={`M ${cx} ${cy - 55} L ${cx + 45} ${cy - 30} L ${cx + 45} ${cy + 10} Q ${cx + 45} ${cy + 45} ${cx} ${cy + 55} Q ${cx - 45} ${cy + 45} ${cx - 45} ${cy + 10} L ${cx - 45} ${cy - 30} Z`}
fill="#f8fafc"
stroke="#cbd5e1"
strokeWidth="2"
/>
<path
d={`M ${cx} ${cy - 55} L ${cx + 45} ${cy - 30} L ${cx + 45} ${cy + 10} Q ${cx + 45} ${cy + 45} ${cx} ${cy + 55} Q ${cx - 45} ${cy + 45} ${cx - 45} ${cy + 10} L ${cx - 45} ${cy - 30} Z`}
fill="none"
stroke={accent}
strokeWidth="2.5"
opacity="0.4"
/>
{/* Checkmark */}
<polyline
points={`${cx - 16},${cy} ${cx - 4},${cy + 14} ${cx + 20},${cy - 14}`}
fill="none"
stroke={accent}
strokeWidth="4"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
);
}
function renderCookie(cx: number, cy: number, accent: string) {
return (
<g>
{/* Cookie circle */}
<circle
cx={cx}
cy={cy}
r="48"
fill="#fef3c7"
stroke="#cbd5e1"
strokeWidth="2"
/>
{/* Chips */}
<circle cx={cx - 14} cy={cy - 16} r="5" fill="#92400e" opacity="0.5" />
<circle cx={cx + 18} cy={cy - 8} r="4" fill="#92400e" opacity="0.5" />
<circle cx={cx - 6} cy={cy + 18} r="5" fill="#92400e" opacity="0.5" />
<circle cx={cx + 12} cy={cy + 14} r="3.5" fill="#92400e" opacity="0.5" />
{/* Strikethrough */}
<line
x1={cx - 38}
y1={cx + 38}
x2={cx + 38}
y2={cy - 38}
stroke={accent}
strokeWidth="4"
strokeLinecap="round"
/>
</g>
);
}
function renderCloud(cx: number, cy: number, accent: string) {
return (
<g>
<path
d={`M ${cx - 40} ${cy + 16} Q ${cx - 52} ${cy + 16} ${cx - 52} ${cy} Q ${cx - 52} ${cy - 20} ${cx - 32} ${cy - 24} Q ${cx - 28} ${cy - 48} ${cx} ${cy - 44} Q ${cx + 28} ${cy - 48} ${cx + 32} ${cy - 24} Q ${cx + 52} ${cy - 20} ${cx + 52} ${cy} Q ${cx + 52} ${cy + 16} ${cx + 40} ${cy + 16} Z`}
fill="#f1f5f9"
stroke="#cbd5e1"
strokeWidth="2"
/>
{/* Lock icon inside cloud */}
<rect
x={cx - 12}
y={cy - 6}
width="24"
height="18"
rx="3"
fill="none"
stroke={accent}
strokeWidth="2.5"
/>
<path
d={`M ${cx - 6} ${cy - 6} L ${cx - 6} ${cy - 14} Q ${cx - 6} ${cy - 22} ${cx} ${cy - 22} Q ${cx + 6} ${cy - 22} ${cx + 6} ${cy - 14} L ${cx + 6} ${cy - 6}`}
fill="none"
stroke={accent}
strokeWidth="2.5"
/>
<circle cx={cx} cy={cy + 3} r="2.5" fill={accent} />
</g>
);
}
function renderLock(cx: number, cy: number, accent: string) {
return (
<g>
{/* Lock body */}
<rect
x={cx - 24}
y={cy - 8}
width="48"
height="40"
rx="6"
fill="#f1f5f9"
stroke="#cbd5e1"
strokeWidth="2"
/>
{/* Lock shackle */}
<path
d={`M ${cx - 14} ${cy - 8} L ${cx - 14} ${cy - 24} Q ${cx - 14} ${cy - 40} ${cx} ${cy - 40} Q ${cx + 14} ${cy - 40} ${cx + 14} ${cy - 24} L ${cx + 14} ${cy - 8}`}
fill="none"
stroke="#cbd5e1"
strokeWidth="3"
/>
{/* Keyhole */}
<circle cx={cx} cy={cy + 6} r="5" fill={accent} />
<rect x={cx - 2} y={cy + 9} width="4" height="10" rx="1" fill={accent} />
{/* Chain links */}
{[-42, 42].map((offset) => (
<g key={offset}>
<ellipse
cx={cx + offset}
cy={cy + 12}
rx="10"
ry="6"
fill="none"
stroke="#cbd5e1"
strokeWidth="1.5"
/>
<ellipse
cx={cx + offset + (offset > 0 ? 14 : -14)}
cy={cy + 12}
rx="10"
ry="6"
fill="none"
stroke="#cbd5e1"
strokeWidth="1.5"
/>
</g>
))}
</g>
);
}
function renderChart(cx: number, cy: number, accent: string) {
const barWidth = 14;
const heights = [30, 50, 38, 55, 42];
const baseY = cy + 35;
return (
<g>
{/* Bars */}
{heights.map((h, i) => (
<rect
key={i}
x={cx - 48 + i * 22}
y={baseY - h}
width={barWidth}
height={h}
rx="2"
fill={i === 3 ? accent : "#e2e8f0"}
opacity={i === 3 ? 0.8 : 0.6}
/>
))}
{/* Base line */}
<line
x1={cx - 55}
y1={baseY}
x2={cx + 60}
y2={baseY}
stroke="#cbd5e1"
strokeWidth="1.5"
/>
{/* Eye with strikethrough */}
<ellipse
cx={cx}
cy={cy - 30}
rx="22"
ry="14"
fill="none"
stroke={accent}
strokeWidth="2"
/>
<circle cx={cx} cy={cy - 30} r="6" fill={accent} opacity="0.5" />
<line
x1={cx - 28}
y1={cy - 18}
x2={cx + 28}
y2={cy - 42}
stroke={accent}
strokeWidth="2.5"
strokeLinecap="round"
/>
</g>
);
}
function renderLeaf(cx: number, cy: number, accent: string) {
return (
<g>
{/* Leaf shape */}
<path
d={`M ${cx} ${cy + 50} Q ${cx - 50} ${cy + 20} ${cx - 45} ${cy - 30} Q ${cx - 40} ${cy - 55} ${cx} ${cy - 50} Q ${cx + 40} ${cy - 55} ${cx + 45} ${cy - 30} Q ${cx + 50} ${cy + 20} ${cx} ${cy + 50} Z`}
fill="none"
stroke={accent}
strokeWidth="2.5"
opacity="0.7"
/>
<path
d={`M ${cx} ${cy + 50} Q ${cx - 50} ${cy + 20} ${cx - 45} ${cy - 30} Q ${cx - 40} ${cy - 55} ${cx} ${cy - 50} Q ${cx + 40} ${cy - 55} ${cx + 45} ${cy - 30} Q ${cx + 50} ${cy + 20} ${cx} ${cy + 50} Z`}
fill={accent}
opacity="0.08"
/>
{/* Stem/vein */}
<line
x1={cx}
y1={cy + 50}
x2={cx}
y2={cy - 40}
stroke={accent}
strokeWidth="1.5"
opacity="0.5"
/>
{/* Circuit nodes on leaf veins */}
{[-20, 0, 20].map((offset, i) => (
<g key={i}>
<line
x1={cx}
y1={cy + offset}
x2={cx + (i % 2 === 0 ? 20 : -20)}
y2={cy + offset - 10}
stroke={accent}
strokeWidth="1"
opacity="0.4"
/>
<circle
cx={cx + (i % 2 === 0 ? 20 : -20)}
cy={cy + offset - 10}
r="3"
fill={accent}
opacity="0.5"
/>
</g>
))}
</g>
);
}
function renderPrice(cx: number, cy: number, accent: string) {
return (
<g>
{/* Tag shape */}
<path
d={`M ${cx - 30} ${cy - 40} L ${cx + 30} ${cy - 40} L ${cx + 30} ${cy + 20} L ${cx} ${cy + 45} L ${cx - 30} ${cy + 20} Z`}
fill="#f8fafc"
stroke="#cbd5e1"
strokeWidth="2"
/>
{/* Hole */}
<circle
cx={cx}
cy={cy - 26}
r="6"
fill="none"
stroke="#cbd5e1"
strokeWidth="1.5"
/>
{/* Equals sign */}
<line
x1={cx - 14}
y1={cy}
x2={cx + 14}
y2={cy}
stroke={accent}
strokeWidth="3.5"
strokeLinecap="round"
/>
<line
x1={cx - 14}
y1={cy + 12}
x2={cx + 14}
y2={cy + 12}
stroke={accent}
strokeWidth="3.5"
strokeLinecap="round"
/>
</g>
);
}
function renderPrototype(cx: number, cy: number, accent: string) {
return (
<g>
{/* Browser frame */}
<rect
x={cx - 48}
y={cy - 42}
width="96"
height="76"
rx="6"
fill="#f8fafc"
stroke="#cbd5e1"
strokeWidth="2"
/>
{/* Title bar */}
<line
x1={cx - 48}
y1={cy - 28}
x2={cx + 48}
y2={cy - 28}
stroke="#cbd5e1"
strokeWidth="1.5"
/>
{/* Dots */}
<circle cx={cx - 36} cy={cy - 35} r="2.5" fill="#fca5a5" />
<circle cx={cx - 28} cy={cy - 35} r="2.5" fill="#fde68a" />
<circle cx={cx - 20} cy={cy - 35} r="2.5" fill="#86efac" />
{/* Wireframe lines */}
<rect
x={cx - 38}
y={cy - 20}
width="76"
height="8"
rx="2"
fill="#e2e8f0"
/>
<rect
x={cx - 38}
y={cy - 6}
width="50"
height="5"
rx="1.5"
fill="#e2e8f0"
/>
<rect
x={cx - 38}
y={cy + 5}
width="60"
height="5"
rx="1.5"
fill="#e2e8f0"
/>
{/* Accent wireframe block */}
<rect
x={cx - 38}
y={cy + 16}
width="32"
height="14"
rx="3"
fill="none"
stroke={accent}
strokeWidth="1.5"
strokeDasharray="3 2"
/>
<rect
x={cx + 2}
y={cy + 16}
width="36"
height="14"
rx="3"
fill={accent}
opacity="0.12"
stroke={accent}
strokeWidth="1"
/>
</g>
);
}
function renderGear(cx: number, cy: number, accent: string) {
const teeth = 8;
const innerR = 24;
const outerR = 36;
const toothWidth = 0.2;
let d = "";
for (let i = 0; i < teeth; i++) {
const angle = (i * 2 * Math.PI) / teeth;
const a1 = angle - toothWidth;
const a2 = angle + toothWidth;
const _midAngle = (a1 + a2) / 2;
if (i === 0) {
d += `M ${cx + outerR * Math.cos(a1)} ${cy + outerR * Math.sin(a1)} `;
}
d += `L ${cx + outerR * Math.cos(a2)} ${cy + outerR * Math.sin(a2)} `;
const nextAngle = ((i + 1) * 2 * Math.PI) / teeth;
const na1 = nextAngle - toothWidth;
d += `L ${cx + innerR * Math.cos(a2)} ${cy + innerR * Math.sin(a2)} `;
d += `L ${cx + innerR * Math.cos(na1)} ${cy + innerR * Math.sin(na1)} `;
d += `L ${cx + outerR * Math.cos(na1)} ${cy + outerR * Math.sin(na1)} `;
}
d += "Z";
return (
<g>
<path d={d} fill="#f1f5f9" stroke="#cbd5e1" strokeWidth="1.5" />
<circle
cx={cx}
cy={cy}
r="10"
fill="white"
stroke="#cbd5e1"
strokeWidth="1.5"
/>
{/* Infinity loop */}
<path
d={`M ${cx} ${cy} C ${cx - 20} ${cy - 18} ${cx - 28} ${cy + 18} ${cx} ${cy} C ${cx + 28} ${cy - 18} ${cx + 20} ${cy + 18} ${cx} ${cy}`}
fill="none"
stroke={accent}
strokeWidth="2.5"
strokeLinecap="round"
/>
</g>
);
}
function renderHourglass(cx: number, cy: number, accent: string) {
return (
<g>
{/* Top triangle */}
<path
d={`M ${cx - 32} ${cy - 50} L ${cx + 32} ${cy - 50} L ${cx + 4} ${cy} Z`}
fill="#f8fafc"
stroke="#cbd5e1"
strokeWidth="2"
/>
<path
d={`M ${cx - 32} ${cy - 50} L ${cx + 32} ${cy - 50} L ${cx + 4} ${cy} Z`}
fill={accent}
opacity="0.08"
/>
{/* Bottom triangle */}
<path
d={`M ${cx - 4} ${cy} L ${cx + 32} ${cy + 50} L ${cx - 32} ${cy + 50} Z`}
fill="#f8fafc"
stroke="#cbd5e1"
strokeWidth="2"
/>
{/* "Sand" fill in bottom */}
<path
d={`M ${cx - 4} ${cy + 14} L ${cx + 18} ${cy + 50} L ${cx - 18} ${cy + 50} Z`}
fill={accent}
opacity="0.2"
/>
{/* Top and bottom lines */}
<line
x1={cx - 38}
y1={cy - 50}
x2={cx + 38}
y2={cy - 50}
stroke="#0f172a"
strokeWidth="2.5"
strokeLinecap="round"
/>
<line
x1={cx - 38}
y1={cy + 50}
x2={cx + 38}
y2={cy + 50}
stroke="#0f172a"
strokeWidth="2.5"
strokeLinecap="round"
/>
{/* Circuit nodes as "digital sand" */}
{[10, 22, 34].map((y, i) => (
<circle
key={i}
cx={cx + (i - 1) * 6}
cy={cy + y}
r="2"
fill={accent}
opacity={0.5 + i * 0.15}
/>
))}
</g>
);
}
function renderCode(cx: number, cy: number, accent: string) {
return (
<g>
{/* < */}
<polyline
points={`${cx - 20},${cy - 24} ${cx - 46},${cy} ${cx - 20},${cy + 24}`}
fill="none"
stroke={accent}
strokeWidth="5"
strokeLinecap="round"
strokeLinejoin="round"
/>
{/* / */}
<line
x1={cx + 8}
y1={cy - 30}
x2={cx - 8}
y2={cy + 30}
stroke="#cbd5e1"
strokeWidth="3.5"
strokeLinecap="round"
/>
{/* > */}
<polyline
points={`${cx + 20},${cy - 24} ${cx + 46},${cy} ${cx + 20},${cy + 24}`}
fill="none"
stroke={accent}
strokeWidth="5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
);
}
function renderResponsive(cx: number, cy: number, accent: string) {
return (
<g>
{/* Outer desktop */}
<rect
x={cx - 52}
y={cy - 38}
width="104"
height="68"
rx="5"
fill="none"
stroke="#cbd5e1"
strokeWidth="2"
/>
{/* Tablet */}
<rect
x={cx - 30}
y={cy - 24}
width="60"
height="48"
rx="4"
fill="none"
stroke="#94a3b8"
strokeWidth="1.5"
/>
{/* Phone */}
<rect
x={cx - 14}
y={cy - 14}
width="28"
height="42"
rx="4"
fill="none"
stroke={accent}
strokeWidth="2"
/>
{/* Phone screen */}
<rect
x={cx - 10}
y={cy - 8}
width="20"
height="28"
rx="1"
fill={accent}
opacity="0.1"
/>
{/* Phone home button */}
<circle cx={cx} cy={cy + 22} r="2" fill={accent} opacity="0.5" />
</g>
);
}
function renderServer(cx: number, cy: number, accent: string) {
const unitH = 22;
return (
<g>
{/* Server units */}
{[0, 1, 2].map((i) => (
<g key={i}>
<rect
x={cx - 40}
y={cy - 36 + i * (unitH + 4)}
width="80"
height={unitH}
rx="3"
fill="#f8fafc"
stroke="#cbd5e1"
strokeWidth="1.5"
/>
{/* Status LEDs */}
<circle
cx={cx - 28}
cy={cy - 36 + i * (unitH + 4) + unitH / 2}
r="3"
fill={i === 0 ? accent : "#cbd5e1"}
/>
<circle
cx={cx - 18}
cy={cy - 36 + i * (unitH + 4) + unitH / 2}
r="3"
fill={i <= 1 ? accent : "#cbd5e1"}
opacity={i <= 1 ? 0.6 : 1}
/>
{/* Drive bays */}
{[0, 1, 2, 3].map((j) => (
<rect
key={j}
x={cx + 4 + j * 10}
y={cy - 36 + i * (unitH + 4) + 5}
width="6"
height={unitH - 10}
rx="1"
fill="#e2e8f0"
/>
))}
</g>
))}
{/* Signal waves */}
{[10, 18, 26].map((r, i) => (
<path
key={i}
d={`M ${cx + 42 + r} ${cy - 12} Q ${cx + 42 + r + 6} ${cy} ${cx + 42 + r} ${cy + 12}`}
fill="none"
stroke={accent}
strokeWidth="1.5"
opacity={0.7 - i * 0.2}
/>
))}
</g>
);
}
function renderTemplate(cx: number, cy: number, accent: string) {
return (
<g>
{/* Grid layout */}
{[0, 1, 2, 3].map((i) => {
const row = Math.floor(i / 2);
const col = i % 2;
return (
<rect
key={i}
x={cx - 44 + col * 48}
y={cy - 36 + row * 40}
width="40"
height="32"
rx="4"
fill="#f1f5f9"
stroke="#cbd5e1"
strokeWidth="1.5"
/>
);
})}
{/* Strikethrough diagonal */}
<line
x1={cx - 52}
y1={cy + 44}
x2={cx + 52}
y2={cy - 44}
stroke={accent}
strokeWidth="3.5"
strokeLinecap="round"
/>
<line
x1={cx - 52}
y1={cy + 40}
x2={cx + 52}
y2={cy - 48}
stroke="white"
strokeWidth="1"
opacity="0.5"
/>
</g>
);
}
function renderSync(cx: number, cy: number, accent: string) {
const r = 36;
return (
<g>
{/* Circular arrows */}
<path
d={`M ${cx + r} ${cy} A ${r} ${r} 0 0 1 ${cx - r} ${cy}`}
fill="none"
stroke={accent}
strokeWidth="3"
strokeLinecap="round"
/>
<path
d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}`}
fill="none"
stroke="#cbd5e1"
strokeWidth="3"
strokeLinecap="round"
/>
{/* Arrowheads */}
<polygon
points={`${cx - r - 6},${cy - 6} ${cx - r + 6},${cy - 6} ${cx - r},${cy + 2}`}
fill={accent}
/>
<polygon
points={`${cx + r - 6},${cy + 6} ${cx + r + 6},${cy + 6} ${cx + r},${cy - 2}`}
fill="#cbd5e1"
/>
{/* Data dots flowing */}
<circle
cx={cx + 18}
cy={cy - r + 8}
r="3.5"
fill={accent}
opacity="0.7"
/>
<circle
cx={cx - 18}
cy={cy + r - 8}
r="3.5"
fill="#94a3b8"
opacity="0.7"
/>
{/* Center node */}
<circle
cx={cx}
cy={cy}
r="8"
fill="white"
stroke="#cbd5e1"
strokeWidth="1.5"
/>
<circle cx={cx} cy={cy} r="3" fill={accent} />
</g>
);
}
// ─── Icon dispatcher ──────────────────────────────────────────────
const iconRenderers: Record<
ThumbnailIcon,
(_cx: number, _cy: number, _accent: string) => React.ReactNode
> = {
gauge: renderGauge,
bottleneck: renderBottleneck,
plugin: renderPlugin,
shield: renderShield,
cookie: renderCookie,
cloud: renderCloud,
lock: renderLock,
chart: renderChart,
leaf: renderLeaf,
price: renderPrice,
prototype: renderPrototype,
gear: renderGear,
hourglass: renderHourglass,
code: renderCode,
responsive: renderResponsive,
server: renderServer,
template: renderTemplate,
sync: renderSync,
};
// ─── Main Component ──────────────────────────────────────────────
export const BlogThumbnailSVG: React.FC<BlogThumbnailSVGProps> = ({
slug,
variant = "square",
className,
}) => {
const config = blogThumbnails[slug];
if (!config) return null;
const isBanner = variant === "banner";
const vbWidth = isBanner ? 480 : 240;
const vbHeight = isBanner ? 160 : 240;
// For banner, we shift the icon more to the right
const iconCx = isBanner ? 340 : vbWidth / 2;
const iconCy = vbHeight / 2;
return (
<svg
viewBox={`0 0 ${vbWidth} ${vbHeight}`}
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
role="img"
aria-label={`Illustration: ${config.keyword}`}
preserveAspectRatio="xMidYMid slice"
>
{/* Background */}
<rect
width={vbWidth}
height={vbHeight}
fill="#f8fafc"
rx={isBanner ? 8 : 12}
/>
<GridPattern size={30} />
<rect
width={vbWidth}
height={vbHeight}
fill="url(#grid)"
rx={isBanner ? 8 : 12}
/>
{/* Accent strip at top */}
<rect
width={vbWidth}
height="3"
fill={config.accent}
opacity="0.6"
rx="1.5"
/>
{/* Decorative circuit lines */}
<line
x1="0"
y1={vbHeight - 1}
x2={vbWidth}
y2={vbHeight - 1}
stroke="#e2e8f0"
strokeWidth="1"
/>
{/* Corner markers */}
<g opacity="0.25">
<line
x1="12"
y1="16"
x2="28"
y2="16"
stroke="#94a3b8"
strokeWidth="1"
/>
<line
x1="12"
y1="16"
x2="12"
y2="32"
stroke="#94a3b8"
strokeWidth="1"
/>
<line
x1={vbWidth - 12}
y1="16"
x2={vbWidth - 28}
y2="16"
stroke="#94a3b8"
strokeWidth="1"
/>
<line
x1={vbWidth - 12}
y1="16"
x2={vbWidth - 12}
y2="32"
stroke="#94a3b8"
strokeWidth="1"
/>
</g>
{/* Icon - Scale down for banner to fit height better */}
<g
transform={
isBanner
? `translate(${iconCx}, ${iconCy}) scale(0.85) translate(-${iconCx}, -${iconCy})`
: ""
}
>
{iconRenderers[config.icon](iconCx, iconCy, config.accent)}
</g>
{/* Keyword label */}
<text
x="18"
y={vbHeight - 16}
fontSize="9"
fontFamily="ui-monospace, monospace"
fontWeight="700"
fill="#94a3b8"
letterSpacing="0.15em"
>
{config.keyword}
</text>
{/* Accent dot */}
<circle
cx={vbWidth - 18}
cy={vbHeight - 18}
r="3"
fill={config.accent}
opacity="0.6"
/>
</svg>
);
};