/** A layer definition without an `id` — fresh ids are assigned when the template is applied. */ export interface SceneTemplateLayer { type: "text" | "image" | "video" | "shape" | "draw"; name?: string; visible?: boolean; x: number; y: number; width: number; height: number; rotation: number; opacity: number; zIndex: number; props: Record; } export const SCENE_BROWSER_CATEGORIES = [ { id: "all", label: "All Scenes" }, { id: "characters", label: "Characters" }, { id: "business", label: "Business" }, { id: "technology", label: "Technology" }, { id: "nature", label: "Nature" }, { id: "abstract", label: "Abstract" }, { id: "sports", label: "Sports" }, { id: "food", label: "Food" }, ] as const; export type SceneBrowserCategoryId = (typeof SCENE_BROWSER_CATEGORIES)[number]["id"]; export type SceneBrowserContentCategory = Exclude< SceneBrowserCategoryId, "all" >; export type SceneBrowserMediaFilter = "all" | "video" | "photo"; export interface BrowserSceneItem { id: string; name: string; category: SceneBrowserContentCategory; mediaType: "video" | "photo"; characterCount: number; durationLabel: string; gradientFrom: string; gradientTo: string; /** Pre-built layers that populate the scene when selected. */ templateLayers: SceneTemplateLayer[]; } // --------------------------------------------------------------------------- // Layout helpers — canvas is 1280 × 720 // --------------------------------------------------------------------------- /** Two-column layout: solid colour left, image placeholder right. */ function splitLayout( bg: string, titleColor = "#FFFFFF", subtitleColor = "#94a3b8" ): SceneTemplateLayer[] { return [ { type: "shape", x: 0, y: 0, width: 1280, height: 720, rotation: 0, opacity: 1, zIndex: 0, props: { shape: "rect", fill: bg, stroke: bg, strokeWidth: 0, cornerRadius: 0 }, }, { type: "image", x: 660, y: 60, width: 540, height: 600, rotation: 0, opacity: 1, zIndex: 1, props: { src: "", cornerRadius: 12 }, }, { type: "text", x: 80, y: 230, width: 530, height: 120, rotation: 0, opacity: 1, zIndex: 2, props: { text: "Your Main Title", fontSize: 60, fill: titleColor, fontFamily: "Inter, sans-serif", align: "left", letterSpacing: 0, lineHeight: 1.2, animation: "none", }, }, { type: "text", x: 80, y: 380, width: 530, height: 80, rotation: 0, opacity: 1, zIndex: 3, props: { text: "Your Subtitle Here", fontSize: 36, fill: subtitleColor, fontFamily: "Inter, sans-serif", align: "left", letterSpacing: 0, lineHeight: 1.2, animation: "none", }, }, ]; } /** Centred title + subtitle, no image placeholder. */ function centeredLayout( bg: string, titleColor = "#FFFFFF", subtitleColor = "#94a3b8" ): SceneTemplateLayer[] { return [ { type: "shape", x: 0, y: 0, width: 1280, height: 720, rotation: 0, opacity: 1, zIndex: 0, props: { shape: "rect", fill: bg, stroke: bg, strokeWidth: 0, cornerRadius: 0 }, }, { type: "text", x: 80, y: 265, width: 1120, height: 135, rotation: 0, opacity: 1, zIndex: 1, props: { text: "Your Main Title", fontSize: 72, fill: titleColor, fontFamily: "Inter, sans-serif", align: "center", letterSpacing: 0, lineHeight: 1.2, animation: "none", }, }, { type: "text", x: 80, y: 430, width: 1120, height: 80, rotation: 0, opacity: 1, zIndex: 2, props: { text: "Your Subtitle Here", fontSize: 40, fill: subtitleColor, fontFamily: "Inter, sans-serif", align: "center", letterSpacing: 0, lineHeight: 1.2, animation: "none", }, }, ]; } /** Full-bleed image background with a dark overlay + centred text on top. */ function overlayLayout( bg: string, titleColor = "#FFFFFF", subtitleColor = "#e2e8f0" ): SceneTemplateLayer[] { return [ { type: "shape", x: 0, y: 0, width: 1280, height: 720, rotation: 0, opacity: 1, zIndex: 0, props: { shape: "rect", fill: bg, stroke: bg, strokeWidth: 0, cornerRadius: 0 }, }, { type: "image", x: 0, y: 0, width: 1280, height: 720, rotation: 0, opacity: 1, zIndex: 1, props: { src: "", cornerRadius: 0 }, }, { type: "shape", x: 0, y: 0, width: 1280, height: 720, rotation: 0, opacity: 0.55, zIndex: 2, props: { shape: "rect", fill: "#000000", stroke: "#000000", strokeWidth: 0, cornerRadius: 0 }, }, { type: "text", x: 80, y: 265, width: 1120, height: 135, rotation: 0, opacity: 1, zIndex: 3, props: { text: "Your Main Title", fontSize: 72, fill: titleColor, fontFamily: "Inter, sans-serif", align: "center", letterSpacing: 0, lineHeight: 1.2, animation: "none", }, }, { type: "text", x: 80, y: 430, width: 1120, height: 80, rotation: 0, opacity: 1, zIndex: 4, props: { text: "Your Subtitle Here", fontSize: 40, fill: subtitleColor, fontFamily: "Inter, sans-serif", align: "center", letterSpacing: 0, lineHeight: 1.2, animation: "none", }, }, ]; } // --------------------------------------------------------------------------- // Scene catalog // --------------------------------------------------------------------------- export const BROWSER_SCENES: BrowserSceneItem[] = [ // ── Characters ──────────────────────────────────────────────────────────── { id: "man-waving", name: "Man waving hello", category: "characters", mediaType: "video", characterCount: 1, durationLabel: "3-10 sec.", gradientFrom: "from-sky-200", gradientTo: "to-blue-300", templateLayers: splitLayout("#0c1a3d"), }, { id: "woman-presenting", name: "Woman presenting", category: "characters", mediaType: "video", characterCount: 1, durationLabel: "3-10 sec.", gradientFrom: "from-violet-200", gradientTo: "to-purple-300", templateLayers: splitLayout("#1a0a2e"), }, { id: "friendly-greeting", name: "Friendly office greeting", category: "characters", mediaType: "photo", characterCount: 2, durationLabel: "3-10 sec.", gradientFrom: "from-rose-200", gradientTo: "to-pink-300", templateLayers: splitLayout("#2d0a14"), }, { id: "customer-support", name: "Customer support agent", category: "characters", mediaType: "video", characterCount: 1, durationLabel: "3-10 sec.", gradientFrom: "from-cyan-200", gradientTo: "to-teal-300", templateLayers: splitLayout("#071a1a"), }, // ── Business ────────────────────────────────────────────────────────────── { id: "team-meeting", name: "Team meeting", category: "business", mediaType: "video", characterCount: 4, durationLabel: "3-10 sec.", gradientFrom: "from-blue-200", gradientTo: "to-indigo-300", templateLayers: splitLayout("#0a1a3d"), }, { id: "handshake-deal", name: "Handshake closing deal", category: "business", mediaType: "photo", characterCount: 2, durationLabel: "3-10 sec.", gradientFrom: "from-slate-200", gradientTo: "to-gray-300", templateLayers: splitLayout("#0f172a"), }, { id: "startup-pitch", name: "Startup pitch deck", category: "business", mediaType: "video", characterCount: 1, durationLabel: "3-10 sec.", gradientFrom: "from-indigo-200", gradientTo: "to-violet-300", templateLayers: centeredLayout("#0f0f2e"), }, { id: "office-collaboration", name: "Office collaboration", category: "business", mediaType: "video", characterCount: 3, durationLabel: "3-10 sec.", gradientFrom: "from-blue-200", gradientTo: "to-sky-300", templateLayers: splitLayout("#0a1428"), }, // ── Technology ──────────────────────────────────────────────────────────── { id: "city-skyline", name: "City skyline", category: "technology", mediaType: "video", characterCount: 0, durationLabel: "3-10 sec.", gradientFrom: "from-indigo-200", gradientTo: "to-blue-400", templateLayers: overlayLayout("#0a0f2e"), }, { id: "tech-network", name: "Tech network", category: "technology", mediaType: "video", characterCount: 0, durationLabel: "3-10 sec.", gradientFrom: "from-cyan-200", gradientTo: "to-indigo-300", templateLayers: centeredLayout("#071a1a"), }, { id: "coding-desk", name: "Developer at desk", category: "technology", mediaType: "photo", characterCount: 1, durationLabel: "3-10 sec.", gradientFrom: "from-emerald-200", gradientTo: "to-teal-300", templateLayers: splitLayout("#071c14"), }, { id: "data-visualization", name: "Data visualization", category: "technology", mediaType: "video", characterCount: 0, durationLabel: "3-10 sec.", gradientFrom: "from-violet-200", gradientTo: "to-fuchsia-300", templateLayers: centeredLayout("#1a0a2e"), }, // ── Nature ──────────────────────────────────────────────────────────────── { id: "forest-path", name: "Forest morning path", category: "nature", mediaType: "video", characterCount: 0, durationLabel: "3-10 sec.", gradientFrom: "from-green-200", gradientTo: "to-emerald-300", templateLayers: overlayLayout("#071c0f"), }, { id: "ocean-sunset", name: "Ocean sunset", category: "nature", mediaType: "video", characterCount: 0, durationLabel: "3-10 sec.", gradientFrom: "from-amber-200", gradientTo: "to-orange-300", templateLayers: overlayLayout("#1c0f07"), }, { id: "mountain-aerial", name: "Mountain aerial", category: "nature", mediaType: "photo", characterCount: 0, durationLabel: "3-10 sec.", gradientFrom: "from-sky-200", gradientTo: "to-blue-300", templateLayers: overlayLayout("#0c1a2e"), }, { id: "wildlife-meadow", name: "Wildlife meadow", category: "nature", mediaType: "video", characterCount: 0, durationLabel: "3-10 sec.", gradientFrom: "from-lime-200", gradientTo: "to-green-300", templateLayers: overlayLayout("#0a1c07"), }, // ── Abstract ────────────────────────────────────────────────────────────── { id: "abstract-waves", name: "Abstract waves", category: "abstract", mediaType: "video", characterCount: 0, durationLabel: "3-10 sec.", gradientFrom: "from-fuchsia-200", gradientTo: "to-purple-300", templateLayers: centeredLayout("#1a0a2e"), }, { id: "gradient-flow", name: "Gradient flow", category: "abstract", mediaType: "video", characterCount: 0, durationLabel: "3-10 sec.", gradientFrom: "from-pink-200", gradientTo: "to-rose-300", templateLayers: centeredLayout("#1c0a14"), }, { id: "geometric-shapes", name: "Geometric shapes", category: "abstract", mediaType: "photo", characterCount: 0, durationLabel: "3-10 sec.", gradientFrom: "from-indigo-200", gradientTo: "to-violet-300", templateLayers: centeredLayout("#0f0a2e"), }, { id: "particle-burst", name: "Particle burst", category: "abstract", mediaType: "video", characterCount: 0, durationLabel: "3-10 sec.", gradientFrom: "from-blue-200", gradientTo: "to-cyan-300", templateLayers: centeredLayout("#071628"), }, // ── Sports ──────────────────────────────────────────────────────────────── { id: "sports-celebration", name: "Sports celebration", category: "sports", mediaType: "video", characterCount: 3, durationLabel: "3-10 sec.", gradientFrom: "from-orange-200", gradientTo: "to-red-300", templateLayers: splitLayout("#1c0f07"), }, { id: "soccer-goal", name: "Soccer goal moment", category: "sports", mediaType: "video", characterCount: 2, durationLabel: "3-10 sec.", gradientFrom: "from-green-200", gradientTo: "to-lime-300", templateLayers: splitLayout("#0a1c07"), }, { id: "gym-workout", name: "Gym workout", category: "sports", mediaType: "photo", characterCount: 1, durationLabel: "3-10 sec.", gradientFrom: "from-amber-200", gradientTo: "to-yellow-300", templateLayers: splitLayout("#1c1007"), }, { id: "running-track", name: "Running on track", category: "sports", mediaType: "video", characterCount: 1, durationLabel: "3-10 sec.", gradientFrom: "from-sky-200", gradientTo: "to-indigo-300", templateLayers: splitLayout("#0a1428"), }, // ── Food ────────────────────────────────────────────────────────────────── { id: "food-preparation", name: "Food preparation", category: "food", mediaType: "video", characterCount: 1, durationLabel: "3-10 sec.", gradientFrom: "from-amber-200", gradientTo: "to-orange-300", templateLayers: splitLayout("#1c0f07"), }, { id: "restaurant-plating", name: "Restaurant plating", category: "food", mediaType: "photo", characterCount: 0, durationLabel: "3-10 sec.", gradientFrom: "from-rose-200", gradientTo: "to-red-300", templateLayers: overlayLayout("#1c0a0f"), }, { id: "coffee-pour", name: "Coffee pour slow-mo", category: "food", mediaType: "video", characterCount: 0, durationLabel: "3-10 sec.", gradientFrom: "from-yellow-200", gradientTo: "to-amber-300", templateLayers: overlayLayout("#0f0a07"), }, { id: "fresh-ingredients", name: "Fresh ingredients", category: "food", mediaType: "photo", characterCount: 0, durationLabel: "3-10 sec.", gradientFrom: "from-lime-200", gradientTo: "to-green-300", templateLayers: overlayLayout("#0a1c07"), }, ]; export function filterBrowserScenes( scenes: BrowserSceneItem[], options: { categoryId: SceneBrowserCategoryId; mediaFilter: SceneBrowserMediaFilter; search: string; } ): BrowserSceneItem[] { const query = options.search.trim().toLowerCase(); return scenes.filter((scene) => { if (options.categoryId !== "all" && scene.category !== options.categoryId) { return false; } if ( options.mediaFilter !== "all" && scene.mediaType !== options.mediaFilter ) { return false; } if (query && !scene.name.toLowerCase().includes(query)) { return false; } return true; }); }