- Photos: guests and hosts can download all event photos as a ZIP; HEIC/HEIF uploads are now accepted, with a WebP thumbnail generated for gallery display while the original file is preserved for download; uploader name now always shows as a caption - Hosts can send a thank-you note (email/SMS) to guests who RSVP'd yes - Guests can add the event to Google Calendar or download an .ics file, and see a live countdown to the event - Hosts can override a theme's primary/accent colors per event - New textures for a few themes' card backgrounds
246 lines
6.5 KiB
TypeScript
246 lines
6.5 KiB
TypeScript
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<Theme, 'pattern'>;
|
|
|
|
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) };
|
|
}
|