Template System
┌─────────────────────────────────────────────────────────────────────────────┐
│ TEMPLATE SYSTEM OVERVIEW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ SCENE │ │ BLOCK │ │ PROJECT │ │
│ │ Template │ │ Template │ │ Template │ │
│ │ 🎬 │ │ 🧩 │ │ 📁 │ │
│ │ Full scene │ │ Reusable │ │ Multi-scene │ │
│ │ with objects │ │ component │ │ complete │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ └──────────────────────┴──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ TemplateEnvelopeV1 │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ schemaVersion: 1 │ │ │
│ │ │ meta: {...} │ │ │
│ │ │ payload: {...} │ │ │
│ │ │ requiredAssets: []│ │ │
│ │ └─────────────────────┘ │ │
│ └─────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Table of Contents
- Overview
- Template Types
- Schema Structure
- Object DTO
- Timeline Snippet
- Asset References
- Template Stores
- Apply Pipeline
- Save Template Dialog
- Template Gallery
- Validation Rules
- Best Practices
- Troubleshooting
Overview
The Template System allows users to save, share, and reuse animation content at different granularity levels. Templates are self-contained packages that include all necessary metadata, objects, timeline data, and asset references.
Key Concepts
┌────────────────────────────────────────────────────────────────┐
│ TEMPLATE WORKFLOW │
├────────────────────────────────────────────────────────────────┤
│ │
│ CREATE SAVE APPLY │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ Scene │ ──────▶ │ Save │ ────────▶ │ Apply │ │
│ │ or │ export │Dialog │ import │ to │ │
│ │ Block │ │ │ │ Scene │ │
│ └───────┘ └───────┘ └───────┘ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────┐ │ │
│ │ │ Template │ │ │
│ │ │ Store │ │ │
│ │ │ (IndexedDB) │ │ │
│ │ └─────────────┘ │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ID Remapping + Asset Resolution │ │
│ │ (Prevents collisions, resolves asset URLs) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
| Feature | Description |
|---|---|
| Schema Version | Currently 1 — enables future migrations |
| Three Template Types | Scene, Block, Project |
| Asset Resolution | Builtin, Imported, URL-based assets |
| ID Remapping | Generates new UUIDs on apply to prevent conflicts |
| Timeline Preservation | Saves tracks, keyframes, draw steps, camera |
Template Types
Scene Template (🎬)
Complete scene with all objects, background, and timeline.
┌────────────────────────────────────────────────────────────────┐
│ SCENE TEMPLATE │
├────────────────────────────────────────────────────────────────┤
│ │
│ SceneDTO │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ width: 1920 height: 1080 │ │
│ │ durationMs: 10000 │ │
│ │ background: { type: 'solid', color: '#ffffff' } │ │
│ │ objects: [ │ │
│ │ { id: 'obj-1', type: 'text', ... }, │ │
│ │ { id: 'obj-2', type: 'svg', ... }, │ │
│ │ { id: 'obj-3', type: 'image', ... } │ │
│ │ ] │ │
│ │ timeline: { │ │
│ │ tracks: [...], keys: [...], │ │
│ │ drawSteps: [...], camera: [...] │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
Use Cases:
- Title cards and intros
- Lesson scenes
- Conclusion scenes
Block Template (🧩)
Reusable component group without scene context.
┌────────────────────────────────────────────────────────────────┐
│ BLOCK TEMPLATE │
├────────────────────────────────────────────────────────────────┤
│ │
│ BlockDTO │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ objects: [ │ │
│ │ { id: 'text-1', type: 'text', x: 0, y: 0, ... }, │ │
│ │ { id: 'icon-1', type: 'svg', x: 50, y: 0, ... } │ │
│ │ ] │ │
│ │ anchor: { x: 0.5, y: 0.5 } // Center anchor │ │
│ │ timeline: { │ │
│ │ tracks: [...], keys: [...], │ │
│ │ drawSteps: [...] │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ⚡ Blocks insert at cursor position │
│ ⚡ Can include internal animations │
│ │
└────────────────────────────────────────────────────────────────┘
Use Cases:
- Call-out boxes
- Icon + label combos
- Bullet point groups
- Data tables
Project Template (📁)
Multi-scene template with project-level settings.
┌────────────────────────────────────────────────────────────────┐
│ PROJECT TEMPLATE │
├────────────────────────────────────────────────────────────────┤
│ │
│ ProjectDTO │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ name: "Educational Video Template" │ │
│ │ width: 1920 height: 1080 fps: 30 │ │
│ │ stylePresets: { │ │
│ │ typographyThemeId: 'modern-clean', │ │
│ │ handPresetId: 'pen-black', │ │
│ │ backgroundPresetId: 'whiteboard' │ │
│ │ } │ │
│ │ scenes: [ │ │
│ │ { sceneId: 's1', name: 'Intro', order: 0, ... }, │ │
│ │ { sceneId: 's2', name: 'Content', order: 1, ...}, │ │
│ │ { sceneId: 's3', name: 'Outro', order: 2, ... } │ │
│ │ ] │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
Use Cases:
- Complete video templates
- Course structures
- Marketing video series
Schema Structure
Template Envelope (Complete Package)
The TemplateEnvelopeV1 is the root container for all templates:
interface TemplateEnvelopeV1 {
schemaVersion: 1; // Always 1 (current version)
meta: TemplateMetaV1; // Searchable metadata
payload: TemplatePayloadV1; // Scene | Block | Project DTO
requiredAssets?: AssetRef[]; // Assets needed by template
}
┌─────────────────────────────────────────────────────────────────┐
│ TemplateEnvelopeV1 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ schemaVersion: 1 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ meta: TemplateMetaV1 │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ id: "template-uuid-123" │ │ │
│ │ │ type: "scene" | "block" | "project" │ │ │
│ │ │ name: "My Template" │ │ │
│ │ │ description: "..." │ │ │
│ │ │ tags: ["education", "intro"] │ │ │
│ │ │ createdAt: "2025-01-01T00:00:00Z" │ │ │
│ │ │ preview: { path?, blobKey?, dataUrl? } │ │ │
│ │ │ aspectRatio: "16:9" │ │ │
│ │ │ durationMs: 10000 │ │ │
│ │ │ useCaseTags: ["education", "coaching"] │ │ │
│ │ │ formatTags: ["title", "intro"] │ │ │
│ │ │ styleTags: ["minimal", "corporate"] │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ payload: SceneDTO | BlockDTO | ProjectDTO │ │
│ │ (Depends on meta.type) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ requiredAssets: AssetRef[] │ │
│ │ [ │ │
│ │ { type: 'builtin', packId: 'tabler', assetId: '...'} │ │
│ │ { type: 'url', url: 'https://...' } │ │
│ │ { type: 'imported', filename: 'logo.png' } │ │
│ │ ] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Template Metadata Fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | ✓ | Unique template identifier (UUID) |
type | TemplateType | ✓ | 'scene' | 'block' | 'project' |
name | string | ✓ | Display name |
description | string | Longer description | |
tags | string[] | ✓ | Search tags |
createdAt | ISO 8601 | ✓ | Creation timestamp |
updatedAt | ISO 8601 | Last update timestamp | |
preview | object | Thumbnail reference | |
aspectRatio | string | '16:9', '9:16', '1:1', '4:3' | |
durationMs | number | Total duration in milliseconds | |
author | string | Creator name | |
packId | string | Pack identifier for builtin | |
useCaseTags | string[] | Use case categories | |
formatTags | string[] | Format/layout tags | |
styleTags | string[] | Visual style tags |
Tag Categories
┌────────────────────────────────────────────────────────────────┐
│ TAG CATEGORIES │
├────────────────────────────────────────────────────────────────┤
│ │
│ USE CASE TAGS FORMAT TAGS STYLE TAGS │
│ ────────────── ─────────── ────────── │
│ • education • title • minimal │
│ • coaching • intro • corporate │
│ • business • bullet-list • playful │
│ • marketing • steps • bold │
│ • storytelling • comparison • hand-drawn │
│ • social • quote │
│ • callout │
│ • outro │
│ │
│ ASPECT RATIOS │
│ ───────────── │
│ • 16:9 (Landscape - YouTube, presentations) │
│ • 9:16 (Portrait - TikTok, Reels, Stories) │
│ • 1:1 (Square - Instagram posts) │
│ • 4:3 (Classic - Older displays) │
│ │
└────────────────────────────────────────────────────────────────┘
Object DTO
The ObjectDTO interface represents canvas objects in serialized form:
interface ObjectDTO {
id: string; // Object identifier
type: ObjectDTOType; // Object type
x: number; // X position
y: number; // Y position
width: number; // Object width
height: number; // Object height
rotation?: number; // Rotation in degrees
props: Record<string, unknown>; // Type-specific properties
// Animation settings (for simple animations)
animationStart?: number; // Start time in seconds
animationDuration?: number; // Duration in seconds
animationType?: AnimationType; // Animation type
animationEasing?: string; // Easing function
}
Object Types
| Type | Description | Key Props |
|---|---|---|
text | Text element | text, fontSize, fontFamily, color, fontWeight |
svg | SVG graphic | src, fill, stroke, strokeWidth |
image | Raster image | src, opacity, borderRadius |
shape | Basic shape | shapeType, fill, stroke, cornerRadius |
group | Object group | children: ObjectDTO[] |
drawing | Hand drawing | points, stroke, strokeWidth |
videoEmbed | Embedded video | videoUrl, autoplay, muted |
svgPath | SVG path element | d, stroke, strokeWidth, fill |
Animation Types
┌────────────────────────────────────────────────────────────────┐
│ ANIMATION TYPES │
├────────────────────────────────────────────────────────────────┤
│ │
│ fadeIn ░░░░░░░░░░ → ██████████ │
│ Opacity 0 → 1 │
│ │
│ slideIn ← ← ← ▓▓▓▓▓▓▓▓▓▓ │
│ Moves in from off-canvas │
│ │
│ scaleIn · → ○ → ● → ⬤ │
│ Scale 0 → 1 │
│ │
│ drawIn ✏️ ────────▶ │
│ SVG stroke animation │
│ │
│ pathFollow 🔵─────────────────▶ │
│ Object follows SVG path │
│ │
│ typewriter T → Ty → Typ → Type → ... │
│ Character-by-character reveal │
│ │
│ none (No animation) │
│ │
└────────────────────────────────────────────────────────────────┘
Timeline Snippet
Templates preserve full timeline state via TimelineSnippetDTO:
interface TimelineSnippetDTO {
// First-class timeline tracks + keyframes
tracks?: TemplateTimelineTrackDTO[];
keys?: TemplateTimelineKeyDTO[];
// Draw animation
drawSteps?: DrawStepDTO[];
// Camera
cameraMode?: 'static' | 'track' | 'follow-hand' | 'follow-object';
cameraFollowTargetId?: string | null;
camera?: CameraKeyframeDTO[];
// Audio
audio?: AudioClipDTO[];
}
Timeline Track Structure
┌────────────────────────────────────────────────────────────────┐
│ TIMELINE TRACKS │
├────────────────────────────────────────────────────────────────┤
│ │
│ TemplateTimelineTrackDTO │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ id: "track-uuid" (remapped on apply) │ │
│ │ ownerId: "object-uuid" (remapped on apply) │ │
│ │ binding: "style.opacity" (property path) │ │
│ │ type: "numeric" (track data type) │ │
│ │ label: "Opacity" (display name) │ │
│ │ category: "property" (property|camera|draw|audio) │ │
│ │ bounds: { min: 0, max: 1 } (value constraints) │ │
│ │ defaultValue: 1 │ │
│ │ easing: "ease-out" │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ TemplateTimelineKeyDTO │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ id: "key-uuid" (remapped on apply) │ │
│ │ trackId: "track-uuid" (remapped on apply) │ │
│ │ time: 1000 (milliseconds) │ │
│ │ value: 0.5 (numeric value) │ │
│ │ interp: "bezier" (interpolation) │ │
│ │ easing: "ease-in-out" │ │
│ │ tangentMode: "auto" (auto|linear|flat|broken) │ │
│ │ inTangent: { dx: -100, dy: 0 } │ │
│ │ outTangent: { dx: 100, dy: 0 } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
Draw Steps & Camera
┌────────────────────────────────────────────────────────────────┐
│ DRAW & CAMERA DATA │
├────────────────────────────────────────────────────────────────┤
│ │
│ DrawStepDTO │
│ ┌────────────────────────────────────────┐ │
│ │ objectId: "obj-uuid" (target object) │ │
│ │ startMs: 0 (start time) │ │
│ │ durationMs: 2000 (draw duration) │ │
│ │ easing: "linear" (optional) │ │
│ └────────────────────────────────────────┘ │
│ │
│ CameraKeyframeDTO │
│ ┌────────────────────────────────────────┐ │
│ │ timeMs: 0 (time in ms) │ │
│ │ x: 960 (pan X) │ │
│ │ y: 540 (pan Y) │ │
│ │ zoom: 1.0 (zoom level) │ │
│ │ easing: "ease-out" (optional) │ │
│ └────────────────────────────────────────┘ │
│ │
│ AudioClipDTO │
│ ┌────────────────────────────────────────┐ │
│ │ assetRef: "audio-uuid" (asset ref) │ │
│ │ startMs: 0 (start time) │ │
│ │ durationMs: 5000 (clip length) │ │
│ │ volume: 0.8 (0-1 range) │ │
│ └────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
Asset References
Templates track required assets to ensure portability:
type AssetRefType = 'builtin' | 'imported' | 'url';
interface AssetRef {
type: AssetRefType;
assetId?: string; // For builtin: asset ID
packId?: string; // For builtin: pack ID
filename?: string; // For imported: original filename
url?: string; // For url: external URL
mime?: string; // MIME type hint
}
┌────────────────────────────────────────────────────────────────┐
│ ASSET REFERENCE TYPES │
├────────────────────────────────────────────────────────────────┤
│ │
│ BUILTIN ASSETS │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ type: 'builtin' │ │
│ │ packId: 'tabler' (icon pack name) │ │
│ │ assetId: 'arrow-right' (icon identifier) │ │
│ │ │ │
│ │ → Resolves to: /assets/builtin/tabler/arrow-right.svg │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ IMPORTED ASSETS │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ type: 'imported' │ │
│ │ filename: 'company-logo.png' │ │
│ │ mime: 'image/png' │ │
│ │ │ │
│ │ → Resolves to: /api/assets/imported/company-logo.png │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ URL ASSETS │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ type: 'url' │ │
│ │ url: 'https://example.com/image.jpg' │ │
│ │ │ │
│ │ → Used directly (external resource) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
[!warning] Asset Resolution When applying templates with imported assets, ensure the original files are available in your project. Missing assets will be replaced with placeholders.
Template Stores
The template system uses a layered store architecture:
┌────────────────────────────────────────────────────────────────┐
│ STORE ARCHITECTURE │
├────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ CompositeTemplateStore │ │
│ │ (Unified interface for all template access) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ USER │ │ BUILTIN │ │ CLOUD │ │
│ │ Store │ │ Store │ │ Store │ │
│ │ (IndexedDB) │ │ (Static) │ │ (Future) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ PRIORITY ORDER: │
│ 1. User (allows override of builtin) │
│ 2. Builtin (shipped with app) │
│ 3. Cloud (future: team templates) │
│ │
└────────────────────────────────────────────────────────────────┘
Store Interface
All stores implement the TemplateStore interface:
interface TemplateStore {
list(query?: TemplateQuery): Promise<TemplateMetaV1[]>;
get(id: string): Promise<TemplateEnvelopeV1 | null>;
save(template: TemplateEnvelopeV1): Promise<void>;
remove(id: string): Promise<void>;
}
Query Parameters
interface TemplateQuery {
type?: TemplateType; // Filter by template type
tags?: string[]; // Match any tag
useCaseTags?: TemplateUseCaseTag[];
formatTags?: TemplateFormatTag[];
styleTags?: TemplateStyleTag[];
aspectRatio?: TemplateAspectRatio;
search?: string; // Full-text search
packId?: string; // Filter by pack
limit?: number; // Pagination
offset?: number;
}
Apply Pipeline
When applying a template, the system goes through several transformation steps:
┌────────────────────────────────────────────────────────────────┐
│ APPLY PIPELINE │
├────────────────────────────────────────────────────────────────┤
│ │
│ 1. FETCH TEMPLATE │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ templateStore.get(templateId) │ │
│ │ → Retrieves TemplateEnvelopeV1 from store │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 2. ID REMAPPING │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ generateIdMap(objectIds) │ │
│ │ remapObjectIds(object, idMap) │ │
│ │ remapTimelineSnippet(timeline, idMap) │ │
│ │ │ │
│ │ • All object IDs → new UUIDs │ │
│ │ • Track IDs → new UUIDs │ │
│ │ • Keyframe IDs → new UUIDs │ │
│ │ • Cross-references updated (pathFollow, etc.) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 3. ASSET RESOLUTION │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ resolveAssets(requiredAssets) │ │
│ │ rewriteTemplatePropsWithResolvedAssets(objects) │ │
│ │ │ │
│ │ • Builtin → /assets/builtin/{pack}/{id} │ │
│ │ • Imported → /api/assets/imported/{filename} │ │
│ │ • URL → used directly │ │
│ │ • Missing → fallback placeholder │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 4. CONVERT DTOs TO RUNTIME OBJECTS │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ objectDtoToSceneObject(dto) │ │
│ │ │ │
│ │ • ObjectDTO → SceneObject │ │
│ │ • Apply defaults │ │
│ │ • Validate constraints │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 5. APPLY TO SCENE/PROJECT │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Scene template: applySceneTemplate() │ │
│ │ Block template: applyBlockTemplate() │ │
│ │ Project template: applyProjectTemplate() │ │
│ │ │ │
│ │ • Update store state │ │
│ │ • Import timeline data │ │
│ │ • Apply background settings │ │
│ │ • Preload assets │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
Apply Modes
┌────────────────────────────────────────────────────────────────┐
│ APPLY MODES │
├────────────────────────────────────────────────────────────────┤
│ │
│ SCENE TEMPLATES │
│ ───────────────── │
│ • replace: Clear scene, add template content │
│ • merge: Add template objects to existing scene │
│ │
│ BLOCK TEMPLATES │
│ ──────────────── │
│ • insert: Add at specified position (or cursor) │
│ │
│ PROJECT TEMPLATES │
│ ───────────────── │
│ • Creates entirely new project │
│ • Replaces current project state │
│ │
└────────────────────────────────────────────────────────────────┘
Save Template Dialog
The Save Template Dialog allows users to save current work as templates:
┌────────────────────────────────────────────────────────────────┐
│ SAVE TEMPLATE DIALOG │
├────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Save as Template [X] │ │
│ ├────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ Template Name: [My Intro Scene_______________] │ │
│ │ │ │
│ │ Template Type: [🎬 Scene ▼] │ │
│ │ │ │
│ │ Description: │ │
│ │ [A clean intro scene with title and... ] │ │
│ │ [subtitle animation_______________________________] │ │
│ │ │ │
│ │ Use Case: │ │
│ │ [✓] education [ ] coaching [✓] business │ │
│ │ [ ] marketing [ ] storytelling [ ] social │ │
│ │ │ │
│ │ Format: │ │
│ │ [✓] title [✓] intro [ ] bullet-list [ ] steps │ │
│ │ │ │
│ │ Preview: [Upload Thumbnail] or [Auto-Generate] │ │
│ │ │ │
│ │ [Cancel] [💾 Save Template] │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
Save Options
| Option | Description |
|---|---|
| Scene | Save entire current scene |
| Block | Save selected objects as reusable block |
| Project | Save entire project with all scenes |
[!tip] Saving Blocks To save a block template, first select the objects you want to include, then choose "Block" as the template type.
Template Gallery
Browse and apply templates from the Template Gallery:
┌────────────────────────────────────────────────────────────────┐
│ TEMPLATE GALLERY │
├────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ [🔍 Search templates...________________________] │ │
│ │ │ │
│ │ Type: [All ▼] Aspect: [All ▼] Tags: [Select tags...] │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ ┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐ │ │
│ │ │ Preview│ │ │ │ Preview│ │ │ │ Preview│ │ │
│ │ │ 📷 │ │ │ │ 📷 │ │ │ │ 📷 │ │ │
│ │ └────────┘ │ │ └────────┘ │ │ └────────┘ │ │
│ │ Title Card │ │ Intro │ │ Bullet │ │
│ │ 🎬 scene │ │ 🎬 scene │ │ 🧩 block │ │
│ │ [Apply] │ │ [Apply] │ │ [Apply] │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ ┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐ │ │
│ │ │ Preview│ │ │ │ Preview│ │ │ │ Preview│ │ │
│ │ │ 📷 │ │ │ │ 📷 │ │ │ │ 📷 │ │ │
│ │ └────────┘ │ │ └────────┘ │ │ └────────┘ │ │
│ │ Quote Box │ │ Full Proj │ │ CTA Block │ │
│ │ 🧩 block │ │ 📁 project │ │ 🧩 block │ │
│ │ [Apply] │ │ [Apply] │ │ [Apply] │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ Source: [All ▼] [📦 Builtin] [👤 User] [☁️ Cloud] │
│ │
└────────────────────────────────────────────────────────────────┘
Gallery Features
- Search: Full-text search across name, description, and tags
- Type Filter: Scene, Block, or Project templates
- Aspect Ratio: Filter by canvas dimensions
- Tag Filters: Use case, format, and style tags
- Source Filter: Builtin, User, or Cloud templates
- Preview: Thumbnail preview with metadata
- Quick Apply: Double-click or click Apply button
Validation Rules
Schema Validation
| Rule | Description |
|---|---|
schemaVersion | Must equal 1 (current version) |
meta.id | Non-empty string (UUID recommended) |
meta.type | One of: 'scene', 'block', 'project' |
meta.name | Non-empty string, max 100 characters |
meta.tags | Array of strings |
meta.createdAt | Valid ISO 8601 date string |
payload | Must match declared meta.type |
Object Validation
| Rule | Description |
|---|---|
id | Non-empty string |
type | Valid ObjectDTOType |
x, y | Finite numbers |
width, height | Positive finite numbers |
rotation | Number (default: 0) |
props | Object with type-specific properties |
Type Guards
// Check payload type
isSceneDTO(payload); // → true if SceneDTO
isBlockDTO(payload); // → true if BlockDTO
isProjectDTO(payload); // → true if ProjectDTO
// Check envelope type
isSceneTemplate(envelope); // → true if scene template
isBlockTemplate(envelope); // → true if block template
isProjectTemplate(envelope); // → true if project template
Best Practices
Creating Templates
┌────────────────────────────────────────────────────────────────┐
│ TEMPLATE BEST PRACTICES │
├────────────────────────────────────────────────────────────────┤
│ │
│ ✅ DO ❌ DON'T │
│ ───── ──────── │
│ Use builtin assets Include large embedded images │
│ Add descriptive tags Use vague names │
│ Include preview thumbnail Skip metadata │
│ Test template application Use absolute paths │
│ Group related objects Include sensitive data │
│ Use relative positioning Hard-code specific IDs │
│ │
└────────────────────────────────────────────────────────────────┘
Template Organization
| Practice | Benefit |
|---|---|
| Use clear naming conventions | Easy discovery |
| Add comprehensive tags | Better search results |
| Include descriptions | Users understand purpose |
| Create preview thumbnails | Visual browsing |
| Group by use case | Logical organization |
[!tip] Block vs Scene Create block templates for reusable components that can be inserted anywhere. Use scene templates for complete layouts with specific timing and background.
Troubleshooting
Common Issues
┌────────────────────────────────────────────────────────────────┐
│ TROUBLESHOOTING │
├────────────────────────────────────────────────────────────────┤
│ │
│ ISSUE: Template not found │
│ ──────────────────────────── │
│ • Check template ID is correct │
│ • Verify template exists in store │
│ • Try refreshing template list │
│ │
│ ISSUE: Missing assets after apply │
│ ──────────────────────────────── │
│ • Imported assets must be re-imported │
│ • Check asset resolution warnings │
│ • Verify builtin pack is available │
│ │
│ ISSUE: Timeline not preserved │
│ ──────────────────────────────── │
│ • Ensure timeline snippet is exported │
│ • Check track/key remapping │
│ • Verify timeline service is available │
│ │
│ ISSUE: Objects positioned incorrectly │
│ ──────────────────────────────────── │
│ • Block templates use anchor for positioning │
│ • Scene templates use absolute positions │
│ • Check canvas dimensions match │
│ │
│ ISSUE: Save fails │
│ ──────────────────────────────── │
│ • Check IndexedDB quota │
│ • Validate required fields │
│ • Check browser storage permissions │
│ │
└────────────────────────────────────────────────────────────────┘
Debug Information
// Check template store status
const templates = await templateStore.list();
console.log('Available templates:', templates.length);
// Validate template before save
console.log('Schema version:', envelope.schemaVersion);
console.log('Template type:', envelope.meta.type);
console.log('Object count:', envelope.payload.objects?.length);
console.log('Required assets:', envelope.requiredAssets?.length);
Summary
The Template System provides a comprehensive solution for saving, sharing, and reusing animation content:
┌────────────────────────────────────────────────────────────────┐
│ SUMMARY │
├────────────────────────────────────────────────────────────────┤
│ │
│ 📦 SCHEMA VERSION 1 │
│ └─ Stable format with migration support │
│ │
│ 🎬 THREE TEMPLATE TYPES │
│ ├─ Scene: Full scene with objects + timeline │
│ ├─ Block: Reusable component group │
│ └─ Project: Multi-scene complete project │
│ │
│ 🔧 ROBUST APPLY PIPELINE │
│ ├─ ID remapping prevents conflicts │
│ ├─ Asset resolution handles all sources │
│ └─ Timeline preservation maintains animations │
│ │
│ 💾 LAYERED STORAGE │
│ ├─ Builtin templates (shipped) │
│ ├─ User templates (IndexedDB) │
│ └─ Cloud templates (future) │
│ │
└────────────────────────────────────────────────────────────────┘