-
Education
+
+ Education
+
{education.map(ed => (
diff --git a/cv-engine/src/templates/registry.ts b/cv-engine/src/templates/registry.ts
index 0021b78..fd8d7f4 100644
--- a/cv-engine/src/templates/registry.ts
+++ b/cv-engine/src/templates/registry.ts
@@ -9,6 +9,8 @@ import modernThumb from '../assets/templates/modern.svg';
import minimalThumb from '../assets/templates/minimal.svg';
import timelineThumb from '../assets/templates/timeline.svg';
import type { CV } from '../schema/cvSchema';
+import type { ColorTheme } from '../types/colors';
+import { generateThumbnailDataUrl, type TemplateId } from '../utils/thumbnailGenerator';
import React from 'react';
export type TemplateCategory = 'ATS' | 'Visual';
@@ -30,4 +32,26 @@ export const templatesRegistry: TemplateMeta[] = [
{ id: 'timeline', name: 'Timeline', category: 'Visual', thumbnail: timelineThumb, component: TimelineTemplate, supportsPhoto: false },
];
-export const templatesMap = Object.fromEntries(templatesRegistry.map(t => [t.id, t]));
\ No newline at end of file
+export const templatesMap = Object.fromEntries(templatesRegistry.map(t => [t.id, t]));
+
+// Function to get template thumbnail with color theme applied
+export const getTemplateThumbnail = (templateId: string, colorTheme: ColorTheme): string => {
+ const template = templatesMap[templateId];
+ if (!template) {
+ return '';
+ }
+
+ // For now, use dynamic generation for supported templates
+ const supportedTemplates: TemplateId[] = ['ats', 'classic', 'modern', 'minimal', 'timeline'];
+ if (supportedTemplates.includes(templateId as TemplateId)) {
+ try {
+ return generateThumbnailDataUrl(templateId as TemplateId, colorTheme);
+ } catch (error) {
+ console.warn(`Failed to generate thumbnail for ${templateId}:`, error);
+ return template.thumbnail; // Fallback to static thumbnail
+ }
+ }
+
+ // Fallback to static thumbnail for unsupported templates
+ return template.thumbnail;
+};
\ No newline at end of file
diff --git a/cv-engine/src/types/colors.ts b/cv-engine/src/types/colors.ts
new file mode 100644
index 0000000..26dd712
--- /dev/null
+++ b/cv-engine/src/types/colors.ts
@@ -0,0 +1,157 @@
+export type ColorTheme =
+ | 'gray'
+ | 'dark-gray'
+ | 'red'
+ | 'orange'
+ | 'yellow'
+ | 'green'
+ | 'blue'
+ | 'indigo'
+ | 'black'
+ | null;
+
+export interface ColorPalette {
+ primary: string; // Main accent color
+ secondary: string; // Secondary accent color
+ accent: string; // Light accent/background
+ text: string; // Primary text color
+ textSecondary: string; // Secondary text color
+ border: string; // Border color
+ background: string; // Background color
+}
+
+export const colorThemes: Record, ColorPalette> = {
+ 'gray': {
+ primary: '#6b7280',
+ secondary: '#9ca3af',
+ accent: '#f3f4f6',
+ text: '#1f2937',
+ textSecondary: '#6b7280',
+ border: '#e5e7eb',
+ background: '#ffffff'
+ },
+ 'dark-gray': {
+ primary: '#374151',
+ secondary: '#6b7280',
+ accent: '#f9fafb',
+ text: '#111827',
+ textSecondary: '#4b5563',
+ border: '#d1d5db',
+ background: '#ffffff'
+ },
+ 'red': {
+ primary: '#dc2626',
+ secondary: '#ef4444',
+ accent: '#fef2f2',
+ text: '#1f2937',
+ textSecondary: '#6b7280',
+ border: '#fecaca',
+ background: '#ffffff'
+ },
+ 'orange': {
+ primary: '#ea580c',
+ secondary: '#f97316',
+ accent: '#fff7ed',
+ text: '#1f2937',
+ textSecondary: '#6b7280',
+ border: '#fed7aa',
+ background: '#ffffff'
+ },
+ 'yellow': {
+ primary: '#d97706',
+ secondary: '#f59e0b',
+ accent: '#fffbeb',
+ text: '#1f2937',
+ textSecondary: '#6b7280',
+ border: '#fde68a',
+ background: '#ffffff'
+ },
+ 'green': {
+ primary: '#059669',
+ secondary: '#10b981',
+ accent: '#f0fdf4',
+ text: '#1f2937',
+ textSecondary: '#6b7280',
+ border: '#bbf7d0',
+ background: '#ffffff'
+ },
+ 'blue': {
+ primary: '#2563eb',
+ secondary: '#3b82f6',
+ accent: '#eff6ff',
+ text: '#1f2937',
+ textSecondary: '#6b7280',
+ border: '#bfdbfe',
+ background: '#ffffff'
+ },
+ 'indigo': {
+ primary: '#4f46e5',
+ secondary: '#6366f1',
+ accent: '#eef2ff',
+ text: '#1f2937',
+ textSecondary: '#6b7280',
+ border: '#c7d2fe',
+ background: '#ffffff'
+ },
+ 'black': {
+ primary: '#000000',
+ secondary: '#1f2937',
+ accent: '#f9fafb',
+ text: '#000000',
+ textSecondary: '#374151',
+ border: '#e5e7eb',
+ background: '#ffffff'
+ }
+};
+
+export const colorThemeLabels: Record, string> = {
+ 'gray': 'Gray',
+ 'dark-gray': 'Dark Gray',
+ 'red': 'Red',
+ 'orange': 'Orange',
+ 'yellow': 'Yellow',
+ 'green': 'Green',
+ 'blue': 'Blue',
+ 'indigo': 'Indigo',
+ 'black': 'Black'
+};
+
+export const getColorThemeLabel = (theme: ColorTheme): string => {
+ if (theme === null) return 'Default';
+ return colorThemeLabels[theme];
+};
+
+export const colorThemeOrder: ColorTheme[] = [
+ null, // Default option first
+ 'blue',
+ 'green',
+ 'indigo',
+ 'orange',
+ 'red',
+ 'yellow',
+ 'gray',
+ 'dark-gray',
+ 'black'
+];
+
+// Convert color theme to CSS custom properties
+export const getThemeCSS = (theme: ColorTheme): React.CSSProperties => {
+ if (theme === null) {
+ // Return neutral/original colors for default theme
+ return {
+ '--theme-primary': '#1f2937', // gray-800 - neutral dark color
+ '--theme-secondary': '#374151', // gray-700 - slightly lighter
+ '--theme-accent': '#f9fafb', // gray-50 - very light background
+ '--theme-text': '#1f2937', // gray-800 - dark text
+ '--theme-background': '#ffffff', // white background
+ } as React.CSSProperties;
+ }
+ const colors = colorThemes[theme];
+ return {
+ '--theme-primary': colors.primary,
+ '--theme-secondary': colors.secondary,
+ '--theme-accent': colors.accent,
+ '--theme-text': colors.text,
+ '--theme-background': colors.background,
+ } as React.CSSProperties;
+};
\ No newline at end of file
diff --git a/cv-engine/src/utils/thumbnailGenerator.ts b/cv-engine/src/utils/thumbnailGenerator.ts
new file mode 100644
index 0000000..3c1145f
--- /dev/null
+++ b/cv-engine/src/utils/thumbnailGenerator.ts
@@ -0,0 +1,144 @@
+import type { ColorTheme } from '../types/colors';
+import { colorThemes } from '../types/colors';
+
+// Base SVG templates for each template type
+const baseSVGs = {
+ ats: ``,
+
+ classic: ``,
+
+ modern: ``,
+
+ minimal: ``,
+
+ timeline: ``
+};
+
+export type TemplateId = keyof typeof baseSVGs;
+
+export const generateThumbnail = (templateId: TemplateId, colorTheme: ColorTheme): string => {
+ const baseSvg = baseSVGs[templateId];
+
+ if (!baseSvg) {
+ throw new Error(`No base SVG found for template: ${templateId}`);
+ }
+
+ // Use default blue theme for null colorTheme
+ const palette = colorTheme ? colorThemes[colorTheme] : colorThemes.blue;
+
+ // Replace color placeholders with actual colors
+ return baseSvg
+ .replace(/\{\{primary\}\}/g, palette.primary)
+ .replace(/\{\{secondary\}\}/g, palette.secondary)
+ .replace(/\{\{accent\}\}/g, palette.accent)
+ .replace(/\{\{text\}\}/g, palette.text)
+ .replace(/\{\{textSecondary\}\}/g, palette.textSecondary)
+ .replace(/\{\{border\}\}/g, palette.border)
+ .replace(/\{\{background\}\}/g, palette.background);
+};
+
+export const generateThumbnailDataUrl = (templateId: TemplateId, colorTheme: ColorTheme): string => {
+ const svg = generateThumbnail(templateId, colorTheme);
+ return `data:image/svg+xml;base64,${btoa(svg)}`;
+};
\ No newline at end of file