feat: certificates, landing pages, ai tutor, website update, compliance
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import type { CertificateDesign, CertificateRenderData } from '../types';
|
||||
|
||||
export function escapeHtml(input: unknown): string {
|
||||
return String(input ?? '').replace(/[&<>"']/g, (char) => {
|
||||
switch (char) {
|
||||
case '&':
|
||||
return '&';
|
||||
case '<':
|
||||
return '<';
|
||||
case '>':
|
||||
return '>';
|
||||
case '"':
|
||||
return '"';
|
||||
default:
|
||||
return ''';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function getYear(value: string | undefined | null): string {
|
||||
const match = String(value ?? '').match(/\b(19|20|21)\d{2}\b/);
|
||||
if (match) return match[0];
|
||||
|
||||
return String(new Date().getFullYear());
|
||||
}
|
||||
|
||||
export function shadeColor(hex: string, percent: number): string {
|
||||
const normalized = hex.startsWith('#') ? hex.slice(1) : hex;
|
||||
if (normalized.length !== 6) return hex;
|
||||
|
||||
const numeric = parseInt(normalized, 16);
|
||||
if (Number.isNaN(numeric)) return hex;
|
||||
|
||||
const offset = Math.round((percent / 100) * 255);
|
||||
const clamp = (value: number) => Math.max(0, Math.min(255, value));
|
||||
const red = clamp(((numeric >> 16) & 0xff) + offset);
|
||||
const green = clamp(((numeric >> 8) & 0xff) + offset);
|
||||
const blue = clamp((numeric & 0xff) + offset);
|
||||
const next = (red << 16) | (green << 8) | blue;
|
||||
|
||||
return '#' + next.toString(16).padStart(6, '0');
|
||||
}
|
||||
|
||||
export interface TemplateRenderArgs {
|
||||
design: CertificateDesign;
|
||||
data: CertificateRenderData;
|
||||
}
|
||||
|
||||
export interface TemplateRenderOutput {
|
||||
body: string;
|
||||
styles: string;
|
||||
}
|
||||
|
||||
export type TemplateRenderer = (args: TemplateRenderArgs) => TemplateRenderOutput;
|
||||
|
||||
export const FONTS_LINK_HREF =
|
||||
'https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&family=Cinzel:wght@400;500;600;700&family=DM+Mono:wght@400;500&family=Bodoni+Moda:ital,wght@0,400;0,700;1,400&family=Archivo+Black&family=JetBrains+Mono:wght@400;500&family=Playfair+Display:ital,wght@0,400;0,700;0,900;1,400&family=Space+Grotesk:wght@400;500;700&display=swap';
|
||||
|
||||
export const BASE_STYLES = `
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html, body { width: 1100px; height: 780px; background: transparent; }
|
||||
body { -webkit-font-smoothing: antialiased; }
|
||||
.cert { width: 1100px; height: 780px; position: relative; overflow: hidden; }
|
||||
`;
|
||||
Reference in New Issue
Block a user