import { damaskPattern, confettiPattern, leafPattern, polkaDotPattern, snowflakePattern, starPattern, scratchPattern, pixelGridPattern, floralLacePattern, palmLeafPattern, wheatPattern, gradCapPattern, texturePattern, combinePatterns, type Pattern, } from '@/lib/patterns'; import { deriveThemeColors, type ThemeColors } from '@/lib/color'; export type Theme = { id: string; name: string; emoji: string; /** Light card vs. dark card - drives how resolveTheme derives a full * palette from a host's custom primary/accent choice (see color.ts). */ mode: 'light' | 'dark'; colors: ThemeColors; pattern: Pattern; /** Rebuilds this theme's pattern for an arbitrary color set - used by * resolveTheme when a host overrides the default colors. */ buildPattern: (colors: ThemeColors) => Pattern; }; type ThemeDefinition = Omit; function defineTheme(def: ThemeDefinition): Theme { return { ...def, pattern: def.buildPattern(def.colors) }; } // Colors applied via CSS custom properties (see ThemedInviteLayout). // Fonts are mapped separately in src/lib/fonts.ts since next/font classes // must be static imports, not computed dynamically. `pattern` is a tileable // inline-SVG background (see src/lib/patterns.ts) that gives each theme a // distinct texture, not just a color/font swap. `buildPattern` regenerates // that same pattern for a different color set - see resolveTheme below. export const THEMES: Theme[] = [ defineTheme({ id: 'classic-elegant', name: 'Classic Elegant', emoji: 'đŸĨ‚', mode: 'light', colors: { bg: '#f5f1ea', surface: '#ffffff', primary: '#4b3b2a', accent: '#b08d57', text: '#2b2118', }, buildPattern: (c) => combinePatterns(damaskPattern(c.accent), texturePattern('/textures/exclusive-paper.png', 560, 420)), }), defineTheme({ id: 'birthday-confetti', name: 'Birthday Confetti', emoji: '🎉', mode: 'light', colors: { bg: '#fff3f8', surface: '#ffffff', primary: '#e0218a', accent: '#ffb703', text: '#3a1030', }, buildPattern: (c) => confettiPattern([c.primary, c.accent, '#3dd6c2']), }), defineTheme({ id: 'garden-party', name: 'Garden Party', emoji: 'đŸŒŋ', mode: 'light', colors: { bg: '#f1f7ee', surface: '#ffffff', primary: '#3f6b3a', accent: '#e0a458', text: '#233720', }, buildPattern: (c) => leafPattern(c.primary, c.accent), }), defineTheme({ id: 'modern-minimal', name: 'Modern Minimal', emoji: 'âŦ›', mode: 'light', colors: { bg: '#fafafa', surface: '#ffffff', primary: '#111827', accent: '#2563eb', text: '#111827', }, buildPattern: (c) => polkaDotPattern(c.accent, 1.75), }), defineTheme({ id: 'holiday-festive', name: 'Holiday Festive', emoji: 'â„ī¸', mode: 'dark', colors: { bg: '#0f2d24', surface: '#153a2e', primary: '#e63946', accent: '#f1c40f', text: '#fdf6ec', }, buildPattern: (c) => combinePatterns(snowflakePattern(c.accent), texturePattern('/textures/fresh-snow.png', 500, 500)), }), defineTheme({ id: 'baby-shower-pastel', name: 'Baby Shower Pastel', emoji: 'đŸŧ', mode: 'light', colors: { bg: '#f4f1fb', surface: '#ffffff', primary: '#8e7cc3', accent: '#f6b6c1', text: '#3a3350', }, buildPattern: (c) => starPattern([c.primary, c.accent]), }), defineTheme({ id: 'goth-grunge', name: 'Goth Grunge', emoji: '💀', mode: 'dark', colors: { bg: '#121212', surface: '#1c1c1c', primary: '#b91c1c', accent: '#9333ea', text: '#e5e5e5', }, buildPattern: (c) => combinePatterns(scratchPattern(c.accent), texturePattern('/textures/dark-leather.png', 398, 484)), }), defineTheme({ id: 'retro-arcade', name: 'Retro Arcade', emoji: 'đŸ•šī¸', mode: 'dark', colors: { bg: '#0d0221', surface: '#1a0b3d', primary: '#ff2e88', accent: '#00e5ff', text: '#eafffb', }, buildPattern: (c) => pixelGridPattern(c.accent, [c.primary, c.accent]), }), defineTheme({ id: 'wedding-romance', name: 'Wedding Romance', emoji: '💍', mode: 'light', colors: { bg: '#fdf5f2', surface: '#ffffff', primary: '#8c5b6b', accent: '#c9a45c', text: '#3a2a2e', }, buildPattern: (c) => combinePatterns(floralLacePattern(c.accent), texturePattern('/textures/white-linen.png', 400, 300)), }), defineTheme({ id: 'tropical-luau', name: 'Tropical Luau', emoji: 'đŸŒē', mode: 'light', colors: { bg: '#e6fbf7', surface: '#ffffff', primary: '#0f766e', accent: '#f59e0b', text: '#0b3d3a', }, buildPattern: (c) => palmLeafPattern(c.primary, c.accent), }), defineTheme({ id: 'rustic-harvest', name: 'Rustic Harvest', emoji: '🍂', mode: 'light', colors: { bg: '#f7ecd9', surface: '#fffaf1', primary: '#7a4a24', accent: '#c1622d', text: '#3d2812', }, buildPattern: (c) => combinePatterns(wheatPattern(c.accent), texturePattern('/textures/cardboard.png', 600, 600)), }), defineTheme({ id: 'graduation-grad', name: 'Graduation', emoji: '🎓', mode: 'dark', colors: { bg: '#0b1f3a', surface: '#122a4d', primary: '#f2c14e', accent: '#f2c14e', text: '#f5f5f0', }, buildPattern: (c) => gradCapPattern(c.accent), }), ]; export const DEFAULT_THEME_ID = THEMES[0].id; export function getTheme(id: string): Theme { return THEMES.find((t) => t.id === id) ?? THEMES[0]; } /** * Resolves a theme for rendering, with optional host-chosen primary/accent * overrides (Event.themePrimaryColor / themeAccentColor). With no overrides, * returns the theme's own hand-tuned default palette and pattern completely * unchanged. With one or both overrides, derives a full contrast-safe * palette (see deriveThemeColors) and rebuilds the pattern to match, so a * customized theme never ends up illegible. */ export function resolveTheme( id: string, primaryOverride?: string | null, accentOverride?: string | null ): Theme { const base = getTheme(id); if (!primaryOverride && !accentOverride) return base; const colors = deriveThemeColors( base.mode, primaryOverride || base.colors.primary, accentOverride || base.colors.accent ); return { ...base, colors, pattern: base.buildPattern(colors) }; }