@nanggo/social-preview
Version:
Generate beautiful social media preview images from any URL
227 lines (217 loc) • 7.5 kB
JavaScript
"use strict";
/**
* Minimal Template
* A clean, modern minimalist design focusing on simplicity and readability
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.minimalTemplate = void 0;
exports.generateMinimalOverlay = generateMinimalOverlay;
const utils_1 = require("../utils");
const validators_1 = require("../utils/validators");
const fonts_1 = require("../constants/fonts");
const shared_1 = require("./shared");
/**
* Minimal template configuration
*/
exports.minimalTemplate = {
name: 'minimal',
layout: {
padding: 100,
titlePosition: 'center',
descriptionPosition: 'below-title',
imagePosition: 'none',
logoPosition: 'bottom-center',
},
typography: {
title: {
fontSize: 64,
fontWeight: '300',
lineHeight: 1.1,
maxLines: 2,
},
description: {
fontSize: 26,
fontWeight: '300',
lineHeight: 1.6,
maxLines: 2,
},
siteName: {
fontSize: 16,
fontWeight: '500',
},
},
effects: {
gradient: {
type: 'none',
colors: [],
direction: '0deg',
opacity: 0,
},
blur: {
radius: 0,
areas: 'none',
},
shadow: {
text: false,
box: false,
},
borderRadius: 0,
},
imageProcessing: {
brightness: 1.0,
requiresTransparentCanvas: true,
},
overlayGenerator: generateMinimalOverlay,
};
/**
* Generate minimal template SVG overlay
*/
function generateMinimalOverlay(metadata, width, height, options = {}, template = exports.minimalTemplate) {
const padding = template.layout.padding;
const textColor = (0, validators_1.validateColor)(options.colors?.text || '#000000');
const accentColor = (0, validators_1.validateColor)(options.colors?.accent || '#000000');
const backgroundColor = (0, validators_1.validateColor)(options.colors?.background || '#ffffff');
// Typography settings with minimal approach
const titleFontSize = template.typography.title.fontSize;
const titleLineHeight = template.typography.title.lineHeight || 1.1;
const descFontSize = template.typography.description?.fontSize || 26;
const descLineHeight = template.typography.description?.lineHeight || 1.6;
const siteNameFontSize = template.typography.siteName?.fontSize || 16;
// Text wrapping with generous spacing
const maxTextWidth = width - padding * 2;
const titleLines = (0, utils_1.wrapText)(metadata.title, maxTextWidth, titleFontSize, template.typography.title.maxLines || 2, 'inter');
const descLines = metadata.description
? (0, utils_1.wrapText)(metadata.description, maxTextWidth, descFontSize, template.typography.description?.maxLines || 2, 'inter')
: [];
const TITLE_DESCRIPTION_GAP = 60;
const layout = (0, shared_1.layoutCenteredTitleDescription)({
height,
titleLineCount: titleLines.length,
titleFontSize,
titleLineHeight,
descLineCount: descLines.length,
descFontSize,
descLineHeight,
gap: TITLE_DESCRIPTION_GAP,
});
const titleStartY = layout.titleStartY;
const descStartY = layout.descStartY;
return `
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
<defs>
${(0, shared_1.createSvgStyleCdata)(`
.minimal-title {
font-family: ${fonts_1.SYSTEM_FONT_STACK};
font-size: ${titleFontSize}px;
font-weight: 300;
fill: ${textColor};
letter-spacing: -0.03em;
line-height: ${titleLineHeight};
}
.minimal-description {
font-family: ${fonts_1.SYSTEM_FONT_STACK};
font-size: ${descFontSize}px;
font-weight: 300;
fill: ${textColor};
opacity: 0.7;
letter-spacing: -0.01em;
line-height: ${descLineHeight};
}
.minimal-sitename {
font-family: ${fonts_1.SYSTEM_FONT_STACK};
font-size: ${siteNameFontSize}px;
font-weight: 500;
fill: ${accentColor};
text-transform: uppercase;
letter-spacing: 0.2em;
opacity: 0.6;
}
.minimal-domain {
font-family: ${fonts_1.SYSTEM_FONT_STACK};
font-size: 14px;
font-weight: 400;
fill: ${textColor};
opacity: 0.4;
letter-spacing: 0.05em;
}
`)}
</defs>
<!-- Clean background -->
<rect width="${width}" height="${height}" fill="${backgroundColor}"/>
<!-- Title - centered with generous spacing -->
${titleLines
.map((line, index) => `
<text
x="${width / 2}"
y="${titleStartY + index * titleFontSize * titleLineHeight}"
class="minimal-title"
text-anchor="middle"
>
${(0, utils_1.escapeXml)(line)}
</text>
`)
.join('')}
<!-- Description - if present -->
${descLines.length > 0
? descLines
.map((line, index) => `
<text
x="${width / 2}"
y="${descStartY + index * descFontSize * descLineHeight}"
class="minimal-description"
text-anchor="middle"
>
${(0, utils_1.escapeXml)(line)}
</text>
`)
.join('')
: ''}
<!-- Bottom section - minimal branding -->
<g transform="translate(${width / 2}, ${height - 80})">
${metadata.siteName
? `
<text x="0" y="0" class="minimal-sitename" text-anchor="middle">
${(0, utils_1.escapeXml)(metadata.siteName.toUpperCase())}
</text>
`
: ''}
${metadata.domain
? `
<text x="0" y="25" class="minimal-domain" text-anchor="middle">
${(0, utils_1.escapeXml)(metadata.domain)}
</text>
`
: ''}
<!-- Minimal geometric accent -->
<g transform="translate(0, ${metadata.siteName || metadata.domain ? 15 : -10})">
<circle cx="-20" cy="0" r="1.5" fill="${accentColor}" opacity="0.3"/>
<circle cx="0" cy="0" r="1.5" fill="${accentColor}" opacity="0.6"/>
<circle cx="20" cy="0" r="1.5" fill="${accentColor}" opacity="0.3"/>
</g>
</g>
<!-- Subtle corner elements for balance -->
<g opacity="0.1">
<!-- Top left -->
<rect x="40" y="40" width="2" height="20" fill="${accentColor}"/>
<rect x="40" y="40" width="20" height="2" fill="${accentColor}"/>
<!-- Bottom right -->
<rect x="${width - 62}" y="${height - 60}" width="2" height="20" fill="${accentColor}"/>
<rect x="${width - 60}" y="${height - 42}" width="20" height="2" fill="${accentColor}"/>
</g>
<!-- Optional divider line above bottom section -->
${metadata.siteName || metadata.domain
? `
<line
x1="${width / 2 - 30}"
y1="${height - 120}"
x2="${width / 2 + 30}"
y2="${height - 120}"
stroke="${accentColor}"
stroke-width="1"
opacity="0.2"
/>
`
: ''}
</svg>
`;
}