This file provides comprehensive context for AI assistants (like Claude, ChatGPT, etc.) working on the Card0r project.
Card0r is a full-stack TypeScript application that generates personalised video greeting cards using AI-powered message generation and Remotion-based React video rendering.
Language Standard: UK English is used throughout the codebase for all user-facing text.
Tech Stack:
- Frontend: React 19 + Vite + TypeScript + TailwindCSS + shadcn/ui
- Backend: Node.js + Express + TypeScript + Remotion
- Video: Remotion (React-based video compositions)
- AI: OpenAI GPT-4 for message generation
- Media: Jamendo API for background music
- State: Zustand for client state management
- Deployment: Docker + docker-compose
Card0r/
├── frontend/ # React SPA (port 5173 dev, 3000 prod)
├── backend/ # Express API (port 3001)
├── remotion/ # Remotion video compositions and decorations
├── shared/ # Shared TypeScript types
├── docker-compose.yml # Orchestrates both services
├── README.md # Main documentation
├── Non-Techie-Readme.md # Plain-language setup guide
└── ai.md # This file
User → Frontend → Backend API → Services → Remotion → Videos
↓
OpenAI GPT-4 (messages)
↓
Jamendo API (music)
Located in backend/src/services/:
-
validation.ts
- Validates OpenAI and Jamendo API keys
- Makes test API calls to verify credentials
- Returns validation status for each key
-
csv-parser.ts
- Parses CSV and Excel (.xlsx, .xls) files
- Smart column detection (name, message, recipient, etc.)
- Handles malformed data gracefully
- Returns recipients array with errors
-
openai-service.ts
- Integrates with OpenAI GPT-4
- Holiday-specific prompts for each theme
- Configurable message length (5-100 words via
targetWordCount) - Configurable creativity (0-1 via
creativity, maps to temperature 0.3-1.2) - Handles rate limiting and errors
-
jamendo-service.ts
- Searches Jamendo for holiday-appropriate music
- Filters by duration (30+ seconds)
- Returns top 5 tracks per theme
- Handles API errors gracefully
-
remotion-renderer.ts
- Bundles and renders Remotion compositions
- Passes props (name, message, theme, senderName) to compositions
- Supports multiple resolutions (1080p, 4K, square, social)
- H.264 encoding with quality optimisation
- Videos stored in
backend/videos/directory
-
video-generator.ts
- Orchestrates entire video generation pipeline
- Manages job queue (in-memory)
- Tracks progress for each video
- Supports cancellation of individual jobs or entire batches
- Handles batch processing
- Cleanup after completion
-
zip-generator.ts
- Creates ZIP archives of completed videos
- Tracks ZIP generation progress
- Supports batch downloads
Located in backend/src/routes/:
- validation.ts -
POST /api/validate-keys - upload.ts -
POST /api/upload-csv(with Multer middleware) - messages.ts -
POST /api/generate-messages(requires x-openai-key header)- Accepts
targetWordCount(5-100) andcreativity(0-1) parameters
- Accepts
- music.ts -
GET /api/music/:theme(requires x-jamendo-key header) - videos.ts:
POST /api/videos/generate- Start video generationGET /api/videos/status/:jobId- Check job statusPOST /api/videos/cancel/:jobId- Cancel all pending/processing jobs in batchPOST /api/videos/cancel/:jobId/:videoJobId- Cancel specific video jobPOST /api/videos/download-zip/:jobId- Start ZIP generationGET /api/videos/download-zip/:jobId/progress- Check ZIP progressDELETE /api/videos/delete/:filename- Delete a video filePOST /api/videos/delete-batch- Delete multiple video files
Located in remotion/src/:
Core Compositions:
- Root.tsx - Registers Remotion compositions
- CardComposition.tsx - Main composition orchestrating all slides, decorations, and fade transitions
- types.ts - Type definitions and
HOLIDAY_COLORSfor all 17 themes
Slides (remotion/src/slides/):
- IntroSlide.tsx - Theme name and recipient greeting with fade animation
- MessageSlide.tsx - Animated message display (line-by-line reveal)
- SenderSlide.tsx - "From: [senderName]" display
- OutroSlide.tsx - Closing animation with pulse effect
Important: All slides have transparent backgrounds to allow decorations to show through.
Video Structure:
- 1-second fade in (content starts after fade completes)
- Intro slide: 5 seconds
- Message slide: Dynamic duration based on message length
- Sender reveal: 3 seconds
- Outro: 3 seconds
- 1-second fade out
Watermark:
- Positioned bottom-right
- Font size:
Math.max(32, Math.round(width / 50)) - Text: "Created by Card0r - available at err0r.dev/card0r"
Decorations (remotion/src/decorations/):
Each theme has its own decoration component with theme-specific animations:
| File | Theme | Animations |
|---|---|---|
ChristmasDecoration.tsx |
Christmas | Snow particles, Santa sleigh, lights, ornaments, sparkle overlay |
NewYearDecoration.tsx |
New Year | Fireworks, confetti, champagne bubbles |
ValentinesDecoration.tsx |
Valentine's | Hearts, rose petals, Cupid arrows |
EasterDecoration.tsx |
Easter | Easter eggs, bunnies, butterflies |
HalloweenDecoration.tsx |
Halloween | Bats, ghosts, spiders, pumpkins |
ThanksgivingDecoration.tsx |
Thanksgiving | Autumn leaves, acorns, pumpkins |
HanukkahDecoration.tsx |
Hanukkah | Menorah, Stars of David, dreidels, gelt |
DiwaliDecoration.tsx |
Diwali | Diyas, rangoli, fireworks, sparklers |
ChineseNewYearDecoration.tsx |
Chinese New Year | Lanterns, dragon, red envelopes |
IslamicDecoration.tsx |
Islamic holidays | Crescents, lanterns, geometric patterns |
RoshHashanahDecoration.tsx |
Rosh Hashanah | Honey, apples, shofar, pomegranates |
PassoverDecoration.tsx |
Passover | Matzah, wine, seder plate elements |
ThankYouDecoration.tsx |
Thank You | Hearts, flowers, ribbons, gifts |
CongratulationsDecoration.tsx |
Congratulations | Balloons, confetti, streamers, fireworks |
ParticleDecoration.tsx |
Fallback | Generic particle system |
Animation Utilities (remotion/src/utils/):
animations.ts- Core animation helpers (usePulse, wrapText, calculateMessageDuration)decorationAnimations.tsx- Shared animation components:SparkleOverlay- Random twinkling starsGlowPulse- Breathing glow effectConfettiBurst- Confetti explosionScalePulse- Scale breathing animationFloatMotion- Vertical bobbing
Animation System:
- Pre-seeded Y/X positions for immediate particle visibility from frame 0
- Reduced delays (30 frames max) for fast particle appearance
- Relative timing using
durationInFramesfor special animations - Modulo-based wrapping:
(startY + (frame + delay) * speed) % (height + 100) - Continuous animations throughout entire video duration
- SparkleOverlay added to most decoration components for enhanced visual appeal
Located in frontend/src/components/:
UI Components (ui/):
- button.tsx, dialog.tsx, input.tsx, label.tsx, textarea.tsx
- card.tsx, progress.tsx, radio-group.tsx, separator.tsx, select.tsx, slider.tsx
- sonner.tsx (toast notifications)
- All based on Radix UI primitives with Tailwind styling
Public Assets (public/):
favicon.svg- Custom "C" favicon with orange/yellow gradientapple-touch-icon.png- iOS home screen icon (180x180)template.csv- Downloadable CSV template with Name,Message columns
Feature Components:
- SplashScreen.tsx - Animated entrance with Mail icon and Framer Motion particles
- MainLayout.tsx - Top nav, dark mode toggle, settings button, err0r.dev footer link
- SetupScreen.tsx - Initial API key entry (1Password autofill disabled)
- SettingsModal.tsx - API key management with validation (1Password autofill disabled)
- FileUploader.tsx - Drag-drop CSV/Excel upload with downloadable template
- RecipientForm.tsx - Manual entry form with sender name field
- RecipientTable.tsx - List of recipients with edit/delete
- HolidaySelector.tsx - 17 holiday cards in 5 categories (keyboard accessible)
- FormatPicker.tsx - Radio group for export formats
- MusicSelector.tsx - Music track selection from Jamendo (keyboard accessible)
- VideoGenerator.tsx - Three-step accordion: AI settings, message review/edit, video generation with cancellation
- VideoGallery.tsx - Grid with preview, download, and delete functionality
- DownloadStep.tsx - Final download interface with batch ZIP download
- ConfirmStartOverDialog.tsx - Warning dialog when starting over with completed videos
- ConfirmModeChangeDialog.tsx - Confirmation when switching input modes
- InputModeToggle.tsx - Toggle between CSV upload and manual entry
Accessibility Features:
- ARIA labels on all interactive elements
- Keyboard navigation (tabIndex, onKeyDown handlers)
- Screen reader announcements (aria-live regions)
- Focus indicators on custom components
- Form field descriptions (aria-describedby)
- Required field indicators (aria-required)
Utilities (lib/):
api.ts- API client with typed methods for all backend endpoints including cancellationutils.ts- Utility functions (cn for className merging)
Generation Flow (VideoGenerator.tsx):
- Step 1: Configure AI Settings - Adjust message length (5-100 words) and creativity (closer to original vs. more imaginative)
- Step 2: Review & Edit - User reviews AI-generated messages, can edit inline, must click "Confirm Messages & Generate Videos"
- Step 3: Generate Videos - Only starts after user confirms messages; tracks progress with polling; supports cancellation
Important: Videos do NOT auto-generate. User must explicitly confirm messages before video generation begins.
Located in frontend/src/stores/:
-
settingsStore.ts
- Persisted to localStorage
- Stores: openaiKey, jamendoKey, darkMode, hasCompletedSetup
- Actions: setters and toggleDarkMode
-
recipientsStore.ts
- Stores: recipients[], recipientsWithMessages[], senderName, messagesConfirmed
- Actions: add, remove, update, setRecipients, setSenderName, setMessagesConfirmed, updateRecipientMessage, clear
- Note:
messagesConfirmedmust be true before videos can generate
-
videoStore.ts
- Stores: selectedTheme, selectedFormat, selectedMusicUrl, currentJobId, jobs[]
- Actions: setters, updateJobProgress, removeJob, clearVideoState
-
uiStore.ts
- Stores: showSplash, showSetup, isLoading, currentStep
- Actions: setters, goBack, goForward, canGoBack, resetWizard
Located in shared/src/index.ts:
Key Enums:
HolidayTheme- 17 holiday values (christmas, new_year, valentines_day, easter, halloween, thanksgiving, hanukkah, diwali, chinese_new_year, eid_al_fitr, eid_al_adha, ramadan, rosh_hashanah, passover, lunar_new_year, thank_you, congratulations)VideoFormat- 4 format values (1080p, 4k, square, social)
Key Interfaces:
Recipient- { id, name, messageGuidance }RecipientWithMessage- extends Recipient with generatedMessageVideoGenerationJob- { id, status, progress, recipientName, videoUrl, error }- status: 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled'
BatchVideoResponse- { jobId, jobs[] }MessageGenerationRequest- includestargetWordCount(5-100) andcreativity(0-1)ZipGenerationResponse- { status, message, totalVideos }ZipProgressResponse- { status, progress, zipPath, error }- All API request/response types
17 themes with unique visual effects:
christmas- Snow particles, Santa sleigh, lights, ornaments, sparkle overlaynew_year- Fireworks, confetti, champagne bubbleseaster- Easter eggs, bunnies, butterfliesvalentines_day- Hearts, rose petals, Cupid arrowshalloween- Bats, ghosts, spiders, pumpkinsthanksgiving- Autumn leaves, acorns, pumpkins
rosh_hashanah- Honey, apples, shofar, pomegranates, gold/amberhanukkah- Menorah, Stars of David, dreidels, geltpassover- Matzah, wine, seder plate, earth tones
eid_al_fitr- Crescent moons, lanterns, geometric patternseid_al_adha- Crescent moons, lanterns, geometric patternsramadan- Crescent moons, lanterns, geometric patterns
chinese_new_year- Lanterns, dragon, red envelopesdiwali- Diyas, rangoli, fireworks, sparklerslunar_new_year- Lanterns, red/gold particles
thank_you- Hearts, flowers, ribbons, giftscongratulations- Balloons, confetti, streamers, fireworks, trophy
'1080p': { width: 1920, height: 1080 } // Standard HD
'4k': { width: 3840, height: 2160 } // Ultra HD
'square': { width: 1080, height: 1080 } // Instagram posts
'social': { width: 1080, height: 1920 } // Stories/TikTokRequest:
{
"openai": "sk-...",
"jamendo": "..."
}Response:
{
"openai": { "valid": true },
"jamendo": { "valid": true }
}Request: FormData with file Response:
{
"recipients": [{ "id": "...", "name": "...", "messageGuidance": "..." }],
"errors": []
}Headers: x-openai-key: sk-...
Request:
{
"recipients": [...],
"theme": "christmas",
"senderName": "John",
"targetWordCount": 50,
"creativity": 0.5
}Response:
{
"recipients": [{ ...recipient, "generatedMessage": "..." }]
}Headers: x-jamendo-key: ...
Response:
{
"tracks": [{ "id": "...", "name": "...", "downloadUrl": "...", "duration": 35 }]
}Request:
{
"recipients": [{ ...recipientWithMessage }],
"theme": "christmas",
"format": "1080p",
"musicUrl": "https://...",
"senderName": "John"
}Response:
{
"jobId": "...",
"jobs": [{ "id": "...", "status": "pending", "progress": 0, "recipientName": "..." }]
}Response:
{
"jobId": "...",
"jobs": [{ "id": "...", "status": "completed", "progress": 100, "videoUrl": "/videos/..." }]
}Response:
{
"success": true,
"message": "All pending jobs cancelled"
}Response:
{
"success": true,
"message": "Video job cancelled"
}Response:
{
"status": "processing",
"message": "ZIP generation started",
"totalVideos": 5
}Response:
{
"status": "completed",
"progress": 100,
"zipPath": "/videos/card0r_batch_123.zip"
}Response:
{
"success": true,
"message": "Video deleted successfully"
}Request:
{
"filenames": ["video1.mp4", "video2.mp4"]
}Response:
{
"success": true,
"message": "Deleted 2 of 2 videos"
}-
Update Shared Types (
shared/src/index.ts)export enum HolidayTheme { // ...existing NEW_HOLIDAY = 'new_holiday', }
-
Add Colours (
remotion/src/types.ts)export const HOLIDAY_COLORS: Record<HolidayTheme, ThemeColors> = { // ...existing new_holiday: { bg: '#...', primary: '#...', secondary: '#...', accent: '#...' }, }
-
Create Decoration Component (
remotion/src/decorations/NewHolidayDecoration.tsx)import { SparkleOverlay } from '../utils/decorationAnimations'; export function NewHolidayDecoration({ width, height }: DecorationProps) { const frame = useCurrentFrame(); const { durationInFrames } = useVideoConfig(); // Generate particles with pre-seeded positions const particles = useMemo(() => Array.from({ length: 30 }, (_, i) => ({ x: random(`x-${i}`) * width, startY: random(`startY-${i}`) * (height + 100), speed: 0.5 + random(`speed-${i}`) * 1.5, delay: random(`delay-${i}`) * 30, })), [width, height] ); return ( <AbsoluteFill> <SparkleOverlay count={25} color="#FFD700" seed="new-holiday" /> {/* Render particles... */} </AbsoluteFill> ); }
-
Register Decoration (
remotion/src/decorations/index.ts)import { NewHolidayDecoration } from './NewHolidayDecoration'; export function getDecorationComponent(theme: HolidayTheme) { switch (theme) { // ...existing case 'new_holiday': return NewHolidayDecoration; } }
-
Add Music Keywords (
backend/src/services/jamendo-service.ts)const MUSIC_KEYWORDS: Record<HolidayTheme, string[]> = { // ...existing new_holiday: ['keyword1', 'keyword2', ...], }
-
Add OpenAI Prompt (
backend/src/services/openai-service.ts)const HOLIDAY_PROMPTS: Record<HolidayTheme, string> = { // ...existing [HolidayTheme.NEW_HOLIDAY]: 'Create a ... greeting', }
-
Add to Frontend Selector (
frontend/src/components/HolidaySelector.tsx)const HOLIDAYS: HolidayOption[] = [ // ...existing { id: HolidayTheme.NEW_HOLIDAY, name: 'New Holiday', emoji: '...', category: 'Western', gradient: 'from-... to-...' }, ]
The video structure in remotion/src/CardComposition.tsx:
- Fade in: 1 second
- Intro: 5 seconds (theme name, recipient greeting)
- Message: Dynamic duration based on word count (~3.5 words/second)
- Sender: 3 seconds (displays "From: [senderName]")
- Outro: 3 seconds (sparkle effect)
- Fade out: 1 second
To modify, update the timing constants and Sequence components in CardComposition.tsx. Remember to update calculateTotalFrames() if changing durations.
When adding new components:
- Add
aria-labelto icon-only buttons - Use
role="button"for clickable non-button elements - Add
tabIndex={0}andonKeyDownhandlers for keyboard navigation - Use
aria-live="polite"for dynamic status updates - Link form fields to descriptions with
aria-describedby - Mark required fields with
aria-required="true" - Hide decorative icons with
aria-hidden="true"
# From root - starts both frontend and backend
npm run dev
# Or individually
npm run dev:frontend
npm run dev:backend# Single command builds all
npm run build
# Or individually
npm run build:shared
npm run build:remotion
npm run build:frontend
npm run build:backend# Build and run
docker-compose up --build
# Run detached
docker-compose up -d
# View logs
docker-compose logs -f
# Stop
docker-compose downDocker Configuration Notes:
- Backend uses
node:20-slim(Debian-based) for Remotion compatibility with Chrome Headless Shell - Chrome Headless Shell is pre-downloaded during Docker build via
ensureBrowser() - Frontend uses
nginx:alpinewith proper file permissions for static assets - Shared package is symlinked into node_modules during build
cd remotion
npx remotion studioBackend:
- Logs go to console
- Video files in
backend/videos/ - Temp files in
backend/temp/ - Check FFmpeg:
ffmpeg -version
Frontend:
- React DevTools extension
- Console logs for API calls
- Network tab for API debugging
Remotion:
- Use
npx remotion studioto preview compositions - Check particle visibility from frame 0
- Verify decoration components render before slides
All video-related paths use __dirname relative paths for consistency:
// In routes/videos.ts, services/remotion-renderer.ts, services/zip-generator.ts:
const VIDEOS_DIR = path.join(__dirname, '../../videos');
// In server.ts:
app.use('/videos', express.static(path.join(__dirname, '../videos')));This ensures videos are stored and served from the same location regardless of where the process is started.
- Sequential: Videos generated one at a time
- Time per video: 30-60 seconds (1080p), 60-90 seconds (4K)
- Memory: ~200-400MB during rendering
- Disk: ~5-10MB per 1080p video, ~20-40MB per 4K
- Parallel Processing: Generate multiple videos concurrently
- Job Queue: Use Redis or BullMQ for persistent jobs
- Caching: Cache generated messages and music
- Lambda Rendering: Use Remotion Lambda for cloud rendering
- In-Memory Jobs: Job tracking lost on server restart
- Sequential Processing: No parallel video generation
- No Persistence: Recipients and jobs not saved to database
- API Rate Limits: OpenAI and Jamendo have rate limits
- Large Batches: Recommended max 50 recipients
- Browser Compatibility: Requires modern browser for frontend
PORT=3001
NODE_ENV=development|production
FRONTEND_URL=http://localhost:5173
MAX_FILE_SIZE=10485760
TEMP_DIR=./temp
VIDEOS_DIR=./videosVITE_API_URL=http://localhost:3001/apiexpress- Web framework@remotion/renderer- Remotion video rendering@remotion/bundler- Remotion bundlingopenai- OpenAI API clientaxios- HTTP client for Jamendomulter- File upload handlingcsv-parse- CSV parsingxlsx- Excel parsingarchiver- ZIP file creation
remotion- Core framework@remotion/cli- CLI tools@remotion/bundler- Webpack bundling
reactv19 - UI libraryzustand- State managementframer-motion- Animations@radix-ui/*- Headless UI componentslucide-react- Iconssonner- Toast notificationspapaparse- CSV parsing client-sidexlsx- Excel parsing client-sidetailwindcss- Styling
- Components: PascalCase (e.g.,
SplashScreen.tsx) - Utilities: camelCase (e.g.,
animations.ts) - Stores: camelCase with Store suffix (e.g.,
settingsStore.ts) - Services: kebab-case (e.g.,
openai-service.ts) - Decorations: PascalCase with Decoration suffix (e.g.,
ChristmasDecoration.tsx) - Types: PascalCase interfaces/types
- API routes: kebab-case (e.g.,
videos.ts)
- TypeScript: Strict mode enabled
- Language: UK English for all user-facing text
- Formatting: Prettier (recommended)
- Linting: ESLint configured
- Comments: JSDoc for complex functions
- Error Handling: Try-catch with proper error messages
# Development
npm run dev # Start both frontend and backend
npm run dev:frontend # Frontend only
npm run dev:backend # Backend only
# Building
npm run build # Build all
npm run build:shared # Build shared types
npm run build:remotion # Build Remotion bundle
npm run build:frontend # Build frontend
npm run build:backend # Build backend
# Production
npm start # Run both in production mode
docker-compose up # Run with Docker
# Remotion
cd remotion && npx remotion studio # Preview compositions- OpenAI API Docs: https://platform.openai.com/docs
- Jamendo API Docs: https://developer.jamendo.com/v3.0
- Remotion Docs: https://www.remotion.dev/docs
- Radix UI: https://www.radix-ui.com/
- shadcn/ui: https://ui.shadcn.com/
When working on this project, consider asking:
- "Should this feature require authentication?"
- "How should errors be handled and displayed to users?"
- "Is this change backward compatible with existing data?"
- "Does this impact video generation performance?"
- "Should this be configurable via environment variables?"
- "Does this need to be persisted across sessions?"
- "Does this text need to use UK English spelling?"
- "Does this component need accessibility attributes?"
- User Experience First: Intuitive, beautiful, responsive
- Accessibility: WCAG AA compliant, keyboard navigable
- Type Safety: TypeScript everywhere
- Modern Stack: Latest stable versions
- Performance: Optimise for speed where possible
- Simplicity: Prefer simple solutions over complex ones
- Documentation: Code should be self-documenting
- UK English: All user-facing text uses British spelling
Last Updated: 2025-12-24 Version: 1.3.0