Files
flatrender/src/lib/scene-browser-data.ts
T

624 lines
16 KiB
TypeScript
Raw Normal View History

/** 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<string, unknown>;
}
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;
});
}