export type VideoSidebarCategoryId = | "all" | "animation" | "intros" | "editing" | "invitation" | "holiday" | "slideshow" | "presentations" | "social" | "ads" | "sales" | "music"; export type AspectRatioFilter = | "all" | "widescreen" | "portrait" | "square" | "fourFive"; export type DurationFilter = "all" | "flexible" | "fixed"; export type RefineType = "templates" | "packs"; export interface VideoSidebarCategory { id: VideoSidebarCategoryId; label: string; } export const VIDEO_SIDEBAR_CATEGORIES: VideoSidebarCategory[] = [ { id: "all", label: "All Templates" }, { id: "animation", label: "Animation Videos" }, { id: "intros", label: "Intros and Logos" }, { id: "editing", label: "Video Editing" }, { id: "invitation", label: "Invitation Videos" }, { id: "holiday", label: "Holiday Videos" }, { id: "slideshow", label: "Slideshow" }, { id: "presentations", label: "Presentations" }, { id: "social", label: "Social Media Videos" }, { id: "ads", label: "Video Ad Templates" }, { id: "sales", label: "Sales Videos" }, { id: "music", label: "Music Visualization" }, ]; export const ASPECT_RATIO_OPTIONS: { id: AspectRatioFilter; label: string; }[] = [ { id: "all", label: "All Sizes" }, { id: "widescreen", label: "16:9" }, { id: "portrait", label: "9:16" }, { id: "square", label: "1:1" }, { id: "fourFive", label: "4:5" }, ]; export type TemplateDetailAspectRatio = "16:9" | "9:16"; export const TEMPLATE_STYLE_COUNT = 4; export interface VideoCatalogTemplate { id: string; name: string; videoCategory: Exclude; aspectRatio: Exclude; aspectRatios?: readonly TemplateDetailAspectRatio[]; durationType: "flexible" | "fixed"; premium: boolean; sceneCount: number; supports4k: boolean; colorChange: boolean; scriptToVideo: boolean; description?: string; isNew?: boolean; } export function getVideoTemplateCategoryLabel( category: Exclude ): string { const match = VIDEO_SIDEBAR_CATEGORIES.find((item) => item.id === category); return match?.label ?? category; } export function getTemplateDetailAspectRatios( template: VideoCatalogTemplate ): TemplateDetailAspectRatio[] { if (template.aspectRatios && template.aspectRatios.length > 0) { return [...template.aspectRatios]; } return ["16:9", "9:16"]; } export function getVideoTemplateStyleImageSrc( templateId: string, styleIndex: number ): string { return `https://picsum.photos/seed/${templateId}-style${styleIndex}/240/135`; } export function getVideoTemplateExampleImageSrc( templateId: string, exampleIndex: number ): string { return `https://picsum.photos/seed/${templateId}-example${exampleIndex}/520/325`; } const templatesByCategory: Record< Exclude, string[] > = { animation: [ "Whiteboard Animation Toolkit", "3D Explainer Video Toolkit", "Trendy Explainer Toolkit", "Factory of 3D Animations", "Anime Stories Pack", "Healthcare Explainer Toolkit", ], intros: [ "Abstract Distortion Intro", "Glossy Bubbles Intro", "Neon Soundwaves Visualizer", "Minimal Logo Reveal", "Glitch Intro Pack", ], editing: [ "Cinematic Color Grade", "Quick Cut Montage", "Documentary Style Opener", ], invitation: [ "Wedding Invitation Slideshow", "Birthday Party Invite", "Corporate Event Opening", ], holiday: [ "Christmas Greeting Card", "New Year Countdown", "Seasonal Sale Promo", ], slideshow: [ "Polaroid Frames Slideshow", "Flipping Slideshow", "Fragmented Transitions Slideshow", "Parallax Circles", "Bokeh Effects Slideshow", ], presentations: [ "Business Presentation Pack", "Startup Pitch Deck", "Quarterly Report Intro", ], social: [ "Instagram Carousel", "TikTok Hook Pack", "Story Highlight Reel", "LinkedIn Promo", ], ads: [ "Product Launch Ad", "App Promo Vertical", "Flash Sale Countdown", ], sales: [ "SaaS Explainer", "Real Estate Walkthrough", "Restaurant Promo", ], music: [ "Audio Spectrum Visualizer", "Vinyl Record Spin", "Beat Sync Typography", ], }; const aspectRatios: Exclude[] = [ "widescreen", "portrait", "square", "fourFive", ]; function buildVideoCatalog(): VideoCatalogTemplate[] { const items: VideoCatalogTemplate[] = []; let index = 0; for (const [category, names] of Object.entries(templatesByCategory)) { const videoCategory = category as Exclude; for (const baseName of names) { for (let variant = 0; variant < 2; variant += 1) { const name = variant > 0 ? `${baseName} ${variant + 1}` : baseName; const detailAspectRatios: TemplateDetailAspectRatio[] = index % 3 === 0 ? ["16:9"] : ["16:9", "9:16"]; items.push({ id: `vtpl-${category}-${index}`, name, videoCategory, aspectRatio: aspectRatios[index % aspectRatios.length], aspectRatios: detailAspectRatios, durationType: index % 3 === 0 ? "fixed" : "flexible", premium: index % 4 === 0, sceneCount: 5 + (index % 12) * 10, supports4k: index % 2 === 0, colorChange: index % 3 !== 0, scriptToVideo: index % 5 === 0, isNew: index < 8, }); index += 1; } } } return items; } /** Featured presets on /studio/video/new — ids match TEMPLATE_GALLERY_ITEMS */ const ONBOARDING_PRESET_TEMPLATES: VideoCatalogTemplate[] = [ { id: "promo-reel", name: "Animated Inspirational Video", videoCategory: "animation", aspectRatio: "widescreen", aspectRatios: ["16:9", "9:16"], durationType: "flexible", premium: false, sceneCount: 12, supports4k: true, colorChange: true, scriptToVideo: false, isNew: true, }, { id: "product-launch", name: "Cybersecurity Company Promo", videoCategory: "ads", aspectRatio: "widescreen", aspectRatios: ["16:9", "9:16"], durationType: "flexible", premium: true, sceneCount: 8, supports4k: true, colorChange: true, scriptToVideo: false, }, { id: "brand-story", name: "Get to Know Your Customers Day", videoCategory: "social", aspectRatio: "widescreen", aspectRatios: ["16:9", "9:16"], durationType: "flexible", premium: false, sceneCount: 10, supports4k: false, colorChange: true, scriptToVideo: true, }, { id: "instagram-carousel", name: "SEO Agency Introduction", videoCategory: "social", aspectRatio: "square", aspectRatios: ["16:9", "9:16"], durationType: "flexible", premium: false, sceneCount: 6, supports4k: false, colorChange: true, scriptToVideo: false, }, { id: "tiktok-hook", name: "Tech Startup Promo", videoCategory: "social", aspectRatio: "portrait", aspectRatios: ["9:16"], durationType: "flexible", premium: false, sceneCount: 5, supports4k: false, colorChange: true, scriptToVideo: false, isNew: true, }, { id: "pitch-deck", name: "Corporate Explainer", videoCategory: "presentations", aspectRatio: "widescreen", aspectRatios: ["16:9"], durationType: "fixed", premium: false, sceneCount: 15, supports4k: true, colorChange: true, scriptToVideo: false, }, { id: "hero-promo", name: "Hero Product Launch", videoCategory: "ads", aspectRatio: "widescreen", aspectRatios: ["16:9", "9:16"], durationType: "flexible", premium: true, sceneCount: 9, supports4k: true, colorChange: true, scriptToVideo: false, }, { id: "event-recap", name: "Event Recap Highlight", videoCategory: "slideshow", aspectRatio: "widescreen", aspectRatios: ["16:9", "9:16"], durationType: "flexible", premium: false, sceneCount: 11, supports4k: true, colorChange: true, scriptToVideo: false, }, ]; export const VIDEO_TEMPLATES_CATALOG = [ ...ONBOARDING_PRESET_TEMPLATES, ...buildVideoCatalog(), ]; export interface VideoTemplateFilters { search: string; sidebarCategory: VideoSidebarCategoryId; aspectRatio: AspectRatioFilter; duration: DurationFilter; premiumOnly: boolean; supports4k: boolean; colorChange: boolean; scriptToVideo: boolean; } export function filterVideoCatalog( templates: VideoCatalogTemplate[], filters: VideoTemplateFilters ): VideoCatalogTemplate[] { const query = filters.search.trim().toLowerCase(); return templates.filter((template) => { if ( filters.sidebarCategory !== "all" && template.videoCategory !== filters.sidebarCategory ) { return false; } if ( filters.aspectRatio !== "all" && template.aspectRatio !== filters.aspectRatio ) { return false; } if ( filters.duration !== "all" && template.durationType !== filters.duration ) { return false; } if (filters.premiumOnly && !template.premium) return false; if (filters.supports4k && !template.supports4k) return false; if (filters.colorChange && !template.colorChange) return false; if (filters.scriptToVideo && !template.scriptToVideo) return false; if (query && !template.name.toLowerCase().includes(query)) return false; return true; }); } export function getVideoTemplateImageSrc(id: string): string { return `https://picsum.photos/seed/${id}/640/360`; } export interface VideoTemplateSection { id: string; title: string; count: number; templates: VideoCatalogTemplate[]; } export function buildVideoTemplateSections( filtered: VideoCatalogTemplate[], sidebarCategory: VideoSidebarCategoryId ): VideoTemplateSection[] { const newlyReleased = filtered.filter((t) => t.isNew).slice(0, 8); const sections: VideoTemplateSection[] = []; if (newlyReleased.length > 0 && sidebarCategory === "all") { sections.push({ id: "newly-released", title: "Newly released", count: newlyReleased.length, templates: newlyReleased, }); } const categories = sidebarCategory === "all" ? VIDEO_SIDEBAR_CATEGORIES.filter((c) => c.id !== "all") : VIDEO_SIDEBAR_CATEGORIES.filter((c) => c.id === sidebarCategory); for (const category of categories) { const templates = filtered .filter((t) => t.videoCategory === category.id) .slice(0, 12); if (templates.length === 0) continue; sections.push({ id: category.id, title: category.label, count: filtered.filter((t) => t.videoCategory === category.id).length, templates, }); } return sections; } export function toProjectTemplate( template: VideoCatalogTemplate ): { id: string; name: string; category: "Video" } { return { id: template.id, name: template.name, category: "Video", }; } // ── Admin API → catalog helpers ─────────────────────────────────────────────── /** * Map an admin category name (or slug) to the closest hardcoded * VideoSidebarCategoryId. Falls back to "social" when nothing matches. */ export function adminCategoryNameToSidebarId( categoryName?: string ): Exclude { if (!categoryName) return "social"; const n = categoryName.toLowerCase(); if (n.includes("animat")) return "animation"; if (n.includes("intro") || n.includes("logo")) return "intros"; if (n.includes("edit")) return "editing"; if (n.includes("invit")) return "invitation"; if ( n.includes("holiday") || n.includes("christmas") || n.includes("new year") ) return "holiday"; if (n.includes("slide")) return "slideshow"; if ( n.includes("present") || n.includes("pitch") || n.includes("deck") ) return "presentations"; if ( n.includes("social") || n.includes("instagram") || n.includes("tiktok") || n.includes("reel") ) return "social"; if (n.includes("ad") || n.includes("promo") || n.includes("ads")) return "ads"; if (n.includes("sale") || n.includes("real estate")) return "sales"; if (n.includes("music") || n.includes("audio")) return "music"; return "social"; } /** * Convert a raw AdminProject (from admin-api.ts) to a VideoCatalogTemplate * so admin-managed templates can be shown on the templates page. * * Import type only — do not import from admin-api in this file at runtime. */ export interface AdminProjectLike { slug: string; title: string; description?: string; type: "video" | "image"; categoryName?: string; coverImageUrl?: string; previewVideoUrl?: string; } export function adminProjectToCatalogTemplate( p: AdminProjectLike ): VideoCatalogTemplate { return { id: p.slug, name: p.title, videoCategory: adminCategoryNameToSidebarId(p.categoryName), aspectRatio: "widescreen", durationType: "flexible", premium: false, sceneCount: 0, supports4k: false, colorChange: false, scriptToVideo: false, description: p.description, isNew: true, }; }