diff --git a/src/__tests__/card-themes.test.ts b/src/__tests__/card-themes.test.ts
new file mode 100644
index 0000000..5f9e203
--- /dev/null
+++ b/src/__tests__/card-themes.test.ts
@@ -0,0 +1,76 @@
+import { readFileSync } from 'node:fs'
+import { describe, expect, it } from 'vitest'
+import {
+ CARD_THEMES,
+ CLASSIC_CARD_THEMES,
+ DESIGNER_CARD_THEMES,
+ getCardThemeDefinition,
+ getThemeRecommendedUpdates,
+ isDesignerCardTheme,
+} from '@/lib/card-themes'
+
+const css = readFileSync(new URL('../index.css', import.meta.url), 'utf8')
+
+describe('card themes', () => {
+ it('keeps the existing classic themes and adds eight designer themes', () => {
+ expect(CLASSIC_CARD_THEMES.map((theme) => theme.id)).toEqual([
+ 'teacher-pro',
+ 'minimal',
+ 'classroom-cute',
+ 'bold-vocabulary',
+ 'picture-focus',
+ 'quiz-card',
+ 'playful-pop',
+ 'calm-study',
+ 'ink-saver',
+ ])
+
+ expect(DESIGNER_CARD_THEMES.map((theme) => theme.id)).toEqual([
+ 'dreamy-classroom',
+ 'storybook',
+ 'notebook-doodle',
+ 'retro-schoolhouse',
+ 'botanical-study',
+ 'candy-pop',
+ 'space-explorer',
+ 'modern-editorial',
+ ])
+ })
+
+ it('has unique ids and valid starting palettes', () => {
+ expect(new Set(CARD_THEMES.map((theme) => theme.id)).size).toBe(CARD_THEMES.length)
+
+ for (const theme of CARD_THEMES) {
+ expect(theme.name.length).toBeGreaterThan(0)
+ expect(theme.description.length).toBeGreaterThan(10)
+ expect(theme.mainColor).toMatch(/^#[0-9a-f]{6}$/i)
+ expect(theme.accentColor).toMatch(/^#[0-9a-f]{6}$/i)
+ expect(getCardThemeDefinition(theme.id)).toEqual(theme)
+ }
+ })
+
+ it('marks only the designer collection as designer themes', () => {
+ for (const theme of DESIGNER_CARD_THEMES) {
+ expect(isDesignerCardTheme(theme.id)).toBe(true)
+ }
+ for (const theme of CLASSIC_CARD_THEMES) {
+ expect(isDesignerCardTheme(theme.id)).toBe(false)
+ }
+ })
+
+ it('theme application only changes theme and its starting palette', () => {
+ const updates = getThemeRecommendedUpdates('dreamy-classroom')
+ expect(Object.keys(updates).sort()).toEqual(['accentColor', 'mainColor', 'theme'])
+ expect(updates).toEqual({
+ theme: 'dreamy-classroom',
+ mainColor: '#342a4f',
+ accentColor: '#8b6bbd',
+ })
+ })
+
+ it('ships print-safe CSS surfaces for every designer theme', () => {
+ for (const theme of DESIGNER_CARD_THEMES) {
+ expect(css).toContain(`[data-card-theme='${theme.id}']`)
+ }
+ })
+})
diff --git a/src/components/CardThemeDecorations.tsx b/src/components/CardThemeDecorations.tsx
new file mode 100644
index 0000000..f8c3851
--- /dev/null
+++ b/src/components/CardThemeDecorations.tsx
@@ -0,0 +1,175 @@
+import type { CardTheme } from '@/lib/types'
+import { isDesignerCardTheme } from '@/lib/card-themes'
+
+interface CardThemeDecorationsProps {
+ theme: CardTheme
+}
+
+const commonSvgProps = {
+ viewBox: '0 0 100 100',
+ preserveAspectRatio: 'none' as const,
+ 'aria-hidden': true,
+ focusable: false,
+}
+
+export function CardThemeDecorations({ theme }: CardThemeDecorationsProps) {
+ if (!isDesignerCardTheme(theme)) return null
+
+ return (
+
+ {theme === 'dreamy-classroom' && }
+ {theme === 'storybook' && }
+ {theme === 'notebook-doodle' && }
+ {theme === 'retro-schoolhouse' && }
+ {theme === 'botanical-study' && }
+ {theme === 'candy-pop' && }
+ {theme === 'space-explorer' && }
+ {theme === 'modern-editorial' && }
+
+ )
+}
+
+function DreamyClassroom() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+function Storybook() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+function NotebookDoodle() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+function RetroSchoolhouse() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+function BotanicalStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+function CandyPop() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+function SpaceExplorer() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+function ModernEditorial() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/DesignPanel.tsx b/src/components/DesignPanel.tsx
index 5831e69..8440bee 100644
--- a/src/components/DesignPanel.tsx
+++ b/src/components/DesignPanel.tsx
@@ -1,12 +1,15 @@
+import type { CSSProperties } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Slider } from '@/components/ui/slider'
import { Switch } from '@/components/ui/switch'
import { Input } from '@/components/ui/input'
-import type { PrintSettings, DuplexMode, CardType, CardsPerPage, Orientation, PaperSize } from '@/lib/types'
+import type { PrintSettings, CardTheme, DuplexMode, CardType, CardsPerPage, Orientation, PaperSize } from '@/lib/types'
import { DESIGN_PRESETS, getActiveDesignPreset } from '@/lib/design-presets'
import type { DesignPreset } from '@/lib/design-presets'
+import { CLASSIC_CARD_THEMES, DESIGNER_CARD_THEMES, getCardThemeDefinition, getThemeRecommendedUpdates } from '@/lib/card-themes'
+import { CardThemeDecorations } from '@/components/CardThemeDecorations'
import { Palette, TextAlignCenter, TextAlignLeft, TextAlignRight, ArrowsCounterClockwise, Cards, Check } from '@phosphor-icons/react'
import { toast } from 'sonner'
@@ -46,6 +49,37 @@ const PREVIEW_BACKGROUNDS: Record = {
'ink-saver': '#ffffff',
}
+function ThemePreview({ theme }: { theme: CardTheme }) {
+ const item = getCardThemeDefinition(theme)
+
+ return (
+
+
+
+
+ {item.previewLabel}
+
+
+
+
+
+ )
+}
+
function StylePreview({ item }: { item: DesignPreset }) {
const style = item.updates
const imageHeight = `${Math.round(Math.min(style.imageHeightRatio, 0.72) * 100)}%`
@@ -96,6 +130,12 @@ export function DesignPanel({ settings, cardType, onUpdate, onUpdateCardType, on
toast.success(`${item.name} applied`)
}
+ function applyTheme(theme: CardTheme) {
+ const definition = getCardThemeDefinition(theme)
+ onUpdate(getThemeRecommendedUpdates(theme))
+ toast.success(`${definition.name} theme applied`)
+ }
+
return (
@@ -224,6 +264,76 @@ export function DesignPanel({ settings, cardType, onUpdate, onUpdateCardType, on
+
+
+
+
+ Theme Gallery
+
+ Choose the card's visual world. Designer themes add real framing, illustration, texture, and composition—not just a different gradient.
+
+
+
+
+
Designer collection
+
Print-safe CSS and vector artwork. No external image packs or licensing dependencies.
+
+
+ {DESIGNER_CARD_THEMES.map((theme) => (
+
applyTheme(theme.id)}
+ aria-pressed={settings.theme === theme.id}
+ className={`relative rounded-lg border-2 p-2 text-left transition-all hover:-translate-y-0.5 hover:shadow-md ${
+ settings.theme === theme.id ? 'border-primary bg-primary/5' : 'border-border hover:border-primary/50'
+ }`}
+ >
+
+
+
+
{theme.name}
+ {settings.theme === theme.id && (
+
+
+ Selected
+
+ )}
+
+
{theme.description}
+
+
+ ))}
+
+
+
+
+
+
Classic themes
+
Everything FlashForge already had remains available.
+
+
+ {CLASSIC_CARD_THEMES.map((theme) => (
+
applyTheme(theme.id)}
+ aria-pressed={settings.theme === theme.id}
+ className={`rounded-lg border-2 p-2 text-left transition-colors ${
+ settings.theme === theme.id ? 'border-primary bg-primary/5' : 'border-border hover:border-primary/50'
+ }`}
+ >
+
+ {theme.name}
+
+ ))}
+
+
+
+ Choosing a theme applies its starting palette but does not change paper size, cards per page, duplex alignment, offsets, or your card content. You can customize colors and typography below.
+
+
+
Customize selected style
Fine-tune colors, typography, borders, alignment, and image balance. Any change shows the style as Customized.
diff --git a/src/components/FlashCardDisplay.tsx b/src/components/FlashCardDisplay.tsx
index 0dd62d4..8de067d 100644
--- a/src/components/FlashCardDisplay.tsx
+++ b/src/components/FlashCardDisplay.tsx
@@ -1,8 +1,11 @@
+import type { CSSProperties } from 'react'
import type { FlashCard, PrintSettings } from '@/lib/types'
import { PlacedImage } from '@/components/PlacedImage'
import { getCardSideImage } from '@/lib/image-placement'
import { calculateFontSize } from '@/lib/print-utils'
import { cn } from '@/lib/utils'
+import { CardThemeDecorations } from '@/components/CardThemeDecorations'
+import { isDesignerCardTheme } from '@/lib/card-themes'
interface FlashCardDisplayProps {
card: FlashCard | null
@@ -63,7 +66,7 @@ export function FlashCardDisplay({
? settings.fontSize
: calculateFontSize(settings.fontSize, settings.cardsPerPage, textLength, !!imageUrl && hasText)
const secondaryFontSize = fontSize * 0.72
- const themeClasses = getThemeClasses(settings.theme)
+ const isDesignerTheme = isDesignerCardTheme(settings.theme)
const borderClass = settings.showBorder ? 'border-2' : 'border-0'
const roundedClass = settings.showRoundedCorners ? 'rounded-xl' : 'rounded-none'
@@ -80,20 +83,24 @@ export function FlashCardDisplay({
return (
- {!isBackgroundMode && (
+
+
+ {!isBackgroundMode && !isDesignerTheme && (
#{cardNumber}
@@ -137,7 +144,7 @@ export function FlashCardDisplay({
{showSetTitle && settings.showSetTitle && (
{showSetTitle}
@@ -147,13 +154,13 @@ export function FlashCardDisplay({
{isBackgroundMode ? (
/* Background mode: text sits at the bottom over the gradient scrim */
{text && (
) : (
-
+
{imageUrl && (
{settings.footerText}
@@ -262,18 +269,3 @@ export function FlashCardDisplay({
)
}
-
-function getThemeClasses(theme: string): string {
- const themes: Record
= {
- minimal: 'bg-gradient-to-br from-white to-slate-50',
- 'classroom-cute': 'bg-gradient-to-br from-amber-50 to-orange-50',
- 'bold-vocabulary': 'bg-gradient-to-br from-blue-50 to-purple-50',
- 'picture-focus': 'bg-gradient-to-br from-gray-50 to-slate-100',
- 'ink-saver': 'bg-white',
- 'quiz-card': 'bg-gradient-to-br from-slate-50 to-slate-100',
- 'playful-pop': 'bg-gradient-to-br from-pink-50 via-white to-cyan-50',
- 'teacher-pro': 'bg-gradient-to-br from-white via-slate-50 to-blue-50',
- 'calm-study': 'bg-gradient-to-br from-emerald-50 to-teal-50',
- }
- return themes[theme] || 'bg-gradient-to-br from-white to-slate-50'
-}
diff --git a/src/index.css b/src/index.css
index 767b23c..bf3af80 100644
--- a/src/index.css
+++ b/src/index.css
@@ -109,6 +109,263 @@
--radius-full: 9999px;
}
+
+
+.flash-card-surface {
+ --card-main: #1e293b;
+ --card-accent: #0ea5e9;
+ --card-theme-gold: #c6a252;
+ --card-theme-soft: #fff7ed;
+ --card-theme-tape: #f0d79b;
+ --card-theme-pink: #f7a8c8;
+ --card-theme-cyan: #8fdde7;
+ --card-theme-yellow: #f7d56a;
+ --card-theme-planet: #a78bfa;
+ isolation: isolate;
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
+ }
+
+ .flash-card-surface[data-card-theme='minimal'] {
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
+ }
+
+ .flash-card-surface[data-card-theme='classroom-cute'] {
+ background: linear-gradient(135deg, #fffbeb 0%, #fff7ed 100%);
+ }
+
+ .flash-card-surface[data-card-theme='bold-vocabulary'] {
+ background: linear-gradient(135deg, #eff6ff 0%, #faf5ff 100%);
+ }
+
+ .flash-card-surface[data-card-theme='picture-focus'] {
+ background: linear-gradient(135deg, #f9fafb 0%, #f1f5f9 100%);
+ }
+
+ .flash-card-surface[data-card-theme='ink-saver'] {
+ background: #ffffff;
+ }
+
+ .flash-card-surface[data-card-theme='quiz-card'] {
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+ }
+
+ .flash-card-surface[data-card-theme='playful-pop'] {
+ background:
+ radial-gradient(circle at 15% 15%, rgba(244, 114, 182, 0.12), transparent 28%),
+ radial-gradient(circle at 85% 82%, rgba(34, 211, 238, 0.12), transparent 30%),
+ #ffffff;
+ }
+
+ .flash-card-surface[data-card-theme='teacher-pro'] {
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 54%, #eff6ff 100%);
+ }
+
+ .flash-card-surface[data-card-theme='calm-study'] {
+ background: linear-gradient(135deg, #ecfdf5 0%, #f0fdfa 100%);
+ }
+
+ .flash-card-surface[data-card-theme='dreamy-classroom'] {
+ --card-theme-gold: #c6a252;
+ --card-theme-soft: #fff7ed;
+ background:
+ radial-gradient(circle at 20% 10%, rgba(199, 174, 230, 0.22), transparent 30%),
+ radial-gradient(circle at 83% 83%, rgba(244, 199, 221, 0.18), transparent 32%),
+ linear-gradient(145deg, #fffdf8 0%, #fffaf2 48%, #fbf7ff 100%);
+ }
+
+ .flash-card-surface[data-card-theme='storybook'] {
+ --card-theme-soft: #fff1e8;
+ background:
+ radial-gradient(circle at 15% 18%, rgba(240, 176, 153, 0.18), transparent 28%),
+ radial-gradient(circle at 87% 78%, rgba(172, 199, 159, 0.17), transparent 34%),
+ linear-gradient(145deg, #fffdf7 0%, #fff8f1 58%, #f7fbf2 100%);
+ }
+
+ .flash-card-surface[data-card-theme='notebook-doodle'] {
+ --card-theme-tape: #f2d28c;
+ background-color: #fffdf7;
+ background-image:
+ linear-gradient(90deg, transparent 0 12%, rgba(231, 111, 81, 0.16) 12% 12.4%, transparent 12.4% 100%),
+ repeating-linear-gradient(0deg, transparent 0 20px, rgba(81, 126, 164, 0.13) 20px 21px);
+ }
+
+ .flash-card-surface[data-card-theme='retro-schoolhouse'] {
+ background:
+ repeating-linear-gradient(0deg, rgba(108, 75, 48, 0.025) 0 1px, transparent 1px 4px),
+ linear-gradient(145deg, #fffaf0 0%, #f8efd9 100%);
+ }
+
+ .flash-card-surface[data-card-theme='botanical-study'] {
+ background:
+ radial-gradient(circle at 18% 82%, rgba(123, 155, 121, 0.13), transparent 32%),
+ radial-gradient(circle at 84% 12%, rgba(84, 116, 94, 0.12), transparent 30%),
+ linear-gradient(145deg, #fbfcf7 0%, #f3f7ef 100%);
+ }
+
+ .flash-card-surface[data-card-theme='candy-pop'] {
+ --card-theme-pink: #f7a8c8;
+ --card-theme-cyan: #8fdde7;
+ --card-theme-yellow: #f7d56a;
+ background:
+ radial-gradient(circle at 10% 12%, rgba(247, 168, 200, 0.28), transparent 24%),
+ radial-gradient(circle at 88% 20%, rgba(143, 221, 231, 0.24), transparent 25%),
+ radial-gradient(circle at 78% 88%, rgba(247, 213, 106, 0.22), transparent 28%),
+ linear-gradient(145deg, #fffaff 0%, #fffdf8 100%);
+ }
+
+ .flash-card-surface[data-card-theme='space-explorer'] {
+ --card-theme-planet: #a78bfa;
+ background:
+ radial-gradient(circle at 78% 18%, rgba(99, 102, 241, 0.44), transparent 26%),
+ radial-gradient(circle at 18% 84%, rgba(8, 145, 178, 0.24), transparent 32%),
+ linear-gradient(145deg, #111936 0%, #18214a 45%, #0f172a 100%);
+ }
+
+ .flash-card-surface[data-card-theme='modern-editorial'] {
+ background:
+ linear-gradient(118deg, rgba(232, 93, 63, 0.08) 0 18%, transparent 18% 100%),
+ linear-gradient(145deg, #ffffff 0%, #f7f7f5 100%);
+ }
+
+ .flash-card-surface[data-card-theme='dreamy-classroom'] .flash-card-content,
+ .flash-card-surface[data-card-theme='storybook'] .flash-card-content,
+ .flash-card-surface[data-card-theme='retro-schoolhouse'] .flash-card-content,
+ .flash-card-surface[data-card-theme='botanical-study'] .flash-card-content {
+ padding: 1.75rem;
+ }
+
+ .flash-card-surface[data-card-theme='notebook-doodle'] .flash-card-content {
+ padding-left: 2rem;
+ }
+
+ .flash-card-surface[data-card-theme='modern-editorial'] .flash-card-content {
+ padding-left: 2.25rem;
+ padding-right: 1.75rem;
+ }
+
+ .flash-card-surface[data-card-theme='dreamy-classroom'] .flash-card-primary,
+ .flash-card-surface[data-card-theme='storybook'] .flash-card-primary {
+ letter-spacing: 0.015em;
+ }
+
+ .flash-card-surface[data-card-theme='retro-schoolhouse'] .flash-card-primary {
+ letter-spacing: 0.025em;
+ text-transform: none;
+ }
+
+ .flash-card-surface[data-card-theme='modern-editorial'] .flash-card-primary {
+ letter-spacing: -0.025em;
+ }
+
+ .flash-card-surface[data-card-theme='space-explorer'] .flash-card-secondary {
+ color: var(--card-accent) !important;
+ }
+
+ .flash-card-surface[data-card-theme='dreamy-classroom'] .flash-card-image-frame {
+ border: 1px solid rgba(198, 162, 82, 0.7);
+ border-radius: 14px;
+ background: rgba(255, 255, 255, 0.72);
+ padding: 4px;
+ }
+
+ .flash-card-surface[data-card-theme='storybook'] .flash-card-image-frame {
+ border: 2px solid var(--card-accent);
+ border-radius: 18px 18px 12px 12px;
+ background: #fffdf7;
+ padding: 4px;
+ }
+
+ .flash-card-surface[data-card-theme='notebook-doodle'] .flash-card-image-frame {
+ border: 2px solid var(--card-main);
+ border-radius: 4px;
+ background: #ffffff;
+ padding: 5px;
+ transform: rotate(-0.7deg);
+ }
+
+ .flash-card-surface[data-card-theme='retro-schoolhouse'] .flash-card-image-frame {
+ border: 2px solid var(--card-main);
+ border-radius: 2px;
+ background: #fffaf0;
+ padding: 4px;
+ }
+
+ .flash-card-surface[data-card-theme='botanical-study'] .flash-card-image-frame {
+ border: 1px solid var(--card-accent);
+ border-radius: 14px;
+ background: #ffffff;
+ padding: 5px;
+ }
+
+ .flash-card-surface[data-card-theme='candy-pop'] .flash-card-image-frame {
+ border: 3px solid rgba(255, 255, 255, 0.86);
+ border-radius: 20px;
+ background: #ffffff;
+ padding: 4px;
+ box-shadow: 0 5px 0 rgba(91, 58, 122, 0.08);
+ }
+
+ .flash-card-surface[data-card-theme='space-explorer'] .flash-card-image-frame {
+ border: 1px solid var(--card-accent);
+ border-radius: 14px;
+ background: rgba(15, 23, 42, 0.78);
+ padding: 4px;
+ }
+
+ .flash-card-surface[data-card-theme='modern-editorial'] .flash-card-image-frame {
+ border: 0;
+ border-radius: 2px;
+ background: #ffffff;
+ padding: 0;
+ }
+
+ .flash-card-surface[data-card-theme='dreamy-classroom'] .flash-card-set-title,
+ .flash-card-surface[data-card-theme='storybook'] .flash-card-set-title,
+ .flash-card-surface[data-card-theme='retro-schoolhouse'] .flash-card-set-title {
+ opacity: 0.92;
+ border-radius: 999px;
+ background: var(--card-accent);
+ color: #ffffff !important;
+ padding: 0.18rem 0.5rem;
+ }
+
+ .flash-card-surface[data-card-theme='notebook-doodle'] .flash-card-set-title {
+ opacity: 0.86;
+ background: #fff4cf;
+ border: 1px solid var(--card-accent);
+ color: var(--card-main) !important;
+ padding: 0.15rem 0.45rem;
+ transform: rotate(1deg);
+ }
+
+ .flash-card-surface[data-card-theme='space-explorer'] .flash-card-set-title {
+ opacity: 0.9;
+ color: var(--card-main) !important;
+ border: 1px solid var(--card-accent);
+ border-radius: 999px;
+ padding: 0.16rem 0.45rem;
+ background: rgba(15, 23, 42, 0.55);
+ }
+
+ .flash-card-surface[data-card-theme='modern-editorial'] .flash-card-set-title {
+ opacity: 1;
+ color: var(--card-accent) !important;
+ font-weight: 800;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ }
+
+ .flash-card-surface[data-card-theme='dreamy-classroom'] .flash-card-footer,
+ .flash-card-surface[data-card-theme='storybook'] .flash-card-footer,
+ .flash-card-surface[data-card-theme='botanical-study'] .flash-card-footer {
+ margin-inline: 1.5rem;
+ }
+
+.theme-preview-card {
+ min-height: 7.5rem;
+}
+
+
/*
* Keep the print portal laid out off-screen instead of display:none.
* PlacedImage measures its frame with ResizeObserver; display:none gives it
diff --git a/src/lib/card-themes.ts b/src/lib/card-themes.ts
new file mode 100644
index 0000000..d245a99
--- /dev/null
+++ b/src/lib/card-themes.ts
@@ -0,0 +1,121 @@
+import type { CardTheme, PrintSettings } from './types'
+
+export type CardThemeFamily = 'classic' | 'designer'
+
+export interface CardThemeDefinition {
+ id: CardTheme
+ name: string
+ description: string
+ family: CardThemeFamily
+ mainColor: string
+ accentColor: string
+ previewLabel: string
+}
+
+export const CLASSIC_CARD_THEMES: CardThemeDefinition[] = [
+ { id: 'teacher-pro', name: 'Teacher Pro', description: 'Clean blue editorial styling', family: 'classic', mainColor: '#183153', accentColor: '#0891b2', previewLabel: 'ABC' },
+ { id: 'minimal', name: 'Minimal', description: 'Quiet modern neutral', family: 'classic', mainColor: '#1e293b', accentColor: '#64748b', previewLabel: 'Aa' },
+ { id: 'classroom-cute', name: 'Classroom Cute', description: 'Warm and friendly', family: 'classic', mainColor: '#7c2d12', accentColor: '#0f766e', previewLabel: 'Hi!' },
+ { id: 'bold-vocabulary', name: 'Bold Vocabulary', description: 'High-energy contrast', family: 'classic', mainColor: '#111827', accentColor: '#dc2626', previewLabel: 'WORD' },
+ { id: 'picture-focus', name: 'Picture Focus', description: 'Let visuals lead', family: 'classic', mainColor: '#111827', accentColor: '#38bdf8', previewLabel: 'Photo' },
+ { id: 'quiz-card', name: 'Quiz Card', description: 'Structured assessment look', family: 'classic', mainColor: '#172554', accentColor: '#ca8a04', previewLabel: 'Q?' },
+ { id: 'playful-pop', name: 'Playful Pop', description: 'Bright kid-friendly palette', family: 'classic', mainColor: '#7c3aed', accentColor: '#ec4899', previewLabel: 'POP' },
+ { id: 'calm-study', name: 'Calm Study', description: 'Soft green focus mode', family: 'classic', mainColor: '#14532d', accentColor: '#0d9488', previewLabel: 'Study' },
+ { id: 'ink-saver', name: 'Ink Saver', description: 'Economical for classroom printing', family: 'classic', mainColor: '#111111', accentColor: '#555555', previewLabel: 'Aa' },
+]
+
+export const DESIGNER_CARD_THEMES: CardThemeDefinition[] = [
+ {
+ id: 'dreamy-classroom',
+ name: 'Dreamy Classroom',
+ description: 'Ornamental gold frame, lavender accents, clouds, sparkles, and ribbon details',
+ family: 'designer',
+ mainColor: '#342a4f',
+ accentColor: '#8b6bbd',
+ previewLabel: 'DREAM',
+ },
+ {
+ id: 'storybook',
+ name: 'Storybook',
+ description: 'Soft illustrated framing with warm paper, scallops, stars, and gentle curves',
+ family: 'designer',
+ mainColor: '#4d3d43',
+ accentColor: '#d98273',
+ previewLabel: 'Story',
+ },
+ {
+ id: 'notebook-doodle',
+ name: 'Notebook Doodle',
+ description: 'Ruled-paper energy with tape, hand-drawn stars, arrows, and classroom doodles',
+ family: 'designer',
+ mainColor: '#1f3b5b',
+ accentColor: '#e76f51',
+ previewLabel: 'NOTE',
+ },
+ {
+ id: 'retro-schoolhouse',
+ name: 'Retro Schoolhouse',
+ description: 'Warm vintage textbook styling with cream stock, burgundy, mustard, and badges',
+ family: 'designer',
+ mainColor: '#6b2737',
+ accentColor: '#d89b31',
+ previewLabel: 'LESSON',
+ },
+ {
+ id: 'botanical-study',
+ name: 'Botanical Study',
+ description: 'Elegant sage paper with fine botanical corners and calm study-room typography',
+ family: 'designer',
+ mainColor: '#29483a',
+ accentColor: '#7b9b79',
+ previewLabel: 'Study',
+ },
+ {
+ id: 'candy-pop',
+ name: 'Candy Pop',
+ description: 'Polished pastel bubbles, stickers, and playful shapes without looking chaotic',
+ family: 'designer',
+ mainColor: '#5b3a7a',
+ accentColor: '#ef6fa8',
+ previewLabel: 'YAY!',
+ },
+ {
+ id: 'space-explorer',
+ name: 'Space Explorer',
+ description: 'Deep indigo card stock with constellations, planets, and bright classroom-safe contrast',
+ family: 'designer',
+ mainColor: '#eef2ff',
+ accentColor: '#67e8f9',
+ previewLabel: 'SPACE',
+ },
+ {
+ id: 'modern-editorial',
+ name: 'Modern Editorial',
+ description: 'Strong typography, offset geometry, and premium magazine-style composition',
+ family: 'designer',
+ mainColor: '#1f2937',
+ accentColor: '#e85d3f',
+ previewLabel: 'TYPE',
+ },
+]
+
+export const CARD_THEMES: CardThemeDefinition[] = [...DESIGNER_CARD_THEMES, ...CLASSIC_CARD_THEMES]
+
+const THEME_BY_ID = new Map(CARD_THEMES.map((theme) => [theme.id, theme]))
+
+export function getCardThemeDefinition(theme: CardTheme): CardThemeDefinition {
+ return THEME_BY_ID.get(theme) ?? CLASSIC_CARD_THEMES[0]
+}
+
+export function getThemeRecommendedUpdates(theme: CardTheme): Pick {
+ const definition = getCardThemeDefinition(theme)
+ return {
+ theme,
+ mainColor: definition.mainColor,
+ accentColor: definition.accentColor,
+ }
+}
+
+export function isDesignerCardTheme(theme: CardTheme): boolean {
+ return getCardThemeDefinition(theme).family === 'designer'
+}
diff --git a/src/lib/types.ts b/src/lib/types.ts
index a6f65ba..322818c 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -127,6 +127,14 @@ export type CardTheme =
| 'playful-pop'
| 'teacher-pro'
| 'calm-study'
+ | 'dreamy-classroom'
+ | 'storybook'
+ | 'notebook-doodle'
+ | 'retro-schoolhouse'
+ | 'botanical-study'
+ | 'candy-pop'
+ | 'space-explorer'
+ | 'modern-editorial'
export interface PrintSettings {
cardsPerPage: CardsPerPage