@nanggo/social-preview
Version:
Generate beautiful social media preview images from any URL
253 lines (239 loc) • 8.89 kB
JavaScript
"use strict";
/**
* Classic Template
* A traditional, business-friendly design with serif typography and conservative layout
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.classicTemplate = void 0;
exports.generateClassicOverlay = generateClassicOverlay;
const utils_1 = require("../utils");
const validators_1 = require("../utils/validators");
const fonts_1 = require("../constants/fonts");
const shared_1 = require("./shared");
/**
* Classic template configuration
*/
exports.classicTemplate = {
name: 'classic',
layout: {
padding: 60,
titlePosition: 'left',
descriptionPosition: 'below-title',
imagePosition: 'right',
logoPosition: 'top-left',
},
typography: {
title: {
fontSize: 48,
fontWeight: '700',
lineHeight: 1.3,
maxLines: 3,
},
description: {
fontSize: 24,
fontWeight: '400',
lineHeight: 1.5,
maxLines: 3,
},
siteName: {
fontSize: 18,
fontWeight: '600',
},
},
effects: {
gradient: {
type: 'linear',
colors: ['rgba(0,0,0,0.1)', 'rgba(0,0,0,0.05)'],
direction: '0deg',
opacity: 0.8,
},
blur: {
radius: 0,
areas: 'none',
},
shadow: {
text: false,
box: true,
},
borderRadius: 8,
},
imageProcessing: {
brightness: 0.9,
requiresTransparentCanvas: true,
},
overlayGenerator: generateClassicOverlay,
};
/**
* Generate classic template SVG overlay
*/
function generateClassicOverlay(metadata, width, height, options = {}, template = exports.classicTemplate) {
const padding = template.layout.padding;
const textColor = (0, validators_1.validateColor)(options.colors?.text || '#1a1a1a');
const accentColor = (0, validators_1.validateColor)(options.colors?.accent || '#2c5aa0');
const backgroundColor = (0, validators_1.validateColor)(options.colors?.background || '#ffffff');
// Typography settings
const titleFontSize = template.typography.title.fontSize;
const titleLineHeight = template.typography.title.lineHeight || 1.3;
const descFontSize = template.typography.description?.fontSize || 24;
const descLineHeight = template.typography.description?.lineHeight || 1.5;
const siteNameFontSize = template.typography.siteName?.fontSize || 18;
// Layout calculations
const contentWidth = Math.floor((width - padding * 3) * 0.6); // 60% for text, 40% for image
const imageWidth = width - contentWidth - padding * 3;
// Text wrapping
const titleLines = (0, utils_1.wrapText)(metadata.title, contentWidth, titleFontSize, template.typography.title.maxLines || 3, 'default');
const descLines = metadata.description
? (0, utils_1.wrapText)(metadata.description, contentWidth, descFontSize, template.typography.description?.maxLines || 3, 'default')
: [];
// Vertical positioning
const headerHeight = 40;
const contentStartY = headerHeight + padding;
const titleStartY = contentStartY + titleFontSize;
const descStartY = titleStartY + titleLines.length * titleFontSize * titleLineHeight + 20;
return `
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
<defs>
${(0, shared_1.createSvgStyleCdata)(`
.classic-title {
font-family: ${fonts_1.SERIF_FONT_STACK};
font-size: ${titleFontSize}px;
font-weight: 700;
fill: ${textColor};
line-height: ${titleLineHeight};
}
.classic-description {
font-family: ${fonts_1.SERIF_FONT_STACK};
font-size: ${descFontSize}px;
font-weight: 400;
fill: ${(0, utils_1.adjustBrightness)(textColor, 20)};
opacity: 0.9;
line-height: ${descLineHeight};
}
.classic-sitename {
font-family: ${fonts_1.SYSTEM_FONT_STACK};
font-size: ${siteNameFontSize}px;
font-weight: 600;
fill: ${accentColor};
text-transform: uppercase;
letter-spacing: 0.1em;
}
.classic-domain {
font-family: ${fonts_1.SYSTEM_FONT_STACK};
font-size: 16px;
font-weight: 400;
fill: ${(0, utils_1.adjustBrightness)(textColor, 40)};
opacity: 0.7;
}
`)}
<!-- Background pattern for texture -->
<pattern id="classicPattern" x="0" y="0" width="100" height="100" patternUnits="userSpaceOnUse">
<rect width="100" height="100" fill="${backgroundColor}"/>
<circle cx="50" cy="50" r="1" fill="${accentColor}" opacity="0.03"/>
</pattern>
<!-- Subtle gradient overlay -->
<linearGradient id="classicGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:${backgroundColor};stop-opacity:1" />
<stop offset="100%" style="stop-color:${(0, utils_1.adjustBrightness)(backgroundColor, -5)};stop-opacity:1" />
</linearGradient>
<!-- Box shadow filter -->
<filter id="classicShadow" x="-50%" y="-50%" width="200%" height="200%">
<feDropShadow dx="0" dy="2" stdDeviation="4" flood-color="rgba(0,0,0,0.1)"/>
</filter>
</defs>
<!-- Background with pattern -->
${metadata.image ? '' : `<rect width="${width}" height="${height}" fill="url(#classicPattern)"/>`}
<!-- Main content background with shadow -->
<rect
x="${padding - 10}"
y="${headerHeight}"
width="${contentWidth + 20}"
height="${height - headerHeight - padding}"
fill="url(#classicGradient)"
rx="8"
filter="url(#classicShadow)"
opacity="0.95"
/>
<!-- Top accent line -->
<rect x="${padding}" y="${headerHeight}" width="${contentWidth}" height="3" fill="${accentColor}"/>
<!-- Site name in header -->
${metadata.siteName
? `
<text x="${padding}" y="${headerHeight - 10}" class="classic-sitename">
${(0, utils_1.escapeXml)(metadata.siteName)}
</text>
`
: ''}
<!-- Title -->
${titleLines
.map((line, index) => `
<text
x="${padding}"
y="${titleStartY + index * titleFontSize * titleLineHeight}"
class="classic-title"
>
${(0, utils_1.escapeXml)(line)}
</text>
`)
.join('')}
<!-- Description -->
${descLines.length > 0
? descLines
.map((line, index) => `
<text
x="${padding}"
y="${descStartY + index * descFontSize * descLineHeight}"
class="classic-description"
>
${(0, utils_1.escapeXml)(line)}
</text>
`)
.join('')
: ''}
<!-- Bottom section with domain -->
<g transform="translate(${padding}, ${height - 40})">
${metadata.domain
? `
<text x="0" y="0" class="classic-domain">
${(0, utils_1.escapeXml)(metadata.domain)}
</text>
`
: ''}
<!-- Decorative element -->
<rect x="0" y="10" width="40" height="2" fill="${accentColor}" opacity="0.6"/>
</g>
<!-- Image placeholder area (if no background image) -->
${!metadata.image
? `
<g transform="translate(${width - imageWidth - padding}, ${contentStartY})">
<!-- Image placeholder -->
<rect
x="0" y="0"
width="${imageWidth}"
height="${Math.min(imageWidth * 0.6, height - contentStartY - padding)}"
fill="${(0, utils_1.adjustBrightness)(accentColor, -20)}"
opacity="0.1"
rx="8"
/>
<text
x="${imageWidth / 2}"
y="${Math.min(imageWidth * 0.6, height - contentStartY - padding) / 2}"
text-anchor="middle"
font-family="sans-serif"
font-size="14"
fill="${accentColor}"
opacity="0.4"
>
${metadata.siteName ? (0, utils_1.escapeXml)(metadata.siteName) : 'IMAGE'}
</text>
</g>
`
: ''}
<!-- Corner accent -->
<g transform="translate(${width - 30}, 20)">
<rect x="0" y="0" width="20" height="3" fill="${accentColor}" opacity="0.8"/>
<rect x="0" y="6" width="15" height="2" fill="${accentColor}" opacity="0.6"/>
<rect x="0" y="10" width="10" height="2" fill="${accentColor}" opacity="0.4"/>
</g>
</svg>
`;
}