Add modern pitch deck slide layouts for market size, validation, traction, business model, team, and thank you slides
- Implemented Market Size Slide Layout with schema and default data - Created Market Validation Slide Layout with customizable comparison data - Developed Company Traction Slide Layout featuring dynamic growth statistics - Introduced Business Model Slide Layout showcasing CAC metrics - Added Modern Team Slide Layout to display team member profiles - Designed Thank You Slide Layout for closing presentations - Included a settings JSON file for modern layout configurations
This commit is contained in:
parent
817abf9b2e
commit
56cb9f0fe2
12 changed files with 1987 additions and 0 deletions
112
servers/nextjs/presentation-layouts/modern/1IntroSlideLayout.tsx
Normal file
112
servers/nextjs/presentation-layouts/modern/1IntroSlideLayout.tsx
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import React from "react";
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = "intro-pitchdeck-slide";
|
||||
export const layoutName = "Intro Pitch Deck Slide";
|
||||
export const layoutDescription =
|
||||
"A visually appealing introduction slide for a pitch deck, featuring a large title, company name, date, and contact information with a modern design.";
|
||||
const introPitchDeckSchema = z.object({
|
||||
title: z.string().min(2).max(15).default("Pitch Deck").meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().default("").meta({
|
||||
description: "Empty description as per the design",
|
||||
}),
|
||||
contactNumber: z.string().default("+123-456-7890").meta({
|
||||
description: "Contact phone number displayed in footer",
|
||||
}),
|
||||
contactAddress: z
|
||||
.string()
|
||||
.default("123 Anywhere St., Any City, ST 123")
|
||||
.meta({
|
||||
description: "Contact address displayed in footer",
|
||||
}),
|
||||
contactWebsite: z.string().default("www.reallygreatsite.com").meta({
|
||||
description: "Contact website URL displayed in footer",
|
||||
}),
|
||||
companyName: z.string().default("presenton").meta({
|
||||
description: "Company name displayed in header",
|
||||
}),
|
||||
date: z.string().default("June 13, 2038").meta({
|
||||
description: "Date of the presentation",
|
||||
}),
|
||||
});
|
||||
|
||||
export const Schema = introPitchDeckSchema;
|
||||
export type IntroPitchDeckData = z.infer<typeof introPitchDeckSchema>;
|
||||
|
||||
interface IntroSlideLayoutProps {
|
||||
data?: Partial<IntroPitchDeckData>;
|
||||
}
|
||||
|
||||
const IntroPitchDeckSlide: React.FC<IntroSlideLayoutProps> = ({
|
||||
data: slideData,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{/* Montserrat Font */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<div
|
||||
className="w-full max-w-[1280px] aspect-video mx-auto relative overflow-hidden rounded-md"
|
||||
style={{
|
||||
fontFamily: "Montserrat, sans-serif",
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
}}
|
||||
>
|
||||
{/* Top Header */}
|
||||
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
|
||||
<span>{slideData?.companyName}</span>
|
||||
<span>{slideData?.date}</span>
|
||||
</div>
|
||||
|
||||
{/* Main Title */}
|
||||
<div
|
||||
className="absolute left-10"
|
||||
style={{
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
}}
|
||||
>
|
||||
<h1
|
||||
className="text-[9.5rem] font-bold text-[#1E4CD9] leading-none inline-block relative"
|
||||
id="pitchdeck-title"
|
||||
style={{ display: "inline-block" }}
|
||||
>
|
||||
{slideData?.title}
|
||||
{/* Blue underline */}
|
||||
<span
|
||||
className="block bg-[#1E4CD9] h-[4px] absolute left-0"
|
||||
style={{
|
||||
width: "100%",
|
||||
bottom: "-0.5em",
|
||||
transition: "width 0.3s",
|
||||
}}
|
||||
/>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Bottom Contact Row */}
|
||||
<div className="absolute bottom-8 left-10 right-10 flex flex-wrap items-center gap-10 text-[#1E4CD9] text-sm font-medium">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg">📞</span>
|
||||
<span>{slideData?.contactNumber}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg">📍</span>
|
||||
<span>{slideData?.contactAddress}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg">🌐</span>
|
||||
<span>{slideData?.contactWebsite}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default IntroPitchDeckSlide;
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
import React from "react";
|
||||
import * as z from "zod";
|
||||
import { ImageSchema, IconSchema } from "@/presentation-layouts/defaultSchemes";
|
||||
|
||||
export const layoutId = "about-company-slide";
|
||||
export const layoutName = "About Our Company Slide";
|
||||
export const layoutDescription =
|
||||
"A slide layout providing an overview of the company, its background, and key information.";
|
||||
|
||||
const aboutCompanySlideSchema = z.object({
|
||||
title: z.string().min(3).max(60).default("About Our Company").meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
content: z
|
||||
.string()
|
||||
.min(50)
|
||||
.max(500)
|
||||
.default(
|
||||
"In the presentation session, the background/introduction can be filled with information that is arranged systematically and effectively with respect to an interesting topic to be used as material for discussion at the opening of the presentation session. The introduction can provide a general overview for those who are listening to your presentation so that the key words on the topic of discussion are emphasized during this background/introductory presentation session.",
|
||||
)
|
||||
.meta({
|
||||
description: "Main content text describing the company or topic",
|
||||
}),
|
||||
companyName: z.string().min(2).max(50).default("presenton").meta({
|
||||
description: "Company name displayed in header",
|
||||
}),
|
||||
date: z.string().min(5).max(30).default("June 13, 2038").meta({
|
||||
description: "Date displayed in header",
|
||||
}),
|
||||
image: ImageSchema.optional().meta({
|
||||
description:
|
||||
"Optional supporting image for the slide (building, office, etc.)",
|
||||
}),
|
||||
});
|
||||
|
||||
export const Schema = aboutCompanySlideSchema;
|
||||
|
||||
export type AboutCompanySlideData = z.infer<typeof aboutCompanySlideSchema>;
|
||||
|
||||
interface AboutCompanySlideLayoutProps {
|
||||
data?: Partial<AboutCompanySlideData>;
|
||||
}
|
||||
|
||||
const AboutCompanySlideLayout: React.FC<AboutCompanySlideLayoutProps> = ({
|
||||
data: slideData,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{/* Import fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video bg-white relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: "Montserrat, sans-serif",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
|
||||
<span>{slideData?.companyName}</span>
|
||||
<span>{slideData?.date}</span>
|
||||
</div>
|
||||
|
||||
{/* Main content area */}
|
||||
<div className="flex h-full px-16 pb-16">
|
||||
{/* Left side - Image */}
|
||||
<div className="flex-1 pr-16 flex items-center pt-8">
|
||||
<div className="w-full h-96 overflow-hidden">
|
||||
{slideData?.image ? (
|
||||
<img
|
||||
src={slideData.image.__image_url__}
|
||||
alt={slideData.image.__image_prompt__}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
/* Default building facade */
|
||||
<div className="w-full h-full bg-gradient-to-br from-gray-200 to-gray-300 relative">
|
||||
{/* Building structure simulation */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-gray-300 to-gray-200"></div>
|
||||
|
||||
{/* Horizontal lines (building floors) */}
|
||||
<div className="absolute inset-0">
|
||||
{[...Array(12)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="absolute w-full border-t border-gray-400 opacity-60"
|
||||
style={{ top: `${(i + 1) * 8}%` }}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Vertical lines (building columns) */}
|
||||
<div className="absolute inset-0">
|
||||
{[...Array(6)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="absolute h-full border-l border-gray-400 opacity-40"
|
||||
style={{ left: `${(i + 1) * 16}%` }}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Windows */}
|
||||
<div className="absolute inset-0 grid grid-cols-4 gap-2 p-4">
|
||||
{[...Array(32)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="bg-blue-100 opacity-60 rounded-sm border border-gray-300"
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Building edge highlight */}
|
||||
<div className="absolute right-0 top-0 w-1 h-full bg-white opacity-80"></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right side - Content */}
|
||||
<div className="flex-1 pl-16 flex flex-col justify-center">
|
||||
<h2 className="text-6xl font-bold text-blue-600 mb-12 leading-tight">
|
||||
{slideData?.title || "About Our Company"}
|
||||
</h2>
|
||||
|
||||
<div className="text-lg text-blue-600 leading-relaxed font-normal max-w-lg">
|
||||
{slideData?.content ||
|
||||
"In the presentation session, the background/introduction can be filled with information that is arranged systematically and effectively with respect to an interesting topic to be used as material for discussion at the opening of the presentation session. The introduction can provide a general overview for those who are listening to your presentation so that the key words on the topic of discussion are emphasized during this background/introductory presentation session."}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutCompanySlideLayout;
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
import React from "react";
|
||||
import * as z from "zod";
|
||||
import { ImageSchema, IconSchema } from "@/presentation-layouts/defaultSchemes";
|
||||
|
||||
export const layoutId = "problem-statement-slide";
|
||||
export const layoutName = "Problem Statement Slide";
|
||||
export const layoutDescription =
|
||||
"A slide layout designed to present a clear problem statement, including categories of problems, company information, and an optional image.";
|
||||
|
||||
const problemStatementSlideSchema = z.object({
|
||||
title: z.string().min(3).max(40).default("Problem").meta({
|
||||
description: "Main title of the problem statement slide",
|
||||
}),
|
||||
description: z
|
||||
.string()
|
||||
.min(50)
|
||||
.max(500)
|
||||
.default(
|
||||
"A problem needs to be discussed further and in detail because this problem is the main foundation in the initial development of a product, service, and decision making. Without a well-defined problem, it will have an impact on a job that is unfocused, unmanaged, and less relevant.",
|
||||
)
|
||||
.meta({
|
||||
description: "Main content text describing the problem statement",
|
||||
}),
|
||||
problemCategories: z
|
||||
.array(
|
||||
z.object({
|
||||
title: z.string().min(3).max(30).meta({
|
||||
description: "Title of the problem category",
|
||||
}),
|
||||
description: z.string().min(20).max(200).meta({
|
||||
description: "Description of the problem category",
|
||||
}),
|
||||
icon: IconSchema.optional().meta({
|
||||
description: "Optional icon for the problem category",
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.min(2)
|
||||
.max(4)
|
||||
.default([
|
||||
{
|
||||
title: "Inefficiency",
|
||||
description:
|
||||
"Businesses struggle to find digital tools that meet their needs, causing operational slowdowns.",
|
||||
icon: {
|
||||
__icon_url__:
|
||||
"https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/alert-triangle.js",
|
||||
__icon_query__: "warning alert inefficiency",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "High Costs",
|
||||
description:
|
||||
"Outdated systems increase expenses, while small businesses struggle to expand their market reach.",
|
||||
icon: {
|
||||
__icon_url__:
|
||||
"https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/trending-up.js",
|
||||
__icon_query__: "trending up costs chart",
|
||||
},
|
||||
},
|
||||
])
|
||||
.meta({
|
||||
description:
|
||||
"List of problem categories with titles, descriptions, and optional icons",
|
||||
}),
|
||||
companyName: z.string().min(2).max(50).default("Rimberio").meta({
|
||||
description: "Company name displayed in header",
|
||||
}),
|
||||
date: z.string().min(5).max(30).default("June 13, 2038").meta({
|
||||
description: "Date displayed in header",
|
||||
}),
|
||||
});
|
||||
|
||||
export const Schema = problemStatementSlideSchema;
|
||||
|
||||
export type ProblemStatementSlideData = z.infer<
|
||||
typeof problemStatementSlideSchema
|
||||
>;
|
||||
|
||||
interface ProblemStatementSlideLayoutProps {
|
||||
data?: Partial<ProblemStatementSlideData>;
|
||||
}
|
||||
|
||||
const ProblemStatementSlideLayout: React.FC<
|
||||
ProblemStatementSlideLayoutProps
|
||||
> = ({ data: slideData }) => {
|
||||
const problemCategories = slideData?.problemCategories || [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Import fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video bg-gradient-to-br from-blue-600 to-blue-700 relative z-20 mx-auto overflow-hidden text-white"
|
||||
style={{
|
||||
fontFamily: "Montserrat, sans-serif",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-white text-sm font-semibold">
|
||||
<span>{slideData?.companyName}</span>
|
||||
<span>{slideData?.date}</span>
|
||||
</div>
|
||||
|
||||
{/* Main content area */}
|
||||
<div className="flex h-full px-16 pb-16">
|
||||
{/* Left side - Main Problem */}
|
||||
<div className="flex-1 pr-16 flex flex-col justify-center">
|
||||
<div className="flex flex-col items-start justify-center h-full">
|
||||
<h2 className="text-7xl font-bold text-white mb-8 leading-tight text-left">
|
||||
{slideData?.title}
|
||||
</h2>
|
||||
|
||||
<div className="text-lg text-white leading-relaxed font-normal mb-12 max-w-lg text-left">
|
||||
{slideData?.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right side - Problem Categories with Icons */}
|
||||
<div className="flex-1 pl-16 flex flex-col justify-center">
|
||||
<div className="w-full max-w-xl mx-auto grid grid-cols-1 gap-8">
|
||||
{problemCategories.map((category, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-start gap-5 bg-white bg-opacity-5 rounded-lg p-5"
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
<img
|
||||
src={category.icon?.__icon_url__}
|
||||
alt={category.icon?.__icon_query__}
|
||||
className="w-12 h-12"
|
||||
style={{ filter: "invert(1)" }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-white mb-1">
|
||||
{category.title}
|
||||
</h3>
|
||||
<p className="text-sm text-blue-100 leading-relaxed max-w-md">
|
||||
{category.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom border line */}
|
||||
<div className="absolute bottom-0 left-0 w-full h-1 bg-white"></div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProblemStatementSlideLayout;
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
import React from "react";
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from "@/presentation-layouts/defaultSchemes";
|
||||
|
||||
export const layoutId = "solution-slide";
|
||||
export const layoutName = "Solution Slide";
|
||||
export const layoutDescription =
|
||||
"A slide layout designed to present a solution to previously identified problems, showcasing key aspects of the solution with sections and icons.";
|
||||
|
||||
const solutionSlideSchema = z.object({
|
||||
companyName: z.string().min(2).max(50).default("presenton").meta({
|
||||
description: "Company name displayed in header",
|
||||
}),
|
||||
date: z.string().min(5).max(50).default("June 13, 2038").meta({
|
||||
description: "Date displayed in header",
|
||||
}),
|
||||
title: z.string().min(3).max(40).default("Solution").meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
mainDescription: z
|
||||
.string()
|
||||
.min(20)
|
||||
.max(300)
|
||||
.default(
|
||||
"Show that we offer a solution that solves the problems previously described and identified. Make sure that the solutions we offer uphold the values of effectiveness, efficiency, and are highly relevant to the market situation and society.",
|
||||
)
|
||||
.meta({
|
||||
description: "Main content text describing the solution",
|
||||
}),
|
||||
sections: z
|
||||
.array(
|
||||
z.object({
|
||||
title: z.string().min(3).max(30).meta({
|
||||
description: "Section title",
|
||||
}),
|
||||
description: z.string().min(5).max(80).meta({
|
||||
description: "Section description",
|
||||
}),
|
||||
icon: IconSchema.optional().meta({
|
||||
description: "Icon for the section",
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.min(2)
|
||||
.max(4)
|
||||
.default([
|
||||
{
|
||||
title: "Market",
|
||||
description: "Innovative and widely accepted.",
|
||||
icon: {
|
||||
__icon_query__: "market innovation",
|
||||
__icon_url__:
|
||||
"https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/globe.js",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Industry",
|
||||
description: "Based on sound market decisions.",
|
||||
icon: {
|
||||
__icon_query__: "industry building",
|
||||
__icon_url__:
|
||||
"https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/building.js",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "SEM",
|
||||
description: "Driven by precise data and analysis.",
|
||||
icon: {
|
||||
__icon_query__: "SEM data analysis",
|
||||
__icon_url__:
|
||||
"https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/chart-bar.js",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "End User",
|
||||
description: "Focused on real user impact.",
|
||||
icon: {
|
||||
__icon_query__: "end user impact",
|
||||
__icon_url__:
|
||||
"https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/user.js",
|
||||
},
|
||||
},
|
||||
])
|
||||
.meta({
|
||||
description:
|
||||
"List of solution sections with titles, descriptions, and optional icons",
|
||||
}),
|
||||
});
|
||||
|
||||
export const Schema = solutionSlideSchema;
|
||||
|
||||
export type SolutionSlideData = z.infer<typeof solutionSlideSchema>;
|
||||
|
||||
interface SolutionSlideLayoutProps {
|
||||
data?: Partial<SolutionSlideData>;
|
||||
}
|
||||
|
||||
const SolutionSlideLayout: React.FC<SolutionSlideLayoutProps> = ({
|
||||
data: slideData,
|
||||
}) => {
|
||||
const sections = slideData?.sections || [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video bg-white relative z-20 mx-auto overflow-hidden border-2 border-gray-800"
|
||||
style={{
|
||||
fontFamily: "Montserrat, sans-serif",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
|
||||
<span>{slideData?.companyName}</span>
|
||||
<span>{slideData?.date}</span>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex justify-center items-center h-full px-16 pb-16 gap-4">
|
||||
{/* Title and Description */}
|
||||
<div className="w-full flex flex-col items-start mb-4">
|
||||
<h1 className="text-7xl font-bold text-blue-600 mb-8 leading-tight text-left">
|
||||
{slideData?.title}
|
||||
</h1>
|
||||
<p className="text-blue-600 text-lg leading-relaxed font-normal mb-12 max-w-lg text-left">
|
||||
{slideData?.mainDescription}
|
||||
</p>
|
||||
</div>
|
||||
{/* Four Small Boxes in a Row */}
|
||||
<div className="grid grid-cols-2 gap-4 w-full max-w-5xl">
|
||||
{sections.map((section, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex flex-col items-center text-center bg-[#F5F8FE] rounded-lg shadow px-3 py-4 min-h-[140px] max-h-[160px]"
|
||||
>
|
||||
<div className="mb-2">
|
||||
{section?.icon?.__icon_url__ && (
|
||||
<img
|
||||
src={section.icon.__icon_url__}
|
||||
alt={section.icon.__icon_query__}
|
||||
className="w-12 h-12 mb-2"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold text-blue-600 mb-1">
|
||||
{section.title}
|
||||
</h2>
|
||||
<div className="w-8 h-1 bg-blue-600 mb-2"></div>
|
||||
<p className="text-blue-600 text-xs leading-snug">
|
||||
{section.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Border */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-blue-600"></div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SolutionSlideLayout;
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
import React from "react";
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from "@/presentation-layouts/defaultSchemes";
|
||||
|
||||
export const layoutId = "product-overview-slide";
|
||||
export const layoutName = "Product Overview Slide";
|
||||
export const layoutDescription =
|
||||
"A slide layout designed to showcase a company's products or services, highlighting their features and benefits in a structured format.";
|
||||
|
||||
const productOverviewSlideSchema = z.object({
|
||||
companyName: z.string().min(2).max(50).default("presenton").meta({
|
||||
description: "Company name displayed in header",
|
||||
}),
|
||||
date: z.string().min(5).max(50).default("June 13, 2038").meta({
|
||||
description: "Date displayed in header",
|
||||
}),
|
||||
title: z.string().min(3).max(40).default("Product Overview").meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
mainDescription: z
|
||||
.string()
|
||||
.min(50)
|
||||
.max(400)
|
||||
.default(
|
||||
"Provide an explanation of the general profile of the services we have. Arrange information about our products services in a systematic and fact-based manner. Also express our pride in the service that we have done well.",
|
||||
)
|
||||
.meta({
|
||||
description: "Main content text describing the product overview",
|
||||
}),
|
||||
products: z
|
||||
.array(
|
||||
z.object({
|
||||
title: z.string().min(3).max(50).meta({
|
||||
description: "Product title",
|
||||
}),
|
||||
description: z.string().min(30).max(200).meta({
|
||||
description: "Product description",
|
||||
}),
|
||||
image: ImageSchema.meta({
|
||||
description: "Product image",
|
||||
}),
|
||||
isBlueBackground: z.boolean().default(false).meta({
|
||||
description: "Whether the product box has a blue background",
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.min(2)
|
||||
.max(4)
|
||||
.default([
|
||||
{
|
||||
title: "Internet of Things",
|
||||
description:
|
||||
"Detail and explain each product. Our examination of community and market issues increases with additional products/services.",
|
||||
image: {
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=300&h=200&fit=crop",
|
||||
__image_prompt__: "Person working on electronics with headphones",
|
||||
},
|
||||
isBlueBackground: true,
|
||||
},
|
||||
{
|
||||
title: "Smart Home Platform",
|
||||
description:
|
||||
"Our alternate product category is available. Our products must work together to solve social and economic issues.",
|
||||
image: {
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1573164713988-8665fc963095?w=300&h=200&fit=crop",
|
||||
__image_prompt__:
|
||||
"Woman working at computer with technical equipment",
|
||||
},
|
||||
isBlueBackground: true,
|
||||
},
|
||||
])
|
||||
.meta({
|
||||
description: "List of products or services to showcase",
|
||||
}),
|
||||
});
|
||||
|
||||
export const Schema = productOverviewSlideSchema;
|
||||
|
||||
export type ProductOverviewSlideData = z.infer<
|
||||
typeof productOverviewSlideSchema
|
||||
>;
|
||||
|
||||
interface ProductOverviewSlideLayoutProps {
|
||||
data?: Partial<ProductOverviewSlideData>;
|
||||
}
|
||||
|
||||
const ProductOverviewSlideLayout: React.FC<ProductOverviewSlideLayoutProps> = ({
|
||||
data: slideData,
|
||||
}) => {
|
||||
const products = slideData?.products || [];
|
||||
|
||||
// Make the product boxes smaller
|
||||
const PRODUCT_BOX_HEIGHT = 400; // px (smaller than before)
|
||||
const PRODUCT_BOX_WIDTH = 200; // px (smaller than before)
|
||||
const TEXT_SECTION_HEIGHT = Math.round(PRODUCT_BOX_HEIGHT * 0.56); // ~190px
|
||||
const IMAGE_SECTION_HEIGHT = PRODUCT_BOX_HEIGHT - TEXT_SECTION_HEIGHT; // ~150px
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video bg-white relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: "Montserrat, sans-serif",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
|
||||
<span>{slideData?.companyName}</span>
|
||||
<span>{slideData?.date}</span>
|
||||
</div>
|
||||
6+
|
||||
{/* Main Content */}
|
||||
<div className="flex h-full px-16 pb-16">
|
||||
{/* Title and Description on the left */}
|
||||
<div className="flex flex-col items-start justify-center w-[48%] pr-8">
|
||||
<h1 className="text-7xl font-bold text-blue-600 mb-8 leading-tight text-left">
|
||||
{slideData?.title}
|
||||
</h1>
|
||||
<p className="text-blue-600 text-lg leading-relaxed font-normal mb-12 max-w-lg text-left">
|
||||
{slideData?.mainDescription}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Product Grid on the right */}
|
||||
<div className="flex flex-row items-center justify-end w-[62%] gap-8">
|
||||
{/* First Column: Normal order (Text above, Image below) */}
|
||||
<div className="flex flex-col items-center gap-4 justify-center h-full">
|
||||
{products[0] && (
|
||||
<div
|
||||
className="flex flex-col items-stretch"
|
||||
style={{
|
||||
width: `${PRODUCT_BOX_WIDTH + 40}px`,
|
||||
height: `${PRODUCT_BOX_HEIGHT + 60}px`,
|
||||
}}
|
||||
>
|
||||
{/* Top Section - Blue background with text */}
|
||||
<div
|
||||
className={`${products[0].isBlueBackground ? "bg-blue-600" : "bg-gray-100"} p-5 flex flex-col justify-center text-center rounded-t-md`}
|
||||
style={{ height: `${TEXT_SECTION_HEIGHT + 32}px` }}
|
||||
>
|
||||
<h2
|
||||
className={`text-xl font-semibold mb-3 ${products[0].isBlueBackground ? "text-white" : "text-blue-600"}`}
|
||||
>
|
||||
{products[0].title}
|
||||
</h2>
|
||||
<p
|
||||
className={`text-sm leading-relaxed ${products[0].isBlueBackground ? "text-white" : "text-blue-600"}`}
|
||||
>
|
||||
{products[0].description}
|
||||
</p>
|
||||
</div>
|
||||
{/* Bottom Section - Image */}
|
||||
<div
|
||||
className="rounded-b-md overflow-hidden"
|
||||
style={{ height: `${IMAGE_SECTION_HEIGHT + 28}px` }}
|
||||
>
|
||||
<img
|
||||
src={products[0].image.__image_url__}
|
||||
alt={
|
||||
products[0].image.__image_prompt__ || products[0].title
|
||||
}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{products[2] && (
|
||||
<div
|
||||
className="flex flex-col items-stretch"
|
||||
style={{
|
||||
width: `${PRODUCT_BOX_WIDTH + 40}px`,
|
||||
height: `${PRODUCT_BOX_HEIGHT + 60}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`${products[2].isBlueBackground ? "bg-blue-600" : "bg-gray-100"} p-5 flex flex-col justify-center text-center rounded-t-md`}
|
||||
style={{ height: `${TEXT_SECTION_HEIGHT + 32}px` }}
|
||||
>
|
||||
<h2
|
||||
className={`text-xl font-semibold mb-3 ${products[2].isBlueBackground ? "text-white" : "text-blue-600"}`}
|
||||
>
|
||||
{products[2].title}
|
||||
</h2>
|
||||
<p
|
||||
className={`text-sm leading-relaxed ${products[2].isBlueBackground ? "text-white" : "text-blue-600"}`}
|
||||
>
|
||||
{products[2].description}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="rounded-b-md overflow-hidden"
|
||||
style={{ height: `${IMAGE_SECTION_HEIGHT + 28}px` }}
|
||||
>
|
||||
<img
|
||||
src={products[2].image.__image_url__}
|
||||
alt={
|
||||
products[2].image.__image_prompt__ || products[2].title
|
||||
}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Second Column: Reverse order (Image above, Text below) */}
|
||||
<div className="flex flex-col items-center gap-4 justify-center h-full">
|
||||
{products[1] && (
|
||||
<div
|
||||
className="flex flex-col items-stretch"
|
||||
style={{
|
||||
width: `${PRODUCT_BOX_WIDTH + 40}px`,
|
||||
height: `${PRODUCT_BOX_HEIGHT + 60}px`,
|
||||
}}
|
||||
>
|
||||
{/* Top Section - Image */}
|
||||
<div
|
||||
className="rounded-t-md overflow-hidden"
|
||||
style={{ height: `${IMAGE_SECTION_HEIGHT + 28}px` }}
|
||||
>
|
||||
<img
|
||||
src={products[1].image.__image_url__}
|
||||
alt={
|
||||
products[1].image.__image_prompt__ || products[1].title
|
||||
}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
{/* Bottom Section - Blue background with text */}
|
||||
<div
|
||||
className={`${products[1].isBlueBackground ? "bg-blue-600" : "bg-gray-100"} p-5 flex flex-col justify-center text-center rounded-b-md`}
|
||||
style={{ height: `${TEXT_SECTION_HEIGHT + 32}px` }}
|
||||
>
|
||||
<h2
|
||||
className={`text-xl font-semibold mb-3 ${products[1].isBlueBackground ? "text-white" : "text-blue-600"}`}
|
||||
>
|
||||
{products[1].title}
|
||||
</h2>
|
||||
<p
|
||||
className={`text-sm leading-relaxed ${products[1].isBlueBackground ? "text-white" : "text-blue-600"}`}
|
||||
>
|
||||
{products[1].description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{products[3] && (
|
||||
<div
|
||||
className="flex flex-col items-stretch"
|
||||
style={{
|
||||
width: `${PRODUCT_BOX_WIDTH + 40}px`,
|
||||
height: `${PRODUCT_BOX_HEIGHT + 60}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="rounded-t-md overflow-hidden"
|
||||
style={{ height: `${IMAGE_SECTION_HEIGHT + 28}px` }}
|
||||
>
|
||||
<img
|
||||
src={products[3].image.__image_url__}
|
||||
alt={
|
||||
products[3].image.__image_prompt__ || products[3].title
|
||||
}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`${products[3].isBlueBackground ? "bg-blue-600" : "bg-gray-100"} p-5 flex flex-col justify-center text-center rounded-b-md`}
|
||||
style={{ height: `${TEXT_SECTION_HEIGHT + 32}px` }}
|
||||
>
|
||||
<h2
|
||||
className={`text-xl font-semibold mb-3 ${products[3].isBlueBackground ? "text-white" : "text-blue-600"}`}
|
||||
>
|
||||
{products[3].title}
|
||||
</h2>
|
||||
<p
|
||||
className={`text-sm leading-relaxed ${products[3].isBlueBackground ? "text-white" : "text-blue-600"}`}
|
||||
>
|
||||
{products[3].description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Bottom Border */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-blue-600"></div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductOverviewSlideLayout;
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
import React from "react";
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from "@/presentation-layouts/defaultSchemes";
|
||||
|
||||
export const layoutId = "market-size-pitchdeck-slide";
|
||||
export const layoutName = "Market Size Pitch Deck Slide";
|
||||
export const layoutDescription =
|
||||
"A professional slide layout designed to present market size statistics, including TAM, SAM, and SOM, with a world map and key metrics.";
|
||||
|
||||
const marketSizeSlideSchema = z.object({
|
||||
title: z.string().default("Market Size").meta({
|
||||
description: "Main slide title",
|
||||
}),
|
||||
companyName: z.string().default("Rimberio").meta({
|
||||
description: "Presenter's name",
|
||||
}),
|
||||
date: z.string().default("June 13, 2038").meta({
|
||||
description: "Presentation date",
|
||||
}),
|
||||
mapImage: ImageSchema.default({
|
||||
__image_url__:
|
||||
"https://upload.wikimedia.org/wikipedia/commons/8/80/World_map_-_low_resolution.svg", // You can quickly find a world map image via a Google search or use a free resource like Wikimedia Commons
|
||||
__image_prompt__: "World map with location pins or points",
|
||||
}),
|
||||
marketStats: z
|
||||
.array(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
value: z.string(),
|
||||
description: z.string(),
|
||||
}),
|
||||
)
|
||||
.min(1)
|
||||
.max(3)
|
||||
.default([
|
||||
{
|
||||
label: "Total Available Market (TAM)",
|
||||
value: "1.4 Billion",
|
||||
description:
|
||||
"In the TAM Section, we can fill in the potential of any person who can buy an offer or the maximum amount of revenue a business can earn by selling their offer.",
|
||||
},
|
||||
{
|
||||
label: "Serviceable Available Market (SAM)",
|
||||
value: "194 Million",
|
||||
description:
|
||||
"It is a part of TAM that has the potential to become a target market for the company by considering the type of product, technology available and geographical conditions.",
|
||||
},
|
||||
{
|
||||
label: "Serviceable Obtainable Market (SOM)",
|
||||
value: "167 Million",
|
||||
description:
|
||||
"The SOM is a smaller fraction of the SAM that is the target of a serviceable and realistically achievable market in the short to medium term.",
|
||||
},
|
||||
]),
|
||||
description: z
|
||||
.string()
|
||||
.default(
|
||||
"Market size is the total amount of all sales and customers that can be seen directly by stakeholders. This technique is usually calculated at the end of the year, the market size can be used by companies to determine the potential of their market and business in the future. This is very useful, especially for new companies that will offer services to those who are interested in our services.",
|
||||
)
|
||||
.meta({
|
||||
description: "Main description text for the slide",
|
||||
}),
|
||||
});
|
||||
|
||||
export const Schema = marketSizeSlideSchema;
|
||||
export type MarketSizeSlideData = z.infer<typeof marketSizeSlideSchema>;
|
||||
|
||||
interface MarketSizeSlideProps {
|
||||
data?: Partial<MarketSizeSlideData>;
|
||||
}
|
||||
|
||||
const MarketSizeSlideLayout: React.FC<MarketSizeSlideProps> = ({
|
||||
data: slideData,
|
||||
}) => {
|
||||
const stats = slideData?.marketStats || [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Montserrat Font */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video bg-white relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: "Montserrat, sans-serif",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
|
||||
<span>{slideData?.companyName || "Rimberio"}</span>
|
||||
<span>{slideData?.date || "June 13, 2038"}</span>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex h-full px-16 pb-16">
|
||||
{/* Title and Map on the left */}
|
||||
<div className="flex flex-col items-center justify-center w-[48%] pr-8 h-full">
|
||||
<div className="flex flex-col items-left justify-center h-full w-full">
|
||||
{/* Move the title down to align with the top of the market stats */}
|
||||
<h1
|
||||
className="text-7xl font-bold text-blue-600 mb-8 leading-tight text-left"
|
||||
style={{ marginTop: "112px" }} // 112px matches top-36 (9rem) of stats
|
||||
>
|
||||
{slideData?.title || "Market Size"}
|
||||
</h1>
|
||||
<div className="w-full bg-[#CBE3CC] rounded-md mb-8 flex items-center justify-center">
|
||||
<img
|
||||
src={
|
||||
slideData?.mapImage?.__image_url__ ||
|
||||
"https://upload.wikimedia.org/wikipedia/commons/8/80/World_map_-_low_resolution.svg"
|
||||
}
|
||||
alt="Market World Map with Points"
|
||||
className="w-full object-contain rounded-md"
|
||||
style={{ maxHeight: 220 }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-blue-600 text-sm leading-relaxed font-normal mb-12 max-w-lg text-left">
|
||||
{slideData?.description ||
|
||||
"Market size is the total amount of all sales and customers that can be seen directly by stakeholders. This technique is usually calculated at the end of the year, the market size can be used by companies to determine the potential of their market and business in the future."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Market Stats on the right */}
|
||||
<div className="flex flex-col items-start justify-center w-[52%] gap-8">
|
||||
<div className="absolute top-36 right-10 w-[42%] space-y-10">
|
||||
{stats.map((stat, index) => (
|
||||
<div key={index}>
|
||||
<div className="space-y-2">
|
||||
<div className="bg-[#1E4CD9] text-white text-sm font-semibold px-3 py-1 inline-block rounded-sm">
|
||||
{stat.label}
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-[#1E4CD9]">
|
||||
{stat.value}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-gray-700 leading-snug">
|
||||
{stat.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarketSizeSlideLayout;
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
import React from "react";
|
||||
import * as z from "zod";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Table, TableHeader, TableBody } from "@/components/ui/table";
|
||||
import { ChartContainer } from "@/components/ui/chart";
|
||||
import { ImageSchema } from "@/presentation-layouts/defaultSchemes";
|
||||
import { BarChart, Bar, XAxis, YAxis, Tooltip, Cell } from "recharts";
|
||||
|
||||
export const layoutId = "market-validation-slide";
|
||||
export const layoutName = "Market Validation Slide";
|
||||
export const layoutDescription =
|
||||
"A slide layout designed to present market validation data, including flexible market validation metrics, comparisons, and an optional decorative image.";
|
||||
|
||||
// Make the schema generic: allow any label/value pairs for comparison
|
||||
const marketValidationSchema = z.object({
|
||||
companyName: z.string().min(2).max(50).default("presenton").meta({
|
||||
description: "Company name in header",
|
||||
}),
|
||||
date: z.string().min(5).max(50).default("June 13, 2038").meta({
|
||||
description: "Date in header",
|
||||
}),
|
||||
title: z.string().min(3).max(40).default("Market Validation").meta({
|
||||
description: "Title of the slide",
|
||||
}),
|
||||
description: z
|
||||
.string()
|
||||
.min(50)
|
||||
.max(400)
|
||||
.default(
|
||||
"It’s a market testing stage to ensure that the products produced by the company can be accepted and effectively used by the broad market. For start-up companies, we can use data already achieved by similar products from other companies.",
|
||||
)
|
||||
.meta({
|
||||
description:
|
||||
"Main description text for the slide explaining market validation",
|
||||
}),
|
||||
// Generic comparisonData: label for row, label for metric, and value
|
||||
comparisonData: z
|
||||
.array(
|
||||
z.object({
|
||||
label: z.string().min(2).max(50).meta({
|
||||
description:
|
||||
"Name of comparison entity (e.g., company, product, etc.)",
|
||||
}),
|
||||
metricLabel: z.string().min(2).max(50).meta({
|
||||
description:
|
||||
"Label for the metric being compared (e.g., Users, Revenue, etc.)",
|
||||
}),
|
||||
value: z.number().min(0).meta({
|
||||
description: "Numeric value for the metric",
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.min(2)
|
||||
.max(5)
|
||||
.default([
|
||||
{ label: "Thynk Unlimited", metricLabel: "Revenue ($K)", value: 2650 },
|
||||
{ label: "Salford & Co.", metricLabel: "Revenue ($K)", value: 1850 },
|
||||
{ label: "Liceria & Co.", metricLabel: "Revenue ($K)", value: 1010 },
|
||||
])
|
||||
.meta({
|
||||
description: "Market benchmark data (generic metric)",
|
||||
}),
|
||||
image: ImageSchema.optional().meta({
|
||||
description: "Optional decorative image",
|
||||
}),
|
||||
});
|
||||
|
||||
export const Schema = marketValidationSchema;
|
||||
|
||||
export type MarketValidationSlideData = z.infer<typeof marketValidationSchema>;
|
||||
|
||||
interface MarketValidationSlideLayoutProps {
|
||||
data?: Partial<MarketValidationSlideData>;
|
||||
}
|
||||
|
||||
const MarketValidationSlideLayout: React.FC<
|
||||
MarketValidationSlideLayoutProps
|
||||
> = ({ data: slideData }) => {
|
||||
const comparisonData = slideData?.comparisonData || [];
|
||||
|
||||
// Chart color palette (shadcn blue and gray)
|
||||
const chartColors = ["#2563eb", "#1e40af", "#60a5fa", "#93c5fd", "#dbeafe"];
|
||||
|
||||
// Determine the metric label to use (assume all rows use the same metricLabel)
|
||||
const metricLabel =
|
||||
comparisonData.length > 0 ? comparisonData[0].metricLabel : "Metric";
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full max-w-[1280px] max-h-[720px] aspect-video bg-white mx-auto rounded shadow-lg overflow-hidden relative z-20"
|
||||
style={{
|
||||
fontFamily: "Montserrat, sans-serif",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
|
||||
<span>{slideData?.companyName}</span>
|
||||
<span>{slideData?.date}</span>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="px-16 py-16 flex h-full gap-8">
|
||||
{/* Left Column */}
|
||||
<div className="flex-1 pr-12 flex flex-col justify-center">
|
||||
<h1 className="text-7xl font-bold text-blue-600 mb-8 leading-tight text-left">
|
||||
{slideData?.title}
|
||||
</h1>
|
||||
<p className="text-blue-600 text-sm leading-relaxed font-normal mb-12 max-w-lg text-left">
|
||||
{slideData?.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right Column - Chart on top, Table on bottom */}
|
||||
<div className="flex-1 flex flex-col justify-center items-center gap-6">
|
||||
{/* Bar Chart */}
|
||||
<Card className="w-full p-4 flex flex-col items-center">
|
||||
<div className="w-full h-64">
|
||||
<ChartContainer
|
||||
config={{
|
||||
value: { label: metricLabel, color: "#2563eb" },
|
||||
}}
|
||||
>
|
||||
<BarChart
|
||||
data={comparisonData}
|
||||
layout="vertical"
|
||||
margin={{ left: 32, right: 16, top: 16, bottom: 16 }}
|
||||
>
|
||||
<XAxis type="number" hide />
|
||||
<YAxis
|
||||
dataKey="label"
|
||||
type="category"
|
||||
width={120}
|
||||
tick={{ fill: "#1e40af", fontWeight: 600 }}
|
||||
/>
|
||||
<Tooltip />
|
||||
{/* Legend removed */}
|
||||
<Bar
|
||||
dataKey="value"
|
||||
name={metricLabel}
|
||||
radius={[8, 8, 8, 8]}
|
||||
>
|
||||
{comparisonData.map((entry, idx) => (
|
||||
<Cell
|
||||
key={entry.label}
|
||||
fill={chartColors[idx % chartColors.length]}
|
||||
/>
|
||||
))}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</Card>
|
||||
{/* Table of comparison data */}
|
||||
<Card className="w-full">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<tr>
|
||||
<th className="text-left px-4 py-2 text-blue-700">
|
||||
{comparisonData.length > 0 ? "Name" : "Name"}
|
||||
</th>
|
||||
<th className="text-left px-4 py-2 text-blue-700">
|
||||
{metricLabel}
|
||||
</th>
|
||||
</tr>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{comparisonData.map((entry) => (
|
||||
<tr key={entry.label}>
|
||||
<td className="px-4 py-2">{entry.label}</td>
|
||||
<td className="px-4 py-2">
|
||||
{entry.value.toLocaleString()}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-blue-600" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarketValidationSlideLayout;
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
import React from "react";
|
||||
import {
|
||||
LineChart,
|
||||
Line,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
Legend,
|
||||
ResponsiveContainer,
|
||||
} from "recharts";
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = "company-traction-slide";
|
||||
export const layoutName = "Company Traction Slide";
|
||||
export const layoutDescription =
|
||||
"A slide layout designed to present company traction data, including growth statistics over the years, a chart visualization, and key metrics in a visually appealing format.";
|
||||
|
||||
// growthStats: list of dicts, each dict is { year: string, <metric1>: number, <metric2>: number, ... }
|
||||
const tractionSchema = z.object({
|
||||
companyName: z.string().default("presention"),
|
||||
date: z.string().default("June 13, 2038"),
|
||||
title: z.string().default("Company Traction"),
|
||||
description: z
|
||||
.string()
|
||||
.default(
|
||||
"Traction is a period where the company is feeling momentum during its development period. If traction momentum is not harnessed, sales figures can decline and the customer base can shrink. In general, companies will judge success by the amount of revenue and new customers they receive.",
|
||||
),
|
||||
// growthStats is a list of objects, each with a 'year' and any number of metric keys (all numbers)
|
||||
growthStats: z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
year: z.string(),
|
||||
})
|
||||
.catchall(z.number()),
|
||||
)
|
||||
.min(1)
|
||||
.max(20)
|
||||
.default([
|
||||
{
|
||||
year: "2020",
|
||||
artificialIntelligence: 5,
|
||||
internetOfThings: 10,
|
||||
others: 8,
|
||||
},
|
||||
{
|
||||
year: "2021",
|
||||
artificialIntelligence: 10,
|
||||
internetOfThings: 20,
|
||||
others: 15,
|
||||
},
|
||||
{
|
||||
year: "2022",
|
||||
artificialIntelligence: 20,
|
||||
internetOfThings: 30,
|
||||
others: 22,
|
||||
},
|
||||
{
|
||||
year: "2023",
|
||||
artificialIntelligence: 28,
|
||||
internetOfThings: 38,
|
||||
others: 29,
|
||||
},
|
||||
{
|
||||
year: "2024",
|
||||
artificialIntelligence: 35,
|
||||
internetOfThings: 45,
|
||||
others: 34,
|
||||
},
|
||||
{
|
||||
year: "2025",
|
||||
artificialIntelligence: 45,
|
||||
internetOfThings: 53,
|
||||
others: 42,
|
||||
},
|
||||
{
|
||||
year: "2026",
|
||||
artificialIntelligence: 55,
|
||||
internetOfThings: 65,
|
||||
others: 52,
|
||||
},
|
||||
{
|
||||
year: "2029",
|
||||
artificialIntelligence: 55,
|
||||
internetOfThings: 65,
|
||||
others: 52,
|
||||
},
|
||||
]),
|
||||
});
|
||||
|
||||
export const Schema = tractionSchema;
|
||||
export type CompanyTractionData = z.infer<typeof tractionSchema>;
|
||||
|
||||
interface Props {
|
||||
data?: Partial<CompanyTractionData>;
|
||||
}
|
||||
|
||||
// Helper: assign colors to series
|
||||
const defaultColors = [
|
||||
"#1E4CD9",
|
||||
"#3b82f6",
|
||||
"#f59e0b",
|
||||
"#10b981",
|
||||
"#ef4444",
|
||||
"#a21caf",
|
||||
"#6366f1",
|
||||
"#f43f5e",
|
||||
"#fbbf24",
|
||||
"#14b8a6",
|
||||
];
|
||||
|
||||
function getSeriesKeys(
|
||||
growthStats: Array<Record<string, string | number>>,
|
||||
): string[] {
|
||||
if (!growthStats.length) return [];
|
||||
// Exclude 'year' or any non-numeric keys
|
||||
const first = growthStats[0];
|
||||
return Object.keys(first).filter(
|
||||
(key) => key !== "year" && typeof first[key] === "number",
|
||||
);
|
||||
}
|
||||
|
||||
// Compute stats for right column, generic for all series
|
||||
function computeStats(
|
||||
growthStats: Array<Record<string, string | number>>,
|
||||
seriesKeys: string[],
|
||||
) {
|
||||
if (!growthStats.length) return [];
|
||||
const first = growthStats[0];
|
||||
const last = growthStats[growthStats.length - 1];
|
||||
return seriesKeys.map((key) => {
|
||||
const start = typeof first[key] === "number" ? (first[key] as number) : 0;
|
||||
const end = typeof last[key] === "number" ? (last[key] as number) : 0;
|
||||
const growth = start === 0 ? 0 : ((end - start) / Math.abs(start)) * 100;
|
||||
return {
|
||||
label: key
|
||||
.replace(/([A-Z])/g, " $1")
|
||||
.replace(/^./, (str) => str.toUpperCase()),
|
||||
value: `${growth >= 0 ? "+" : ""}${Math.round(growth)}% growth`,
|
||||
description: `${key
|
||||
.replace(/([A-Z])/g, " $1")
|
||||
.replace(/^./, (str) => str.toUpperCase())} growth over the period.`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const CompanyTractionSlideLayout: React.FC<Props> = ({ data }) => {
|
||||
const growthStats = data?.growthStats || [];
|
||||
|
||||
// Dynamically determine series keys
|
||||
const seriesKeys = getSeriesKeys(growthStats);
|
||||
|
||||
// Prepare stats for the right column, generic for all series
|
||||
const stats = computeStats(growthStats, seriesKeys);
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<div
|
||||
className="w-full max-w-[1280px] max-h-[720px] aspect-video bg-white mx-auto rounded shadow-lg overflow-hidden relative z-20"
|
||||
style={{
|
||||
fontFamily: "Montserrat, sans-serif",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
|
||||
<span>{data?.companyName}</span>
|
||||
<span>{data?.date}</span>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="px-16 py-16 flex h-full gap-8">
|
||||
{/* Left Column - Chart with Title Below */}
|
||||
<div className="flex-1 pr-12 flex flex-col justify-center">
|
||||
<h1 className="text-7xl font-bold text-blue-600 mb-4 leading-tight text-left">
|
||||
{data?.title}
|
||||
</h1>
|
||||
<div className="bg-white rounded-lg shadow p-4 mb-8">
|
||||
<div className="w-full h-64">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={growthStats}>
|
||||
<CartesianGrid stroke="#e5eafe" />
|
||||
<XAxis
|
||||
dataKey="year"
|
||||
stroke="#1E4CD9"
|
||||
tick={{ fill: "#1E4CD9", fontWeight: 600 }}
|
||||
/>
|
||||
<YAxis
|
||||
stroke="#1E4CD9"
|
||||
tick={{ fill: "#1E4CD9", fontWeight: 600 }}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: "#1E4CD9",
|
||||
border: "none",
|
||||
color: "#fff",
|
||||
}}
|
||||
labelStyle={{ color: "#fff" }}
|
||||
itemStyle={{ color: "#fff" }}
|
||||
/>
|
||||
<Legend
|
||||
wrapperStyle={{ color: "#1E4CD9", fontWeight: 600 }}
|
||||
iconType="circle"
|
||||
/>
|
||||
{seriesKeys.map((key, idx) => (
|
||||
<Line
|
||||
key={key}
|
||||
type="monotone"
|
||||
dataKey={key}
|
||||
stroke={defaultColors[idx % defaultColors.length]}
|
||||
strokeWidth={3}
|
||||
name={key
|
||||
.replace(/([A-Z])/g, " $1")
|
||||
.replace(/^./, (str) => str.toUpperCase())}
|
||||
dot={{
|
||||
r: 4,
|
||||
fill: defaultColors[idx % defaultColors.length],
|
||||
}}
|
||||
activeDot={{
|
||||
r: 6,
|
||||
fill: defaultColors[idx % defaultColors.length],
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column - Description and Stats */}
|
||||
<div className="flex flex-col items-start justify-center w-[52%] gap-8">
|
||||
<p className="text-blue-600 text-base leading-relaxed font-normal mb-6 max-w-xl text-left">
|
||||
{data?.description ||
|
||||
"Traction is a period where the company is feeling momentum during its development period. If traction momentum is not harnessed, sales figures can decline and the customer base can shrink. In general, companies will judge success by the amount of revenue and new customers they receive."}
|
||||
</p>
|
||||
<div className="flex flex-row w-full gap-6">
|
||||
{stats.map((stat, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex-1 bg-[#f5f8ff] rounded-lg shadow-sm px-5 py-4 flex flex-col items-start"
|
||||
>
|
||||
<div className="bg-[#1E4CD9] text-white text-xs font-semibold px-3 py-1 rounded-sm mb-2">
|
||||
{stat.label}
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-[#1E4CD9] mb-1">
|
||||
{stat.value}
|
||||
</div>
|
||||
<p className="text-sm text-gray-700 leading-snug">
|
||||
{stat.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-blue-600" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompanyTractionSlideLayout;
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
import React from "react";
|
||||
import * as z from "zod";
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
CartesianGrid,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
Legend,
|
||||
} from "recharts";
|
||||
|
||||
export const layoutId = "business-model-slide";
|
||||
export const layoutName = "Business Model Slide";
|
||||
export const layoutDescription =
|
||||
"A business model presentation slide displaying CAC metrics and monetization strategy.";
|
||||
|
||||
const businessModelSchema = z.object({
|
||||
companyName: z.string().default("presenton"),
|
||||
date: z.string().default("June 13, 2038"),
|
||||
title: z.string().default("Business Model"),
|
||||
description: z
|
||||
.string()
|
||||
.default(
|
||||
"Describe how you monetize, who your customers are, your distribution channels or fee structure. The goal is to give an idea of how this business will sustain your product or service and explain how your company will make money and achieve its goals. This can be shown with graphs, statistics, or charts. Use the Lifetime Value (LTV) and Customer Acquisition Cost (CAC) metrics to provide a clearer picture.",
|
||||
),
|
||||
cacChart: z
|
||||
.array(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
percentage: z.number(),
|
||||
}),
|
||||
)
|
||||
.default([
|
||||
{ label: "Internet of Things", percentage: 70 },
|
||||
{ label: "Artificial Intelligence", percentage: 60 },
|
||||
]),
|
||||
});
|
||||
|
||||
export const Schema = businessModelSchema;
|
||||
export type BusinessModelData = z.infer<typeof businessModelSchema>;
|
||||
|
||||
interface Props {
|
||||
data?: Partial<BusinessModelData>;
|
||||
}
|
||||
|
||||
const BusinessModelSlide: React.FC<Props> = ({ data }) => {
|
||||
const cacChartData =
|
||||
data?.cacChart && Array.isArray(data.cacChart) && data.cacChart.length > 0
|
||||
? data.cacChart
|
||||
: [
|
||||
{ label: "Internet of Things", percentage: 70 },
|
||||
{ label: "Artificial Intelligence", percentage: 60 },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<div
|
||||
className="w-full max-w-[1280px] max-h-[720px] aspect-video bg-white mx-auto rounded shadow-lg overflow-hidden relative z-20"
|
||||
style={{
|
||||
fontFamily: "Montserrat, sans-serif",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
|
||||
<span>{data?.companyName}</span>
|
||||
<span>{data?.date}</span>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="px-16 py-16 flex h-full gap-8">
|
||||
{/* Left Column - Chart with Title Below */}
|
||||
<div className="flex-1 pr-12 flex flex-col justify-center">
|
||||
<h1 className="text-7xl font-bold text-blue-600 mb-4 leading-tight text-left">
|
||||
{data?.title}
|
||||
</h1>
|
||||
<div className="bg-white rounded-lg shadow p-4 mb-8">
|
||||
<div className="w-full h-64">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart
|
||||
data={cacChartData}
|
||||
margin={{ top: 20, right: 30, left: 10, bottom: 20 }}
|
||||
barCategoryGap="30%"
|
||||
>
|
||||
<CartesianGrid stroke="#e5eafe" />
|
||||
<XAxis
|
||||
dataKey="label"
|
||||
tick={{ fill: "#1E4CD9", fontWeight: 600 }}
|
||||
/>
|
||||
<YAxis
|
||||
tick={{ fill: "#1E4CD9", fontWeight: 600 }}
|
||||
domain={[0, 100]}
|
||||
ticks={[0, 20, 40, 60, 80, 100]}
|
||||
width={40}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: "#1E4CD9",
|
||||
border: "none",
|
||||
color: "#fff",
|
||||
}}
|
||||
labelStyle={{ color: "#fff" }}
|
||||
itemStyle={{ color: "#fff" }}
|
||||
/>
|
||||
<Legend
|
||||
wrapperStyle={{ color: "#1E4CD9", fontWeight: 600 }}
|
||||
iconType="circle"
|
||||
/>
|
||||
<Bar
|
||||
dataKey="percentage"
|
||||
fill="#3b82f6"
|
||||
name="CAC %"
|
||||
maxBarSize={48}
|
||||
radius={[8, 8, 0, 0]}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column - Description and Optional Image */}
|
||||
<div className="flex flex-col items-start justify-center w-[52%] gap-8">
|
||||
<p className="text-blue-600 text-base leading-relaxed font-normal mb-6 max-w-xl text-left">
|
||||
{data?.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-blue-600" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BusinessModelSlide;
|
||||
5
servers/nextjs/presentation-layouts/modern/setting.json
Normal file
5
servers/nextjs/presentation-layouts/modern/setting.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"description": "Modern white and blue business pitch deck layouts with clean, professional design",
|
||||
"ordered": false,
|
||||
"isDefault": false
|
||||
}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
import React from "react";
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from "@/presentation-layouts/defaultSchemes";
|
||||
|
||||
export const layoutId = "modern-team-slide";
|
||||
export const layoutName = "Modern Team Slide";
|
||||
export const layoutDescription =
|
||||
"A clean modern team slide showcasing team members with professional profiles and blue-white design.";
|
||||
|
||||
const teamMemberSchema = z.object({
|
||||
name: z.string().min(2).max(50).meta({
|
||||
description: "Team member's full name",
|
||||
}),
|
||||
position: z.string().min(2).max(50).meta({
|
||||
description: "Job title or position",
|
||||
}),
|
||||
description: z.string().min(20).max(120).meta({
|
||||
description: "Brief professional description of the team member",
|
||||
}),
|
||||
image: ImageSchema,
|
||||
linkedIn: z.string().optional().meta({
|
||||
description: "LinkedIn profile URL (optional)",
|
||||
}),
|
||||
});
|
||||
|
||||
const modernTeamSlideSchema = z.object({
|
||||
title: z.string().min(3).max(40).default("Our Team").meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(10).max(120).optional().meta({
|
||||
description: "Optional subtitle describing the team",
|
||||
}),
|
||||
teamMembers: z
|
||||
.array(teamMemberSchema)
|
||||
.min(2)
|
||||
.max(4)
|
||||
.default([
|
||||
{
|
||||
name: "Sarah Johnson",
|
||||
position: "CEO & Founder",
|
||||
description:
|
||||
"Strategic leader with 15+ years experience in technology and business development. Former VP at Fortune 500 company.",
|
||||
image: {
|
||||
__image_url__:
|
||||
"https://plus.unsplash.com/premium_photo-1661589856899-6dd0871f9db6?fm=jpg&q=60&w=3000&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NXx8YnVzaW5lc3N3b21lbnxlbnwwfHwwfHx8MA%3D%3D",
|
||||
__image_prompt__: "Professional businesswoman CEO headshot",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Michael Chen",
|
||||
position: "CTO",
|
||||
description:
|
||||
"Technology expert specializing in scalable architecture and AI solutions. PhD in Computer Science from MIT.",
|
||||
image: {
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80",
|
||||
__image_prompt__: "Professional businessman CTO headshot",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Emily Rodriguez",
|
||||
position: "VP of Sales",
|
||||
description:
|
||||
"Sales leader with proven track record of building high-performing teams and driving revenue growth in B2B markets.",
|
||||
image: {
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80",
|
||||
__image_prompt__: "Professional businesswoman VP headshot",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "David Kim",
|
||||
position: "Head of Product",
|
||||
description:
|
||||
"Product strategist focused on user experience and market-driven solutions. Former product manager at leading tech companies.",
|
||||
image: {
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80",
|
||||
__image_prompt__: "Professional businessman product manager headshot",
|
||||
},
|
||||
},
|
||||
])
|
||||
.meta({
|
||||
description: "List of team members with their information",
|
||||
}),
|
||||
companyName: z.string().default("presenton").meta({
|
||||
description: "Company name to display in the header",
|
||||
}),
|
||||
date: z.string().default("June 13, 2038").meta({
|
||||
description: "Date to display in the header",
|
||||
}),
|
||||
});
|
||||
|
||||
export const Schema = modernTeamSlideSchema;
|
||||
|
||||
export type ModernTeamSlideData = z.infer<typeof modernTeamSlideSchema>;
|
||||
|
||||
interface ModernTeamSlideLayoutProps {
|
||||
data?: Partial<ModernTeamSlideData>;
|
||||
}
|
||||
|
||||
const ModernTeamSlideLayout: React.FC<ModernTeamSlideLayoutProps> = ({
|
||||
data: slideData,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{/* Import Montserrat Font */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700;800&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full max-w-[1280px] max-h-[720px] aspect-video bg-white mx-auto rounded shadow-lg overflow-hidden relative z-20"
|
||||
style={{
|
||||
fontFamily: "Montserrat, sans-serif",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
|
||||
<span>{slideData?.companyName}</span>
|
||||
<span>{slideData?.date}</span>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="relative z-10 flex flex-col items-start justify-center h-full px-16 pt-24 pb-10">
|
||||
{/* Title */}
|
||||
<h1
|
||||
className="text-7xl font-bold text-blue-600 mb-4 leading-tight text-left"
|
||||
style={{ letterSpacing: "-0.03em" }}
|
||||
>
|
||||
{slideData?.title}
|
||||
</h1>
|
||||
{/* Subtitle */}
|
||||
<p className="text-blue-600 text-lg leading-relaxed font-normal mb-12 max-w-lg text-left">
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
{/* Team Members Row */}
|
||||
<div className="flex flex-row w-full justify-between items-start gap-6 mt-2">
|
||||
{slideData?.teamMembers?.map((member, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex flex-col items-center bg-[#f7f9fc] rounded-lg shadow-md px-6 pt-6 pb-4 w-1/4 min-w-[210px] max-w-[240px] mx-auto"
|
||||
style={{ minHeight: 340 }}
|
||||
>
|
||||
{/* Photo */}
|
||||
<div className="relative w-28 h-28 mb-4 rounded overflow-hidden bg-white border-2 border-blue-100 flex items-center justify-center">
|
||||
<img
|
||||
src={member.image.__image_url__ || ""}
|
||||
alt={member.image.__image_prompt__ || member.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
{/* Name */}
|
||||
<div className="text-lg font-bold text-blue-700 mb-1">
|
||||
{member.name}
|
||||
</div>
|
||||
{/* Position Badge */}
|
||||
<div className="bg-blue-600 text-white text-xs font-semibold px-3 py-1 rounded-sm mb-2 uppercase tracking-wide">
|
||||
{member.position}
|
||||
</div>
|
||||
{/* Description */}
|
||||
<div className="text-sm text-gray-700 text-center mb-2 min-h-[48px]">
|
||||
{member.description}
|
||||
</div>
|
||||
{/* LinkedIn Link (if provided) */}
|
||||
{member.linkedIn && (
|
||||
<a
|
||||
href={member.linkedIn}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center text-xs text-blue-600 hover:text-blue-800 transition-colors duration-200 mt-1"
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 mr-1"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M16.338 16.338H13.67V12.16c0-.995-.017-2.277-1.387-2.277-1.39 0-1.601 1.086-1.601 2.207v4.248H8.014v-8.59h2.559v1.174h.037c.356-.675 1.227-1.387 2.526-1.387 2.703 0 3.203 1.778 3.203 4.092v4.711zM5.005 6.575a1.548 1.548 0 11-.003-3.096 1.548 1.548 0 01.003 3.096zm-1.337 9.763H6.34v-8.59H3.667v8.59zM17.668 1H2.328C1.595 1 1 1.581 1 2.298v15.403C1 18.418 1.595 19 2.328 19h15.34c.734 0 1.332-.582 1.332-1.299V2.298C19 1.581 18.402 1 17.668 1z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
LinkedIn
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/* Bottom Divider */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-blue-600" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModernTeamSlideLayout;
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
import React from "react";
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = "thank-you-slide";
|
||||
export const layoutName = "Thank You Slide";
|
||||
export const layoutDescription =
|
||||
"A simple, plain thank you slide for closing presentations.";
|
||||
|
||||
const thankYouSlideSchema = z.object({
|
||||
title: z.string().min(3).max(40).default("Thank You!").meta({
|
||||
description: "Main thank you message",
|
||||
}),
|
||||
subtitle: z.string().min(0).max(100).default("").meta({
|
||||
description: "Optional subtitle or closing remark",
|
||||
}),
|
||||
companyName: z.string().min(2).max(50).default("Rimberio").meta({
|
||||
description: "Company name displayed in header",
|
||||
}),
|
||||
date: z.string().min(5).max(30).default("June 13, 2038").meta({
|
||||
description: "Date displayed in header",
|
||||
}),
|
||||
address: z
|
||||
.string()
|
||||
.min(5)
|
||||
.max(100)
|
||||
.default("123 Anywhere St., Any City, ST 12345")
|
||||
.meta({
|
||||
description: "Company address for contact section",
|
||||
}),
|
||||
phone: z.string().min(5).max(30).default("+123-456-7890").meta({
|
||||
description: "Company phone number for contact section",
|
||||
}),
|
||||
website: z.string().min(5).max(100).default("www.reallygreatsite.com").meta({
|
||||
description: "Company website for contact section",
|
||||
}),
|
||||
email: z.string().default("info@reallygreatsite.com").meta({
|
||||
description: "Company email address for contact section",
|
||||
}),
|
||||
});
|
||||
|
||||
export const Schema = thankYouSlideSchema;
|
||||
|
||||
export type ThankYouSlideData = z.infer<typeof thankYouSlideSchema>;
|
||||
|
||||
interface ThankYouSlideLayoutProps {
|
||||
data?: Partial<ThankYouSlideData>;
|
||||
}
|
||||
|
||||
const ThankYouSlideLayout: React.FC<ThankYouSlideLayoutProps> = ({ data }) => {
|
||||
return (
|
||||
<>
|
||||
{/* Import fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video relative z-20 mx-auto overflow-hidden flex flex-col"
|
||||
style={{
|
||||
fontFamily: "Montserrat, sans-serif",
|
||||
backgroundColor: "#1E4CD9", // blue background
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-white text-sm font-semibold">
|
||||
<span>{data?.companyName || "Rimberio"}</span>
|
||||
<span>{data?.date || "June 13, 2038"}</span>
|
||||
</div>
|
||||
|
||||
{/* Main content area */}
|
||||
<div className="flex flex-1 flex-col px-16 pb-16 justify-between">
|
||||
{/* Thank You and description */}
|
||||
<div className="flex flex-col items-start w-full pt-16">
|
||||
<h1
|
||||
className="font-bold text-white mb-6 mt-8 text-left w-full"
|
||||
style={{
|
||||
fontSize: "8.5rem", // Increase size beyond text-7xl
|
||||
lineHeight: 1.05,
|
||||
}}
|
||||
>
|
||||
{data?.title || "Thank You!"}
|
||||
</h1>
|
||||
{data?.subtitle && (
|
||||
<div className="text-xl text-blue-100 font-normal text-left w-full mb-2">
|
||||
{data.subtitle}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-xl text-white text-left w-full max-w-3xl mb-0">
|
||||
Write down your hopes for the future of your company. Don't forget
|
||||
to thank the company for the opportunity and convince related
|
||||
parties to support your company.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer area */}
|
||||
<div className="flex w-full items-end justify-between mt-auto">
|
||||
{/* Left: We are ready to assist you */}
|
||||
<div className="flex flex-col">
|
||||
<div
|
||||
className="font-bold text-white text-left mb-3"
|
||||
style={{
|
||||
fontSize: "2rem",
|
||||
marginBottom: 0,
|
||||
}}
|
||||
>
|
||||
We are ready to assist you
|
||||
</div>
|
||||
</div>
|
||||
{/* Right: Contacts */}
|
||||
<div className="flex flex-col items-end text-white text-sm space-y-2 min-w-[220px]">
|
||||
<div className="flex items-center gap-2">
|
||||
<span role="img" aria-label="address">
|
||||
📍
|
||||
</span>
|
||||
{data?.address}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span role="img" aria-label="phone">
|
||||
📞
|
||||
</span>
|
||||
{data?.phone}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span role="img" aria-label="website">
|
||||
🌐
|
||||
</span>
|
||||
{data?.website}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span role="img" aria-label="email">
|
||||
✉️
|
||||
</span>
|
||||
{data?.email}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom border line */}
|
||||
<div className="absolute bottom-0 left-0 w-full h-1 bg-white"></div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThankYouSlideLayout;
|
||||
Loading…
Add table
Reference in a new issue