commit
a33ecae920
70 changed files with 3939 additions and 6019 deletions
|
|
@ -1,87 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-templates/defaultSchemes';
|
||||
|
||||
export const layoutId = 'classic-dark-title-slide'
|
||||
export const layoutName = 'Classic Dark Title Slide'
|
||||
export const layoutDescription = 'A modern title slide with dark gradient background, gradient text, and geographical elements.'
|
||||
|
||||
const titleSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Nepal\'s Imports and\nExports: A Data-Driven\nOverview').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(3).max(100).default('Key Trade Statistics and Trends (2022–2025)').meta({
|
||||
description: "Subtitle text",
|
||||
}),
|
||||
presenter: z.string().min(3).max(50).default('[Your Name]').meta({
|
||||
description: "Presenter name",
|
||||
}),
|
||||
date: z.string().min(3).max(50).default('April 13, 2025').meta({
|
||||
description: "Presentation date",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
__image_url__: 'https://images.pexels.com/photos/9669089/pexels-photo-9669089.jpeg',
|
||||
__image_prompt__: 'Map of Nepal with gradient coloring from orange to red-brown'
|
||||
}).meta({
|
||||
description: "Image of the title slide of the presentation",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = titleSlideSchema
|
||||
|
||||
export type TitleSlideData = z.infer<typeof titleSlideSchema>
|
||||
|
||||
interface TitleSlideLayoutProps {
|
||||
data: Partial<TitleSlideData>
|
||||
}
|
||||
|
||||
const TitleSlideLayout: React.FC<TitleSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, subtitle, presenter, date, image } = slideData;
|
||||
|
||||
return (
|
||||
<div className="w-full h-full shadow-lg flex items-center aspect-video relative z-20 mx-auto bg-gray-900">
|
||||
|
||||
<div className="flex w-full h-full">
|
||||
{/* Left side - Text content */}
|
||||
<div className="flex flex-col flex-1 items-start justify-center space-y-6 p-10">
|
||||
{/* Title */}
|
||||
{title && (
|
||||
<h1 className="text-4xl leading-tight font-bold text-purple-400">
|
||||
{title}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{/* Subtitle */}
|
||||
{subtitle && (
|
||||
<p className="text-white text-xl leading-relaxed font-normal opacity-90">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Presenter and date */}
|
||||
<div className="text-white text-base leading-relaxed font-normal opacity-75">
|
||||
Presenter: {presenter} | {date}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right side - Visual elements */}
|
||||
<div className="flex flex-col flex-1 items-end justify-center relative">
|
||||
{image && (
|
||||
<div className="w-full h-full">
|
||||
<img
|
||||
src={image.__image_url__}
|
||||
alt={image.__image_prompt__}
|
||||
className="w-full h-full object-cover"
|
||||
style={{
|
||||
filter: 'drop-shadow(0 4px 8px rgba(0,0,0,0.3))'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TitleSlideLayout
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart';
|
||||
import { PieChart, Pie, Cell } from "recharts";
|
||||
|
||||
export const layoutId = 'classic-dark-piechart-and-metrics'
|
||||
export const layoutName = 'Classic Dark Pie Chart and Metrics'
|
||||
export const layoutDescription = 'A modern slide with dark background, metrics on the left, and pie chart visualization on the right.'
|
||||
|
||||
const chartDataSchema = z.object({
|
||||
name: z.string().min(2).max(30).meta({ description: "Data point name" }),
|
||||
value: z.number().meta({ description: "Data point value" }),
|
||||
});
|
||||
|
||||
const pieChartAndMetricsSchema = z.object({
|
||||
title: z.string().min(3).max(80).default('Introduction to Nepal\'s Trade').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(100).default('Nepal\'s landlocked geography heavily influences its trade, fostering reliance on India and China.').meta({
|
||||
description: "Description text",
|
||||
}),
|
||||
metrics: z.array(z.object({
|
||||
label: z.string().meta({ description: "Metric label" }),
|
||||
value: z.string().meta({ description: "Metric value" }),
|
||||
percentage: z.string().optional().meta({ description: "Optional percentage" }),
|
||||
})).min(2).max(4).default([
|
||||
{ label: 'Exports (2023)', value: '$2.85 billion', percentage: '6.76% of GDP' },
|
||||
{ label: 'Imports (2023)', value: '$17.39 billion', percentage: '42.64% of GDP' },
|
||||
{ label: 'GDP (2022)', value: '$40.83 billion' },
|
||||
{ label: 'Trade Deficit (2022)', value: '-$12.44 billion' },
|
||||
]).meta({
|
||||
description: "List of key metrics",
|
||||
}),
|
||||
chartData: z.array(chartDataSchema).min(2).max(4).default([
|
||||
{ name: 'Imports', value: 42.64 },
|
||||
{ name: 'Exports', value: 6.76 },
|
||||
{ name: 'Other GDP', value: 50.6 },
|
||||
]).meta({
|
||||
description: "Pie chart data",
|
||||
})
|
||||
})
|
||||
|
||||
const chartConfig = {
|
||||
value: {
|
||||
label: "Value",
|
||||
},
|
||||
name: {
|
||||
label: "Name",
|
||||
},
|
||||
};
|
||||
|
||||
const CHART_COLORS = [
|
||||
'#8b5cf6',
|
||||
'#3b82f6',
|
||||
'#a855f7',
|
||||
];
|
||||
|
||||
export const Schema = pieChartAndMetricsSchema
|
||||
|
||||
export type PieChartAndMetricsData = z.infer<typeof pieChartAndMetricsSchema>
|
||||
|
||||
interface PieChartAndMetricsLayoutProps {
|
||||
data: Partial<PieChartAndMetricsData>
|
||||
}
|
||||
|
||||
const PieChartAndMetricsLayout: React.FC<PieChartAndMetricsLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, description, metrics, chartData } = slideData;
|
||||
|
||||
const CustomLegend = () => (
|
||||
<div className="flex justify-center space-x-8 mt-4">
|
||||
{chartData?.map((entry, index) => (
|
||||
<div key={index} className="flex items-center space-x-2">
|
||||
<div
|
||||
className="w-4 h-4 rounded-sm"
|
||||
style={{ backgroundColor: CHART_COLORS[index % CHART_COLORS.length] }}
|
||||
/>
|
||||
<span className="text-white text-lg font-medium">{entry.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderPieChart = () => {
|
||||
return (
|
||||
<PieChart>
|
||||
<ChartTooltip content={<ChartTooltipContent />} />
|
||||
<Pie
|
||||
data={chartData}
|
||||
fill="#8b5cf6"
|
||||
outerRadius="50%"
|
||||
dataKey="value"
|
||||
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
||||
labelLine={false}
|
||||
fontSize={18}
|
||||
>
|
||||
{chartData?.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full h-full shadow-lg flex items-center aspect-video relative z-20 mx-auto bg-gray-900">
|
||||
|
||||
<div className="flex w-full h-full">
|
||||
{/* Left side - Text content and metrics */}
|
||||
<div className="flex flex-col basis-1/2 items-start justify-start space-y-8 p-12">
|
||||
{/* Title */}
|
||||
{title && (
|
||||
<h1 className="text-5xl leading-tight font-bold text-purple-400">
|
||||
{title}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{/* Description */}
|
||||
{description && (
|
||||
<p className="text-white text-2xl leading-relaxed font-normal opacity-90">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Metrics */}
|
||||
{metrics && metrics.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
{metrics.map((metric, index) => (
|
||||
<div key={index} className="text-white text-xl leading-relaxed font-normal">
|
||||
<span className="opacity-90">• {metric.label}: </span>
|
||||
<span className="font-bold text-purple-300">{metric.value}</span>
|
||||
{metric.percentage && (
|
||||
<span className="opacity-75"> ({metric.percentage})</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right side - Chart */}
|
||||
<div className="flex flex-col basis-1/2 items-center justify-center">
|
||||
<div className="w-full h-full flex flex-col items-center justify-center">
|
||||
<ChartContainer config={chartConfig} className="h-[500px] w-[500px]">
|
||||
{renderPieChart()}
|
||||
</ChartContainer>
|
||||
<CustomLegend />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PieChartAndMetricsLayout
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart';
|
||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Cell } from "recharts";
|
||||
|
||||
export const layoutId = 'classic-dark-bar-graph'
|
||||
export const layoutName = 'Classic Dark Bar Graph'
|
||||
export const layoutDescription = 'A modern slide with dark background, gradient title, bar chart visualization, and footer text.'
|
||||
|
||||
const barDataSchema = z.object({
|
||||
name: z.string().min(2).max(30).meta({ description: "Product name" }),
|
||||
value: z.number().meta({ description: "Export value in millions" }),
|
||||
});
|
||||
|
||||
const barGraphSchema = z.object({
|
||||
title: z.string().min(3).max(80).default('Export Overview: Key Products').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(120).default('Nepal\'s total exports were $1.3 billion in 2022, a 21% decrease from 2021, but showed a 47.5% YoY increase by Nov 2024.').meta({
|
||||
description: "Description text",
|
||||
}),
|
||||
chartData: z.array(barDataSchema).min(2).max(6).default([
|
||||
{ name: 'Soybean Oil (non-crude)', value: 180 },
|
||||
{ name: 'Palm Oil (non-crude)', value: 180 },
|
||||
{ name: 'Carpets/Textile Floor...', value: 80 },
|
||||
{ name: 'Cardamom', value: 50 },
|
||||
{ name: 'Felt Products', value: 40 },
|
||||
]).meta({
|
||||
description: "Bar chart data",
|
||||
}),
|
||||
})
|
||||
|
||||
const chartConfig = {
|
||||
value: {
|
||||
label: "Value ($M)",
|
||||
},
|
||||
name: {
|
||||
label: "Product",
|
||||
},
|
||||
};
|
||||
|
||||
const BAR_COLORS = [
|
||||
'#8b5cf6', // Dark purple for top products
|
||||
'#8b5cf6', // Dark purple for top products
|
||||
'#a855f7', // Light purple for other products
|
||||
'#a855f7', // Light purple for other products
|
||||
'#a855f7', // Light purple for other products
|
||||
];
|
||||
|
||||
export const Schema = barGraphSchema
|
||||
|
||||
export type BarGraphData = z.infer<typeof barGraphSchema>
|
||||
|
||||
interface BarGraphLayoutProps {
|
||||
data: Partial<BarGraphData>
|
||||
}
|
||||
|
||||
const BarGraphLayout: React.FC<BarGraphLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, description, chartData } = slideData;
|
||||
|
||||
const CustomLegend = () => (
|
||||
<div className="flex justify-center space-x-8 mt-8">
|
||||
{chartData?.map((entry, index) => (
|
||||
<div key={index} className="flex items-center space-x-2">
|
||||
<div
|
||||
className="w-4 h-4 rounded-sm"
|
||||
style={{ backgroundColor: BAR_COLORS[index % BAR_COLORS.length] }}
|
||||
/>
|
||||
<span className="text-white text-lg font-medium">{entry.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderBarChart = () => {
|
||||
return (
|
||||
<BarChart
|
||||
data={chartData}
|
||||
margin={{ top: 0, right: 30, left: 20, bottom: 0 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#374151" />
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
height={100}
|
||||
tick={{ fill: '#ffffff', fontSize: 16, fontWeight: 600 }}
|
||||
tickFormatter={(value) => {
|
||||
if (value.length > 15) {
|
||||
return value.substring(0, 15) + '...';
|
||||
}
|
||||
return value;
|
||||
}}
|
||||
/>
|
||||
<YAxis
|
||||
tick={{ fill: '#ffffff', fontSize: 16, fontWeight: 600 }}
|
||||
tickFormatter={(value) => value.toFixed(0)}
|
||||
/>
|
||||
<ChartTooltip content={<ChartTooltipContent />} />
|
||||
<Bar
|
||||
dataKey="value"
|
||||
fill="#8b5cf6"
|
||||
radius={[4, 4, 0, 0]}
|
||||
>
|
||||
{chartData?.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={BAR_COLORS[index % BAR_COLORS.length]} />
|
||||
))}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full h-full shadow-lg flex items-center aspect-video relative z-20 mx-auto bg-gray-900">
|
||||
|
||||
<div className="flex flex-col w-full h-full">
|
||||
{/* Header section */}
|
||||
<div className="flex flex-col items-start justify-start space-y-6 p-12">
|
||||
{/* Title */}
|
||||
{title && (
|
||||
<h1 className="text-5xl leading-tight font-bold text-purple-400">
|
||||
{title}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{/* Description */}
|
||||
{description && (
|
||||
<p className="text-white text-2xl leading-relaxed font-normal opacity-90">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Chart section */}
|
||||
<div className="flex flex-col flex-1 items-center justify-center px-12">
|
||||
<div className="w-full h-full flex flex-col items-center justify-center space-y-4">
|
||||
<ChartContainer config={chartConfig} className="h-[300px] w-full">
|
||||
{renderBarChart()}
|
||||
</ChartContainer>
|
||||
<CustomLegend />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BarGraphLayout
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-templates/defaultSchemes';
|
||||
|
||||
export const layoutId = 'classic-dark-comparison'
|
||||
export const layoutName = 'Classic Dark Comparison'
|
||||
export const layoutDescription = 'A modern slide with dark background, image on the left (2/5), and comparison content on the right (3/5).'
|
||||
|
||||
const comparisonItemSchema = z.object({
|
||||
name: z.string().min(3).max(30).meta({ description: "Commodity name" }),
|
||||
value: z.string().min(3).max(30).meta({ description: "Value" }),
|
||||
when: z.string().min(3).max(30).meta({ description: "When the value was recorded" }),
|
||||
details: z.string().min(3).max(50).optional().meta({ description: "Additional details" }),
|
||||
});
|
||||
|
||||
const comparisonSectionSchema = z.object({
|
||||
title: z.string().min(3).max(50).meta({ description: "Section title" }),
|
||||
items: z.array(comparisonItemSchema).min(1).max(3).meta({ description: "List of items in the section" }),
|
||||
});
|
||||
|
||||
const comparisonSchema = z.object({
|
||||
title: z.string().min(3).max(80).default('Key Commodities in Focus').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
comparisonSections: z.array(comparisonSectionSchema).min(2).max(2).default([
|
||||
{
|
||||
title: 'Exports',
|
||||
items: [
|
||||
{ name: 'Soybean Oil', value: '$186.91 million', when: '2022' },
|
||||
{ name: 'Cardamom', value: '$46.64 million', when: '2022', details: 'primarily to India' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Imports',
|
||||
items: [
|
||||
{ name: 'Crude Soybean Oil', value: '$347.77 million', when: '2022' },
|
||||
{ name: 'Petroleum Products', value: '$3.15 billion', when: '2022', details: '22% of total imports' },
|
||||
{ name: 'Vehicles/Parts', value: '$526 million', when: '2022', details: 'down 45% from 2021' },
|
||||
]
|
||||
}
|
||||
]).meta({
|
||||
description: "Comparison sections with title and data items",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
__image_url__: 'https://images.pexels.com/photos/9669089/pexels-photo-9669089.jpeg',
|
||||
__image_prompt__: 'Map of South Asia showing Nepal and neighboring countries with trade routes highlighted'
|
||||
}).meta({
|
||||
description: "Comparison visualization image",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = comparisonSchema
|
||||
|
||||
export type ComparisonData = z.infer<typeof comparisonSchema>
|
||||
|
||||
interface ComparisonLayoutProps {
|
||||
data: Partial<ComparisonData>
|
||||
}
|
||||
|
||||
const ComparisonLayout: React.FC<ComparisonLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, comparisonSections, image } = slideData;
|
||||
|
||||
return (
|
||||
<div className="w-full h-full shadow-lg flex items-center aspect-video relative z-20 mx-auto bg-gray-900">
|
||||
|
||||
<div className="flex w-full h-full">
|
||||
{/* Left side - Image (2/5) */}
|
||||
<div className="flex flex-col basis-2/5 h-full">
|
||||
{image && (
|
||||
<div className="w-full h-full">
|
||||
<img
|
||||
src={image.__image_url__}
|
||||
alt={image.__image_prompt__}
|
||||
className="w-full h-full object-cover"
|
||||
style={{
|
||||
filter: 'drop-shadow(0 4px 8px rgba(0,0,0,0.3))'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right side - Content (3/5) */}
|
||||
<div className="flex flex-col basis-3/5 items-start justify-start space-y-8 p-12">
|
||||
{/* Title */}
|
||||
{title && (
|
||||
<h1 className="text-5xl leading-tight font-bold text-purple-400">
|
||||
{title}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{/* Comparison Sections */}
|
||||
<div className="flex w-full space-x-12 flex pt-10">
|
||||
{comparisonSections && comparisonSections.map((section, sectionIndex) => (
|
||||
<div key={sectionIndex} className="flex flex-col flex-1 space-y-4">
|
||||
<h2 className="text-3xl leading-tight font-semibold text-purple-300">
|
||||
{section.title}
|
||||
</h2>
|
||||
{section.items && section.items.length > 0 && (
|
||||
<div className="space-y-3">
|
||||
{section.items.map((item, index) => (
|
||||
<div key={index} className="text-white text-lg leading-relaxed font-normal">
|
||||
<span className="opacity-90">• <span className="font-bold">{item.name}</span>: {item.value} ({item.when})</span>
|
||||
{item.details && (
|
||||
<span className="opacity-75">, {item.details}</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ComparisonLayout
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from '@/presentation-templates/defaultSchemes';
|
||||
|
||||
export const layoutId = 'classic-dark-metrics'
|
||||
export const layoutName = 'Classic Dark Metrics'
|
||||
export const layoutDescription = 'A modern slide with dark background, metric cards arranged in a grid with icons and data.'
|
||||
|
||||
const metricItemSchema = z.object({
|
||||
title: z.string().min(3).max(50).meta({ description: "Metric title" }),
|
||||
value: z.string().min(3).max(30).meta({ description: "Metric value" }),
|
||||
percentage: z.string().min(3).max(30).meta({ description: "Percentage value" }),
|
||||
icon: IconSchema.meta({ description: "Icon for the metric" }),
|
||||
});
|
||||
|
||||
const metricsSchema = z.object({
|
||||
title: z.string().min(3).max(80).default('Top Export Destinations').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(120).default('Nepal exports 760 products to 132 countries, with a strong focus on regional trade.').meta({
|
||||
description: "Description text",
|
||||
}),
|
||||
metrics: z.array(metricItemSchema).min(2).max(6).default([
|
||||
{
|
||||
title: 'India',
|
||||
value: '$935 million',
|
||||
percentage: '71.93%',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'star rating'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'United States',
|
||||
value: '$147 million',
|
||||
percentage: '11.32%',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'flag country'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Germany',
|
||||
value: '$33 million',
|
||||
percentage: '2.51%',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'user person'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Turkey',
|
||||
value: '$26 million',
|
||||
percentage: '2.01%',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'pen tool'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'United Kingdom',
|
||||
value: '$24 million',
|
||||
percentage: '1.83%',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'message chat'
|
||||
}
|
||||
},
|
||||
]).meta({
|
||||
description: "Metric cards data",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = metricsSchema
|
||||
|
||||
export type MetricsData = z.infer<typeof metricsSchema>
|
||||
|
||||
interface MetricsLayoutProps {
|
||||
data: Partial<MetricsData>
|
||||
}
|
||||
|
||||
const MetricsLayout: React.FC<MetricsLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, description, metrics } = slideData;
|
||||
|
||||
return (
|
||||
<div className="w-full h-full shadow-lg flex items-center aspect-video relative z-20 mx-auto bg-gray-900">
|
||||
|
||||
<div className="flex flex-col w-full h-full">
|
||||
{/* Header section */}
|
||||
<div className="flex flex-col items-start justify-start space-y-6 p-10">
|
||||
{/* Title */}
|
||||
{title && (
|
||||
<h1 className="text-5xl leading-tight font-bold text-purple-400">
|
||||
{title}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{/* Description */}
|
||||
{description && (
|
||||
<p className="text-white text-xl leading-relaxed font-normal opacity-90">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Metrics Cards Grid */}
|
||||
<div className="flex flex-col flex-1 items-center justify-center px-12">
|
||||
<div className="grid grid-cols-3 gap-x-8 gap-y-12 w-full max-w-4xl">
|
||||
{metrics && metrics.map((metric, index) => (
|
||||
<div key={index} className="flex flex-col items-center">
|
||||
{/* Metric Card with overlapping icon */}
|
||||
<div className="relative w-full bg-gray-800 rounded-lg p-6 text-center border border-purple-500 border-opacity-30 shadow-lg">
|
||||
{/* Icon overlapping the top */}
|
||||
<div className="absolute -top-8 left-1/2 transform -translate-x-1/2 w-16 h-16 bg-purple-600 rounded-full flex items-center justify-center shadow-lg">
|
||||
<img
|
||||
src={metric.icon.__icon_url__}
|
||||
alt={metric.icon.__icon_query__}
|
||||
className="w-6 h-6 object-contain text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content with top padding for icon space */}
|
||||
<div className="pt-8">
|
||||
<h3 className="text-2xl font-bold text-white mb-2">
|
||||
{metric.title}
|
||||
</h3>
|
||||
<p className="text-lg text-white opacity-90">
|
||||
{metric.value} ({metric.percentage})
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MetricsLayout
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-templates/defaultSchemes';
|
||||
|
||||
export const layoutId = 'classic-dark-bullet-point-with-description'
|
||||
export const layoutName = 'Classic Dark Bullet Point with Description'
|
||||
export const layoutDescription = 'A modern slide with dark background, image on the left (2/5), and bullet points with descriptions in boxes on the right (3/5).'
|
||||
|
||||
const bulletPointSchema = z.object({
|
||||
title: z.string().min(3).max(60).meta({ description: "Bullet point title" }),
|
||||
content: z.string().min(10).max(120).meta({ description: "Bullet point content (max 150 characters)" }),
|
||||
});
|
||||
|
||||
const bulletPointWithDescriptionSchema = z.object({
|
||||
title: z.string().min(3).max(80).default('Trade Policies and Challenges').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
bulletPoints: z.array(bulletPointSchema).min(2).max(3).default([
|
||||
{
|
||||
title: 'Tariffs',
|
||||
content: 'Effectively Applied Tariff (2022): 11.59%. Most Favored Nation Tariff (2022): 12.87%. Duty-free imports: $412.11 million.'
|
||||
},
|
||||
{
|
||||
title: 'Forex Reserves',
|
||||
content: '$8.18 billion in 2019, covering 8 months of imports.'
|
||||
},
|
||||
{
|
||||
title: 'Import Ban Impact',
|
||||
content: 'Luxury goods ban (Apr-Dec 2022) cut deficit by 15.45% but reduced export earnings by 21.44%.'
|
||||
}
|
||||
]).meta({
|
||||
description: "Bullet points with descriptions (max 3 items)",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
__image_url__: 'https://images.pexels.com/photos/9669089/pexels-photo-9669089.jpeg',
|
||||
__image_prompt__: 'Stylized mountainous landscape with trade arrows and network connections, dark gradient background with orange sun and purple mountains'
|
||||
}).meta({
|
||||
description: "Visual representation image",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = bulletPointWithDescriptionSchema
|
||||
|
||||
export type BulletPointWithDescriptionData = z.infer<typeof bulletPointWithDescriptionSchema>
|
||||
|
||||
interface BulletPointWithDescriptionLayoutProps {
|
||||
data: Partial<BulletPointWithDescriptionData>
|
||||
}
|
||||
|
||||
const BulletPointWithDescriptionLayout: React.FC<BulletPointWithDescriptionLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, bulletPoints, image } = slideData;
|
||||
|
||||
return (
|
||||
<div className="w-full h-full shadow-lg flex items-center aspect-video relative z-20 mx-auto bg-gray-900">
|
||||
|
||||
<div className="flex w-full h-full">
|
||||
{/* Left side - Image (2/5) */}
|
||||
<div className="flex flex-col basis-2/5 h-full">
|
||||
{image && (
|
||||
<div className="w-full h-full">
|
||||
<img
|
||||
src={image.__image_url__}
|
||||
alt={image.__image_prompt__}
|
||||
className="w-full h-full object-cover"
|
||||
style={{
|
||||
filter: 'drop-shadow(0 4px 8px rgba(0,0,0,0.3))'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right side - Content (3/5) */}
|
||||
<div className="flex flex-col basis-3/5 items-start justify-start space-y-8 p-10">
|
||||
{/* Title */}
|
||||
{title && (
|
||||
<h1 className="text-5xl leading-tight font-bold text-purple-400">
|
||||
{title}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{/* Bullet Points */}
|
||||
{bulletPoints && bulletPoints.length > 0 && (
|
||||
<div className="space-y-6 w-full h-full flex flex-col justify-center">
|
||||
{bulletPoints.map((point, index) => (
|
||||
<div key={index} className="w-full bg-gray-800 rounded-lg py-2 px-4 border border-gray-600 border-opacity-30">
|
||||
<h3 className="text-2xl font-bold text-white mb-4">
|
||||
{point.title}
|
||||
</h3>
|
||||
<p className="text-white text-lg leading-relaxed font-normal opacity-90">
|
||||
{point.content}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BulletPointWithDescriptionLayout
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"description": "Classic dark layout for presentations",
|
||||
"ordered": false,
|
||||
"default": false
|
||||
}
|
||||
|
|
@ -1,269 +0,0 @@
|
|||
import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart';
|
||||
import React from 'react'
|
||||
import { BarChart, Bar, LineChart, Line, PieChart, Pie, AreaChart, Area, ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Cell, ResponsiveContainer } from "recharts";
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from '../defaultSchemes';
|
||||
|
||||
export const layoutId = 'type4-slide'
|
||||
export const layoutName = 'Type4 Slide'
|
||||
export const layoutDescription = 'A chart-focused layout with title, chart visualization, and description text.'
|
||||
|
||||
const chartDataSchema = z.object({
|
||||
name: z.string().meta({ description: "Data point name" }),
|
||||
value: z.number().meta({ description: "Data point value" }),
|
||||
category: z.string().optional().meta({ description: "Category for grouping" }),
|
||||
x: z.number().optional().meta({ description: "X coordinate for scatter plots" }),
|
||||
y: z.number().optional().meta({ description: "Y coordinate for scatter plots" }),
|
||||
});
|
||||
|
||||
|
||||
const type10SlideSchema = z.object({
|
||||
title: z.string().min(3).max(50).default('Chart Analysis').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(8).max(40).default('This is a description of the chart analysis').meta({
|
||||
description: " Short description of the chart analysis",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
icon: IconSchema.meta({
|
||||
description: "Item icon",
|
||||
}),
|
||||
heading: z.string().min(2).max(50).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(130).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(3).default(() => [
|
||||
{
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'A beautiful road in the mountains'
|
||||
},
|
||||
heading: 'First Key Point',
|
||||
description: 'Detailed explanation of the first important point that supports the main topic'
|
||||
},
|
||||
{
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'A beautiful road in the mountains'
|
||||
},
|
||||
heading: 'Second Key Point',
|
||||
description: 'Detailed explanation of the second important point with relevant information'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of numbered items (2-3 items)",
|
||||
}),
|
||||
chartData: z.any().optional().meta({
|
||||
description: "Chart data object",
|
||||
}),
|
||||
isFullSizeChart: z.boolean().default(false).meta({
|
||||
description: "Whether to display chart in full size mode",
|
||||
}),
|
||||
chartType: z.enum(['bar', 'line', 'pie', 'area', 'scatter']).default('line').meta({
|
||||
description: "Type of chart to display",
|
||||
}),
|
||||
data: z.array(chartDataSchema).min(2).max(10).default([
|
||||
{ name: '2021', value: 5 },
|
||||
{ name: '2022', value: 12 },
|
||||
{ name: '2023', value: 18 },
|
||||
{ name: '2024', value: 23 },
|
||||
{ name: '2025', value: 26 },
|
||||
]).meta({
|
||||
description: "Chart data points",
|
||||
}),
|
||||
dataKey: z.string().default('value').meta({
|
||||
description: "Key field for chart values",
|
||||
}),
|
||||
categoryKey: z.string().default('name').meta({
|
||||
description: "Key field for chart categories",
|
||||
}),
|
||||
color: z.string().default('#3b82f6').meta({
|
||||
description: "Primary color for chart elements",
|
||||
}),
|
||||
showLegend: z.boolean().default(false).meta({
|
||||
description: "Whether to show chart legend",
|
||||
}),
|
||||
showTooltip: z.boolean().default(true).meta({
|
||||
description: "Whether to show chart tooltip",
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
const chartConfig = {
|
||||
value: {
|
||||
label: "Value",
|
||||
},
|
||||
name: {
|
||||
label: "Name",
|
||||
},
|
||||
};
|
||||
const CHART_COLORS = [
|
||||
'#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
|
||||
'#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1'
|
||||
];
|
||||
|
||||
export const Schema = type10SlideSchema
|
||||
|
||||
export type Type10SlideData = z.infer<typeof type10SlideSchema>
|
||||
|
||||
interface Type10SlideLayoutProps {
|
||||
data: Partial<Type10SlideData>
|
||||
}
|
||||
|
||||
const Type10SlideLayout: React.FC<Type10SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, items, data, chartType = 'line', color = '#3b82f6', dataKey = 'value', categoryKey = 'name', showLegend = false, showTooltip = true } = slideData;
|
||||
const chartData = data || [];
|
||||
const renderChart = () => {
|
||||
const commonProps = {
|
||||
data: chartData,
|
||||
margin: { top: 10, right: 20, left: 0, bottom: 30 },
|
||||
};
|
||||
|
||||
switch (chartType) {
|
||||
case 'bar':
|
||||
return (
|
||||
<BarChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Bar dataKey={dataKey} fill={color} radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
);
|
||||
|
||||
case 'line':
|
||||
return (
|
||||
<LineChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={color}
|
||||
strokeWidth={3}
|
||||
dot={{ fill: color, strokeWidth: 2, r: 4 }}
|
||||
/>
|
||||
</LineChart>
|
||||
);
|
||||
|
||||
case 'area':
|
||||
return (
|
||||
<AreaChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={color}
|
||||
fill={color}
|
||||
fillOpacity={0.6}
|
||||
/>
|
||||
</AreaChart>
|
||||
);
|
||||
|
||||
case 'pie':
|
||||
return (
|
||||
<PieChart margin={{ top: 10, right: 10, left: 10, bottom: 10 }}>
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Pie
|
||||
data={chartData}
|
||||
cx="50%"
|
||||
cy="40%"
|
||||
outerRadius={70}
|
||||
fill={color}
|
||||
dataKey={dataKey}
|
||||
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
||||
>
|
||||
{chartData.map((entry: any, index: number) => (
|
||||
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
);
|
||||
|
||||
case 'scatter':
|
||||
return (
|
||||
<ScatterChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="x" type="number" />
|
||||
<YAxis dataKey="y" type="number" />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Scatter dataKey="value" fill={color} />
|
||||
</ScatterChart>
|
||||
);
|
||||
|
||||
default:
|
||||
return <div>Unsupported chart type</div>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm w-full max-w-[1280px] px-3 py-[10px] sm:px-12 lg:px-20 sm:py-[40px] lg:py-[86px] shadow-lg max-h-[720px] flex flex-col items-center justify-center aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className='w-full flex flex-col items-start justify-start'>
|
||||
|
||||
{title && <h1 className="text-2xl text-start sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight mb-4 lg:mb-8">
|
||||
{title || 'Chart Analysis'}
|
||||
</h1>}
|
||||
|
||||
</div>
|
||||
<div className={`flex gap-6 w-full items-center `}>
|
||||
<div className="w-1/2">
|
||||
<div className="flex-1 bg-white rounded-lg shadow-sm border border-gray-100 p-4">
|
||||
<ChartContainer config={chartConfig} className="h-full w-full">
|
||||
{renderChart()}
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:w-1/2 relative">
|
||||
<div className="space-y-3 lg:space-y-6">
|
||||
{items && items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex gap-6">
|
||||
<div className="w-[48px] h-[48px]">
|
||||
<div className="w-full h-full bg-blue-600 rounded-lg flex items-center justify-center overflow-hidden">
|
||||
{item.icon?.__icon_url__ && <img
|
||||
src={item.icon?.__icon_url__ || ''}
|
||||
alt={item.icon?.__icon_query__ || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[calc(100%-55px)] space-y-1">
|
||||
{item.heading && <h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
|
||||
{item.heading}
|
||||
</h3>}
|
||||
{item.description && <p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
|
||||
{item.description}
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type10SlideLayout
|
||||
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-templates/defaultSchemes';
|
||||
|
||||
export const layoutId = 'type1-slide'
|
||||
export const layoutName = 'Type1 Slide'
|
||||
export const layoutDescription = 'A clean two-column layout with title and description on the left and a featured image on the right.'
|
||||
|
||||
const type1SlideSchema = z.object({
|
||||
title: z.string().min(3).max(50).default('Hot NOT Reload Working!').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(130).default('This is a test of the hot reload system! If you can see this text, hot reload is working perfectly. Changes should appear instantly without page refresh.').meta({
|
||||
description: "Main description text",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
__image_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__image_prompt__: 'A beautiful road in the mountains'
|
||||
}).meta({
|
||||
description: "Main slide image",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type1SlideSchema
|
||||
|
||||
export type Type1SlideData = z.infer<typeof type1SlideSchema>
|
||||
|
||||
interface Type1SlideLayoutProps {
|
||||
data: Partial<Type1SlideData>
|
||||
}
|
||||
|
||||
const Type1SlideLayout: React.FC<Type1SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, description, image } = slideData;
|
||||
return (
|
||||
<div
|
||||
className=" w-full rounded-sm max-w-[1280px] shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] max-h-[720px] flex items-center aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3 sm:gap-8 md:gap-12 lg:gap-16 w-full">
|
||||
<div className="flex flex-col w-full items-start justify-center space-y-1 md:space-y-2 lg:space-y-6">
|
||||
{/* Title */}
|
||||
{title && <h1 className=" text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
|
||||
{title}
|
||||
</h1>}
|
||||
|
||||
{/* Description */}
|
||||
{description && <p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
|
||||
{description}
|
||||
</p>}
|
||||
</div>
|
||||
|
||||
{/* Image */}
|
||||
<div className="w-full max-h-[600px]">
|
||||
{image && <img
|
||||
src={image?.__image_url__ || ''}
|
||||
alt={image?.__image_prompt__ || title || ''}
|
||||
className="w-full max-h-full object-cover rounded-lg shadow-md"
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type1SlideLayout
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type2-numbered-slide'
|
||||
export const layoutName = 'Type2 Numbered Slide'
|
||||
export const layoutDescription = 'A content layout with title and numbered content items with large numerals and shadow boxes.'
|
||||
|
||||
const type2NumberedSlideSchema = z.object({
|
||||
title: z.string().min(3).max(50).default('Main Title').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(130).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(3).default([
|
||||
{
|
||||
heading: 'First Point',
|
||||
description: 'Description for the first key point that explains important details'
|
||||
},
|
||||
{
|
||||
heading: 'Second Point',
|
||||
description: 'Description for the second key point with relevant information'
|
||||
},
|
||||
{
|
||||
heading: 'Third Point',
|
||||
description: 'Description for the third key point highlighting crucial aspects'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of content items (2-4 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type2NumberedSlideSchema
|
||||
|
||||
export type Type2NumberedSlideData = z.infer<typeof type2NumberedSlideSchema>
|
||||
|
||||
interface Type2NumberedSlideLayoutProps {
|
||||
data: Partial<Type2NumberedSlideData>
|
||||
}
|
||||
|
||||
const Type2NumberedSlideLayout: React.FC<Type2NumberedSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, items } = slideData;
|
||||
const isGridLayout = items?.length && items?.length >= 4
|
||||
const numberTranslations: string[] = ['01', '02', '03', '04', '05', '06']
|
||||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<div className="grid grid-cols-1 bg-white lg:grid-cols-2 relative gap-4 lg:gap-8 mt-4 lg:mt-12">
|
||||
{items?.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full relative shadow-lg rounded-lg p-3 lg:p-6"
|
||||
>
|
||||
<div className="flex gap-3">
|
||||
<div className="text-[32px] leading-[40px] px-1 font-bold mb-4 text-blue-600">
|
||||
{numberTranslations[index] || `0${index + 1}`}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{item.heading && <h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
|
||||
{item.heading}
|
||||
</h3>}
|
||||
{item.description && <p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
|
||||
{item.description}
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderHorizontalContent = () => {
|
||||
return (
|
||||
<div className="flex flex-col lg:flex-row bg-white mt-4 lg:mt-12 w-full relative gap-4 lg:gap-8">
|
||||
{items?.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full relative shadow-lg rounded-lg p-3 lg:p-6"
|
||||
>
|
||||
<div className="text-[32px] leading-[40px] font-semibold lg:mb-4 text-blue-600">
|
||||
{numberTranslations[index] || `0${index + 1}`}
|
||||
</div>
|
||||
<div className="space-y-2 lg:space-y-4">
|
||||
{item.heading && <h3 className=" text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
|
||||
{item.heading}
|
||||
</h3>}
|
||||
{item.description && <p className=" text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
|
||||
{item.description}
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm max-w-[1280px] w-full shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="text-center lg:pb-8 w-full">
|
||||
{title && <h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
|
||||
{title}
|
||||
</h1>}
|
||||
</div>
|
||||
|
||||
{isGridLayout ? renderGridContent() : renderHorizontalContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type2NumberedSlideLayout
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type2-slide'
|
||||
export const layoutName = 'Type2 Slide'
|
||||
export const layoutDescription = 'A flexible content layout with title and multiple content items in default presentation style.'
|
||||
|
||||
const type2SlideSchema = z.object({
|
||||
title: z.string().min(3).max(50).default('Main Title').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(50).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(130).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(3).default([
|
||||
{
|
||||
heading: 'First Point',
|
||||
description: 'Description for the first key point that explains important details'
|
||||
},
|
||||
{
|
||||
heading: 'Second Point',
|
||||
description: 'Description for the second key point with relevant information'
|
||||
},
|
||||
{
|
||||
heading: 'Third Point',
|
||||
description: 'Description for the third key point highlighting crucial aspects'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of content items (2-4 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type2SlideSchema
|
||||
|
||||
export type Type2SlideData = z.infer<typeof type2SlideSchema>
|
||||
|
||||
interface Type2SlideLayoutProps {
|
||||
data: Partial<Type2SlideData>
|
||||
}
|
||||
|
||||
const Type2SlideLayout: React.FC<Type2SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, items } = slideData;
|
||||
const isGridLayout = items?.length && items?.length >= 4
|
||||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 relative gap-6 md:gap-12 mt-4 lg:mt-12">
|
||||
{items?.map((item, index) => (
|
||||
<div key={index} className="w-full relative p-3 lg:p-6 rounded-md"
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
{item.heading && <h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
|
||||
{item.heading}
|
||||
</h3>}
|
||||
{item.description && <p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
|
||||
{item.description}
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderHorizontalContent = () => {
|
||||
return (
|
||||
<div className="flex flex-col lg:flex-row mt-4 lg:mt-12 w-full relative gap-12">
|
||||
{items?.map((item, index) => (
|
||||
<div key={index} className="w-full relative p-3 lg:p-6 rounded-md"
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
>
|
||||
<div className="space-y-2 lg:space-y-4">
|
||||
{item.heading && <h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
|
||||
{item.heading}
|
||||
</h3>}
|
||||
{item.description && <p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
|
||||
{item.description}
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm max-w-[1280px] w-full shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="text-center lg:pb-8 w-full">
|
||||
{title && <h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
|
||||
{title}
|
||||
</h1>}
|
||||
</div>
|
||||
|
||||
{isGridLayout ? renderGridContent() : renderHorizontalContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type2SlideLayout
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type2-timeline-slide'
|
||||
export const layoutName = 'Type2 Timeline Slide'
|
||||
export const layoutDescription = 'A timeline layout with title and content items arranged horizontally with numbered circles and connecting line.'
|
||||
|
||||
const type2TimelineSlideSchema = z.object({
|
||||
title: z.string().min(3).max(50).default('Main Title').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(50).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(130).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(3).default([
|
||||
{
|
||||
heading: 'First Point',
|
||||
description: 'Description for the first key point that explains important details'
|
||||
},
|
||||
{
|
||||
heading: 'Second Point',
|
||||
description: 'Description for the second key point with relevant information'
|
||||
},
|
||||
{
|
||||
heading: 'Third Point',
|
||||
description: 'Description for the third key point highlighting crucial aspects'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of content items (2-4 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type2TimelineSlideSchema
|
||||
|
||||
export type Type2TimelineSlideData = z.infer<typeof type2TimelineSlideSchema>
|
||||
|
||||
interface Type2TimelineSlideLayoutProps {
|
||||
data: Partial<Type2TimelineSlideData>
|
||||
}
|
||||
|
||||
const Type2TimelineSlideLayout: React.FC<Type2TimelineSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, items } = slideData;
|
||||
|
||||
const renderTimelineContent = () => {
|
||||
return (
|
||||
<div className="w-full flex flex-col relative mt-4 lg:mt-16">
|
||||
{/* Timeline Header with Numbers and Line */}
|
||||
<div className="relative flex justify-between w-[85%] mx-auto items-center mb-8 px-8">
|
||||
{/* Horizontal Line */}
|
||||
<div className="absolute z-10 top-1/2 w-[87%] left-1/2 -translate-x-1/2 h-[2px] bg-blue-600" />
|
||||
|
||||
{/* Timeline Numbers */}
|
||||
{items && items.map((_, index) => (
|
||||
<div
|
||||
key={`timeline-${index}`}
|
||||
className="relative z-10 w-12 h-12 rounded-full bg-blue-600 px-1 text-white flex items-center justify-center font-bold text-lg"
|
||||
>
|
||||
<span> {index + 1}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Timeline Content */}
|
||||
<div className="flex justify-between gap-8">
|
||||
{items && items.map((item, index) => (
|
||||
<div key={index} className="flex-1 text-center relative">
|
||||
<div className="space-y-4">
|
||||
{item.heading && <h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
|
||||
{item.heading}
|
||||
</h3>}
|
||||
{item.description && <p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
|
||||
{item.description}
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm max-w-[1280px] w-full shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
>
|
||||
<div className="text-center lg:pb-8 w-full">
|
||||
{title && <h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
|
||||
{title}
|
||||
</h1>}
|
||||
</div>
|
||||
|
||||
{renderTimelineContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type2TimelineSlideLayout
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-templates/defaultSchemes';
|
||||
|
||||
export const layoutId = 'type3-slide'
|
||||
export const layoutName = 'Type3 Slide'
|
||||
export const layoutDescription = 'A centered title with a grid of image cards, each containing a heading and description.'
|
||||
|
||||
const type3SlideSchema = z.object({
|
||||
title: z.string().min(3).max(50).default('Featured Content').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(50).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(130).meta({
|
||||
description: "Item description",
|
||||
}),
|
||||
image: ImageSchema.meta({
|
||||
description: "Item image",
|
||||
})
|
||||
})).min(2).max(3).default([
|
||||
{
|
||||
heading: 'First Feature',
|
||||
description: 'Description for the first featured item with detailed information',
|
||||
image: {
|
||||
__image_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__image_prompt__: 'A beautiful road in the mountains'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Second Feature',
|
||||
description: 'Description for the second featured item with relevant details',
|
||||
image: {
|
||||
__image_url__: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
|
||||
__image_prompt__: 'Modern office workspace'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Third Feature',
|
||||
description: 'Description for the third featured item with important points',
|
||||
image: {
|
||||
__image_url__: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
|
||||
__image_prompt__: 'Laptop with code on screen'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "List of featured items (2-4 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type3SlideSchema
|
||||
|
||||
export type Type3SlideData = z.infer<typeof type3SlideSchema>
|
||||
|
||||
interface Type3SlideLayoutProps {
|
||||
data: Partial<Type3SlideData>
|
||||
}
|
||||
|
||||
const Type3SlideLayout: React.FC<Type3SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, items } = slideData;
|
||||
|
||||
const getGridCols = (length: number) => {
|
||||
switch (length) {
|
||||
case 1: return 'lg:grid-cols-1';
|
||||
case 2: return 'lg:grid-cols-2';
|
||||
case 3: return 'lg:grid-cols-3';
|
||||
case 4: return 'lg:grid-cols-4';
|
||||
default: return 'lg:grid-cols-1';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" shadow-lg rounded-sm w-full max-w-[1280px] px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] font-inter flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="text-center mb-4 lg:mb-16 w-full">
|
||||
{title && <h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
|
||||
{title}
|
||||
</h1>}
|
||||
</div>
|
||||
|
||||
<div className={`grid grid-cols-1 lg:grid-cols-2 ${getGridCols(items?.length || 0)} gap-3 lg:gap-6 w-full`}>
|
||||
{items && items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="flex flex-col w-full rounded-lg overflow-hidden relative"
|
||||
>
|
||||
{/* Image */}
|
||||
<div className="max-md:h-[140px] max-lg:h-[180px] h-48 w-full">
|
||||
<img
|
||||
src={item.image?.__image_url__ || ''}
|
||||
alt={item.image?.__image_prompt__ || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="space-y-2 p-3 lg:p-6">
|
||||
{item.heading && <h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
|
||||
{item.heading}
|
||||
</h3>}
|
||||
{item.description && <p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
|
||||
{item.description}
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type3SlideLayout
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart';
|
||||
import React from 'react'
|
||||
import { BarChart, Bar, LineChart, Line, PieChart, Pie, AreaChart, Area, ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Cell } from "recharts";
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type4-slide'
|
||||
export const layoutName = 'Type4 Slide'
|
||||
export const layoutDescription = 'A chart-focused layout with title, chart visualization, and description text.'
|
||||
|
||||
const chartDataSchema = z.object({
|
||||
name: z.string().meta({ description: "Data point name" }),
|
||||
value: z.number().meta({ description: "Data point value" }),
|
||||
category: z.string().optional().meta({ description: "Category for grouping" }),
|
||||
x: z.number().optional().meta({ description: "X coordinate for scatter plots" }),
|
||||
y: z.number().optional().meta({ description: "Y coordinate for scatter plots" }),
|
||||
});
|
||||
|
||||
|
||||
const type4SlideSchema = z.object({
|
||||
title: z.string().min(3).max(50).default('Chart Analysis').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(130).default('This chart shows important data trends and insights that help understand the current situation and make informed decisions.').meta({
|
||||
description: "Description text for the chart",
|
||||
}),
|
||||
chartData: z.any().optional().meta({
|
||||
description: "Chart data object",
|
||||
}),
|
||||
isFullSizeChart: z.boolean().default(false).meta({
|
||||
description: "Whether to display chart in full size mode",
|
||||
}),
|
||||
chartType: z.enum(['bar', 'line', 'pie', 'area', 'scatter']).default('bar').meta({
|
||||
description: "Type of chart to display",
|
||||
}),
|
||||
data: z.array(chartDataSchema).min(2).max(10).default([
|
||||
{ name: '2021', value: 5 },
|
||||
{ name: '2022', value: 12 },
|
||||
{ name: '2023', value: 18 },
|
||||
{ name: '2024', value: 23 },
|
||||
{ name: '2025', value: 26 },
|
||||
]).meta({
|
||||
description: "Chart data points",
|
||||
}),
|
||||
dataKey: z.string().default('value').meta({
|
||||
description: "Key field for chart values",
|
||||
}),
|
||||
categoryKey: z.string().default('name').meta({
|
||||
description: "Key field for chart categories",
|
||||
}),
|
||||
color: z.string().default('#3b82f6').meta({
|
||||
description: "Primary color for chart elements",
|
||||
}),
|
||||
showLegend: z.boolean().default(false).meta({
|
||||
description: "Whether to show chart legend",
|
||||
}),
|
||||
showTooltip: z.boolean().default(true).meta({
|
||||
description: "Whether to show chart tooltip",
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
const chartConfig = {
|
||||
value: {
|
||||
label: "Value",
|
||||
},
|
||||
name: {
|
||||
label: "Name",
|
||||
},
|
||||
};
|
||||
const CHART_COLORS = [
|
||||
'#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
|
||||
'#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1'
|
||||
];
|
||||
|
||||
export const Schema = type4SlideSchema
|
||||
|
||||
export type Type4SlideData = z.infer<typeof type4SlideSchema>
|
||||
|
||||
interface Type4SlideLayoutProps {
|
||||
data: Partial<Type4SlideData>
|
||||
}
|
||||
|
||||
const Type4SlideLayout: React.FC<Type4SlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
const { title, description, data, dataKey, categoryKey, color, showLegend = false, showTooltip = true, chartType = 'bar' } = slideData;
|
||||
|
||||
const chartData = data || [];
|
||||
const renderChart = () => {
|
||||
const commonProps = {
|
||||
data: chartData,
|
||||
margin: { top: 10, right: 20, left: 0, bottom: 30 },
|
||||
};
|
||||
|
||||
switch (chartType) {
|
||||
case 'bar':
|
||||
return (
|
||||
<BarChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Bar dataKey={dataKey || 'value'} fill={color} radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
);
|
||||
|
||||
case 'line':
|
||||
return (
|
||||
<LineChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey={dataKey || 'value'}
|
||||
stroke={color}
|
||||
strokeWidth={3}
|
||||
dot={{ fill: color, strokeWidth: 2, r: 4 }}
|
||||
/>
|
||||
</LineChart>
|
||||
);
|
||||
|
||||
case 'area':
|
||||
return (
|
||||
<AreaChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey={dataKey || 'value'}
|
||||
stroke={color}
|
||||
fill={color}
|
||||
fillOpacity={0.6}
|
||||
/>
|
||||
</AreaChart>
|
||||
);
|
||||
|
||||
case 'pie':
|
||||
return (
|
||||
<PieChart margin={{ top: 10, right: 10, left: 10, bottom: 10 }}>
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Pie
|
||||
data={chartData}
|
||||
cx="50%"
|
||||
cy="40%"
|
||||
outerRadius={70}
|
||||
fill={color}
|
||||
dataKey={dataKey || 'value'}
|
||||
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
||||
>
|
||||
{chartData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
);
|
||||
|
||||
case 'scatter':
|
||||
return (
|
||||
<ScatterChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="x" type="number" />
|
||||
<YAxis dataKey="y" type="number" />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Scatter dataKey={dataKey || 'value'} fill={color} />
|
||||
</ScatterChart>
|
||||
);
|
||||
|
||||
default:
|
||||
return <div>Unsupported chart type</div>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm w-full max-w-[1280px] px-3 py-[10px] sm:px-12 lg:px-20 sm:py-[40px] lg:py-[86px] shadow-lg max-h-[720px] flex flex-col items-center justify-center aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
{title && <h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight mb-4 lg:mb-8">
|
||||
{title}
|
||||
</h1>}
|
||||
|
||||
<div className={`flex w-full items-center `}>
|
||||
<div className="w-full">
|
||||
<div className="flex-1 bg-white rounded-lg shadow-sm border border-gray-100 p-4">
|
||||
<ChartContainer config={chartConfig} className="h-full w-full">
|
||||
{renderChart()}
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full text-center">
|
||||
{description && <p className={`text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal`}>
|
||||
{description}
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type4SlideLayout
|
||||
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type5-slide'
|
||||
export const layoutName = 'Type5 Slide'
|
||||
export const layoutDescription = 'A two-column layout with title and description on the left, and numbered items with large numerals on the right.'
|
||||
|
||||
const type5SlideSchema = z.object({
|
||||
title: z.string().min(3).max(50).default('Key Points').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(130).default('Here is the main description that provides context and introduction to the numbered points on the right side.').meta({
|
||||
description: "Main description text",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(50).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(130).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(3).default([
|
||||
{
|
||||
heading: 'First Key Point',
|
||||
description: 'Detailed explanation of the first important point that supports the main topic'
|
||||
},
|
||||
{
|
||||
heading: 'Second Key Point',
|
||||
description: 'Detailed explanation of the second important point with relevant information'
|
||||
},
|
||||
{
|
||||
heading: 'Third Key Point',
|
||||
description: 'Detailed explanation of the third important point that concludes the discussion'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of numbered items (2-3 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type5SlideSchema
|
||||
|
||||
export type Type5SlideData = z.infer<typeof type5SlideSchema>
|
||||
|
||||
interface Type5SlideLayoutProps {
|
||||
data: Partial<Type5SlideData>
|
||||
}
|
||||
|
||||
const Type5SlideLayout: React.FC<Type5SlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
const { title, description, items } = slideData;
|
||||
return (
|
||||
<div
|
||||
className="rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="flex flex-col lg:flex-row gap-4 sm:gap-18 md:gap-16 items-center w-full">
|
||||
{/* Left section - Title and Description */}
|
||||
<div className="lg:w-1/2 lg:space-y-8 ">
|
||||
{title && <h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
|
||||
{title}
|
||||
</h1>}
|
||||
|
||||
{description && <p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
|
||||
{description}
|
||||
</p>}
|
||||
</div>
|
||||
|
||||
{/* Right section - Numbered items */}
|
||||
<div className="lg:w-1/2 relative">
|
||||
<div className="space-y-3 lg:space-y-6">
|
||||
{slideData?.items?.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex gap-6">
|
||||
<div className="text-[26px] lg:text-[32px] leading-[40px] px-1 font-bold mb-4 text-blue-600">
|
||||
{`0${index + 1}`}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{item.heading && <h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
|
||||
{item.heading}
|
||||
</h3>}
|
||||
{item.description && <p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
|
||||
{item.description}
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type5SlideLayout
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from '@/presentation-templates/defaultSchemes';
|
||||
|
||||
export const layoutId = 'type6-slide'
|
||||
export const layoutName = 'Type6 Slide'
|
||||
export const layoutDescription = 'A centered title with a flexible grid of icon-based content items, adapting layout based on item count.'
|
||||
|
||||
const type6SlideSchema = z.object({
|
||||
title: z.string().min(3).max(50).default('Our Services').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(50).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(130).meta({
|
||||
description: "Item description",
|
||||
}),
|
||||
icon: IconSchema,
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
heading: 'Professional Service',
|
||||
description: 'High-quality professional services tailored to your specific needs and requirements',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'Professional Service'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Expert Consultation',
|
||||
description: 'Expert advice and consultation from experienced professionals in the field',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'Expert Consultation'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Quality Assurance',
|
||||
description: 'Comprehensive quality assurance processes to ensure excellent results',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'Quality Assurance'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Customer Support',
|
||||
description: 'Dedicated customer support available to assist you throughout the process',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'Customer Support'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "List of service items (2-6 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type6SlideSchema
|
||||
|
||||
export type Type6SlideData = z.infer<typeof type6SlideSchema>
|
||||
|
||||
interface Type6SlideLayoutProps {
|
||||
data: Partial<Type6SlideData>
|
||||
}
|
||||
|
||||
const Type6SlideLayout: React.FC<Type6SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, items } = slideData;
|
||||
const isGridLayout = items && items.length >= 4
|
||||
|
||||
const getGridCols = (length: number) => {
|
||||
switch (length) {
|
||||
case 1: return 'lg:grid-cols-1';
|
||||
case 2: return 'lg:grid-cols-2';
|
||||
case 3: return 'lg:grid-cols-3';
|
||||
case 4: return 'lg:grid-cols-4';
|
||||
case 5: return 'lg:grid-cols-5';
|
||||
case 6: return 'lg:grid-cols-6';
|
||||
default: return 'lg:grid-cols-1';
|
||||
}
|
||||
}
|
||||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<div className={`grid grid-cols-1 ${items && items.length > 4 ? 'md:grid-cols-3' : 'md:grid-cols-2'} gap-4 sm:gap-6 lg:gap-8 mt-4 lg:mt-12 w-full`}>
|
||||
{items && items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex items-start gap-2 mg:gap-4">
|
||||
<div className="flex-shrink-0 lg:w-16">
|
||||
<div className="w-12 h-12 lg:w-16 lg:h-16 bg-blue-600 rounded-lg flex items-center justify-center text-white text-xl lg:text-2xl">
|
||||
<img src={item.icon.__icon_url__} className='w-full h-full object-contain' alt={item.icon.__icon_query__} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold mb-2">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderHorizontalContent = () => {
|
||||
return (
|
||||
<div className={`grid grid-cols-1 sm:grid-cols-2 ${getGridCols(items?.length || 0)} w-full gap-3 lg:gap-8 mt-4 lg:mt-12`}>
|
||||
{items && items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="text-center mb-4">
|
||||
<div className="w-16 h-16 lg:w-20 lg:h-20 bg-blue-600 rounded-lg flex items-center justify-center text-white text-2xl lg:text-3xl mx-auto mb-4">
|
||||
<img src={item.icon.__icon_url__} className='w-full h-full object-contain' alt={item.icon.__icon_query__} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:space-y-4 mt-2 lg:mt-4">
|
||||
{item.heading && <h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold text-center">
|
||||
{item.heading}
|
||||
</h3>}
|
||||
{item.description && <p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal text-center">
|
||||
{item.description}
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="text-center sm:pb-2 lg:pb-8 w-full">
|
||||
{title && <h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
|
||||
{title}
|
||||
</h1>}
|
||||
</div>
|
||||
|
||||
{isGridLayout ? renderGridContent() : renderHorizontalContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type6SlideLayout
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from '@/presentation-templates/defaultSchemes';
|
||||
|
||||
export const layoutId = 'type7-slide'
|
||||
export const layoutName = 'Type7 Slide'
|
||||
export const layoutDescription = 'A centered title with a flexible grid of icon-based content items, adapting layout based on item count.'
|
||||
|
||||
const type7SlideSchema = z.object({
|
||||
title: z.string().min(3).max(50).default('Our Services').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(50).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(130).meta({
|
||||
description: "Item description",
|
||||
}),
|
||||
icon: IconSchema.default({
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'Default icon'
|
||||
}).meta({
|
||||
description: "Icon for the item",
|
||||
})
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
heading: 'Professional Service',
|
||||
description: 'High-quality professional services tailored to your specific needs and requirements',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'Professional service icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Expert Consultation',
|
||||
description: 'Expert advice and consultation from experienced professionals in the field',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'Expert consultation icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Quality Assurance',
|
||||
description: 'Comprehensive quality assurance processes to ensure excellent results',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'Quality assurance icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Customer Support',
|
||||
description: 'Dedicated customer support available to assist you throughout the process',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'Customer support icon'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "List of service items (2-6 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type7SlideSchema
|
||||
|
||||
export type Type7SlideData = z.infer<typeof type7SlideSchema>
|
||||
|
||||
interface Type7SlideLayoutProps {
|
||||
data: Partial<Type7SlideData>
|
||||
}
|
||||
|
||||
const Type7SlideLayout: React.FC<Type7SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, items } = slideData;
|
||||
const isGridLayout = items && items.length >= 4
|
||||
|
||||
const getGridCols = (length: number) => {
|
||||
switch (length) {
|
||||
case 1: return 'lg:grid-cols-1';
|
||||
case 2: return 'lg:grid-cols-2';
|
||||
case 3: return 'lg:grid-cols-3';
|
||||
case 4: return 'lg:grid-cols-4';
|
||||
case 5: return 'lg:grid-cols-5';
|
||||
case 6: return 'lg:grid-cols-6';
|
||||
default: return 'lg:grid-cols-1';
|
||||
}
|
||||
}
|
||||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<div className={`grid grid-cols-1 ${items && items.length > 4 ? 'md:grid-cols-3' : 'md:grid-cols-2'} gap-4 sm:gap-6 lg:gap-8 mt-4 lg:mt-12 w-full`}>
|
||||
{items && items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex items-start gap-2 md:gap-4">
|
||||
<div className="flex-shrink-0 lg:w-16">
|
||||
<div className="w-12 h-12 lg:w-16 lg:h-16 bg-blue-600 rounded-lg flex items-center justify-center overflow-hidden">
|
||||
<img
|
||||
src={item.icon?.__icon_url__ || ''}
|
||||
alt={item.icon?.__icon_query__ || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{item.heading && <h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold mb-2">
|
||||
{item.heading}
|
||||
</h3>}
|
||||
{item.description && <p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
|
||||
{item.description}
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderHorizontalContent = () => {
|
||||
return (
|
||||
<div className={`grid grid-cols-1 sm:grid-cols-2 ${getGridCols(items?.length || 0)} w-full gap-3 lg:gap-8 mt-4 lg:mt-12`}>
|
||||
{items && items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="text-center mb-4">
|
||||
<div className="w-16 h-16 lg:w-20 lg:h-20 bg-blue-600 rounded-lg flex items-center justify-center mx-auto mb-4 overflow-hidden">
|
||||
{item.icon?.__icon_url__ && <img
|
||||
src={item.icon?.__icon_url__ || ''}
|
||||
alt={item.icon?.__icon_query__ || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:space-y-4 mt-2 lg:mt-4">
|
||||
{item.heading && <h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight text-center">
|
||||
{item.heading}
|
||||
</h3>}
|
||||
{item.description && <p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed text-center">
|
||||
{item.description}
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
>
|
||||
<div className="text-center sm:pb-2 lg:pb-8 w-full">
|
||||
{title && <h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
|
||||
{title}
|
||||
</h1>}
|
||||
</div>
|
||||
|
||||
{isGridLayout ? renderGridContent() : renderHorizontalContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type7SlideLayout
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from '@/presentation-templates/defaultSchemes';
|
||||
|
||||
export const layoutId = 'type8-slide'
|
||||
export const layoutName = 'Type8 Slide'
|
||||
export const layoutDescription = 'A two-column layout with title and description on the left, and icon-based items on the right.'
|
||||
|
||||
const type8SlideSchema = z.object({
|
||||
title: z.string().min(3).max(50).default('Key Features').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(130).default('Here is the main description that provides context and introduces the key features outlined on the right side.').meta({
|
||||
description: "Main description text",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(50).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(130).meta({
|
||||
description: "Item description",
|
||||
}),
|
||||
icon: IconSchema.default({
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'Default icon'
|
||||
}).meta({
|
||||
description: "Icon for the item",
|
||||
})
|
||||
})).min(2).max(3).default([
|
||||
{
|
||||
heading: 'Advanced Features',
|
||||
description: 'Cutting-edge functionality designed to enhance productivity and user experience',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'Advanced features icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Reliable Performance',
|
||||
description: 'Consistent and dependable performance across all platforms and devices',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'Reliable performance icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Secure Environment',
|
||||
description: 'Enterprise-grade security measures to protect your data and privacy',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_query__: 'Secure environment icon'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "List of featured items (2-3 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type8SlideSchema
|
||||
|
||||
export type Type8SlideData = z.infer<typeof type8SlideSchema>
|
||||
|
||||
interface Type8SlideLayoutProps {
|
||||
data: Partial<Type8SlideData>
|
||||
}
|
||||
|
||||
const Type8SlideLayout: React.FC<Type8SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, description, items } = slideData;
|
||||
|
||||
const renderItems = () => {
|
||||
if (items && items.length === 2) {
|
||||
// Vertical stacked layout for 2 items
|
||||
return (
|
||||
<div className="space-y-4 lg:space-y-8">
|
||||
{items && items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="text-center mb-4">
|
||||
<div className="w-16 h-16 lg:w-20 lg:h-20 bg-blue-600 rounded-lg flex items-center justify-center mx-auto mb-4 overflow-hidden">
|
||||
{item.icon?.__icon_url__ && <img
|
||||
src={item.icon?.__icon_url__ || ''}
|
||||
alt={item.icon?.__icon_query__ || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1 lg:space-y-3">
|
||||
{item.heading && <h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold text-center">
|
||||
{item.heading}
|
||||
</h3>}
|
||||
{item.description && <p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal text-center">
|
||||
{item.description}
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
// Horizontal layout with side icons for 3+ items
|
||||
return (
|
||||
<div className="space-y-4 lg:space-y-8">
|
||||
{items && items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-[64px] h-[64px]">
|
||||
<div className="w-full h-full bg-blue-600 rounded-lg flex items-center justify-center overflow-hidden">
|
||||
{item.icon?.__icon_url__ && <img
|
||||
src={item.icon?.__icon_url__ || ''}
|
||||
alt={item.icon?.__icon_query__ || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[calc(100%-70px)] lg:space-y-3">
|
||||
{item.heading && <h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
|
||||
{item.heading}
|
||||
</h3>}
|
||||
{item.description && <p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
|
||||
{item.description}
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" shadow-lg w-full max-w-[1280px] rounded-sm font-inter px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-8 lg:gap-16 items-center w-full">
|
||||
{/* Left section - Title and Description */}
|
||||
<div className="space-y-2 lg:space-y-6">
|
||||
{title && <h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
|
||||
{title}
|
||||
</h1>}
|
||||
|
||||
{description && <p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
|
||||
{description}
|
||||
</p>}
|
||||
</div>
|
||||
|
||||
{/* Right section - Items */}
|
||||
<div className="relative">
|
||||
{renderItems()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type8SlideLayout
|
||||
|
|
@ -1,253 +0,0 @@
|
|||
import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart';
|
||||
import React from 'react'
|
||||
import { BarChart, Bar, LineChart, Line, PieChart, Pie, AreaChart, Area, ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Cell, ResponsiveContainer } from "recharts";
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type4-slide'
|
||||
export const layoutName = 'Type4 Slide'
|
||||
export const layoutDescription = 'A chart-focused layout with title, chart visualization, and description text.'
|
||||
|
||||
const chartDataSchema = z.object({
|
||||
name: z.string().meta({ description: "Data point name" }),
|
||||
value: z.number().meta({ description: "Data point value" }),
|
||||
category: z.string().optional().meta({ description: "Category for grouping" }),
|
||||
x: z.number().optional().meta({ description: "X coordinate for scatter plots" }),
|
||||
y: z.number().optional().meta({ description: "Y coordinate for scatter plots" }),
|
||||
});
|
||||
|
||||
|
||||
const type9SlideSchema = z.object({
|
||||
title: z.string().min(3).max(50).default('Chart Analysis').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(50).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(130).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(3).default([
|
||||
{
|
||||
heading: 'First Key Point',
|
||||
description: 'Detailed explanation of the first important point that supports the main topic'
|
||||
},
|
||||
{
|
||||
heading: 'Second Key Point',
|
||||
description: 'Detailed explanation of the second important point with relevant information'
|
||||
},
|
||||
{
|
||||
heading: 'Third Key Point',
|
||||
description: 'Detailed explanation of the third important point that concludes the discussion'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of numbered items (2-3 items)",
|
||||
}),
|
||||
chartData: z.any().optional().meta({
|
||||
description: "Chart data object",
|
||||
}),
|
||||
isFullSizeChart: z.boolean().default(false).meta({
|
||||
description: "Whether to display chart in full size mode",
|
||||
}),
|
||||
chartType: z.enum(['bar', 'line', 'pie', 'area', 'scatter']).default('pie').meta({
|
||||
description: "Type of chart to display",
|
||||
}),
|
||||
data: z.array(chartDataSchema).min(2).max(10).default([
|
||||
{ name: '2021', value: 5 },
|
||||
{ name: '2022', value: 12 },
|
||||
{ name: '2023', value: 18 },
|
||||
{ name: '2024', value: 23 },
|
||||
{ name: '2025', value: 26 },
|
||||
]).meta({
|
||||
description: "Chart data points",
|
||||
}),
|
||||
dataKey: z.string().default('value').meta({
|
||||
description: "Key field for chart values",
|
||||
}),
|
||||
categoryKey: z.string().default('name').meta({
|
||||
description: "Key field for chart categories",
|
||||
}),
|
||||
color: z.string().default('#3b82f6').meta({
|
||||
description: "Primary color for chart elements",
|
||||
}),
|
||||
showLegend: z.boolean().default(false).meta({
|
||||
description: "Whether to show chart legend",
|
||||
}),
|
||||
showTooltip: z.boolean().default(true).meta({
|
||||
description: "Whether to show chart tooltip",
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
const chartConfig = {
|
||||
value: {
|
||||
label: "Value",
|
||||
},
|
||||
name: {
|
||||
label: "Name",
|
||||
},
|
||||
};
|
||||
const CHART_COLORS = [
|
||||
'#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
|
||||
'#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1'
|
||||
];
|
||||
|
||||
export const Schema = type9SlideSchema
|
||||
|
||||
export type Type9SlideData = z.infer<typeof type9SlideSchema>
|
||||
|
||||
interface Type9SlideLayoutProps {
|
||||
data: Partial<Type9SlideData>
|
||||
}
|
||||
|
||||
const Type9SlideLayout: React.FC<Type9SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, items, data, chartType = 'line', color = '#3b82f6', dataKey = 'value', categoryKey = 'name', showLegend = false, showTooltip = true } = slideData;
|
||||
const chartData = data || [];
|
||||
const renderChart = () => {
|
||||
const commonProps = {
|
||||
data: chartData,
|
||||
margin: { top: 10, right: 20, left: 0, bottom: 30 },
|
||||
};
|
||||
|
||||
switch (chartType) {
|
||||
case 'bar':
|
||||
return (
|
||||
<BarChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Bar dataKey={dataKey} fill={color} radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
);
|
||||
|
||||
case 'line':
|
||||
return (
|
||||
<LineChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={color}
|
||||
strokeWidth={3}
|
||||
dot={{ fill: color, strokeWidth: 2, r: 4 }}
|
||||
/>
|
||||
</LineChart>
|
||||
);
|
||||
|
||||
case 'area':
|
||||
return (
|
||||
<AreaChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={color}
|
||||
fill={color}
|
||||
fillOpacity={0.6}
|
||||
/>
|
||||
</AreaChart>
|
||||
);
|
||||
|
||||
case 'pie':
|
||||
return (
|
||||
<ResponsiveContainer >
|
||||
<PieChart margin={{ top: 0, right: 0, left: 0, bottom: 0 }}>
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Pie
|
||||
isAnimationActive={false}
|
||||
data={chartData}
|
||||
cx="50%"
|
||||
cy="40%"
|
||||
outerRadius={80}
|
||||
fill={color}
|
||||
dataKey={dataKey}
|
||||
|
||||
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
||||
>
|
||||
{chartData && chartData.map((entry: any, index: number) => (
|
||||
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
|
||||
case 'scatter':
|
||||
return (
|
||||
<ScatterChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="x" type="number" />
|
||||
<YAxis dataKey="y" type="number" />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Scatter dataKey="value" fill={color} />
|
||||
</ScatterChart>
|
||||
);
|
||||
|
||||
default:
|
||||
return <div>Unsupported chart type</div>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm w-full max-w-[1280px] px-3 py-[10px] sm:px-12 lg:px-20 sm:py-[40px] lg:py-[86px] shadow-lg max-h-[720px] flex flex-col items-center justify-center aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
{title && <h1 className="text-2xl text-start sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight mb-4 lg:mb-8">
|
||||
{title}
|
||||
</h1>}
|
||||
|
||||
<div className={`flex gap-6 w-full items-center `}>
|
||||
<div className="w-1/2">
|
||||
<div className="flex-1 bg-white rounded-lg shadow-sm border border-gray-100 p-4">
|
||||
<ChartContainer config={chartConfig} className="h-full w-full">
|
||||
{renderChart()}
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:w-1/2 relative">
|
||||
<div className="space-y-3 lg:space-y-6">
|
||||
{items && items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex gap-6">
|
||||
<div className="text-[26px] lg:text-[32px] leading-[40px] px-1 font-bold mb-4 text-blue-600">
|
||||
{`0${index + 1}`}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{item.heading && <h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
|
||||
{item.heading}
|
||||
</h3>}
|
||||
{item.description && <p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
|
||||
{item.description}
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type9SlideLayout
|
||||
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
import React, { useEffect, useRef } from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type-mermaid-slide'
|
||||
export const layoutName = 'Mermaid Chart Slide'
|
||||
export const layoutDescription = 'A clean layout for displaying Mermaid diagrams with title and optional description.'
|
||||
|
||||
const typeMermaidSlideSchema = z.object({
|
||||
title: z.string().min(3).max(50).default('Process Flow').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(200).optional().meta({
|
||||
description: "Optional description text to provide context for the diagram",
|
||||
}),
|
||||
mermaidCode: z.string().min(10).default(`graph LR
|
||||
A[Start] --> B{Is it working?}
|
||||
B -->|Yes| C[Great!]
|
||||
B -->|No| D[Fix it]
|
||||
D --> B
|
||||
C --> E[End]`).meta({
|
||||
description: "Mermaid diagram code, and it must be a graph LR",
|
||||
}),
|
||||
theme: z.enum(['default', 'dark', 'forest', 'neutral']).default('default').meta({
|
||||
description: "Mermaid theme to use",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = typeMermaidSlideSchema
|
||||
|
||||
export type TypeMermaidSlideData = z.infer<typeof typeMermaidSlideSchema>
|
||||
|
||||
interface TypeMermaidSlideLayoutProps {
|
||||
data: Partial<TypeMermaidSlideData>
|
||||
}
|
||||
|
||||
const TypeMermaidSlideLayout: React.FC<TypeMermaidSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const { title, description, mermaidCode, theme } = slideData;
|
||||
const mermaidRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loadMermaid = async () => {
|
||||
try {
|
||||
// Dynamically import mermaid
|
||||
const mermaid = (await import('mermaid')).default;
|
||||
|
||||
// Initialize mermaid with the selected theme
|
||||
mermaid.initialize({
|
||||
startOnLoad: true,
|
||||
theme: theme || 'default',
|
||||
themeVariables: {
|
||||
primaryColor: '#3b82f6',
|
||||
primaryTextColor: '#1f2937',
|
||||
primaryBorderColor: '#e5e7eb',
|
||||
lineColor: '#6b7280',
|
||||
secondaryColor: '#f3f4f6',
|
||||
tertiaryColor: '#ffffff'
|
||||
},
|
||||
flowchart: {
|
||||
useMaxWidth: true,
|
||||
htmlLabels: true,
|
||||
curve: 'basis'
|
||||
}
|
||||
});
|
||||
|
||||
if (mermaidRef.current && mermaidCode) {
|
||||
// Clear previous content
|
||||
mermaidRef.current.innerHTML = '';
|
||||
|
||||
// Create a unique ID for this diagram
|
||||
const diagramId = `mermaid-${Date.now()}`;
|
||||
|
||||
// Render the diagram
|
||||
const { svg } = await mermaid.render(diagramId, mermaidCode);
|
||||
mermaidRef.current.innerHTML = svg;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading or rendering mermaid:', error);
|
||||
if (mermaidRef.current) {
|
||||
mermaidRef.current.innerHTML = `
|
||||
<div class="flex items-center justify-center h-full text-red-500">
|
||||
<div class="text-center">
|
||||
<p class="text-lg font-semibold">Error rendering diagram</p>
|
||||
<p class="text-sm mt-2">Please check your Mermaid syntax</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadMermaid();
|
||||
}, [mermaidCode, theme]);
|
||||
|
||||
return (
|
||||
<div className="w-full rounded-sm max-w-[1280px] shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] max-h-[720px] flex flex-col items-center justify-center aspect-video bg-white relative z-20 mx-auto">
|
||||
{/* Title */}
|
||||
{title && (
|
||||
<h1 className="text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold text-gray-900 mb-4 lg:mb-8 text-center">
|
||||
{title}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{/* Description */}
|
||||
{description && (
|
||||
<p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal mb-6 lg:mb-8 text-center max-w-4xl">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Mermaid Diagram Container */}
|
||||
<div className="flex-1 w-full flex items-center justify-center">
|
||||
<div
|
||||
ref={mermaidRef}
|
||||
className="w-full h-full flex items-center justify-center min-h-[300px] max-h-[400px] overflow-hidden"
|
||||
style={{
|
||||
// Ensure the SVG scales properly
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Fallback content if no mermaid code is provided */}
|
||||
{!mermaidCode && (
|
||||
<div className="flex-1 w-full flex items-center justify-center">
|
||||
<div className="text-center text-gray-500">
|
||||
<p className="text-lg font-semibold">No diagram to display</p>
|
||||
<p className="text-sm mt-2">Please provide Mermaid diagram code</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TypeMermaidSlideLayout
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"description": "Default layout for presentations",
|
||||
"ordered": false,
|
||||
"default": false
|
||||
}
|
||||
|
|
@ -35,17 +35,25 @@ const BasicInfoSlideLayout: React.FC<BasicInfoSlideLayoutProps> = ({ data: slide
|
|||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;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: 'Poppins, sans-serif'
|
||||
fontFamily: 'var(--heading-font-family,Inter)',
|
||||
background:"var(--card-background-color,#ffffff)"
|
||||
}}
|
||||
>
|
||||
{(slideData as any)?.__companyName__ && (
|
||||
<div className="absolute top-0 left-0 right-0 px-8 sm:px-12 lg:px-20 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm sm:text-base font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || 'Company Name'}
|
||||
</span>
|
||||
<div className="h-[2px] flex-1 opacity-70" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Main Content */}
|
||||
|
|
@ -64,21 +72,25 @@ const BasicInfoSlideLayout: React.FC<BasicInfoSlideLayoutProps> = ({ data: slide
|
|||
{/* Right Section - Content */}
|
||||
<div className="flex-1 flex flex-col justify-center pl-8 space-y-6">
|
||||
{/* Title */}
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
<h1 style={{ color: "var(--text-heading-color,#111827)" }} className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Product Overview'}
|
||||
</h1>
|
||||
|
||||
{/* Purple accent line */}
|
||||
<div className="w-20 h-1 bg-purple-600"></div>
|
||||
<div style={{background:"var(--text-heading-color,#9333ea)"}} className="w-20 h-1 bg-purple-600"></div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-base sm:text-lg text-gray-700 leading-relaxed">
|
||||
<p style={{color:"var(--text-body-color,#4b5563)"}} className="text-base sm:text-lg text-gray-700 leading-relaxed">
|
||||
{slideData?.description || 'Our product offers customizable dashboards for real-time reporting and data-driven decisions. It integrates with third-party tools to enhance operations and scales with business growth for improved efficiency.'}
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema, IconSchema } from '@/presentation-templates/defaultSchemes';
|
||||
import { RemoteSvgIcon } from '@/app/hooks/useRemoteSvgIcon';
|
||||
|
||||
export const layoutId = 'bullet-icons-only-slide'
|
||||
export const layoutName = 'Bullet Icons Only'
|
||||
export const layoutDescription = 'A slide layout with title, grid of bullet points with icons (no descriptions), and a supporting image.'
|
||||
export const layoutDescription = 'A slide layout with title, grid of bullet points (title and description) with icons, and a supporting image.'
|
||||
|
||||
const bulletIconsOnlySlideSchema = z.object({
|
||||
title: z.string().min(3).max(40).default('Solutions').meta({
|
||||
|
|
@ -29,7 +30,7 @@ const bulletIconsOnlySlideSchema = z.object({
|
|||
title: 'Custom Software',
|
||||
subtitle: 'We create tailored software to optimize processes and boost efficiency.',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_url__: 'https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/code-bold.svg',
|
||||
__icon_query__: 'code software development'
|
||||
}
|
||||
},
|
||||
|
|
@ -37,7 +38,7 @@ const bulletIconsOnlySlideSchema = z.object({
|
|||
title: 'Digital Consulting',
|
||||
subtitle: 'Our consultants guide organizations in leveraging the latest technologies.',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_url__: 'https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/users-four-bold.svg',
|
||||
__icon_query__: 'users consulting team'
|
||||
}
|
||||
},
|
||||
|
|
@ -45,7 +46,7 @@ const bulletIconsOnlySlideSchema = z.object({
|
|||
title: 'Support Services',
|
||||
subtitle: 'We provide ongoing support to help businesses adapt and maintain performance.',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_url__: 'https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/headphones-bold.svg',
|
||||
__icon_query__: 'headphones support service'
|
||||
}
|
||||
},
|
||||
|
|
@ -53,7 +54,7 @@ const bulletIconsOnlySlideSchema = z.object({
|
|||
title: 'Scalable Marketing',
|
||||
subtitle: 'Our data-driven strategies help businesses expand their reach and engagement.',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_url__: 'https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/code-bold.svg',
|
||||
__icon_query__: 'trending up marketing growth'
|
||||
}
|
||||
}
|
||||
|
|
@ -87,17 +88,26 @@ const BulletIconsOnlySlideLayout: React.FC<BulletIconsOnlySlideLayoutProps> = ({
|
|||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;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: 'Poppins, sans-serif'
|
||||
fontFamily: 'var(--heading-font-family,Inter)',
|
||||
background:"var(--card-background-color,#ffffff)"
|
||||
}}
|
||||
>
|
||||
{/* {(slideData as any)?.__companyName__ && ( */}
|
||||
<div className="absolute top-0 left-0 right-0 px-8 sm:px-12 lg:px-20 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm sm:text-base font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || 'Company Name'}
|
||||
</span>
|
||||
<div className="h-[2px] flex-1 opacity-70" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
{/* )} */}
|
||||
{/* Decorative Wave Patterns */}
|
||||
<div className="absolute top-0 left-0 w-32 h-full opacity-10 overflow-hidden">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 400" fill="none">
|
||||
|
|
@ -113,11 +123,11 @@ const BulletIconsOnlySlideLayout: React.FC<BulletIconsOnlySlideLayoutProps> = ({
|
|||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="relative z-10 flex h-full px-8 sm:px-12 lg:px-20 pt-8 pb-8">
|
||||
<div className="relative z-10 flex h-full px-8 sm:px-12 lg:px-20 pt-12 pb-8">
|
||||
{/* Left Section - Title and Bullet Points */}
|
||||
<div className="flex-1 flex flex-col pr-8">
|
||||
{/* Title */}
|
||||
<h1 className="text-5xl sm:text-6xl lg:text-7xl font-bold text-gray-900 mb-8">
|
||||
<h1 style={{ color: "var(--text-heading-color,#111827)" }} className="text-5xl sm:text-6xl lg:text-7xl font-bold text-gray-900 mb-8">
|
||||
{slideData?.title || 'Solutions'}
|
||||
</h1>
|
||||
|
||||
|
|
@ -126,24 +136,26 @@ const BulletIconsOnlySlideLayout: React.FC<BulletIconsOnlySlideLayoutProps> = ({
|
|||
{bulletPoints.map((bullet, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`flex items-start space-x-4 p-4 rounded-lg transition-all duration-200 hover:bg-gray-50`}
|
||||
className={`flex items-start space-x-4 p-4 rounded-lg`}
|
||||
>
|
||||
{/* Icon */}
|
||||
<div className="flex-shrink-0 w-12 h-12 bg-purple-600 rounded-full flex items-center justify-center">
|
||||
<img
|
||||
src={bullet.icon.__icon_url__}
|
||||
alt={bullet.icon.__icon_query__}
|
||||
className="w-6 h-6 object-contain brightness-0 invert"
|
||||
<div style={{background:"var(--primary-accent-color,#9333ea)"}} className="flex-shrink-0 w-12 h-12 rounded-full flex items-center justify-center">
|
||||
<RemoteSvgIcon
|
||||
url={bullet.icon.__icon_url__}
|
||||
strokeColor={"currentColor"}
|
||||
className="w-6 h-6"
|
||||
color="var(--text-heading-color,#ffffff)"
|
||||
title={bullet.icon.__icon_query__}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg sm:text-xl font-semibold text-gray-900 mb-1">
|
||||
<h3 style={{color:"var(--text-heading-color,#111827)"}} className="text-lg sm:text-xl font-semibold text-gray-900 mb-1">
|
||||
{bullet.title}
|
||||
</h3>
|
||||
{bullet.subtitle && (
|
||||
<p className="text-sm text-gray-700 leading-relaxed">
|
||||
<p style={{color:"var(--text-body-color,#4b5563)"}} className="text-sm text-gray-700 leading-relaxed">
|
||||
{bullet.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
|
@ -156,14 +168,14 @@ const BulletIconsOnlySlideLayout: React.FC<BulletIconsOnlySlideLayoutProps> = ({
|
|||
{/* Right Section - Image */}
|
||||
<div className="flex-shrink-0 w-96 flex items-center justify-center relative">
|
||||
{/* Decorative Elements */}
|
||||
<div className="absolute top-8 right-8 text-purple-600 opacity-60">
|
||||
<div style={{color:"var(--primary-accent-color,#9333ea)"}} className="absolute top-8 right-8 text-purple-600 opacity-60">
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="currentColor">
|
||||
<path d="M16 0l4.12 8.38L28 12l-7.88 3.62L16 24l-4.12-8.38L4 12l7.88-3.62L16 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="absolute top-16 left-8 opacity-20">
|
||||
<svg width="80" height="20" viewBox="0 0 80 20" className="text-purple-600">
|
||||
<svg width="80" height="20" viewBox="0 0 80 20" className="text-purple-600" style={{color:"var(--primary-accent-color,#9333ea)"}}>
|
||||
<path
|
||||
d="M0 10 Q20 0 40 10 T80 10"
|
||||
stroke="currentColor"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema, IconSchema } from '@/presentation-templates/defaultSchemes';
|
||||
import { RemoteSvgIcon } from '@/app/hooks/useRemoteSvgIcon';
|
||||
|
||||
export const layoutId = 'bullet-with-icons-slide'
|
||||
export const layoutName = 'Bullet with Icons'
|
||||
|
|
@ -20,10 +21,10 @@ const bulletWithIconsSlideSchema = z.object({
|
|||
description: "Supporting image for the slide",
|
||||
}),
|
||||
bulletPoints: z.array(z.object({
|
||||
title: z.string().min(2).max(80).meta({
|
||||
title: z.string().min(2).max(60).meta({
|
||||
description: "Bullet point title",
|
||||
}),
|
||||
description: z.string().min(10).max(150).meta({
|
||||
description: z.string().min(10).max(100).meta({
|
||||
description: "Bullet point description",
|
||||
}),
|
||||
icon: IconSchema,
|
||||
|
|
@ -32,7 +33,7 @@ const bulletWithIconsSlideSchema = z.object({
|
|||
title: 'Inefficiency',
|
||||
description: 'Businesses struggle to find digital tools that meet their needs, causing operational slowdowns.',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_url__: 'https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/checks-bold.svg',
|
||||
__icon_query__: 'warning alert inefficiency'
|
||||
}
|
||||
},
|
||||
|
|
@ -40,7 +41,7 @@ const bulletWithIconsSlideSchema = z.object({
|
|||
title: 'High Costs',
|
||||
description: 'Outdated systems increase expenses, while small businesses struggle to expand their market reach.',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_url__: 'https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/fediverse-logo-bold.svg',
|
||||
__icon_query__: 'trending up costs chart'
|
||||
}
|
||||
}
|
||||
|
|
@ -62,25 +63,32 @@ const BulletWithIconsSlideLayout: React.FC<BulletWithIconsSlideLayoutProps> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;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-gray-50 to-white relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: 'Poppins, sans-serif'
|
||||
fontFamily: 'var(--heading-font-family,Inter)',
|
||||
background:"var(--card-background-color,#ffffff)"
|
||||
}}
|
||||
>
|
||||
{(slideData as any)?.__companyName__ && (
|
||||
<div className="absolute top-0 left-0 right-0 px-8 sm:px-12 lg:px-20 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm sm:text-base font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || 'Company Name'}
|
||||
</span>
|
||||
<div className="h-[2px] flex-1 opacity-70" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex flex-col h-full px-8 sm:px-12 lg:px-20 pt-8 pb-8">
|
||||
<div className="flex flex-col h-full px-8 sm:px-12 lg:px-20 pt-12 pb-8">
|
||||
{/* Title Section - Full Width */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900">
|
||||
<h1 style={{ color: "var(--text-heading-color,#111827)" }} className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900">
|
||||
{slideData?.title || 'Problem'}
|
||||
</h1>
|
||||
</div>
|
||||
|
|
@ -94,7 +102,7 @@ const BulletWithIconsSlideLayout: React.FC<BulletWithIconsSlideLayoutProps> = ({
|
|||
<svg className="w-full h-full opacity-30" viewBox="0 0 200 200">
|
||||
<defs>
|
||||
<pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
|
||||
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="#8b5cf6" strokeWidth="0.5"/>
|
||||
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="var(--primary-accent-color,#9333ea)" strokeWidth="0.5"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||
|
|
@ -113,7 +121,7 @@ const BulletWithIconsSlideLayout: React.FC<BulletWithIconsSlideLayoutProps> = ({
|
|||
</div>
|
||||
|
||||
{/* Decorative Sparkle */}
|
||||
<div className="absolute top-20 right-8 text-purple-600">
|
||||
<div style={{color:"var(--primary-accent-color,#9333ea)"}} className="absolute top-20 right-8 text-purple-600">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 0l3.09 6.26L22 9l-6.91 2.74L12 18l-3.09-6.26L2 9l6.91-2.74L12 0z"/>
|
||||
</svg>
|
||||
|
|
@ -123,7 +131,7 @@ const BulletWithIconsSlideLayout: React.FC<BulletWithIconsSlideLayoutProps> = ({
|
|||
{/* Right Section - Content */}
|
||||
<div className="flex-1 flex flex-col justify-center pl-8 lg:pl-16">
|
||||
{/* Description */}
|
||||
<p className="text-lg text-gray-700 leading-relaxed mb-8">
|
||||
<p style={{color:"var(--text-body-color,#4b5563)"}} className="text-lg text-gray-700 leading-relaxed mb-8">
|
||||
{slideData?.description || 'Businesses face challenges with outdated technology and rising costs, limiting efficiency and growth in competitive markets.'}
|
||||
</p>
|
||||
|
||||
|
|
@ -132,21 +140,23 @@ const BulletWithIconsSlideLayout: React.FC<BulletWithIconsSlideLayoutProps> = ({
|
|||
{bulletPoints.map((bullet, index) => (
|
||||
<div key={index} className="flex items-start space-x-4">
|
||||
{/* Icon */}
|
||||
<div className="flex-shrink-0 w-12 h-12 bg-white rounded-lg shadow-md flex items-center justify-center">
|
||||
<img
|
||||
src={bullet.icon.__icon_url__}
|
||||
alt={bullet.icon.__icon_query__}
|
||||
className="w-6 h-6 object-contain text-gray-700"
|
||||
<div style={{background:"var(--primary-accent-color,#9333ea)"}} className="flex-shrink-0 w-12 h-12 rounded-lg shadow-md flex items-center justify-center">
|
||||
<RemoteSvgIcon
|
||||
url={bullet.icon.__icon_url__}
|
||||
strokeColor={"currentColor"}
|
||||
className="w-6 h-6"
|
||||
color="var(--text-heading-color,#ffffff)"
|
||||
title={bullet.icon.__icon_query__}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">
|
||||
<h3 style={{color:"var(--text-heading-color,#111827)"}} className="text-xl font-semibold text-gray-900 mb-2">
|
||||
{bullet.title}
|
||||
</h3>
|
||||
<div className="w-12 h-0.5 bg-purple-600 mb-3"></div>
|
||||
<p className="text-base text-gray-700 leading-relaxed">
|
||||
<div style={{background:"var(--primary-accent-color,#9333ea)"}} className="w-12 h-0.5 bg-purple-600 mb-3"></div>
|
||||
<p style={{color:"var(--text-body-color,#4b5563)"}} className="text-base text-gray-700 leading-relaxed">
|
||||
{bullet.description}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from '@/presentation-templates/defaultSchemes';
|
||||
import { RemoteSvgIcon } from '@/app/hooks/useRemoteSvgIcon';
|
||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent } from "@/components/ui/chart";
|
||||
import { BarChart, Bar, LineChart, Line, PieChart, Pie, AreaChart, Area, ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Cell, ResponsiveContainer } from "recharts";
|
||||
|
||||
|
|
@ -64,7 +65,7 @@ const chartWithBulletsSlideSchema = z.object({
|
|||
title: 'Total Addressable Market',
|
||||
description: 'Companies can use TAM to plan future expansion and investment.',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_url__: 'https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/chart-line-up-bold.svg',
|
||||
__icon_query__: 'target market scope'
|
||||
}
|
||||
},
|
||||
|
|
@ -72,7 +73,7 @@ const chartWithBulletsSlideSchema = z.object({
|
|||
title: 'Serviceable Available Market',
|
||||
description: 'Indicates more measurable market segments for sales efforts.',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_url__: 'https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/chart-line-up-bold.svg',
|
||||
__icon_query__: 'pie chart analysis'
|
||||
}
|
||||
},
|
||||
|
|
@ -80,7 +81,7 @@ const chartWithBulletsSlideSchema = z.object({
|
|||
title: 'Serviceable Obtainable Market',
|
||||
description: 'Help companies plan development strategies according to the market.',
|
||||
icon: {
|
||||
__icon_url__: '/static/icons/placeholder.png',
|
||||
__icon_url__: 'https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/chart-line-up-bold.svg',
|
||||
__icon_query__: 'trending up growth'
|
||||
}
|
||||
}
|
||||
|
|
@ -120,7 +121,7 @@ const BULLET_COLORS = [
|
|||
const ChartWithBulletsSlideLayout: React.FC<ChartWithBulletsSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const chartData = slideData?.chartData?.data || [];
|
||||
const chartType = slideData?.chartData?.type;
|
||||
const color = slideData?.color || '#3b82f6';
|
||||
const color = slideData?.color || 'var(--primary-accent-color,#9333ea)';
|
||||
const xAxis = chartType === 'scatter' ? 'x' : 'name';
|
||||
const yAxis = chartType === 'scatter' ? 'y' : 'value';
|
||||
const showLegend = slideData?.showLegend || false;
|
||||
|
|
@ -128,6 +129,14 @@ const ChartWithBulletsSlideLayout: React.FC<ChartWithBulletsSlideLayoutProps> =
|
|||
const bulletPoints = slideData?.bulletPoints || []
|
||||
|
||||
const renderChart = () => {
|
||||
const renderPieLabel = (props: any) => {
|
||||
const { name, percent, x, y, textAnchor } = props;
|
||||
return (
|
||||
<text x={x} y={y} textAnchor={textAnchor} fill="var(--text-body-color,#4b5563)" fontSize={12}>
|
||||
{`${name} ${(percent * 100).toFixed(0)}%`}
|
||||
</text>
|
||||
);
|
||||
};
|
||||
const commonProps = {
|
||||
data: chartData,
|
||||
margin: { top: 20, right: 30, left: 40, bottom: 60 },
|
||||
|
|
@ -137,9 +146,9 @@ const ChartWithBulletsSlideLayout: React.FC<ChartWithBulletsSlideLayoutProps> =
|
|||
case 'bar':
|
||||
return (
|
||||
<BarChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={xAxis} />
|
||||
<YAxis />
|
||||
<CartesianGrid strokeDasharray="3 3" stroke={color} />
|
||||
<XAxis dataKey={xAxis} tick={{ fill: 'var(--text-body-color,#4b5563)', fontWeight: 600 }} />
|
||||
<YAxis tick={{ fill: 'var(--text-body-color,#4b5563)', fontWeight: 600 }} />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Bar dataKey={yAxis} fill={color} radius={[4, 4, 0, 0]} />
|
||||
|
|
@ -149,9 +158,9 @@ const ChartWithBulletsSlideLayout: React.FC<ChartWithBulletsSlideLayoutProps> =
|
|||
case 'line':
|
||||
return (
|
||||
<LineChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={xAxis} />
|
||||
<YAxis />
|
||||
<CartesianGrid strokeDasharray="3 3" stroke={color} />
|
||||
<XAxis dataKey={xAxis} tick={{ fill: 'var(--text-body-color,#4b5563)', fontWeight: 600 }} />
|
||||
<YAxis tick={{ fill: 'var(--text-body-color,#4b5563)', fontWeight: 600 }} />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Line
|
||||
|
|
@ -167,9 +176,9 @@ const ChartWithBulletsSlideLayout: React.FC<ChartWithBulletsSlideLayoutProps> =
|
|||
case 'area':
|
||||
return (
|
||||
<AreaChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={xAxis} />
|
||||
<YAxis />
|
||||
<CartesianGrid strokeDasharray="3 3" stroke={color} />
|
||||
<XAxis dataKey={xAxis} tick={{ fill: 'var(--text-body-color,#4b5563)', fontWeight: 600 }} />
|
||||
<YAxis tick={{ fill: 'var(--text-body-color,#4b5563)', fontWeight: 600 }} />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Area
|
||||
|
|
@ -194,10 +203,10 @@ const ChartWithBulletsSlideLayout: React.FC<ChartWithBulletsSlideLayoutProps> =
|
|||
outerRadius={70}
|
||||
fill={color}
|
||||
dataKey={yAxis}
|
||||
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
||||
label={renderPieLabel}
|
||||
>
|
||||
{chartData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
|
||||
{chartData.map((_, index) => (
|
||||
<Cell key={`cell-${index}`} fill={color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
|
|
@ -206,9 +215,9 @@ const ChartWithBulletsSlideLayout: React.FC<ChartWithBulletsSlideLayoutProps> =
|
|||
case 'scatter':
|
||||
return (
|
||||
<ScatterChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={xAxis} type="number" />
|
||||
<YAxis dataKey={yAxis} type="number" />
|
||||
<CartesianGrid strokeDasharray="3 3" stroke={color} />
|
||||
<XAxis dataKey={xAxis} type="number" tick={{ fill: 'var(--text-body-color,#4b5563)', fontWeight: 600 }} />
|
||||
<YAxis dataKey={yAxis} type="number" tick={{ fill: 'var(--text-body-color,#4b5563)', fontWeight: 600 }} />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Scatter dataKey="value" fill={color} />
|
||||
|
|
@ -222,34 +231,42 @@ const ChartWithBulletsSlideLayout: React.FC<ChartWithBulletsSlideLayoutProps> =
|
|||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;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: 'Poppins, sans-serif'
|
||||
fontFamily: 'var(--heading-font-family,Inter)',
|
||||
background:"var(--card-background-color,#ffffff)"
|
||||
}}
|
||||
>
|
||||
{(slideData as any)?.__companyName__ && (
|
||||
<div className="absolute top-0 left-0 right-0 px-8 sm:px-12 lg:px-20 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm sm:text-base font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || 'Company Name'}
|
||||
</span>
|
||||
<div className="h-[2px] flex-1 opacity-70" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Main Content */}
|
||||
<div className="flex h-full px-8 sm:px-12 lg:px-20 pt-8 pb-8">
|
||||
{/* Left Section - Title, Description, Chart */}
|
||||
<div className="flex-1 flex flex-col pr-8">
|
||||
{/* Title */}
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 mb-4">
|
||||
<h1 style={{ color: "var(--text-heading-color,#111827)" }} className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 mb-4">
|
||||
{slideData?.title || 'Market Size'}
|
||||
</h1>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-base text-gray-700 leading-relaxed mb-8">
|
||||
<p style={{color:"var(--text-body-color,#4b5563)"}} className="text-base text-gray-700 leading-relaxed mb-8">
|
||||
{slideData?.description || 'Businesses face challenges with outdated technology and rising costs, limiting efficiency and growth in competitive markets.'}
|
||||
</p>
|
||||
|
||||
{/* Chart Container */}
|
||||
<div className="flex-1 bg-white rounded-lg shadow-sm border border-gray-100 p-4">
|
||||
<div className="flex-1 rounded-lg shadow-sm border border-gray-100 p-4" style={{ background: 'var(--primary-accent-color,#F5F8FE)' }}>
|
||||
<ChartContainer config={chartConfig} className="h-full w-full">
|
||||
{renderChart()}
|
||||
</ChartContainer>
|
||||
|
|
@ -263,25 +280,27 @@ const ChartWithBulletsSlideLayout: React.FC<ChartWithBulletsSlideLayoutProps> =
|
|||
key={index}
|
||||
className="rounded-2xl p-6 text-white"
|
||||
style={{
|
||||
backgroundColor: BULLET_COLORS[index % BULLET_COLORS.length]
|
||||
backgroundColor: 'var(--primary-accent-color,#9333ea)'
|
||||
}}
|
||||
>
|
||||
{/* Icon and Title */}
|
||||
<div className="flex items-center space-x-3 mb-3">
|
||||
<div className="w-8 h-8 bg-white/20 rounded-lg flex items-center justify-center">
|
||||
<img
|
||||
src={bullet.icon.__icon_url__}
|
||||
alt={bullet.icon.__icon_query__}
|
||||
className="w-5 h-5 object-contain brightness-0 invert"
|
||||
<div style={{background:"var(--primary-accent-color,#9333ea)"}} className="w-8 h-8 rounded-lg flex items-center justify-center">
|
||||
<RemoteSvgIcon
|
||||
url={bullet.icon.__icon_url__}
|
||||
strokeColor={"currentColor"}
|
||||
className="w-5 h-5"
|
||||
color="var(--text-heading-color,#ffffff)"
|
||||
title={bullet.icon.__icon_query__}
|
||||
/>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold">
|
||||
<h3 style={{color:"var(--text-heading-color,#ffffff)"}} className="text-lg font-semibold">
|
||||
{bullet.title}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-sm leading-relaxed opacity-90">
|
||||
<p style={{color:"var(--text-body-color,#ffffff)"}} className="text-sm leading-relaxed opacity-90">
|
||||
{bullet.description}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ const introSlideSchema = z.object({
|
|||
presenterName: z.string().min(2).max(50).default('John Doe').meta({
|
||||
description: "Name of the presenter",
|
||||
}),
|
||||
presentationDate: z.string().min(2).max(50).default('December 2024').meta({
|
||||
description: "Date of the presentation",
|
||||
presentationDate: z.string().min(2).max(50).default('December 2025').meta({
|
||||
description: "Date of the presentation must be the latest date like today's date",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
__image_url__: 'https://images.unsplash.com/photo-1552664730-d307ca884978?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80',
|
||||
|
|
@ -44,22 +44,29 @@ const IntroSlideLayout: React.FC<IntroSlideLayoutProps> = ({ data: slideData })
|
|||
const presenterInitials = getInitials(slideData?.presenterName || 'John Doe');
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;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: 'Poppins, sans-serif'
|
||||
background:"var(--card-background-color,#ffffff)"
|
||||
,fontFamily:"var(--heading-font-family,Inter)"
|
||||
}}
|
||||
>
|
||||
{(slideData as any)?.__companyName__ && (
|
||||
<div className="absolute top-0 left-0 right-0 px-8 sm:px-12 lg:px-20 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm sm:text-base font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || 'Company Name'}
|
||||
</span>
|
||||
<div className="h-[2px] flex-1 opacity-70" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="relative z-10 flex h-full px-8 sm:px-12 lg:px-20 pb-8">
|
||||
<div className="relative z-10 flex h-full px-8 sm:px-12 lg:px-20 pt-12 pb-8">
|
||||
{/* Left Section - Image */}
|
||||
<div className="flex-1 flex items-center justify-center pr-8">
|
||||
<div className="w-full max-w-lg h-80 rounded-2xl overflow-hidden shadow-lg">
|
||||
|
|
@ -74,34 +81,34 @@ const IntroSlideLayout: React.FC<IntroSlideLayoutProps> = ({ data: slideData })
|
|||
{/* Right Section - Content */}
|
||||
<div className="flex-1 flex flex-col justify-center pl-8 space-y-6">
|
||||
{/* Title */}
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
<h1 style={{ color: "var(--text-heading-color,#111827)" }} className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Product Overview'}
|
||||
</h1>
|
||||
|
||||
{/* Purple accent line */}
|
||||
<div className="w-20 h-1 bg-purple-600"></div>
|
||||
<div style={{background:"var(--text-heading-color,#9333ea)"}} className="w-20 h-1 bg-purple-600"></div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-base sm:text-lg text-gray-700 leading-relaxed">
|
||||
<p style={{color:"var(--text-body-color,#4b5563)"}} className="text-base sm:text-lg text-gray-700 leading-relaxed">
|
||||
{slideData?.description || 'Our product offers customizable dashboards for real-time reporting and data-driven decisions. It integrates with third-party tools to enhance operations and scales with business growth for improved efficiency.'}
|
||||
</p>
|
||||
|
||||
{/* Presenter Section */}
|
||||
<div className="bg-white/50 backdrop-blur-sm rounded-lg p-4 lg:p-6 border border-gray-200 shadow-sm">
|
||||
<div style={{background:"var(--card-background-color,rgb(255 255 255 / 0.5))"}} className="bg-white/50 backdrop-blur-sm rounded-lg p-4 lg:p-6 border border-gray-200 shadow-sm">
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Custom Initials Icon */}
|
||||
<div className="w-10 h-10 lg:w-12 lg:h-12 bg-purple-600 rounded-full flex items-center justify-center">
|
||||
<span className="text-white font-bold text-sm lg:text-base">
|
||||
<div style={{background:"var(--primary-accent-color,#9333ea)"}} className="w-10 h-10 lg:w-12 lg:h-12 bg-purple-600 rounded-full flex items-center justify-center">
|
||||
<span className="font-bold text-sm lg:text-base" style={{color:"var(--text-heading-color,#FFFFFF)"}}>
|
||||
{presenterInitials}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Presenter Info */}
|
||||
<div className="flex flex-col">
|
||||
<span className="text-lg lg:text-xl font-bold text-gray-900">
|
||||
<span style={{color:"var(--text-heading-color,#111827)"}} className="text-lg lg:text-xl font-bold text-gray-900">
|
||||
{slideData?.presenterName || 'John Doe'}
|
||||
</span>
|
||||
<span className="text-sm lg:text-base text-gray-600 font-medium">
|
||||
<span style={{color:"var(--text-body-color,#4b5563)"}} className="text-sm lg:text-base text-gray-600 font-medium">
|
||||
{slideData?.presentationDate || 'December 2024'}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const metricsSlideSchema = z.object({
|
|||
description: "Main title of the slide",
|
||||
}),
|
||||
metrics: z.array(z.object({
|
||||
label: z.string().min(2).max(100).meta({
|
||||
label: z.string().min(2).max(50).meta({
|
||||
description: "Metric label/title"
|
||||
}),
|
||||
value: z.string().min(1).max(10).meta({
|
||||
|
|
@ -77,17 +77,25 @@ const MetricsSlideLayout: React.FC<MetricsSlideLayoutProps> = ({ data: slideData
|
|||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;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 flex flex-col"
|
||||
style={{
|
||||
fontFamily: 'Poppins, sans-serif'
|
||||
fontFamily: 'var(--heading-font-family,Inter)',
|
||||
background:"var(--card-background-color,#ffffff)"
|
||||
}}
|
||||
>
|
||||
{(slideData as any)?.__companyName__ && (
|
||||
<div className="absolute top-0 left-0 right-0 px-8 sm:px-12 lg:px-20 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm sm:text-base font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || 'Company Name'}
|
||||
</span>
|
||||
<div className="h-[2px] flex-1 opacity-70" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Decorative Wave Patterns */}
|
||||
<div className="absolute top-0 left-0 w-64 h-full opacity-10 overflow-hidden">
|
||||
<svg className="w-full h-full" viewBox="0 0 200 400" fill="none">
|
||||
|
|
@ -108,11 +116,11 @@ const MetricsSlideLayout: React.FC<MetricsSlideLayoutProps> = ({ data: slideData
|
|||
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="relative z-10 px-8 sm:px-12 lg:px-20 pb-12 flex-1 flex flex-col justify-center">
|
||||
<div className="relative z-10 px-8 sm:px-12 lg:px-20 pt-10 pb-12 flex-1 flex flex-col justify-center">
|
||||
<div className="space-y-12">
|
||||
{/* Title */}
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900">
|
||||
<h1 style={{ color: "var(--text-heading-color,#111827)" }} className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900">
|
||||
{slideData?.title || 'Company Traction'}
|
||||
</h1>
|
||||
</div>
|
||||
|
|
@ -124,23 +132,22 @@ const MetricsSlideLayout: React.FC<MetricsSlideLayoutProps> = ({ data: slideData
|
|||
{metrics.map((metric, index) => (
|
||||
<div key={index} className={`text-center space-y-4 ${getItemClasses(metrics.length)}`}>
|
||||
{/* Label */}
|
||||
<div className="text-sm text-gray-600 font-medium">
|
||||
<div className="text-sm text-gray-600 font-medium" style={{color:"var(--text-body-color,#ffffff)"}}>
|
||||
{metric.label}
|
||||
</div>
|
||||
|
||||
{/* Large Metric Value */}
|
||||
<div className="text-4xl sm:text-5xl lg:text-6xl font-bold text-purple-600">
|
||||
<div style={{color:"var(--text-heading-color,#9333ea)"}} className="text-4xl sm:text-5xl lg:text-6xl font-bold text-purple-600">
|
||||
{metric.value}
|
||||
</div>
|
||||
|
||||
{/* Description Box */}
|
||||
<div
|
||||
className="bg-purple-50 rounded-lg p-4 lg:p-5 text-center mt-4"
|
||||
style={{
|
||||
backgroundColor: 'rgba(139, 92, 246, 0.08)'
|
||||
}}
|
||||
style={{background:"var(--primary-accent-color,#9333ea)"}}
|
||||
|
||||
>
|
||||
<p className="text-xs sm:text-sm text-gray-700 leading-relaxed">
|
||||
<p style={{color:"var(--text-body-color,#ffffff)"}} className="text-xs sm:text-sm text-gray-700 leading-relaxed">
|
||||
{metric.description}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -54,18 +54,25 @@ const MetricsWithImageSlideLayout: React.FC<MetricsWithImageSlideLayoutProps> =
|
|||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;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: 'Poppins, sans-serif'
|
||||
fontFamily: 'var(--heading-font-family,Inter)',
|
||||
background:"var(--card-background-color,#ffffff)"
|
||||
}}
|
||||
>
|
||||
{(slideData as any)?.__companyName__ && (
|
||||
<div className="absolute top-0 left-0 right-0 px-8 sm:px-12 lg:px-20 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm sm:text-base font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || 'Company Name'}
|
||||
</span>
|
||||
<div className="h-[2px] flex-1 opacity-70" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Decorative Wave Patterns */}
|
||||
<div className="absolute bottom-0 left-0 w-48 h-48 opacity-10 overflow-hidden">
|
||||
<svg className="w-full h-full" viewBox="0 0 200 200" fill="none">
|
||||
|
|
@ -81,7 +88,7 @@ const MetricsWithImageSlideLayout: React.FC<MetricsWithImageSlideLayoutProps> =
|
|||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="relative z-10 flex h-full px-8 sm:px-12 lg:px-20 pb-8">
|
||||
<div className="relative z-10 flex h-full px-8 sm:px-12 lg:px-20 pt-12 pb-8">
|
||||
{/* Left Section - Image */}
|
||||
<div className="flex-1 flex items-center justify-center pr-8">
|
||||
<div className="w-full max-w-lg h-96 rounded-2xl overflow-hidden shadow-lg">
|
||||
|
|
@ -96,12 +103,12 @@ const MetricsWithImageSlideLayout: React.FC<MetricsWithImageSlideLayoutProps> =
|
|||
{/* Right Section - Content and Metrics */}
|
||||
<div className="flex-1 flex flex-col justify-center pl-8 space-y-6">
|
||||
{/* Title */}
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
<h1 style={{ color: "var(--text-heading-color,#111827)" }} className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Competitive Advantage'}
|
||||
</h1>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-base sm:text-lg text-gray-700 leading-relaxed">
|
||||
<p style={{color:"var(--text-body-color,#4b5563)"}} className="text-base sm:text-lg text-gray-700 leading-relaxed">
|
||||
{slideData?.description || 'Ginyard International Co. stands out by offering custom digital solutions tailored to client needs, alongside long-term support to ensure lasting relationships and continuous adaptation.'}
|
||||
</p>
|
||||
|
||||
|
|
@ -109,10 +116,10 @@ const MetricsWithImageSlideLayout: React.FC<MetricsWithImageSlideLayoutProps> =
|
|||
<div className="grid grid-cols-2 gap-6">
|
||||
{metrics.map((metric, index) => (
|
||||
<div key={index} className="text-center space-y-2">
|
||||
<div className="text-sm text-gray-600 font-medium">
|
||||
<div style={{color:"var(--text-body-color,#4b5563)"}} className="text-sm text-gray-600 font-medium">
|
||||
{metric.label}
|
||||
</div>
|
||||
<div className="text-3xl sm:text-4xl lg:text-5xl font-bold text-purple-600">
|
||||
<div style={{color:"var(--text-heading-color,#9333ea)"}} className="text-3xl sm:text-4xl lg:text-5xl font-bold text-purple-600">
|
||||
{metric.value}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -58,30 +58,37 @@ const NumberedBulletsSlideLayout: React.FC<NumberedBulletsSlideLayoutProps> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;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: 'Poppins, sans-serif'
|
||||
fontFamily: 'var(--heading-font-family,Inter)',
|
||||
background:"var(--card-background-color,#ffffff)"
|
||||
}}
|
||||
>
|
||||
{(slideData as any)?.__companyName__ && (
|
||||
<div className="absolute top-0 left-0 right-0 px-8 sm:px-12 lg:px-20 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm sm:text-base font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || 'Company Name'}
|
||||
</span>
|
||||
<div className="h-[2px] flex-1 opacity-70" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main Content Container */}
|
||||
<div className="px-8 sm:px-12 lg:px-20 pt-8 pb-8 h-full">
|
||||
<div className="px-8 sm:px-12 lg:px-20 pt-12 pb-8 h-full">
|
||||
{/* Top Section - Title and Image */}
|
||||
<div className="flex items-start justify-between mb-8">
|
||||
{/* Title Section */}
|
||||
<div className="flex-1 pr-8">
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 leading-tight mb-4">
|
||||
<h1 style={{ color: "var(--text-heading-color,#111827)" }} className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 leading-tight mb-4">
|
||||
{slideData?.title || 'Market Validation'}
|
||||
</h1>
|
||||
{/* Purple accent line */}
|
||||
<div className="w-24 h-1 bg-purple-600 mb-6"></div>
|
||||
<div style={{background:"var(--text-heading-color,#9333ea)"}} className="w-24 h-1 bg-purple-600 mb-6"></div>
|
||||
</div>
|
||||
|
||||
{/* Image Section */}
|
||||
|
|
@ -89,7 +96,7 @@ const NumberedBulletsSlideLayout: React.FC<NumberedBulletsSlideLayoutProps> = ({
|
|||
<img
|
||||
src={slideData?.image?.__image_url__ || ''}
|
||||
alt={slideData?.image?.__image_prompt__ || slideData?.title || ''}
|
||||
className="w-full h-full object-cover rounded-lg shadow-md"
|
||||
className="w-full h-full object-cover rounded-lg shadow-md" style={{background:"var(--tertiary-accent-color,#e5e7eb)"}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -100,17 +107,17 @@ const NumberedBulletsSlideLayout: React.FC<NumberedBulletsSlideLayoutProps> = ({
|
|||
<div key={index} className="flex items-start space-x-4">
|
||||
{/* Number */}
|
||||
<div className="flex-shrink-0">
|
||||
<div className="text-4xl sm:text-5xl font-bold text-gray-900">
|
||||
<div style={{color:"var(--text-heading-color,#111827)"}} className="text-4xl sm:text-5xl font-bold text-gray-900">
|
||||
{String(index + 1).padStart(2, '0')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 pt-2">
|
||||
<h3 className="text-xl sm:text-2xl font-bold text-gray-900 mb-3">
|
||||
<h3 style={{color:"var(--text-heading-color,#111827)"}} className="text-xl sm:text-2xl font-bold text-gray-900 mb-3">
|
||||
{bullet.title}
|
||||
</h3>
|
||||
<p className="text-base text-gray-700 leading-relaxed">
|
||||
<p style={{color:"var(--text-body-color,#4b5563)"}} className="text-base text-gray-700 leading-relaxed">
|
||||
{bullet.description}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -132,9 +139,9 @@ const NumberedBulletsSlideLayout: React.FC<NumberedBulletsSlideLayoutProps> = ({
|
|||
/>
|
||||
<defs>
|
||||
<linearGradient id="wave-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stopColor="#8b5cf6" />
|
||||
<stop offset="50%" stopColor="#a855f7" />
|
||||
<stop offset="100%" stopColor="#c084fc" />
|
||||
<stop offset="0%" stopColor="var(--primary-accent-color,#9333ea)" />
|
||||
<stop offset="50%" stopColor="var(--primary-accent-color,#9333ea)" />
|
||||
<stop offset="100%" stopColor="var(--primary-accent-color,#9333ea)" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
|
|
|||
|
|
@ -36,17 +36,25 @@ const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data: slideData })
|
|||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;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: 'Poppins, sans-serif'
|
||||
fontFamily: 'var(--heading-font-family,Inter)',
|
||||
background:"var(--card-background-color,#ffffff)"
|
||||
}}
|
||||
>
|
||||
{(slideData as any)?.__companyName__ && (
|
||||
<div className="absolute top-0 left-0 right-0 px-8 sm:px-12 lg:px-20 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm sm:text-base font-semibold" style={{ color: 'var(--text-heading-color, #ffffff)' }}>
|
||||
{(slideData as any)?.__companyName__ || 'Company Name'}
|
||||
</span>
|
||||
<div className="h-[2px] flex-1 opacity-70" style={{ backgroundColor: 'var(--text-heading-color, #ffffff)' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Background Image */}
|
||||
<div
|
||||
className="absolute inset-0 w-full h-full bg-cover bg-center bg-no-repeat"
|
||||
|
|
@ -55,8 +63,11 @@ const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data: slideData })
|
|||
}}
|
||||
/>
|
||||
|
||||
{/* Background Overlay */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-black/60 via-black/40 to-black/60"></div>
|
||||
{/* Background Overlay - low opacity primary accent */}
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{ backgroundColor: 'var(--primary-accent-color, #9333ea)', opacity: 0.3 }}
|
||||
></div>
|
||||
|
||||
{/* Decorative Elements */}
|
||||
<div className="absolute top-0 left-0 w-32 h-32 bg-purple-600/20 rounded-full blur-3xl"></div>
|
||||
|
|
@ -64,16 +75,16 @@ const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data: slideData })
|
|||
<div className="absolute top-1/2 left-1/4 w-24 h-24 bg-white/10 rounded-full blur-2xl"></div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="relative z-10 px-8 sm:px-12 lg:px-20 py-12 flex-1 flex flex-col justify-center h-full">
|
||||
<div className="relative z-10 px-8 sm:px-12 lg:px-20 pt-14 py-12 flex-1 flex flex-col justify-center h-full">
|
||||
<div className="text-center space-y-8 max-w-4xl mx-auto">
|
||||
|
||||
{/* Heading */}
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-white leading-tight">
|
||||
<h1 style={{ color: "var(--text-heading-color,#111827)" }} className="text-3xl sm:text-4xl lg:text-5xl font-bold text-white leading-tight">
|
||||
{slideData?.heading || 'Words of Wisdom'}
|
||||
</h1>
|
||||
{/* Purple accent line */}
|
||||
<div className="w-20 h-1 bg-purple-400 mx-auto"></div>
|
||||
<div style={{background:"var(--primary-accent-color,#9333ea)"}} className="w-20 h-1 bg-purple-400 mx-auto"></div>
|
||||
</div>
|
||||
|
||||
{/* Quote Section */}
|
||||
|
|
@ -81,7 +92,7 @@ const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data: slideData })
|
|||
{/* Quote Icon */}
|
||||
<div className="flex justify-center">
|
||||
<svg
|
||||
className="w-12 h-12 text-purple-300 opacity-80"
|
||||
className="w-12 h-12 text-purple-300 opacity-80" style={{color:"var(--primary-accent-color,#9333ea)"}}
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
|
|
@ -90,24 +101,24 @@ const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data: slideData })
|
|||
</div>
|
||||
|
||||
{/* Quote Text */}
|
||||
<blockquote className="text-xl sm:text-2xl lg:text-3xl font-medium text-white leading-relaxed italic">
|
||||
<blockquote style={{color:"var(--text-body-color,#ffffff)"}} className="text-xl sm:text-2xl lg:text-3xl font-medium text-white leading-relaxed italic">
|
||||
"{slideData?.quote || 'Success is not final, failure is not fatal: it is the courage to continue that counts. The future belongs to those who believe in the beauty of their dreams.'}"
|
||||
</blockquote>
|
||||
|
||||
{/* Author */}
|
||||
<div className="flex justify-center items-center space-x-4">
|
||||
<div className="w-16 h-px bg-purple-300"></div>
|
||||
<div style={{background:"var(--primary-accent-color,#9333ea)"}} className="w-16 h-px bg-purple-300"></div>
|
||||
<cite className="text-base sm:text-lg text-purple-200 font-semibold not-italic">
|
||||
{slideData?.author || 'Winston Churchill'}
|
||||
</cite>
|
||||
<div className="w-16 h-px bg-purple-300"></div>
|
||||
<div style={{background:"var(--primary-accent-color,#9333ea)"}} className="w-16 h-px bg-purple-300"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Decorative Border */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-2 bg-gradient-to-r from-purple-600 via-purple-400 to-purple-600"></div>
|
||||
{/* Bottom Decorative Border uses heading color */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-2" style={{ backgroundColor: 'var(--text-heading-color,#111827)' }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -52,17 +52,25 @@ const TableInfoSlideLayout: React.FC<TableInfoSlideLayoutProps> = ({ data: slide
|
|||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;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 flex flex-col"
|
||||
style={{
|
||||
fontFamily: 'Poppins, sans-serif'
|
||||
fontFamily: 'var(--heading-font-family,Inter)',
|
||||
background:"var(--card-background-color,#ffffff)"
|
||||
}}
|
||||
>
|
||||
{(slideData as any)?.__companyName__ && (
|
||||
<div className="absolute top-0 left-0 right-0 px-8 sm:px-12 lg:px-20 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm sm:text-base font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || 'Company Name'}
|
||||
</span>
|
||||
<div className="h-[2px] flex-1 opacity-70" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Decorative Wave Patterns */}
|
||||
<div className="absolute top-0 left-0 w-64 h-full opacity-10 overflow-hidden">
|
||||
<svg className="w-full h-full" viewBox="0 0 200 400" fill="none">
|
||||
|
|
@ -81,26 +89,26 @@ const TableInfoSlideLayout: React.FC<TableInfoSlideLayoutProps> = ({ data: slide
|
|||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="relative z-10 px-8 sm:px-12 lg:px-20 py-8 flex-1 flex flex-col justify-between">
|
||||
<div className="relative z-10 px-8 sm:px-12 lg:px-20 pt-12 py-8 flex-1 flex flex-col justify-between">
|
||||
|
||||
{/* Title Section */}
|
||||
<div className="text-center space-y-4">
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900">
|
||||
<h1 style={{ color: "var(--text-heading-color,#111827)" }} className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900">
|
||||
{slideData?.title || 'Market Comparison'}
|
||||
</h1>
|
||||
{/* Purple accent line */}
|
||||
<div className="w-20 h-1 bg-purple-600 mx-auto"></div>
|
||||
<div style={{background:"var(--primary-accent-color,#9333ea)"}} className="w-20 h-1 bg-purple-600 mx-auto"></div>
|
||||
</div>
|
||||
|
||||
{/* Table Section */}
|
||||
<div className="flex-1 flex items-center justify-center py-8">
|
||||
<div className="w-full max-w-4xl">
|
||||
<div className="bg-white rounded-lg shadow-lg border border-gray-200 overflow-hidden">
|
||||
<div style={{ background: "var(--tertiary-accent-color,#e5e7eb)", borderColor: "var(--secondary-accent-color,#e5e7eb)" }} className="bg-white rounded-lg shadow-lg border overflow-hidden">
|
||||
{/* Table Header */}
|
||||
<div className="bg-purple-600 text-white">
|
||||
<div style={{ backgroundColor: "var(--primary-accent-color,#9333ea)" }}>
|
||||
<div className="grid gap-px" style={{ gridTemplateColumns: `repeat(${tableHeaders.length}, 1fr)` }}>
|
||||
{tableHeaders.map((header, index) => (
|
||||
<div key={index} className="px-6 py-4 font-semibold text-center text-sm sm:text-base">
|
||||
<div key={index} className="px-6 py-4 font-semibold text-center text-sm sm:text-base" style={{color:"var(--text-heading-color,#111827)"}}>
|
||||
{header}
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -112,11 +120,20 @@ const TableInfoSlideLayout: React.FC<TableInfoSlideLayoutProps> = ({ data: slide
|
|||
{tableRows.map((row, rowIndex) => (
|
||||
<div
|
||||
key={rowIndex}
|
||||
className={`grid gap-px ${rowIndex % 2 === 0 ? 'bg-gray-50' : 'bg-white'} hover:bg-purple-50 transition-colors duration-200`}
|
||||
className={`grid gap-px ${rowIndex % 2 === 0 ? 'bg-gray-50' : 'bg-white'} transition-colors duration-200`}
|
||||
style={{ gridTemplateColumns: `repeat(${tableHeaders.length}, 1fr)` }}
|
||||
>
|
||||
{row.slice(0, tableHeaders.length).map((cell, cellIndex) => (
|
||||
<div key={cellIndex} className="px-6 py-4 text-center text-sm sm:text-base text-gray-800">
|
||||
<div
|
||||
key={cellIndex}
|
||||
className="px-6 py-4 text-center text-sm sm:text-base"
|
||||
style={{
|
||||
color: "var(--text-body-color,#4b5563)",
|
||||
background: cellIndex % 2 === 0
|
||||
? "var(--secondary-accent-color,#e5e7eb)"
|
||||
: "var(--tertiary-accent-color,#f3f4f6)",
|
||||
}}
|
||||
>
|
||||
{cell}
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -131,7 +148,7 @@ const TableInfoSlideLayout: React.FC<TableInfoSlideLayoutProps> = ({ data: slide
|
|||
{/* Description Section */}
|
||||
<div className="text-center space-y-4">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<p className="text-sm sm:text-base text-gray-700 leading-relaxed">
|
||||
<p style={{color:"var(--text-body-color,#4b5563)"}} className="text-sm sm:text-base text-gray-700 leading-relaxed">
|
||||
{slideData?.description || 'This comparison shows our competitive position in the market. While we currently have a smaller market share, our growth rate significantly exceeds competitors, indicating strong potential for future expansion.'}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -48,27 +48,34 @@ const TableOfContentsSlideLayout: React.FC<TableOfContentsSlideLayoutProps> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-lg px-8 sm:px-12 lg:px-20 py-8 sm:py-12 lg:py-16 max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
style={{
|
||||
fontFamily: 'Poppins, sans-serif'
|
||||
fontFamily: 'var(--heading-font-family,Inter)',
|
||||
background:"var(--card-background-color,#ffffff)"
|
||||
}}
|
||||
>
|
||||
{(slideData as any)?.__companyName__ && (
|
||||
<div className="absolute top-0 left-0 right-0 px-8 sm:px-12 lg:px-20 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm sm:text-base font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || 'Company Name'}
|
||||
</span>
|
||||
<div className="h-[2px] flex-1 opacity-70" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Title Section */}
|
||||
<div className="text-center mb-8 sm:mb-12">
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 mb-4">
|
||||
<div className="text-center mb-8 sm:mb-12 mt-6">
|
||||
<h1 style={{ color: "var(--text-heading-color,#111827)" }} className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 mb-4">
|
||||
Table of Contents
|
||||
</h1>
|
||||
{/* Decorative Wave */}
|
||||
<div className="flex justify-center">
|
||||
<svg width="80" height="20" viewBox="0 0 80 20" className="text-purple-600">
|
||||
<svg width="80" height="20" viewBox="0 0 80 20" className="text-purple-600" style={{color:"var(--primary-accent-color,#9333ea)"}}>
|
||||
<path
|
||||
d="M0 10 Q20 0 40 10 T80 10"
|
||||
stroke="currentColor"
|
||||
|
|
@ -87,21 +94,21 @@ const TableOfContentsSlideLayout: React.FC<TableOfContentsSlideLayoutProps> = ({
|
|||
<div key={section.number} className="flex items-center justify-between group">
|
||||
<div className="flex items-center space-x-4">
|
||||
{/* Number Box */}
|
||||
<div className="w-12 h-12 sm:w-14 sm:h-14 bg-purple-600 rounded-xl flex items-center justify-center text-white font-bold text-lg sm:text-xl group-hover:bg-purple-700 transition-colors">
|
||||
<div style={{background:"var(--primary-accent-color,#9333ea)", color:"var(--text-heading-color,#ffffff)"}} className="w-12 h-12 sm:w-14 sm:h-14 bg-purple-600 rounded-xl flex items-center justify-center text-white font-bold text-lg sm:text-xl group-hover:bg-purple-700 transition-colors">
|
||||
{section.number}
|
||||
</div>
|
||||
{/* Title */}
|
||||
<span className="text-lg sm:text-xl font-medium text-gray-800 group-hover:text-purple-600 transition-colors">
|
||||
<span style={{color:"var(--text-heading-color,#111827)"}} className="text-lg sm:text-xl font-medium text-gray-800 group-hover:text-purple-600 transition-colors">
|
||||
{section.title}
|
||||
</span>
|
||||
</div>
|
||||
{/* Page Number */}
|
||||
<div className="text-right">
|
||||
<span className="text-lg sm:text-xl text-gray-600">
|
||||
<span style={{color:"var(--text-body-color,#4b5563)"}} className="text-lg sm:text-xl text-gray-600">
|
||||
{section.pageNumber}
|
||||
</span>
|
||||
{/* Dotted line effect */}
|
||||
<div className="text-gray-300 text-sm mt-1">
|
||||
<div style={{color:"var(--text-body-color,#4b5563)"}} className="text-gray-300 text-sm mt-1">
|
||||
.....
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -115,21 +122,21 @@ const TableOfContentsSlideLayout: React.FC<TableOfContentsSlideLayoutProps> = ({
|
|||
<div key={section.number} className="flex items-center justify-between group">
|
||||
<div className="flex items-center space-x-4">
|
||||
{/* Number Box */}
|
||||
<div className="w-12 h-12 sm:w-14 sm:h-14 bg-purple-600 rounded-xl flex items-center justify-center text-white font-bold text-lg sm:text-xl group-hover:bg-purple-700 transition-colors">
|
||||
<div style={{background:"var(--primary-accent-color,#9333ea)", color:"var(--text-heading-color,#ffffff)"}} className="w-12 h-12 sm:w-14 sm:h-14 bg-purple-600 rounded-xl flex items-center justify-center text-white font-bold text-lg sm:text-xl group-hover:bg-purple-700 transition-colors">
|
||||
{section.number}
|
||||
</div>
|
||||
{/* Title */}
|
||||
<span className="text-lg sm:text-xl font-medium text-gray-800 group-hover:text-purple-600 transition-colors">
|
||||
<span style={{color:"var(--text-heading-color,#111827)"}} className="text-lg sm:text-xl font-medium text-gray-800 group-hover:text-purple-600 transition-colors">
|
||||
{section.title}
|
||||
</span>
|
||||
</div>
|
||||
{/* Page Number */}
|
||||
<div className="text-right">
|
||||
<span className="text-lg sm:text-xl text-gray-600">
|
||||
<span style={{color:"var(--text-body-color,#4b5563)"}} className="text-lg sm:text-xl text-gray-600">
|
||||
{section.pageNumber}
|
||||
</span>
|
||||
{/* Dotted line effect */}
|
||||
<div className="text-gray-300 text-sm mt-1">
|
||||
<div style={{color:"var(--text-body-color,#4b5563)"}} className="text-gray-300 text-sm mt-1">
|
||||
.....
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const teamSlideSchema = z.object({
|
|||
position: 'CEO',
|
||||
description: 'Strategic leader with 15+ years experience in digital transformation and business growth.',
|
||||
image: {
|
||||
__image_url__: 'https://images.unsplash.com/photo-1494790108755-2616b612994a?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
|
||||
__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 businesswoman CEO headshot'
|
||||
}
|
||||
},
|
||||
|
|
@ -92,18 +92,25 @@ const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data: slideData }) =>
|
|||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;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: 'Poppins, sans-serif'
|
||||
fontFamily: 'var(--heading-font-family,Inter)',
|
||||
background:"var(--card-background-color,#ffffff)"
|
||||
}}
|
||||
>
|
||||
{(slideData as any)?.__companyName__ && (
|
||||
<div className="absolute top-0 left-0 right-0 px-8 sm:px-12 lg:px-20 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm sm:text-base font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || 'Company Name'}
|
||||
</span>
|
||||
<div className="h-[2px] flex-1 opacity-70" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Decorative Wave Pattern */}
|
||||
<div className="absolute bottom-0 left-0 w-80 h-40 opacity-10 overflow-hidden">
|
||||
<svg className="w-full h-full" viewBox="0 0 300 150" fill="none">
|
||||
|
|
@ -113,19 +120,19 @@ const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data: slideData }) =>
|
|||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="relative z-10 flex h-full px-8 sm:px-12 lg:px-20 pb-8">
|
||||
<div className="relative z-10 flex h-full px-8 sm:px-12 lg:px-20 pt-12 pb-8">
|
||||
{/* Left Section - Title and Company Description */}
|
||||
<div className="flex-1 flex flex-col justify-center pr-8 space-y-6">
|
||||
{/* Title */}
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
<h1 style={{ color: "var(--text-heading-color,#111827)" }} className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Our Team Members'}
|
||||
</h1>
|
||||
|
||||
{/* Purple accent line */}
|
||||
<div className="w-20 h-1 bg-purple-600"></div>
|
||||
<div style={{background:"var(--primary-accent-color,#9333ea)"}} className="w-20 h-1 bg-purple-600"></div>
|
||||
|
||||
{/* Company Description */}
|
||||
<p className="text-base sm:text-lg text-gray-700 leading-relaxed">
|
||||
<p style={{color:"var(--text-body-color,#4b5563)"}} className="text-base sm:text-lg text-gray-700 leading-relaxed">
|
||||
{slideData?.companyDescription || 'Ginyard International Co. is a leading provider of innovative digital solutions tailored for businesses. Our mission is to empower organizations to achieve their goals through cutting-edge technology and strategic partnerships.'}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -136,7 +143,7 @@ const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data: slideData }) =>
|
|||
{teamMembers.map((member, index) => (
|
||||
<div key={index} className="text-center space-y-3">
|
||||
{/* Member Photo */}
|
||||
<div className="w-32 h-32 mx-auto rounded-lg overflow-hidden shadow-md">
|
||||
<div className="w-32 h-32 mx-auto rounded-lg overflow-hidden shadow-md" style={{background:"var(--tertiary-accent-color,#e5e7eb)"}}>
|
||||
<img
|
||||
src={member.image.__image_url__ || ''}
|
||||
alt={member.image.__image_prompt__ || member.name}
|
||||
|
|
@ -146,13 +153,13 @@ const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data: slideData }) =>
|
|||
|
||||
{/* Member Info */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
<h3 style={{color:"var(--text-heading-color,#111827)"}} className="text-lg font-semibold text-gray-900">
|
||||
{member.name}
|
||||
</h3>
|
||||
<p className="text-sm font-medium text-gray-600 italic mb-2">
|
||||
<p style={{color:"var(--text-body-color,#4b5563)"}} className="text-sm font-medium text-gray-600 italic mb-2">
|
||||
{member.position}
|
||||
</p>
|
||||
<p className="text-xs text-gray-600 leading-relaxed px-2">
|
||||
<p style={{color:"var(--text-body-color,#4b5563)"}} className="text-xs text-gray-600 leading-relaxed px-2">
|
||||
{member.description}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,168 +0,0 @@
|
|||
import * as z from "zod";
|
||||
|
||||
import { ImageSchema, IconSchema } from "../defaultSchemes";
|
||||
|
||||
export const layoutName = "About Us Slide";
|
||||
export const layoutId = "about-us-slide";
|
||||
export const layoutDescription =
|
||||
"A slide with an introduction to the organization";
|
||||
|
||||
// Schema definition
|
||||
export const Schema = z.object({
|
||||
sectionTitle: z.string().min(3).max(25).default("ABOUT US").meta({
|
||||
description:
|
||||
"Main section heading - can be used for any organizational introduction",
|
||||
}),
|
||||
|
||||
sectionSubtitle: z
|
||||
.string()
|
||||
.min(5)
|
||||
.max(40)
|
||||
.default("GET TO KNOW US BETTER")
|
||||
.meta({
|
||||
description:
|
||||
"Supporting subtitle that invites audience engagement and builds connection",
|
||||
}),
|
||||
|
||||
organizationDescription: z
|
||||
.string()
|
||||
.min(50)
|
||||
.max(300)
|
||||
.default(
|
||||
"We believe in the transformative power of innovation, strategic thinking, and cutting-edge solutions. Our mission is simple: to empower organizations with comprehensive strategies that not only elevate performance but also drive tangible growth and success."
|
||||
)
|
||||
.meta({
|
||||
description:
|
||||
"Primary description of the organization's mission, values, and approach",
|
||||
}),
|
||||
|
||||
additionalContext: z
|
||||
.string()
|
||||
.min(30)
|
||||
.max(150)
|
||||
.default(
|
||||
"What sets us apart is not just our expertise but our commitment to understanding the unique needs of each client."
|
||||
)
|
||||
.meta({
|
||||
description:
|
||||
"Additional context or differentiating statement about the organization",
|
||||
}),
|
||||
|
||||
featuredImage: ImageSchema.default({
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1454165804606-c3d57bc86b40?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
|
||||
__image_prompt__:
|
||||
"Professional business team analyzing data and working collaboratively",
|
||||
}).meta({
|
||||
description:
|
||||
"Primary visual that represents the organization's work or environment",
|
||||
}),
|
||||
|
||||
showVisualAccents: z.boolean().default(true).meta({
|
||||
description: "Whether to display decorative visual accent elements",
|
||||
}),
|
||||
|
||||
showColorBlocks: z.boolean().default(true).meta({
|
||||
description:
|
||||
"Whether to show colored background blocks for visual hierarchy",
|
||||
}),
|
||||
|
||||
showAccentSquare: z.boolean().default(true).meta({
|
||||
description: "Whether to display the accent square decoration element",
|
||||
}),
|
||||
});
|
||||
|
||||
// Type inference
|
||||
type SchemaType = z.infer<typeof Schema>;
|
||||
|
||||
// Component definitionz
|
||||
const AboutUsSlide = ({ data }: { data: Partial<SchemaType> }) => {
|
||||
const {
|
||||
sectionTitle,
|
||||
sectionSubtitle,
|
||||
organizationDescription,
|
||||
additionalContext,
|
||||
featuredImage,
|
||||
showVisualAccents,
|
||||
showColorBlocks,
|
||||
showAccentSquare,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
|
||||
{/* Main Content Area */}
|
||||
<div className="h-full flex">
|
||||
{/* Left Side - Content */}
|
||||
<div className="w-3/5 relative bg-white px-16 py-12 flex flex-col justify-center">
|
||||
{/* Title Section */}
|
||||
<div className="mb-0">
|
||||
{sectionTitle && (
|
||||
<h1 className="text-3xl lg:text-4xl font-black text-teal-700 leading-tight mb-4">
|
||||
{sectionTitle}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{sectionSubtitle && (
|
||||
<p className="text-base font-semibold text-gray-800 tracking-wide mb-6">
|
||||
{sectionSubtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Decorative gray line */}
|
||||
<div className="w-1 h-20 bg-gray-400 mb-8"></div>
|
||||
</div>
|
||||
|
||||
{/* Description Text */}
|
||||
{organizationDescription && (
|
||||
<p className="text-base leading-relaxed text-gray-700 max-w-xl">
|
||||
{organizationDescription}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Additional Text */}
|
||||
{additionalContext && (
|
||||
<div>
|
||||
<p className="text-base leading-relaxed text-gray-700 max-w-xl">
|
||||
{additionalContext}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Side - Image and Decorative Elements */}
|
||||
<div className="w-2/5 relative">
|
||||
{/* Yellow Square - Top Right */}
|
||||
{showAccentSquare && (
|
||||
<div className="absolute bottom-0 right-0 w-24 h-24 bg-yellow-300 z-10"></div>
|
||||
)}
|
||||
|
||||
{/* Decorative Circle - On Yellow Square */}
|
||||
{showVisualAccents && (
|
||||
<div className="absolute top-6 right-6 w-6 h-6 border-2 border-teal-600 rounded-full z-20"></div>
|
||||
)}
|
||||
|
||||
{/* Business Image - Left positioned */}
|
||||
{featuredImage?.__image_url__ && (
|
||||
<div className="absolute right-36 top-1/2 -translate-y-1/2 h-[500px] w-[350px] z-20 shadow-lg">
|
||||
<img
|
||||
src={featuredImage.__image_url__}
|
||||
alt={featuredImage.__image_prompt__}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Teal Accent Areas */}
|
||||
{showColorBlocks && (
|
||||
<>
|
||||
{/* Vertical Teal Strip - Center */}
|
||||
<div className="absolute top-0 right-0 bottom-16 h-full w-[300px] bg-teal-600 z-10"></div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutUsSlide;
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
import React from "react";
|
||||
import * as z from "zod";
|
||||
|
||||
import { ImageSchema, IconSchema } from "../defaultSchemes";
|
||||
import {
|
||||
ChartContainer,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/components/ui/chart";
|
||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid } from "recharts";
|
||||
|
||||
export const layoutName = "Business Model Slide";
|
||||
export const layoutId = "business-model-slide";
|
||||
export const layoutDescription = "A slide with a business model";
|
||||
|
||||
// Schema definition
|
||||
export const Schema = z.object({
|
||||
sectionTitle: z.string().min(3).max(30).default("BUSINESS MODEL").meta({
|
||||
description:
|
||||
"Main section heading - adapt to presentation topic (e.g., 'Revenue Strategy', 'Funding Model', 'Implementation Plan', 'Solution Framework')",
|
||||
}),
|
||||
|
||||
sectionSubtitle: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(60)
|
||||
.default("SUSTAINABLE REVENUE AND VALUE CREATION")
|
||||
.meta({
|
||||
description:
|
||||
"Supporting subtitle that describes the approach - adapt to topic (e.g., 'Carbon Reduction Strategy', 'Healthcare Delivery Model', 'Educational Framework')",
|
||||
}),
|
||||
|
||||
modelDescription: z
|
||||
.string()
|
||||
.min(50)
|
||||
.max(300)
|
||||
.default(
|
||||
"Our business model focuses on creating sustainable value through multiple revenue streams, strategic partnerships, and customer-centric solutions. We prioritize long-term relationships and scalable growth opportunities."
|
||||
)
|
||||
.meta({
|
||||
description:
|
||||
"IMPORTANT: Provide topic-specific description of the model/approach. For global warming: describe carbon reduction strategies, renewable energy adoption, sustainability metrics. For healthcare: treatment protocols, patient care models. For education: learning methodologies, curriculum design. Always provide concrete, relevant details for the presentation topic.",
|
||||
}),
|
||||
|
||||
headerVisual: ImageSchema.default({
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1559136555-9303baea8ebd?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
|
||||
__image_prompt__:
|
||||
"Business strategy meeting with charts, graphs and team collaboration",
|
||||
}).meta({
|
||||
description:
|
||||
"Header visual representing the topic area - ADAPT the image prompt to match presentation topic (e.g., 'Climate scientists analyzing global warming data', 'Medical team reviewing patient care protocols', 'Teachers planning educational curriculum')",
|
||||
}),
|
||||
|
||||
chartData: z
|
||||
.array(
|
||||
z.object({
|
||||
category: z.string().min(3).max(25),
|
||||
value: z.number().min(0).max(100),
|
||||
color: z.string().min(3).max(20).optional(),
|
||||
})
|
||||
)
|
||||
.min(2)
|
||||
.max(6)
|
||||
.default([
|
||||
{ category: "Product Sales", value: 45, color: "#22C55E" },
|
||||
{ category: "Services", value: 30, color: "#0891B2" },
|
||||
{ category: "Partnerships", value: 15, color: "#FDE047" },
|
||||
{ category: "Licensing", value: 10, color: "#F97316" },
|
||||
])
|
||||
.meta({
|
||||
description:
|
||||
"CRITICAL: Provide actual data relevant to the presentation topic. For global warming: CO2 emission sources (Transport 29%, Energy 25%, Industry 21%, Agriculture 24%), temperature rise by decade, renewable energy adoption rates. For healthcare: treatment success rates, patient demographics, cost breakdowns. For education: student performance metrics, learning outcomes, resource allocation. Always use REAL topic-specific data with appropriate categories and realistic values.",
|
||||
}),
|
||||
|
||||
showChart: z.boolean().default(true).meta({
|
||||
description:
|
||||
"Whether to display the data visualization - typically keep true for data-driven presentations",
|
||||
}),
|
||||
|
||||
showVisualAccents: z.boolean().default(true).meta({
|
||||
description: "Whether to display decorative visual accent elements",
|
||||
}),
|
||||
});
|
||||
|
||||
// Chart configuration
|
||||
const chartConfig = {
|
||||
series1: {
|
||||
label: "Series 1",
|
||||
color: "#1D9A8A",
|
||||
},
|
||||
series2: {
|
||||
label: "Series 2",
|
||||
color: "#E8F4B8",
|
||||
},
|
||||
series3: {
|
||||
label: "Series 3",
|
||||
color: "#A8C97F",
|
||||
},
|
||||
};
|
||||
|
||||
// Type inference
|
||||
type SchemaType = z.infer<typeof Schema>;
|
||||
|
||||
// Component definition
|
||||
const BusinessModelSlide = ({ data }: { data: Partial<SchemaType> }) => {
|
||||
const {
|
||||
sectionTitle,
|
||||
sectionSubtitle,
|
||||
modelDescription,
|
||||
headerVisual,
|
||||
chartData,
|
||||
showChart,
|
||||
showVisualAccents,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
|
||||
{/* Header Image Section */}
|
||||
{headerVisual?.__image_url__ && (
|
||||
<div className="h-32 w-full relative">
|
||||
<img
|
||||
src={headerVisual.__image_url__}
|
||||
alt={headerVisual.__image_prompt__}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black bg-opacity-40"></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main Content Area */}
|
||||
<div className="flex h-[calc(100%-8rem)]">
|
||||
{/* Left Side - Content */}
|
||||
<div className="w-1/2 px-16 py-8 flex flex-col justify-start">
|
||||
{/* Title Section */}
|
||||
<div className="mb-6">
|
||||
{sectionTitle && (
|
||||
<h1 className="text-3xl lg:text-4xl font-black text-teal-700 leading-tight mb-4">
|
||||
{sectionTitle}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{sectionSubtitle && (
|
||||
<p className="text-base font-semibold text-gray-800 tracking-wide mb-6">
|
||||
{sectionSubtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Model Description */}
|
||||
{modelDescription && (
|
||||
<div>
|
||||
<p className="text-base leading-relaxed text-gray-700">
|
||||
{modelDescription}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Visual Accents */}
|
||||
{showVisualAccents && (
|
||||
<>
|
||||
<div className="absolute bottom-8 left-8 w-6 h-6 bg-yellow-300 rounded-full"></div>
|
||||
<div className="absolute top-40 right-1/2 w-4 h-4 bg-teal-600 rounded-full"></div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Side - Chart */}
|
||||
<div className="w-1/2 px-8 py-8 flex flex-col justify-center">
|
||||
{showChart && chartData && chartData.length > 0 && (
|
||||
<div className="h-80 w-full">
|
||||
<ChartContainer
|
||||
config={{
|
||||
value: {
|
||||
label: "Value",
|
||||
color: "hsl(var(--chart-1))",
|
||||
},
|
||||
}}
|
||||
className="h-full w-full"
|
||||
>
|
||||
<BarChart
|
||||
data={chartData}
|
||||
margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
|
||||
<XAxis
|
||||
dataKey="category"
|
||||
stroke="#6b7280"
|
||||
fontSize={12}
|
||||
angle={-45}
|
||||
textAnchor="end"
|
||||
height={60}
|
||||
/>
|
||||
<YAxis stroke="#6b7280" fontSize={12} />
|
||||
<ChartTooltip content={<ChartTooltipContent />} />
|
||||
<Bar dataKey="value" fill="#0891b2" radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom accent strip */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-2 bg-teal-600"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BusinessModelSlide;
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
import * as z from "zod";
|
||||
|
||||
import { ImageSchema, IconSchema } from "../defaultSchemes";
|
||||
|
||||
export const layoutName = "Market Size Slide";
|
||||
export const layoutId = "market-size-slide";
|
||||
export const layoutDescription = "A slide with a market size analysis";
|
||||
|
||||
// Schema definition
|
||||
export const Schema = z.object({
|
||||
sectionTitle: z.string().min(3).max(30).default("MARKET ANALYSIS").meta({
|
||||
description:
|
||||
"Main section heading - can be 'Market Size', 'Market Opportunity', 'Industry Overview', or similar",
|
||||
}),
|
||||
|
||||
sectionSubtitle: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(60)
|
||||
.default("UNDERSTANDING THE OPPORTUNITY LANDSCAPE")
|
||||
.meta({
|
||||
description:
|
||||
"Supporting subtitle that frames the market discussion and opportunity scope",
|
||||
}),
|
||||
|
||||
marketDefinitions: z
|
||||
.array(
|
||||
z.object({
|
||||
marketType: z.string().min(3).max(30),
|
||||
marketDescription: z.string().min(20).max(150),
|
||||
marketValue: z.string().min(3).max(25).optional(),
|
||||
})
|
||||
)
|
||||
.min(2)
|
||||
.max(3)
|
||||
.default([
|
||||
{
|
||||
marketType: "Total Addressable Market (TAM)",
|
||||
marketDescription:
|
||||
"The overall revenue opportunity available if we achieved 100% market share across all segments and geographies.",
|
||||
marketValue: "$50B",
|
||||
},
|
||||
{
|
||||
marketType: "Serviceable Addressable Market (SAM)",
|
||||
marketDescription:
|
||||
"The portion of TAM targeted by our products and services within our geographic reach.",
|
||||
marketValue: "$15B",
|
||||
},
|
||||
{
|
||||
marketType: "Serviceable Obtainable Market (SOM)",
|
||||
marketDescription:
|
||||
"The portion of SAM that we can realistically capture based on our resources and market conditions.",
|
||||
marketValue: "$3B",
|
||||
},
|
||||
])
|
||||
.meta({
|
||||
description:
|
||||
"List of market definitions and opportunities with descriptions and potential values",
|
||||
}),
|
||||
|
||||
visualRepresentation: ImageSchema.default({
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1597149962419-0d900ac2b46c?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
|
||||
__image_prompt__:
|
||||
"World map showing global market reach and geographic distribution",
|
||||
}).meta({
|
||||
description:
|
||||
"Visual that represents market scope - could be a world map, chart, or geographic visualization",
|
||||
}),
|
||||
|
||||
showYellowUnderline: z.boolean().default(true).meta({
|
||||
description: "Whether to display the decorative yellow underline accent",
|
||||
}),
|
||||
|
||||
showVisualAccents: z.boolean().default(true).meta({
|
||||
description: "Whether to display decorative visual accent elements",
|
||||
}),
|
||||
});
|
||||
|
||||
// Type inference
|
||||
type SchemaType = z.infer<typeof Schema>;
|
||||
|
||||
// Component definition
|
||||
const MarketSizeSlide = ({ data }: { data: Partial<SchemaType> }) => {
|
||||
const {
|
||||
sectionTitle,
|
||||
sectionSubtitle,
|
||||
marketDefinitions,
|
||||
visualRepresentation,
|
||||
showYellowUnderline,
|
||||
showVisualAccents,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
|
||||
{/* Main Content Area */}
|
||||
<div className="h-full flex">
|
||||
{/* Left Side - Content */}
|
||||
<div className="w-3/5 relative bg-white px-16 py-12 flex flex-col justify-start">
|
||||
{/* Title Section */}
|
||||
<div className="mb-8">
|
||||
{sectionTitle && (
|
||||
<h1 className="text-3xl lg:text-4xl font-black leading-tight mb-4">
|
||||
{sectionTitle}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{sectionSubtitle && (
|
||||
<p className="text-base font-semibold tracking-wide mb-4">
|
||||
{sectionSubtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Yellow Decorative Underline */}
|
||||
{showYellowUnderline && (
|
||||
<div className="w-24 h-1 bg-yellow-300 mb-8"></div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Market Definitions List */}
|
||||
{marketDefinitions && marketDefinitions.length > 0 && (
|
||||
<div className="space-y-6">
|
||||
{marketDefinitions.map((market, index) => (
|
||||
<div key={index} className="border-l-4 border-teal-600 pl-6">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="text-lg font-bold text-gray-900">
|
||||
{market.marketType}
|
||||
</h3>
|
||||
{market.marketValue && (
|
||||
<span className="text-xl font-bold text-teal-600">
|
||||
{market.marketValue}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-base leading-relaxed text-gray-700">
|
||||
{market.marketDescription}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Side - Visual Representation */}
|
||||
<div className="w-2/5 relative bg-gray-50">
|
||||
{/* Visual Accents */}
|
||||
{showVisualAccents && (
|
||||
<>
|
||||
{/* Decorative circles */}
|
||||
<div className="absolute top-8 right-8 w-6 h-6 bg-teal-600 rounded-full opacity-60 z-20"></div>
|
||||
<div className="absolute bottom-12 left-8 w-4 h-4 bg-yellow-300 rounded-full z-20"></div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Visual Representation */}
|
||||
{visualRepresentation?.__image_url__ && (
|
||||
<div className="absolute inset-8 shadow-lg">
|
||||
<img
|
||||
src={visualRepresentation.__image_url__}
|
||||
alt={visualRepresentation.__image_prompt__}
|
||||
className="w-full h-full object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom accent strip */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-3 bg-teal-600"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarketSizeSlide;
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
import * as z from "zod";
|
||||
|
||||
import { ImageSchema, IconSchema } from "../defaultSchemes";
|
||||
|
||||
export const layoutName = "Our Service Slide";
|
||||
export const layoutId = "our-service-slide";
|
||||
export const layoutDescription = "A slide with a list of services";
|
||||
|
||||
// Schema definition
|
||||
export const Schema = z.object({
|
||||
sectionTitle: z.string().min(3).max(30).default("OUR SERVICES").meta({
|
||||
description:
|
||||
"Main section heading - can be 'Our Services', 'What We Offer', 'Service Portfolio', or similar",
|
||||
}),
|
||||
|
||||
sectionSubtitle: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(60)
|
||||
.default("COMPREHENSIVE SOLUTIONS TAILORED FOR SUCCESS")
|
||||
.meta({
|
||||
description:
|
||||
"Supporting subtitle that describes the service approach or value proposition",
|
||||
}),
|
||||
|
||||
bulletPoints: z
|
||||
.array(z.string().min(10).max(40))
|
||||
.min(4)
|
||||
.max(6)
|
||||
.default([
|
||||
"Customized solutions for your business",
|
||||
"Expert guidance and support",
|
||||
"Innovative technology solutions",
|
||||
"Comprehensive service portfolio",
|
||||
])
|
||||
.meta({
|
||||
description: "Bullet points to describe the services offered",
|
||||
}),
|
||||
|
||||
serviceHighlight: ImageSchema.default({
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1556761175-b413da4baf72?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
|
||||
__image_prompt__:
|
||||
"Professional service delivery or team working on client solutions",
|
||||
}).meta({
|
||||
description:
|
||||
"Visual that represents service delivery, expertise, or client collaboration",
|
||||
}),
|
||||
|
||||
showVisualAccents: z.boolean().default(true).meta({
|
||||
description: "Whether to display decorative visual accent elements",
|
||||
}),
|
||||
|
||||
showColorBlocks: z.boolean().default(true).meta({
|
||||
description:
|
||||
"Whether to show colored background sections for visual hierarchy",
|
||||
}),
|
||||
});
|
||||
|
||||
// Type inference
|
||||
type SchemaType = z.infer<typeof Schema>;
|
||||
|
||||
// Component definition
|
||||
const OurServiceSlide = ({ data }: { data: Partial<SchemaType> }) => {
|
||||
const {
|
||||
sectionTitle,
|
||||
sectionSubtitle,
|
||||
serviceHighlight,
|
||||
showVisualAccents,
|
||||
showColorBlocks,
|
||||
bulletPoints,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
|
||||
{/* Main Content Area */}
|
||||
<div className="h-full flex">
|
||||
{/* Left - Title */}
|
||||
<div className="w-1/2 px-16 py-12 bg-gray-100">
|
||||
{sectionTitle && (
|
||||
<h1 className="text-3xl lg:text-4xl font-black text-teal-700 leading-tight mb-4">
|
||||
{sectionTitle}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{sectionSubtitle && (
|
||||
<p className="text-base font-semibold text-gray-800 tracking-wide">
|
||||
{sectionSubtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="mt-16 space-y-4">
|
||||
{bulletPoints &&
|
||||
bulletPoints.map((point, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="text-base font-semibold text-gray-800 tracking-wide"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="min-w-8 min-h-8 bg-teal-700 rounded-full "></div>
|
||||
<p className="text-lg font-medium text-gray-800 tracking-wide">
|
||||
{point}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Visual Accents */}
|
||||
{showVisualAccents && (
|
||||
<>
|
||||
{/* Decorative elements */}
|
||||
<div className="absolute bottom-16 left-16 w-8 h-8 bg-yellow-300 rounded-full"></div>
|
||||
<div className="absolute top-20 right-20 w-4 h-4 bg-teal-600 rounded-full"></div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right - Service Highlight */}
|
||||
<div className="w-1/2 relative">
|
||||
{/* Service Highlight Image */}
|
||||
{serviceHighlight?.__image_url__ && (
|
||||
<div className="h-full w-full">
|
||||
<img
|
||||
src={serviceHighlight.__image_url__}
|
||||
alt={serviceHighlight.__image_prompt__}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Color overlay if enabled */}
|
||||
{showColorBlocks && (
|
||||
<div className="absolute inset-0 bg-teal-600 bg-opacity-20"></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom accent strip */}
|
||||
{showColorBlocks && (
|
||||
<div className="absolute bottom-0 left-0 right-0 h-3 bg-teal-600"></div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OurServiceSlide;
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
import * as z from "zod";
|
||||
|
||||
import { ImageSchema, IconSchema } from "../defaultSchemes";
|
||||
|
||||
export const layoutName = "Problems Slide";
|
||||
export const layoutId = "problems-slide";
|
||||
export const layoutDescription = "A slide with a list of problems";
|
||||
|
||||
// Schema definition
|
||||
export const Schema = z.object({
|
||||
sectionTitle: z.string().min(3).max(25).default("CHALLENGES").meta({
|
||||
description:
|
||||
"Main section heading - can be 'Problems', 'Challenges', 'Issues', or similar",
|
||||
}),
|
||||
|
||||
sectionSubtitle: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(50)
|
||||
.default("KEY CHALLENGES TO ADDRESS")
|
||||
.meta({
|
||||
description: "Supporting subtitle that frames the problem discussion",
|
||||
}),
|
||||
|
||||
challengeItems: z
|
||||
.array(
|
||||
z.object({
|
||||
itemNumber: z.string().min(1).max(3),
|
||||
challengeTitle: z.string().min(5).max(40),
|
||||
challengeDescription: z.string().min(20).max(200),
|
||||
})
|
||||
)
|
||||
.min(2)
|
||||
.max(3)
|
||||
.default([
|
||||
{
|
||||
itemNumber: "01",
|
||||
challengeTitle: "Inefficient Processes",
|
||||
challengeDescription:
|
||||
"Current workflows and systems lack optimization, leading to wasted resources and reduced productivity across all operational areas.",
|
||||
},
|
||||
{
|
||||
itemNumber: "02",
|
||||
challengeTitle: "Limited Scalability",
|
||||
challengeDescription:
|
||||
"Existing infrastructure and methodologies cannot accommodate growth, creating bottlenecks that hinder expansion and progress.",
|
||||
},
|
||||
{
|
||||
itemNumber: "03",
|
||||
challengeTitle: "Resource Constraints",
|
||||
challengeDescription:
|
||||
"Limited availability of key resources including time, budget, and skilled personnel creates barriers to achieving desired outcomes.",
|
||||
},
|
||||
])
|
||||
.meta({
|
||||
description:
|
||||
"List of key challenges or problems with numbered identification and detailed descriptions",
|
||||
}),
|
||||
|
||||
supportingVisual: ImageSchema.default({
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1542744173-8e7e53415bb0?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
|
||||
__image_prompt__:
|
||||
"Professional workspace showing analysis and problem-solving activities",
|
||||
}).meta({
|
||||
description:
|
||||
"Visual that supports the problem discussion - could show analysis, challenges, or work environment",
|
||||
}),
|
||||
|
||||
showVisualAccents: z.boolean().default(true).meta({
|
||||
description: "Whether to display decorative visual accent elements",
|
||||
}),
|
||||
|
||||
showColorBlocks: z.boolean().default(true).meta({
|
||||
description: "Whether to show colored accent blocks for visual hierarchy",
|
||||
}),
|
||||
});
|
||||
|
||||
// Type inference
|
||||
type SchemaType = z.infer<typeof Schema>;
|
||||
|
||||
// Component definition
|
||||
const ProblemsSlide = ({ data }: { data: Partial<SchemaType> }) => {
|
||||
const {
|
||||
sectionTitle,
|
||||
sectionSubtitle,
|
||||
challengeItems,
|
||||
supportingVisual,
|
||||
showVisualAccents,
|
||||
showColorBlocks,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
|
||||
{/* Main Content Area */}
|
||||
<div className="h-full flex">
|
||||
{/* Left Side - Content */}
|
||||
<div className="w-3/5 relative bg-white px-16 py-12 flex flex-col justify-start">
|
||||
{/* Title Section */}
|
||||
<div className="mb-12">
|
||||
{sectionTitle && (
|
||||
<h1 className="text-3xl lg:text-4xl font-black text-teal-700 leading-tight mb-4">
|
||||
{sectionTitle}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{sectionSubtitle && (
|
||||
<p className="text-base font-semibold text-gray-800 tracking-wide">
|
||||
{sectionSubtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Challenge Items List */}
|
||||
{challengeItems && challengeItems.length > 0 && (
|
||||
<div className="space-y-8">
|
||||
{challengeItems.map((item, index) => (
|
||||
<div key={index} className="flex items-start space-x-6">
|
||||
{/* Number Circle */}
|
||||
<div className="w-16 h-16 bg-yellow-200 rounded-full flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-teal-700 font-bold text-xl">
|
||||
{item.itemNumber}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-3">
|
||||
{item.challengeTitle}
|
||||
</h3>
|
||||
<p className="text-base leading-relaxed text-gray-700">
|
||||
{item.challengeDescription}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Side - Image and Decorative Elements */}
|
||||
<div className="w-2/5 relative">
|
||||
{/* Decorative Circle */}
|
||||
{showVisualAccents && (
|
||||
<div className="absolute top-12 left-8 w-8 h-8 border-4 border-teal-600 rounded-full z-20"></div>
|
||||
)}
|
||||
|
||||
{/* Supporting Visual */}
|
||||
{supportingVisual?.__image_url__ && (
|
||||
<div className="absolute top-8 right-8 bottom-20 left-4 shadow-lg">
|
||||
<img
|
||||
src={supportingVisual.__image_url__}
|
||||
alt={supportingVisual.__image_prompt__}
|
||||
className="w-full h-full object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Teal Accent Block - Right Edge */}
|
||||
{showColorBlocks && (
|
||||
<div className="absolute top-0 right-0 bottom-0 w-16 bg-teal-600 z-10"></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Teal Stripe */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-4 bg-teal-600 z-5"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProblemsSlide;
|
||||
|
|
@ -1,208 +0,0 @@
|
|||
import * as z from "zod";
|
||||
|
||||
import { ImageSchema, IconSchema } from "../defaultSchemes";
|
||||
|
||||
export const layoutName = "Solutions Slide";
|
||||
export const layoutId = "solutions-slide";
|
||||
export const layoutDescription = "A slide with a list of solutions";
|
||||
|
||||
// Schema definition
|
||||
export const Schema = z.object({
|
||||
sectionTitle: z.string().min(3).max(25).default("OUR SOLUTIONS").meta({
|
||||
description:
|
||||
"Main section heading - can be 'Solutions', 'Our Approach', 'How We Help', or similar",
|
||||
}),
|
||||
|
||||
sectionSubtitle: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(50)
|
||||
.default("COMPREHENSIVE SOLUTIONS FOR YOUR NEEDS")
|
||||
.meta({
|
||||
description: "Supporting subtitle that frames the solution discussion",
|
||||
}),
|
||||
|
||||
solutionItems: z
|
||||
.array(
|
||||
z.object({
|
||||
itemNumber: z.string().min(1).max(3),
|
||||
solutionTitle: z.string().min(5).max(40),
|
||||
solutionDescription: z.string().min(20).max(200),
|
||||
})
|
||||
)
|
||||
.min(2)
|
||||
.max(3)
|
||||
.default([
|
||||
{
|
||||
itemNumber: "01",
|
||||
solutionTitle: "Process Optimization",
|
||||
solutionDescription:
|
||||
"Streamline workflows and implement efficient systems that reduce waste, improve productivity, and maximize resource utilization across all operational areas.",
|
||||
},
|
||||
{
|
||||
itemNumber: "02",
|
||||
solutionTitle: "Scalable Infrastructure",
|
||||
solutionDescription:
|
||||
"Build robust, flexible systems and methodologies that can grow with your organization, eliminating bottlenecks and supporting expansion efforts.",
|
||||
},
|
||||
{
|
||||
itemNumber: "03",
|
||||
solutionTitle: "Resource Management",
|
||||
solutionDescription:
|
||||
"Strategic allocation and optimization of available resources including time, budget, and personnel to achieve maximum impact and desired outcomes.",
|
||||
},
|
||||
])
|
||||
.meta({
|
||||
description:
|
||||
"List of key solutions or approaches with numbered identification and detailed descriptions",
|
||||
}),
|
||||
|
||||
primaryVisual: ImageSchema.default({
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1560472354-b33ff0c44a43?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
|
||||
__image_prompt__:
|
||||
"Modern workspace with team collaboration and strategic planning",
|
||||
}).meta({
|
||||
description:
|
||||
"Primary visual representing teamwork, strategy, or solution implementation",
|
||||
}),
|
||||
|
||||
brandingVisual: ImageSchema.default({
|
||||
__image_url__: "https://via.placeholder.com/150x80/22C55E/FFFFFF?text=LOGO",
|
||||
__image_prompt__: "Organization logo or brand mark",
|
||||
}).meta({
|
||||
description: "Logo or branding element to maintain visual identity",
|
||||
}),
|
||||
|
||||
showYellowUnderline: z.boolean().default(true).meta({
|
||||
description: "Whether to display the decorative yellow underline accent",
|
||||
}),
|
||||
|
||||
showVisualAccents: z.boolean().default(true).meta({
|
||||
description: "Whether to display decorative visual accent elements",
|
||||
}),
|
||||
|
||||
showColorBlocks: z.boolean().default(true).meta({
|
||||
description:
|
||||
"Whether to show colored background blocks for visual hierarchy",
|
||||
}),
|
||||
});
|
||||
|
||||
// Type inference
|
||||
type SchemaType = z.infer<typeof Schema>;
|
||||
|
||||
// Component definition
|
||||
const SolutionsSlide = ({ data }: { data: Partial<SchemaType> }) => {
|
||||
const {
|
||||
sectionTitle,
|
||||
sectionSubtitle,
|
||||
solutionItems,
|
||||
primaryVisual,
|
||||
brandingVisual,
|
||||
showYellowUnderline,
|
||||
showVisualAccents,
|
||||
showColorBlocks,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
|
||||
{/* Main Content Area */}
|
||||
<div className="h-full flex">
|
||||
{/* Left Side - Images and Branding */}
|
||||
<div className="w-2/5 relative bg-gray-100 flex flex-col">
|
||||
{/* Top Image Area */}
|
||||
{primaryVisual?.__image_url__ && (
|
||||
<div className="flex-1 relative">
|
||||
<img
|
||||
src={primaryVisual.__image_url__}
|
||||
alt={primaryVisual.__image_prompt__}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Bottom Branding Area */}
|
||||
<div className="h-24 bg-white flex items-center justify-center px-8">
|
||||
{brandingVisual?.__image_url__ && (
|
||||
<img
|
||||
src={brandingVisual.__image_url__}
|
||||
alt={brandingVisual.__image_prompt__}
|
||||
className="h-12 object-contain"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Side - Content */}
|
||||
<div className="w-3/5 relative bg-teal-600 px-16 py-12 flex flex-col justify-start">
|
||||
{/* Title Section */}
|
||||
<div className="mb-8">
|
||||
{sectionTitle && (
|
||||
<h1 className="text-3xl lg:text-4xl font-black text-white leading-tight mb-4">
|
||||
{sectionTitle}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{sectionSubtitle && (
|
||||
<p className="text-base font-semibold text-gray-100 tracking-wide mb-4">
|
||||
{sectionSubtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Yellow Decorative Underline */}
|
||||
{showYellowUnderline && (
|
||||
<div className="w-24 h-1 bg-yellow-300"></div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Solution Items List */}
|
||||
{solutionItems && solutionItems.length > 0 && (
|
||||
<div className="space-y-8">
|
||||
{solutionItems.map((item, index) => (
|
||||
<div key={index} className="flex items-start space-x-6">
|
||||
{/* Number Circle */}
|
||||
<div className="w-16 h-16 bg-yellow-300 rounded-full flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-teal-700 font-bold text-xl">
|
||||
{item.itemNumber}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-bold text-white mb-3">
|
||||
{item.solutionTitle}
|
||||
</h3>
|
||||
<p className="text-base leading-relaxed text-gray-100">
|
||||
{item.solutionDescription}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Visual Accents */}
|
||||
{showVisualAccents && (
|
||||
<>
|
||||
{/* Decorative circles */}
|
||||
<div className="absolute top-8 right-8 w-4 h-4 bg-yellow-300 rounded-full"></div>
|
||||
<div className="absolute bottom-16 right-12 w-3 h-3 bg-yellow-200 rounded-full"></div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Color blocks for visual hierarchy */}
|
||||
{showColorBlocks && (
|
||||
<>
|
||||
{/* Bottom accent strip */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-3 bg-yellow-300"></div>
|
||||
{/* Side accent */}
|
||||
<div className="absolute top-0 left-0 bottom-0 w-1 bg-yellow-400"></div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SolutionsSlide;
|
||||
|
|
@ -1,268 +0,0 @@
|
|||
import * as z from "zod";
|
||||
import { ImageSchema, IconSchema } from "../defaultSchemes";
|
||||
|
||||
export const layoutName = "Statistic Circular Slide";
|
||||
export const layoutId = "statistic-circular-slide";
|
||||
export const layoutDescription = "A slide with a circular statistic";
|
||||
|
||||
// Schema definition
|
||||
export const Schema = z.object({
|
||||
sectionTitle: z.string().min(3).max(20).default("CLIENT SATISFACTION").meta({
|
||||
description:
|
||||
"Main section heading - adapt to presentation topic (e.g., 'Climate Progress', 'Treatment Success', 'Learning Achievement', 'Project Completion')",
|
||||
}),
|
||||
|
||||
sectionSubtitle: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(35)
|
||||
.default("MEASURING OUR IMPACT AND SUCCESS")
|
||||
.meta({
|
||||
description:
|
||||
"Supporting subtitle that provides context - adapt to topic (e.g., 'Tracking Climate Action Progress', 'Monitoring Patient Recovery Rates', 'Assessing Educational Outcomes')",
|
||||
}),
|
||||
|
||||
description: z
|
||||
.string()
|
||||
.min(2)
|
||||
.max(230)
|
||||
.default(
|
||||
"At the heart of our success lies the unwavering satisfaction of our clients. We take pride in fostering lasting partnerships, consistently exceeding expectations, and delivering results that not only meet but surpass the unique objectives of each client we serve."
|
||||
)
|
||||
.meta({
|
||||
description: "Name of the organization or entity being measured",
|
||||
}),
|
||||
|
||||
brandLogo: ImageSchema.default({
|
||||
__image_url__: "https://via.placeholder.com/40x40/22C55E/FFFFFF?text=L",
|
||||
__image_prompt__:
|
||||
"Professional organization logo - clean and modern design",
|
||||
}).meta({
|
||||
description: "Logo or brand mark representing the organization",
|
||||
}),
|
||||
|
||||
satisfactionRate: z
|
||||
.object({
|
||||
value: z.number().min(0).max(100),
|
||||
label: z.string().min(5).max(30),
|
||||
percentage: z.string().min(2).max(5),
|
||||
})
|
||||
.default({
|
||||
value: 90,
|
||||
label: "CLIENT'S REPEAT ORDER",
|
||||
percentage: "90%",
|
||||
})
|
||||
.meta({
|
||||
description:
|
||||
"CRITICAL: Provide topic-specific circular progress metric. For global warming: {value: 33, label: 'CO2 REDUCTION ACHIEVED', percentage: '33%'} or {value: 78, label: 'RENEWABLE ENERGY ADOPTION', percentage: '78%'}. For healthcare: {value: 95, label: 'PATIENT RECOVERY RATE', percentage: '95%'} or {value: 87, label: 'TREATMENT SUCCESS RATE', percentage: '87%'}. For education: {value: 92, label: 'GRADUATION SUCCESS RATE', percentage: '92%'}. Use realistic percentages and meaningful labels.",
|
||||
}),
|
||||
|
||||
statisticBlocks: z
|
||||
.array(
|
||||
z.object({
|
||||
percentage: z.string().min(2).max(5),
|
||||
description: z.string().min(20).max(150),
|
||||
backgroundColor: z.enum(["teal", "beige"]),
|
||||
})
|
||||
)
|
||||
.min(2)
|
||||
.max(2)
|
||||
.default([
|
||||
{
|
||||
percentage: "90%",
|
||||
description:
|
||||
"Our client loyalty speaks volumes as evidenced by a robust repeat order rate",
|
||||
backgroundColor: "teal",
|
||||
},
|
||||
{
|
||||
percentage: "99%",
|
||||
description:
|
||||
"Our paramount focus on client satisfaction is the bedrock of our agency's success.",
|
||||
backgroundColor: "beige",
|
||||
},
|
||||
])
|
||||
.meta({
|
||||
description:
|
||||
"ESSENTIAL: Provide two topic-relevant supporting statistics. For global warming: [{percentage: '1.1°C', description: 'Global temperature increase since pre-industrial times represents urgent need for climate action', backgroundColor: 'teal'}, {percentage: '410ppm', description: 'Current atmospheric CO2 levels are the highest in human history requiring immediate intervention', backgroundColor: 'beige'}]. For healthcare: [{percentage: '85%', description: 'Early detection rates have improved significantly with advanced screening technologies', backgroundColor: 'teal'}, {percentage: '72h', description: 'Average patient response time demonstrates our commitment to rapid care delivery', backgroundColor: 'beige'}]. Always provide factual, impactful statistics.",
|
||||
}),
|
||||
|
||||
companyLogo: ImageSchema.default({
|
||||
__image_url__: "https://via.placeholder.com/40x40/FFFFFF/1D9A8A?text=C",
|
||||
__image_prompt__: "Clean modern company logo icon in white",
|
||||
}).meta({
|
||||
description: "Company logo icon",
|
||||
}),
|
||||
|
||||
companyName: z.string().min(2).max(25).default("Deskpro").meta({
|
||||
description: "Company name for branding",
|
||||
}),
|
||||
});
|
||||
|
||||
// Type inference
|
||||
type SchemaType = z.infer<typeof Schema>;
|
||||
|
||||
// Component definition
|
||||
const StatisticCircularSlide = ({ data }: { data: Partial<SchemaType> }) => {
|
||||
const {
|
||||
sectionTitle,
|
||||
sectionSubtitle,
|
||||
description,
|
||||
brandLogo,
|
||||
satisfactionRate,
|
||||
statisticBlocks,
|
||||
companyLogo,
|
||||
companyName,
|
||||
} = data;
|
||||
|
||||
const getBackgroundClass = (bg: string) => {
|
||||
switch (bg) {
|
||||
case "teal":
|
||||
return "bg-teal-600 text-white";
|
||||
case "beige":
|
||||
return "bg-yellow-200 text-gray-900";
|
||||
default:
|
||||
return "bg-gray-200 text-gray-900";
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate stroke dash array for circular progress
|
||||
const radius = 150;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
const strokeDasharray = circumference;
|
||||
const strokeDashoffset =
|
||||
circumference - (circumference * (satisfactionRate?.value || 90)) / 100;
|
||||
|
||||
return (
|
||||
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
|
||||
{/* Main Content Area */}
|
||||
<div className="h-full flex flex-col">
|
||||
{/* Header Section */}
|
||||
<div className="flex h-32">
|
||||
{/* Left - Title */}
|
||||
<div className="w-1/2 px-16 py-8 bg-gray-100 flex flex-col justify-center">
|
||||
{sectionTitle && (
|
||||
<h1 className="text-4xl lg:text-5xl font-black text-teal-700 leading-tight mb-2">
|
||||
{sectionTitle}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{sectionSubtitle && (
|
||||
<p className="text-base font-semibold text-gray-800 tracking-wide">
|
||||
{sectionSubtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right - Company Branding */}
|
||||
<div className="w-1/2 bg-teal-600 px-16 py-8 flex items-center justify-end">
|
||||
<div className="flex items-center space-x-3">
|
||||
{companyLogo?.__image_url__ && (
|
||||
<div className="w-10 h-10">
|
||||
<img
|
||||
src={companyLogo.__image_url__}
|
||||
alt={companyLogo.__image_prompt__}
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{companyName && (
|
||||
<span className="text-xl font-bold text-white">
|
||||
{companyName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content Section */}
|
||||
<div className="flex-1 flex">
|
||||
{/* Left Side - Circular Chart */}
|
||||
<div className="w-1/2 px-8 py-8 bg-gray-100 flex items-center justify-center">
|
||||
<div className="relative">
|
||||
{/* Circular Progress SVG */}
|
||||
<svg width="340" height="340" className="transform -rotate-90">
|
||||
{/* Background circle */}
|
||||
<circle
|
||||
cx="170"
|
||||
cy="170"
|
||||
r={radius}
|
||||
stroke="#E5E7EB"
|
||||
strokeWidth="25"
|
||||
fill="transparent"
|
||||
/>
|
||||
{/* Progress circle */}
|
||||
<circle
|
||||
cx="170"
|
||||
cy="170"
|
||||
r={radius}
|
||||
stroke="#1D9A8A"
|
||||
strokeWidth="25"
|
||||
fill="transparent"
|
||||
strokeDasharray={strokeDasharray}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
strokeLinecap="round"
|
||||
className="transition-all duration-1000 ease-out"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{/* Center Content */}
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
||||
{satisfactionRate?.label && (
|
||||
<p className="text-sm font-semibold text-gray-800 mb-4 text-center max-w-32 leading-tight">
|
||||
{satisfactionRate.label}
|
||||
</p>
|
||||
)}
|
||||
{satisfactionRate?.percentage && (
|
||||
<span className="text-7xl font-black text-teal-700">
|
||||
{satisfactionRate.percentage}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Side - Content and Statistics */}
|
||||
<div className="w-1/2 bg-white flex flex-col">
|
||||
{/* Description */}
|
||||
<div className="px-16 py-8 flex-1 flex items-start justify-center flex-col">
|
||||
{description && (
|
||||
<p className="text-base leading-relaxed text-gray-700">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Statistics Blocks */}
|
||||
{statisticBlocks && statisticBlocks.length > 0 && (
|
||||
<div className="px-16 pb-8 space-y-4">
|
||||
{statisticBlocks.map((block, index) => (
|
||||
<div key={index} className="flex items-stretch">
|
||||
{/* Percentage Block */}
|
||||
<div
|
||||
className={`w-24 h-20 ${getBackgroundClass(
|
||||
block.backgroundColor
|
||||
)} flex items-center justify-center flex-shrink-0`}
|
||||
>
|
||||
<span className="text-2xl font-black">
|
||||
{block.percentage}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Description Block */}
|
||||
<div className="flex-1 border-2 border-gray-900 p-4 h-20 flex items-center">
|
||||
<p className="text-sm leading-relaxed text-gray-900">
|
||||
{block.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatisticCircularSlide;
|
||||
|
|
@ -1,361 +0,0 @@
|
|||
import React from "react";
|
||||
import * as z from "zod";
|
||||
import { ImageSchema, IconSchema } from "../defaultSchemes";
|
||||
import {
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/components/ui/chart";
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
AreaChart,
|
||||
Area,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
} from "recharts";
|
||||
|
||||
export const layoutName = "Statistic Dual Chart Slide";
|
||||
export const layoutId = "statistic-dual-chart-slide";
|
||||
export const layoutDescription = "A slide with a statistic and a chart";
|
||||
|
||||
// Schema definition
|
||||
export const Schema = z.object({
|
||||
sectionTitle: z.string().min(3).max(30).default("PERFORMANCE METRICS").meta({
|
||||
description:
|
||||
"Main section heading - adapt to presentation topic (e.g., 'Climate Analysis', 'Health Outcomes', 'Research Data', 'Impact Assessment')",
|
||||
}),
|
||||
|
||||
organizationName: z
|
||||
.string()
|
||||
.min(2)
|
||||
.max(30)
|
||||
.default("Your Organization")
|
||||
.meta({
|
||||
description: "Name of the organization or entity presenting the data",
|
||||
}),
|
||||
|
||||
brandLogo: ImageSchema.default({
|
||||
__image_url__: "https://via.placeholder.com/40x40/22C55E/FFFFFF?text=L",
|
||||
__image_prompt__:
|
||||
"Professional organization logo - clean and modern design",
|
||||
}).meta({
|
||||
description: "Logo or brand mark representing the organization",
|
||||
}),
|
||||
|
||||
barChartData: z
|
||||
.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
series1: z.number(),
|
||||
series2: z.number(),
|
||||
series3: z.number(),
|
||||
})
|
||||
)
|
||||
.min(5)
|
||||
.max(5)
|
||||
.default([
|
||||
{ name: "Item 1", series1: 5, series2: 5, series3: 8 },
|
||||
{ name: "Item 2", series1: 8, series2: 8, series3: 15 },
|
||||
{ name: "Item 3", series1: 15, series2: 10, series3: 18 },
|
||||
{ name: "Item 4", series1: 18, series2: 14, series3: 22 },
|
||||
{ name: "Item 5", series1: 22, series2: 20, series3: 8 },
|
||||
])
|
||||
.meta({
|
||||
description:
|
||||
"CRITICAL: Provide topic-specific data for the left bar chart. For global warming: 5 years of data (2020-2024) with CO2 emissions by sector (Transport, Industry, Energy) with actual values. For healthcare: Patient outcomes across 5 categories (Prevention, Treatment, Recovery) with real percentages. For education: Student performance across 5 metrics (Reading, Math, Science) with grade levels. Use realistic data patterns and values.",
|
||||
}),
|
||||
|
||||
areaChartData: z
|
||||
.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
series1: z.number(),
|
||||
series2: z.number(),
|
||||
series3: z.number(),
|
||||
})
|
||||
)
|
||||
.min(5)
|
||||
.max(5)
|
||||
.default([
|
||||
{ name: "Item 1", series1: 20, series2: 30, series3: 15 },
|
||||
{ name: "Item 2", series1: 40, series2: 45, series3: 35 },
|
||||
{ name: "Item 3", series1: 45, series2: 50, series3: 80 },
|
||||
{ name: "Item 4", series1: 50, series2: 45, series3: 85 },
|
||||
{ name: "Item 5", series1: 80, series2: 75, series3: 120 },
|
||||
])
|
||||
.meta({
|
||||
description:
|
||||
"CRITICAL: Provide topic-specific data for the right area chart. For global warming: Cumulative data over 5 time periods showing renewable energy adoption, carbon reduction efforts, and policy implementations with realistic growth trends. For healthcare: Cumulative patient care metrics showing improvement over time. For education: Progressive learning outcomes showing student advancement. Ensure data shows meaningful trends relevant to the topic.",
|
||||
}),
|
||||
|
||||
leftChartTitle: z
|
||||
.string()
|
||||
.min(5)
|
||||
.max(40)
|
||||
.default("Our Customer's Satisfaction")
|
||||
.meta({
|
||||
description:
|
||||
"IMPORTANT: Provide topic-specific title for left chart. For global warming: 'Global CO2 Emissions by Sector', 'Temperature Rise by Region', 'Renewable Energy Adoption'. For healthcare: 'Patient Treatment Outcomes', 'Healthcare Quality Metrics', 'Recovery Success Rates'. For education: 'Student Performance by Subject', 'Learning Progress Assessment', 'Academic Achievement Trends'.",
|
||||
}),
|
||||
|
||||
leftChartDescription: z
|
||||
.string()
|
||||
.min(20)
|
||||
.max(200)
|
||||
.default(
|
||||
"An impressive client satisfaction rate underscores our unwavering commitment to delivering exceptional service and exceeding expectations."
|
||||
)
|
||||
.meta({
|
||||
description:
|
||||
"ESSENTIAL: Provide topic-relevant description explaining the left chart data. For global warming: Explain emission sources, trends, and implications. For healthcare: Describe treatment effectiveness and patient outcomes. For education: Explain performance metrics and learning indicators. Make it informative and specific to the data shown.",
|
||||
}),
|
||||
|
||||
rightChartTitle: z.string().min(5).max(40).default("Repeat Order Rate").meta({
|
||||
description:
|
||||
"IMPORTANT: Provide topic-specific title for right chart. For global warming: 'Climate Action Progress', 'Carbon Reduction Timeline', 'Sustainability Milestones'. For healthcare: 'Patient Recovery Timeline', 'Treatment Progress Tracking', 'Health Improvement Trajectory'. For education: 'Learning Progress Over Time', 'Student Development Path', 'Academic Growth Timeline'.",
|
||||
}),
|
||||
|
||||
rightChartDescription: z
|
||||
.string()
|
||||
.min(20)
|
||||
.max(200)
|
||||
.default(
|
||||
"Our remarkable client repeat order rate of 123 times are testament to the quality of our products/services and the trust our clients place in our ability."
|
||||
)
|
||||
.meta({
|
||||
description:
|
||||
"ESSENTIAL: Provide topic-relevant description explaining the right chart's cumulative/timeline data. For global warming: Describe progress in climate action, policy impact, or environmental improvements. For healthcare: Explain patient journey and recovery progression. For education: Describe learning advancement and skill development over time. Make it specific and data-driven.",
|
||||
}),
|
||||
});
|
||||
|
||||
// Chart configuration
|
||||
const chartConfig = {
|
||||
series1: {
|
||||
label: "Series 1",
|
||||
color: "#1D9A8A",
|
||||
},
|
||||
series2: {
|
||||
label: "Series 2",
|
||||
color: "#A8C97F",
|
||||
},
|
||||
series3: {
|
||||
label: "Series 3",
|
||||
color: "#E8F4B8",
|
||||
},
|
||||
};
|
||||
|
||||
// Type inference
|
||||
type SchemaType = z.infer<typeof Schema>;
|
||||
|
||||
// Component definition
|
||||
const StatisticDualChartSlide = ({ data }: { data: Partial<SchemaType> }) => {
|
||||
const {
|
||||
sectionTitle,
|
||||
organizationName,
|
||||
brandLogo,
|
||||
barChartData,
|
||||
areaChartData,
|
||||
leftChartTitle,
|
||||
leftChartDescription,
|
||||
rightChartTitle,
|
||||
rightChartDescription,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
|
||||
{/* Header Section */}
|
||||
<div className="h-20 bg-teal-600 px-16 py-4 flex justify-between items-center">
|
||||
{/* Title */}
|
||||
{sectionTitle && (
|
||||
<h1 className="text-4xl font-black text-white">{sectionTitle}</h1>
|
||||
)}
|
||||
|
||||
{/* Company Branding */}
|
||||
<div className="flex items-center space-x-3">
|
||||
{brandLogo?.__image_url__ && (
|
||||
<div className="w-8 h-8">
|
||||
<img
|
||||
src={brandLogo.__image_url__}
|
||||
alt={brandLogo.__image_prompt__}
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{organizationName && (
|
||||
<span className="text-lg font-bold text-white">
|
||||
{organizationName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content Section */}
|
||||
<div className="flex-1 h-[calc(100%-80px)] flex">
|
||||
{/* Left Chart Section */}
|
||||
<div className="w-1/2 p-8 bg-gray-50 flex flex-col">
|
||||
{/* Chart Legend */}
|
||||
<div className="flex items-center justify-start mb-4 space-x-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 bg-teal-600 rounded-full"></div>
|
||||
<span className="text-sm text-gray-600">Series 1</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 bg-green-400 rounded-full"></div>
|
||||
<span className="text-sm text-gray-600">Series 2</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 bg-yellow-200 rounded-full"></div>
|
||||
<span className="text-sm text-gray-600">Series 3</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bar Chart */}
|
||||
{barChartData && barChartData.length > 0 && (
|
||||
<div className="flex-1 mb-6">
|
||||
<ChartContainer config={chartConfig} className="h-full w-full">
|
||||
<BarChart
|
||||
data={barChartData}
|
||||
margin={{ top: 10, right: 20, left: 0, bottom: 30 }}
|
||||
barCategoryGap="20%"
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fontSize: 12, fill: "#666" }}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fontSize: 12, fill: "#666" }}
|
||||
/>
|
||||
<ChartTooltip content={<ChartTooltipContent />} />
|
||||
<Bar
|
||||
dataKey="series1"
|
||||
fill="#1D9A8A"
|
||||
radius={[2, 2, 0, 0]}
|
||||
barSize={15}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="series2"
|
||||
fill="#A8C97F"
|
||||
radius={[2, 2, 0, 0]}
|
||||
barSize={15}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="series3"
|
||||
fill="#E8F4B8"
|
||||
radius={[2, 2, 0, 0]}
|
||||
barSize={15}
|
||||
/>
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Chart Description */}
|
||||
<div className="space-y-3">
|
||||
{leftChartTitle && (
|
||||
<h3 className="text-xl font-bold text-gray-900">
|
||||
{leftChartTitle}
|
||||
</h3>
|
||||
)}
|
||||
{leftChartDescription && (
|
||||
<p className="text-base leading-relaxed text-gray-700">
|
||||
{leftChartDescription}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Chart Section */}
|
||||
<div className="w-1/2 p-8 bg-white flex flex-col">
|
||||
{/* Chart Legend */}
|
||||
<div className="flex items-center justify-end mb-4 space-x-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 bg-yellow-200 rounded-full"></div>
|
||||
<span className="text-sm text-gray-600">Series 1</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 bg-green-400 rounded-full"></div>
|
||||
<span className="text-sm text-gray-600">Series 2</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 bg-teal-600 rounded-full"></div>
|
||||
<span className="text-sm text-gray-600">Series 3</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Area Chart */}
|
||||
{areaChartData && areaChartData.length > 0 && (
|
||||
<div className="flex-1 mb-6">
|
||||
<ChartContainer config={chartConfig} className="h-full w-full">
|
||||
<AreaChart
|
||||
data={areaChartData}
|
||||
margin={{ top: 10, right: 20, left: 0, bottom: 30 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fontSize: 12, fill: "#666" }}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fontSize: 12, fill: "#666" }}
|
||||
/>
|
||||
<ChartTooltip content={<ChartTooltipContent />} />
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="series3"
|
||||
stackId="1"
|
||||
stroke="#1D9A8A"
|
||||
fill="#1D9A8A"
|
||||
fillOpacity={0.8}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="series2"
|
||||
stackId="1"
|
||||
stroke="#A8C97F"
|
||||
fill="#A8C97F"
|
||||
fillOpacity={0.8}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="series1"
|
||||
stackId="1"
|
||||
stroke="#E8F4B8"
|
||||
fill="#E8F4B8"
|
||||
fillOpacity={0.8}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Chart Description */}
|
||||
<div className="space-y-3">
|
||||
{rightChartTitle && (
|
||||
<h3 className="text-xl font-bold text-gray-900">
|
||||
{rightChartTitle}
|
||||
</h3>
|
||||
)}
|
||||
{rightChartDescription && (
|
||||
<p className="text-base leading-relaxed text-gray-700">
|
||||
{rightChartDescription}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatisticDualChartSlide;
|
||||
|
|
@ -1,301 +0,0 @@
|
|||
import React from "react";
|
||||
import * as z from "zod";
|
||||
import { ImageSchema, IconSchema } from "../defaultSchemes";
|
||||
import {
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/components/ui/chart";
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid } from "recharts";
|
||||
|
||||
export const layoutName = "Statistic Slide";
|
||||
export const layoutId = "statistic-slide";
|
||||
export const layoutDescription = "A slide with a statistic";
|
||||
|
||||
// Schema definition
|
||||
export const Schema = z.object({
|
||||
sectionTitle: z.string().min(3).max(30).default("KEY STATISTICS").meta({
|
||||
description:
|
||||
"Main section heading - adapt to presentation topic (e.g., 'Climate Data', 'Health Metrics', 'Performance Stats', 'Research Findings')",
|
||||
}),
|
||||
|
||||
sectionSubtitle: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(60)
|
||||
.default("DATA-DRIVEN INSIGHTS AND PERFORMANCE")
|
||||
.meta({
|
||||
description:
|
||||
"Supporting subtitle that frames the data - adapt to topic (e.g., 'Global Temperature Trends and Impact', 'Patient Outcomes and Recovery Rates', 'Student Achievement and Progress')",
|
||||
}),
|
||||
|
||||
statisticValue: z.string().min(1).max(15).default("85%").meta({
|
||||
description:
|
||||
"CRITICAL: Provide the most important statistic for the topic. For global warming: '1.1°C', '+2.1°C', '410ppm', '33%'. For healthcare: '95%', '72 hours', '89%'. For education: '78%', '3.2 GPA', '92%'. Use real, impactful numbers relevant to the presentation topic.",
|
||||
}),
|
||||
|
||||
statisticLabel: z
|
||||
.string()
|
||||
.min(5)
|
||||
.max(40)
|
||||
.default("Client Satisfaction Rate")
|
||||
.meta({
|
||||
description:
|
||||
"IMPORTANT: Provide topic-specific label for the main statistic. For global warming: 'Global Temperature Rise Since 1880', 'CO2 Concentration Increase', 'Arctic Ice Loss Rate'. For healthcare: 'Patient Recovery Rate', 'Treatment Success Rate', 'Early Detection Rate'. For education: 'Graduation Success Rate', 'Student Engagement Level', 'Learning Improvement Rate'.",
|
||||
}),
|
||||
|
||||
supportingVisual: ImageSchema.default({
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1460925895917-afdab827c52f?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
|
||||
__image_prompt__:
|
||||
"Business analytics dashboard with charts and data visualization",
|
||||
}).meta({
|
||||
description:
|
||||
"ADAPT the image prompt to match the presentation topic: For global warming: 'Climate monitoring station with temperature sensors and weather equipment', 'Scientists analyzing ice core data in Arctic research facility'. For healthcare: 'Medical monitoring equipment displaying patient vital signs', 'Healthcare analytics dashboard showing treatment outcomes'. For education: 'Educational assessment data on computer screens', 'Students using digital learning platforms'.",
|
||||
}),
|
||||
|
||||
bulletPoints: z
|
||||
.array(z.string().min(10).max(100))
|
||||
.min(2)
|
||||
.max(5)
|
||||
.default([
|
||||
"Consistent performance improvement over 12 months",
|
||||
"High customer retention and satisfaction scores",
|
||||
"Measurable ROI across all key performance indicators",
|
||||
"Data-driven decision making and strategic optimization",
|
||||
])
|
||||
.meta({
|
||||
description:
|
||||
"ESSENTIAL: Provide topic-relevant supporting facts and insights. For global warming: 'Global average temperature has risen 1.1°C since pre-industrial times', 'Arctic sea ice is declining at 13% per decade', 'CO2 levels are highest in 3 million years', 'Renewable energy adoption increased 85% in last decade'. For healthcare: 'Early detection improves survival rates by 85%', 'Telemedicine reduced patient wait times by 60%', 'Preventive care decreased hospital readmissions by 40%'. Always provide factual, verifiable statements related to the presentation topic.",
|
||||
}),
|
||||
|
||||
chartData: z
|
||||
.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
series1: z.number(),
|
||||
series2: z.number(),
|
||||
series3: z.number(),
|
||||
})
|
||||
)
|
||||
.min(5)
|
||||
.max(5)
|
||||
.default([
|
||||
{ name: "Jan", series1: 18, series2: 0, series3: 0 },
|
||||
{ name: "Feb", series1: 30, series2: 12, series3: 8 },
|
||||
{ name: "Mar", series1: 26, series2: 38, series3: 20 },
|
||||
{ name: "Apr", series1: 40, series2: 30, series3: 35 },
|
||||
{ name: "May", series1: 42, series2: 45, series3: 32 },
|
||||
])
|
||||
.meta({
|
||||
description:
|
||||
"CRITICAL: Provide topic-specific time-series data for line chart. For global warming: Monthly temperature anomalies, CO2 levels, ice coverage data with realistic values. For healthcare: Patient recovery rates, treatment success metrics, diagnostic accuracy over time. For education: Student performance trends, learning progress, engagement metrics. Use realistic data patterns showing meaningful trends.",
|
||||
}),
|
||||
|
||||
showYellowUnderline: z.boolean().default(true).meta({
|
||||
description: "Whether to display the decorative yellow underline accent",
|
||||
}),
|
||||
|
||||
showVisualAccents: z.boolean().default(true).meta({
|
||||
description: "Whether to display decorative visual accent elements",
|
||||
}),
|
||||
});
|
||||
|
||||
// Chart configuration
|
||||
const chartConfig = {
|
||||
series1: {
|
||||
label: "Series 1",
|
||||
color: "#1D9A8A",
|
||||
},
|
||||
series2: {
|
||||
label: "Series 2",
|
||||
color: "#A8C97F",
|
||||
},
|
||||
series3: {
|
||||
label: "Series 3",
|
||||
color: "#E8F4B8",
|
||||
},
|
||||
};
|
||||
|
||||
// Type inference
|
||||
type SchemaType = z.infer<typeof Schema>;
|
||||
|
||||
// Component definition
|
||||
const StatisticSlide = ({ data }: { data: Partial<SchemaType> }) => {
|
||||
const {
|
||||
sectionTitle,
|
||||
sectionSubtitle,
|
||||
statisticValue,
|
||||
statisticLabel,
|
||||
supportingVisual,
|
||||
bulletPoints,
|
||||
chartData,
|
||||
showYellowUnderline,
|
||||
showVisualAccents,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
|
||||
{/* Main Content Area */}
|
||||
<div className="h-full flex">
|
||||
{/* Left Side - Teal Background */}
|
||||
<div className="w-1/2 relative bg-teal-600 px-16 py-12 flex flex-col text-white">
|
||||
{/* Title Section */}
|
||||
<div className="mb-8">
|
||||
{sectionTitle && (
|
||||
<h1 className="text-3xl lg:text-4xl font-black leading-tight mb-4">
|
||||
{sectionTitle}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{sectionSubtitle && (
|
||||
<p className="text-base font-semibold tracking-wide mb-4">
|
||||
{sectionSubtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Yellow Decorative Underline */}
|
||||
{showYellowUnderline && (
|
||||
<div className="w-24 h-1 bg-yellow-300 mb-8"></div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Large Statistic Display */}
|
||||
<div className="mb-8">
|
||||
{statisticValue && (
|
||||
<div className="text-8xl font-black text-yellow-300 mb-4">
|
||||
{statisticValue}
|
||||
</div>
|
||||
)}
|
||||
{statisticLabel && (
|
||||
<h2 className="text-2xl font-bold">{statisticLabel}</h2>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Business Image */}
|
||||
{supportingVisual?.__image_url__ && (
|
||||
<div className="flex-1 flex items-end">
|
||||
<div className="w-full h-48">
|
||||
<img
|
||||
src={supportingVisual.__image_url__}
|
||||
alt={supportingVisual.__image_prompt__}
|
||||
className="w-full h-full object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Visual Accents */}
|
||||
{showVisualAccents && (
|
||||
<>
|
||||
<div className="absolute top-8 right-8 w-6 h-6 bg-yellow-300 rounded-full"></div>
|
||||
<div className="absolute bottom-12 left-8 w-4 h-4 bg-yellow-200 rounded-full"></div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Side - White Background with Chart and Bullet Points */}
|
||||
<div className="w-1/2 relative bg-white px-16 py-12">
|
||||
{/* Chart Section */}
|
||||
<div className="flex-1 px-8 pt-8">
|
||||
{/* Chart Legend */}
|
||||
<div className="flex items-center justify-end mb-4 space-x-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 bg-yellow-300 rounded-full"></div>
|
||||
<span className="text-sm text-gray-600">Series 1</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 bg-gray-300 rounded-full"></div>
|
||||
<span className="text-sm text-gray-600">Series 2</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 bg-teal-600 rounded-full"></div>
|
||||
<span className="text-sm text-gray-600">Series 3</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chart Container */}
|
||||
{chartData && chartData.length > 0 && (
|
||||
<div className="h-64">
|
||||
<ChartContainer config={chartConfig} className="h-full w-full">
|
||||
<LineChart
|
||||
data={chartData}
|
||||
margin={{ top: 10, right: 20, left: 0, bottom: 30 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fontSize: 12, fill: "#666" }}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fontSize: 12, fill: "#666" }}
|
||||
/>
|
||||
<ChartTooltip content={<ChartTooltipContent />} />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="series1"
|
||||
stroke="#E8F4B8"
|
||||
strokeWidth={3}
|
||||
dot={{ fill: "#E8F4B8", strokeWidth: 2, r: 4 }}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="series2"
|
||||
stroke="#A8C97F"
|
||||
strokeWidth={3}
|
||||
dot={{ fill: "#A8C97F", strokeWidth: 2, r: 4 }}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="series3"
|
||||
stroke="#1D9A8A"
|
||||
strokeWidth={3}
|
||||
dot={{ fill: "#1D9A8A", strokeWidth: 2, r: 4 }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Bullet Points Section */}
|
||||
<div className="px-8 pb-6 space-y-4 mt-10">
|
||||
{bulletPoints && bulletPoints.length > 0 && (
|
||||
<>
|
||||
{bulletPoints.map((point, index) => {
|
||||
// Rotate colors for visual variety
|
||||
const colors = [
|
||||
"bg-teal-600",
|
||||
"bg-yellow-300",
|
||||
"bg-gray-400",
|
||||
];
|
||||
const dotColor = colors[index % colors.length];
|
||||
|
||||
return (
|
||||
<div key={index} className="flex items-start space-x-4">
|
||||
<div
|
||||
className={`w-6 h-6 ${dotColor} rounded-full flex-shrink-0 mt-1`}
|
||||
></div>
|
||||
<p className="text-base leading-relaxed text-gray-700">
|
||||
{point}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom accent strip */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-3 bg-yellow-300"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatisticSlide;
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
import * as z from "zod";
|
||||
|
||||
import { ImageSchema, IconSchema } from "../defaultSchemes";
|
||||
|
||||
export const layoutName = "Table of Contents Slide";
|
||||
export const layoutId = "table-of-contents-slide";
|
||||
export const layoutDescription = "A slide with a table of contents";
|
||||
|
||||
// Schema definition
|
||||
export const Schema = z.object({
|
||||
sectionTitle: z.string().min(3).max(30).default("TABLE OF CONTENTS").meta({
|
||||
description:
|
||||
"Main heading for the content overview - can be 'Agenda', 'Overview', 'Contents', or similar",
|
||||
}),
|
||||
|
||||
sectionSubtitle: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(60)
|
||||
.default("PRESENTATION OVERVIEW AND AGENDA")
|
||||
.meta({
|
||||
description:
|
||||
"Supporting subtitle that explains what the audience will learn or see",
|
||||
}),
|
||||
|
||||
contentItems: z
|
||||
.array(
|
||||
z.object({
|
||||
itemNumber: z.string().min(1).max(3),
|
||||
contentTitle: z.string().min(3).max(40),
|
||||
contentDescription: z.string().min(10).max(100).optional(),
|
||||
})
|
||||
)
|
||||
.min(3)
|
||||
.max(8)
|
||||
.default([
|
||||
{
|
||||
itemNumber: "01",
|
||||
contentTitle: "Introduction & Welcome",
|
||||
contentDescription: "Brief overview and objectives",
|
||||
},
|
||||
{
|
||||
itemNumber: "02",
|
||||
contentTitle: "About Our Organization",
|
||||
contentDescription: "Background and mission",
|
||||
},
|
||||
{
|
||||
itemNumber: "03",
|
||||
contentTitle: "Key Challenges",
|
||||
contentDescription: "Current issues and opportunities",
|
||||
},
|
||||
{
|
||||
itemNumber: "04",
|
||||
contentTitle: "Our Solutions",
|
||||
contentDescription: "Proposed approaches and methods",
|
||||
},
|
||||
{
|
||||
itemNumber: "05",
|
||||
contentTitle: "Implementation Plan",
|
||||
contentDescription: "Timeline and next steps",
|
||||
},
|
||||
{
|
||||
itemNumber: "06",
|
||||
contentTitle: "Questions & Discussion",
|
||||
contentDescription: "Interactive engagement",
|
||||
},
|
||||
])
|
||||
.meta({
|
||||
description:
|
||||
"List of presentation sections with numbered sequence and brief descriptions",
|
||||
}),
|
||||
|
||||
brandingVisual: ImageSchema.default({
|
||||
__image_url__:
|
||||
"https://via.placeholder.com/200x100/22C55E/FFFFFF?text=BRAND",
|
||||
__image_prompt__: "Organization logo or brand visual element",
|
||||
}).meta({
|
||||
description:
|
||||
"Logo or branding element displayed prominently for visual identity",
|
||||
}),
|
||||
|
||||
showDecorations: z.boolean().default(true).meta({
|
||||
description:
|
||||
"Whether to display decorative visual elements like underlines and accents",
|
||||
}),
|
||||
|
||||
useColumnLayout: z.boolean().default(true).meta({
|
||||
description:
|
||||
"Whether to arrange content items in two columns for better space utilization",
|
||||
}),
|
||||
});
|
||||
|
||||
// Type inference
|
||||
type SchemaType = z.infer<typeof Schema>;
|
||||
|
||||
// Component definition
|
||||
const TableOfContentsSlide = ({ data }: { data: Partial<SchemaType> }) => {
|
||||
const {
|
||||
sectionTitle,
|
||||
sectionSubtitle,
|
||||
contentItems,
|
||||
brandingVisual,
|
||||
showDecorations,
|
||||
useColumnLayout,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
|
||||
{/* Main Content Area */}
|
||||
<div className="h-full flex">
|
||||
{/* Left Side - Content */}
|
||||
<div className="w-3/5 px-16 py-12 flex flex-col justify-start">
|
||||
{/* Title Section */}
|
||||
<div className="mb-12">
|
||||
{sectionTitle && (
|
||||
<h1 className="text-3xl lg:text-4xl font-black text-teal-700 leading-tight mb-4">
|
||||
{sectionTitle}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{/* Decorative underline */}
|
||||
{showDecorations && (
|
||||
<div className="w-32 h-1 bg-yellow-300 mb-6"></div>
|
||||
)}
|
||||
|
||||
{sectionSubtitle && (
|
||||
<p className="text-base font-medium text-gray-700 tracking-wide">
|
||||
{sectionSubtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content Items */}
|
||||
{contentItems && contentItems.length > 0 && (
|
||||
<div
|
||||
className={`grid ${
|
||||
useColumnLayout ? "grid-cols-2" : "grid-cols-1"
|
||||
} gap-x-16 gap-y-8`}
|
||||
>
|
||||
{contentItems.map((item, index) => (
|
||||
<div key={index} className="flex items-start space-x-4">
|
||||
{/* Number Circle */}
|
||||
<div className="w-10 h-10 bg-yellow-200 rounded-full flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-teal-700 font-bold text-sm">
|
||||
{item.itemNumber}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-1">
|
||||
{item.contentTitle}
|
||||
</h3>
|
||||
{item.contentDescription && (
|
||||
<p className="text-sm text-gray-600 leading-relaxed">
|
||||
{item.contentDescription}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Side - Branding and Visual Elements */}
|
||||
<div className="w-2/5 relative bg-gray-50 flex items-center justify-center">
|
||||
{/* Branding Visual */}
|
||||
{brandingVisual?.__image_url__ && (
|
||||
<div className="text-center">
|
||||
<img
|
||||
src={brandingVisual.__image_url__}
|
||||
alt={brandingVisual.__image_prompt__}
|
||||
className="max-w-64 max-h-32 object-contain mx-auto"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Decorative Elements */}
|
||||
{showDecorations && (
|
||||
<>
|
||||
{/* Decorative circles */}
|
||||
<div className="absolute top-8 right-8 w-6 h-6 bg-teal-600 rounded-full opacity-60"></div>
|
||||
<div className="absolute bottom-12 left-8 w-4 h-4 bg-yellow-300 rounded-full"></div>
|
||||
<div className="absolute top-32 left-12 w-3 h-3 bg-teal-400 rounded-full"></div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom accent strip */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-2 bg-teal-600"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableOfContentsSlide;
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
import * as z from "zod";
|
||||
|
||||
import { ImageSchema, IconSchema } from "../defaultSchemes";
|
||||
|
||||
export const layoutName = "Testimonial Slide";
|
||||
export const layoutId = "testimonial-slide";
|
||||
export const layoutDescription = "A slide with a testimonial";
|
||||
|
||||
// Schema definition
|
||||
export const Schema = z.object({
|
||||
sectionTitle: z.string().min(3).max(30).default("CLIENT TESTIMONIALS").meta({
|
||||
description:
|
||||
"Main section heading - can be 'Testimonials', 'Client Feedback', 'Reviews', or similar",
|
||||
}),
|
||||
|
||||
organizationName: z
|
||||
.string()
|
||||
.min(2)
|
||||
.max(30)
|
||||
.default("Your Organization")
|
||||
.meta({
|
||||
description: "Name of the organization or entity being featured",
|
||||
}),
|
||||
|
||||
brandLogo: ImageSchema.default({
|
||||
__image_url__: "https://via.placeholder.com/40x40/22C55E/FFFFFF?text=L",
|
||||
__image_prompt__:
|
||||
"Professional organization logo - clean and modern design",
|
||||
}).meta({
|
||||
description: "Logo or brand mark representing the organization",
|
||||
}),
|
||||
|
||||
testimonialItems: z
|
||||
.array(
|
||||
z.object({
|
||||
clientName: z.string().min(2).max(40),
|
||||
clientTitle: z.string().min(5).max(60),
|
||||
clientCompany: z.string().min(2).max(40),
|
||||
testimonialText: z.string().min(50).max(300),
|
||||
rating: z.number().min(1).max(5),
|
||||
clientPhoto: ImageSchema,
|
||||
})
|
||||
)
|
||||
.min(2)
|
||||
.max(3)
|
||||
.default([
|
||||
{
|
||||
clientName: "Sarah Johnson",
|
||||
clientTitle: "Chief Executive Officer",
|
||||
clientCompany: "TechCorp Solutions",
|
||||
testimonialText:
|
||||
"Working with this team has been transformative for our business. Their expertise, dedication, and innovative approach exceeded our expectations and delivered remarkable results.",
|
||||
rating: 5,
|
||||
clientPhoto: {
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1494790108755-2616b612b830?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80",
|
||||
__image_prompt__: "Professional businesswoman headshot",
|
||||
},
|
||||
},
|
||||
{
|
||||
clientName: "Michael Chen",
|
||||
clientTitle: "Director of Operations",
|
||||
clientCompany: "Global Innovations Inc",
|
||||
testimonialText:
|
||||
"The level of professionalism and quality of service provided was outstanding. They understood our needs perfectly and delivered solutions that truly made a difference.",
|
||||
rating: 5,
|
||||
clientPhoto: {
|
||||
__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 headshot",
|
||||
},
|
||||
},
|
||||
{
|
||||
clientName: "Emily Rodriguez",
|
||||
clientTitle: "Marketing Manager",
|
||||
clientCompany: "Creative Dynamics",
|
||||
testimonialText:
|
||||
"Exceptional service and results that spoke for themselves. The team's attention to detail and commitment to excellence made our collaboration highly successful.",
|
||||
rating: 5,
|
||||
clientPhoto: {
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1580489944761-15a19d654956?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80",
|
||||
__image_prompt__: "Professional woman headshot",
|
||||
},
|
||||
},
|
||||
])
|
||||
.meta({
|
||||
description:
|
||||
"List of client testimonials with ratings, photos, and detailed feedback",
|
||||
}),
|
||||
|
||||
showRatings: z.boolean().default(true).meta({
|
||||
description: "Whether to display star ratings for each testimonial",
|
||||
}),
|
||||
|
||||
showClientPhotos: z.boolean().default(true).meta({
|
||||
description: "Whether to show client photos alongside testimonials",
|
||||
}),
|
||||
});
|
||||
|
||||
// Type inference
|
||||
type SchemaType = z.infer<typeof Schema>;
|
||||
|
||||
// Component definition
|
||||
const TestimonialSlide = ({ data }: { data: Partial<SchemaType> }) => {
|
||||
const {
|
||||
sectionTitle,
|
||||
organizationName,
|
||||
brandLogo,
|
||||
testimonialItems,
|
||||
showRatings,
|
||||
showClientPhotos,
|
||||
} = data;
|
||||
|
||||
// Helper function to render stars
|
||||
const renderStars = (rating: number) => {
|
||||
return Array.from({ length: 5 }, (_, i) => (
|
||||
<svg
|
||||
key={i}
|
||||
className={`w-4 h-4 ${
|
||||
i < rating ? "text-yellow-400" : "text-gray-300"
|
||||
}`}
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||||
</svg>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
|
||||
{/* Header Section */}
|
||||
<div className="h-20 bg-teal-600 px-16 py-4 flex justify-between items-center">
|
||||
{/* Title */}
|
||||
{sectionTitle && (
|
||||
<h1 className="text-2xl font-black text-white">{sectionTitle}</h1>
|
||||
)}
|
||||
|
||||
{/* Company Branding */}
|
||||
<div className="flex items-center space-x-3">
|
||||
{brandLogo?.__image_url__ && (
|
||||
<div className="w-8 h-8">
|
||||
<img
|
||||
src={brandLogo.__image_url__}
|
||||
alt={brandLogo.__image_prompt__}
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{organizationName && (
|
||||
<span className="text-base font-bold text-white">
|
||||
{organizationName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Testimonials Content */}
|
||||
<div className="flex-1 px-16 py-12 bg-gray-50">
|
||||
{testimonialItems && testimonialItems.length > 0 && (
|
||||
<div className="grid grid-cols-3 gap-8 h-full">
|
||||
{testimonialItems.slice(0, 3).map((item, index) => {
|
||||
// Rotate background colors for visual variety
|
||||
const bgColors = ["bg-yellow-100", "bg-teal-100", "bg-gray-100"];
|
||||
const bgColor = bgColors[index % bgColors.length];
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={`${bgColor} rounded-lg p-6 flex flex-col`}
|
||||
>
|
||||
{/* Stars Rating */}
|
||||
{showRatings && (
|
||||
<div className="flex space-x-1 mb-4">
|
||||
{renderStars(item.rating)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Testimonial Text */}
|
||||
<p className="text-base leading-relaxed text-gray-800 mb-6 flex-1">
|
||||
"{item.testimonialText}"
|
||||
</p>
|
||||
|
||||
{/* Client Info */}
|
||||
<div className="flex items-center space-x-4">
|
||||
{/* Client Photo */}
|
||||
{showClientPhotos && item.clientPhoto?.__image_url__ && (
|
||||
<div className="w-12 h-12 rounded-full overflow-hidden flex-shrink-0">
|
||||
<img
|
||||
src={item.clientPhoto.__image_url__}
|
||||
alt={item.clientPhoto.__image_prompt__}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Client Details */}
|
||||
<div className="flex-1">
|
||||
<h4 className="text-lg font-bold text-gray-900">
|
||||
{item.clientName}
|
||||
</h4>
|
||||
<p className="text-sm text-gray-600">
|
||||
{item.clientTitle}
|
||||
</p>
|
||||
<p className="text-sm font-semibold text-teal-600">
|
||||
{item.clientCompany}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TestimonialSlide;
|
||||
|
|
@ -1,229 +0,0 @@
|
|||
import * as z from "zod";
|
||||
|
||||
import { ImageSchema, IconSchema } from "../defaultSchemes";
|
||||
|
||||
export const layoutName = "Thank You Slide";
|
||||
export const layoutId = "thank-you-slide";
|
||||
export const layoutDescription = "A slide with a thank you message";
|
||||
|
||||
// Schema definition
|
||||
export const Schema = z.object({
|
||||
organizationName: z
|
||||
.string()
|
||||
.min(2)
|
||||
.max(30)
|
||||
.default("Your Organization")
|
||||
.meta({
|
||||
description: "Name of the organization, company, or entity presenting",
|
||||
}),
|
||||
|
||||
primaryMessage: z.string().min(3).max(25).default("THANK YOU").meta({
|
||||
description:
|
||||
"Main closing message - can be 'Thank You', 'Questions?', 'Let's Connect', or similar",
|
||||
}),
|
||||
|
||||
secondaryMessage: z
|
||||
.string()
|
||||
.min(5)
|
||||
.max(60)
|
||||
.default("FOR YOUR TIME AND ATTENTION")
|
||||
.meta({
|
||||
description:
|
||||
"Supporting message that completes the primary message or adds context",
|
||||
}),
|
||||
|
||||
brandLogo: ImageSchema.default({
|
||||
__image_url__: "https://via.placeholder.com/40x40/22C55E/FFFFFF?text=L",
|
||||
__image_prompt__:
|
||||
"Professional organization logo - clean and modern design",
|
||||
}).meta({
|
||||
description: "Logo or brand mark representing the presenting organization",
|
||||
}),
|
||||
|
||||
contactDetails: z
|
||||
.object({
|
||||
phoneNumber: z.string().min(10).max(20).default("+1-234-567-8900"),
|
||||
physicalAddress: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(60)
|
||||
.default("123 Business Ave, City, State 12345"),
|
||||
websiteUrl: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(40)
|
||||
.default("www.yourorganization.com"),
|
||||
})
|
||||
.default({
|
||||
phoneNumber: "+1-234-567-8900",
|
||||
physicalAddress: "123 Business Ave, City, State 12345",
|
||||
websiteUrl: "www.yourorganization.com",
|
||||
})
|
||||
.meta({
|
||||
description:
|
||||
"Contact information for follow-up communication and connection",
|
||||
}),
|
||||
|
||||
presentationDate: z
|
||||
.string()
|
||||
.min(3)
|
||||
.max(20)
|
||||
.default("Current Month Year")
|
||||
.meta({
|
||||
description:
|
||||
"Date when the presentation was given or document was created",
|
||||
}),
|
||||
|
||||
showDecorations: z.boolean().default(true).meta({
|
||||
description:
|
||||
"Whether to display decorative visual elements like background shapes",
|
||||
}),
|
||||
|
||||
showNavigationArrow: z.boolean().default(true).meta({
|
||||
description:
|
||||
"Whether to show a navigation arrow for interactive presentations",
|
||||
}),
|
||||
|
||||
showContactInfo: z.boolean().default(true).meta({
|
||||
description: "Whether to display contact information in the footer",
|
||||
}),
|
||||
});
|
||||
|
||||
// Type inference
|
||||
type SchemaType = z.infer<typeof Schema>;
|
||||
|
||||
// Component definition
|
||||
const ThankYouSlide = ({ data }: { data: Partial<SchemaType> }) => {
|
||||
const {
|
||||
organizationName,
|
||||
primaryMessage,
|
||||
secondaryMessage,
|
||||
brandLogo,
|
||||
contactDetails,
|
||||
presentationDate,
|
||||
showDecorations,
|
||||
showNavigationArrow,
|
||||
showContactInfo,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
|
||||
{/* Header with Logo and Arrow */}
|
||||
<div className="absolute top-0 left-0 right-0 px-16 py-8 flex justify-between items-center z-20">
|
||||
{/* Company Logo and Name */}
|
||||
<div className="flex items-center space-x-3">
|
||||
{brandLogo?.__image_url__ && (
|
||||
<div className="w-10 h-10">
|
||||
<img
|
||||
src={brandLogo.__image_url__}
|
||||
alt={brandLogo.__image_prompt__}
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{organizationName && (
|
||||
<span className="text-2xl font-bold text-gray-900">
|
||||
{organizationName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Arrow Button */}
|
||||
{showNavigationArrow && (
|
||||
<div className="w-12 h-12 bg-teal-600 rounded-full flex items-center justify-center">
|
||||
<svg
|
||||
className="w-6 h-6 text-white"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Decorative Circle */}
|
||||
{showDecorations && (
|
||||
<div className="absolute top-20 right-16 w-96 h-96 bg-yellow-100 rounded-full opacity-60 z-10"></div>
|
||||
)}
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="relative z-15 h-full flex flex-col justify-center px-16">
|
||||
<div className="max-w-4xl">
|
||||
{/* Main Title */}
|
||||
{primaryMessage && (
|
||||
<h1 className="text-8xl lg:text-9xl font-black text-teal-700 leading-none tracking-tight mb-4">
|
||||
{primaryMessage}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{/* Subtitle with Circle Bullet */}
|
||||
{secondaryMessage && (
|
||||
<div className="flex items-center space-x-4 mb-12">
|
||||
<div className="w-4 h-4 bg-teal-600 rounded-full"></div>
|
||||
<h2 className="text-2xl font-bold text-gray-800 tracking-wide">
|
||||
{secondaryMessage}
|
||||
</h2>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer with Contact Info */}
|
||||
{showContactInfo && (
|
||||
<div className="absolute bottom-0 left-0 right-0 px-16 py-8 border-t-2 border-gray-300">
|
||||
<div className="flex justify-between items-center text-gray-700">
|
||||
<div className="flex space-x-16 text-sm">
|
||||
{/* Telephone */}
|
||||
{contactDetails?.phoneNumber && (
|
||||
<div>
|
||||
<div className="font-semibold text-gray-900 mb-1">
|
||||
Telephone
|
||||
</div>
|
||||
<div>{contactDetails.phoneNumber}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Address */}
|
||||
{contactDetails?.physicalAddress && (
|
||||
<div>
|
||||
<div className="font-semibold text-gray-900 mb-1">
|
||||
Address
|
||||
</div>
|
||||
<div>{contactDetails.physicalAddress}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Website */}
|
||||
{contactDetails?.websiteUrl && (
|
||||
<div>
|
||||
<div className="font-semibold text-gray-900 mb-1">
|
||||
Website
|
||||
</div>
|
||||
<div>{contactDetails.websiteUrl}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Presentation Date */}
|
||||
{presentationDate && (
|
||||
<div className="text-right">
|
||||
<div className="text-lg font-bold text-gray-900">
|
||||
{presentationDate}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThankYouSlide;
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
import * as z from "zod";
|
||||
|
||||
import { ImageSchema, IconSchema } from "../defaultSchemes";
|
||||
|
||||
export const layoutName = "Title Slide";
|
||||
export const layoutId = "title-slide";
|
||||
export const layoutDescription = "A slide with a title and subtitle";
|
||||
|
||||
// Schema definition
|
||||
export const Schema = z.object({
|
||||
organizationName: z
|
||||
.string()
|
||||
.min(2)
|
||||
.max(25)
|
||||
.default("Your Organization")
|
||||
.meta({
|
||||
description: "Name of the organization, company, or entity presenting",
|
||||
}),
|
||||
|
||||
primaryTitle: z.string().min(3).max(30).default("PRESENTATION TITLE").meta({
|
||||
description:
|
||||
"Main headline or title for the presentation - should be impactful and attention-grabbing",
|
||||
}),
|
||||
|
||||
secondaryTitle: z
|
||||
.string()
|
||||
.min(5)
|
||||
.max(50)
|
||||
.default("PROFESSIONAL PRESENTATION")
|
||||
.meta({
|
||||
description:
|
||||
"Subtitle that provides context about the presentation type or purpose",
|
||||
}),
|
||||
|
||||
brandLogo: ImageSchema.default({
|
||||
__image_url__: "https://via.placeholder.com/40x40/22C55E/FFFFFF?text=L",
|
||||
__image_prompt__:
|
||||
"Professional organization logo - clean and modern design",
|
||||
}).meta({
|
||||
description: "Logo or brand mark representing the presenting organization",
|
||||
}),
|
||||
|
||||
contactDetails: z
|
||||
.object({
|
||||
phoneNumber: z.string().min(10).max(20).default("+1-234-567-8900"),
|
||||
physicalAddress: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(60)
|
||||
.default("123 Business Ave, City, State 12345"),
|
||||
websiteUrl: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(40)
|
||||
.default("www.yourorganization.com"),
|
||||
})
|
||||
.default({
|
||||
phoneNumber: "+1-234-567-8900",
|
||||
physicalAddress: "123 Business Ave, City, State 12345",
|
||||
websiteUrl: "www.yourorganization.com",
|
||||
})
|
||||
.meta({
|
||||
description:
|
||||
"Contact information including phone, address, and website for follow-up communication",
|
||||
}),
|
||||
|
||||
presentationDate: z
|
||||
.string()
|
||||
.min(3)
|
||||
.max(20)
|
||||
.default("Current Month Year")
|
||||
.meta({
|
||||
description: "Date when the presentation is being given or was created",
|
||||
}),
|
||||
|
||||
showDecorations: z.boolean().default(true).meta({
|
||||
description:
|
||||
"Whether to display decorative visual elements like background shapes",
|
||||
}),
|
||||
|
||||
showNavigationArrow: z.boolean().default(true).meta({
|
||||
description:
|
||||
"Whether to show a navigation arrow button for presentation flow",
|
||||
}),
|
||||
});
|
||||
|
||||
// Type inference
|
||||
type SchemaType = z.infer<typeof Schema>;
|
||||
|
||||
// Component definition
|
||||
const ThynkTitleSlide = ({ data }: { data: Partial<SchemaType> }) => {
|
||||
const {
|
||||
organizationName,
|
||||
primaryTitle,
|
||||
secondaryTitle,
|
||||
brandLogo,
|
||||
contactDetails,
|
||||
presentationDate,
|
||||
showDecorations,
|
||||
showNavigationArrow,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
|
||||
{/* Header with Logo and Arrow */}
|
||||
<div className="absolute top-0 left-0 right-0 px-16 py-8 flex justify-between items-center z-20">
|
||||
{/* Company Logo and Name */}
|
||||
<div className="flex items-center space-x-3">
|
||||
{brandLogo?.__image_url__ && (
|
||||
<div className="w-10 h-10">
|
||||
<img
|
||||
src={brandLogo.__image_url__}
|
||||
alt={brandLogo.__image_prompt__}
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{organizationName && (
|
||||
<span className="text-2xl font-bold text-gray-900">
|
||||
{organizationName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Arrow Button */}
|
||||
{showNavigationArrow && (
|
||||
<div className="w-12 h-12 bg-teal-600 rounded-full flex items-center justify-center">
|
||||
<svg
|
||||
className="w-6 h-6 text-white"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Decorative Circle */}
|
||||
{showDecorations && (
|
||||
<div className="absolute top-20 right-16 w-96 h-96 bg-yellow-100 rounded-full opacity-60 z-10"></div>
|
||||
)}
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="relative h-full flex flex-col justify-center px-16">
|
||||
<div className="">
|
||||
{/* Main Title */}
|
||||
{primaryTitle && (
|
||||
<h1 className="text-4xl lg:text-5xl font-black text-teal-700 leading-none tracking-tight mb-4">
|
||||
{primaryTitle}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{/* Subtitle with Circle Bullet */}
|
||||
{secondaryTitle && (
|
||||
<div className="flex items-center space-x-4 mb-12">
|
||||
<div className="w-4 h-4 bg-teal-600 rounded-full"></div>
|
||||
<h2 className="text-xl font-bold text-gray-800 tracking-wide">
|
||||
{secondaryTitle}
|
||||
</h2>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer with Contact Info */}
|
||||
<div className="absolute bottom-0 left-0 right-0 px-16 py-8 border-t-2 border-gray-300">
|
||||
<div className="flex justify-between items-center text-gray-700">
|
||||
<div className="flex space-x-16 text-sm">
|
||||
{/* Telephone */}
|
||||
{contactDetails?.phoneNumber && (
|
||||
<div>
|
||||
<div className="font-semibold text-gray-900 mb-1">
|
||||
Telephone
|
||||
</div>
|
||||
<div>{contactDetails.phoneNumber}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Address */}
|
||||
{contactDetails?.physicalAddress && (
|
||||
<div>
|
||||
<div className="font-semibold text-gray-900 mb-1">Address</div>
|
||||
<div>{contactDetails.physicalAddress}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Website */}
|
||||
{contactDetails?.websiteUrl && (
|
||||
<div>
|
||||
<div className="font-semibold text-gray-900 mb-1">Website</div>
|
||||
<div>{contactDetails.websiteUrl}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Presentation Date */}
|
||||
{presentationDate && (
|
||||
<div className="text-right">
|
||||
<div className="text-lg font-bold text-gray-900">
|
||||
{presentationDate}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThynkTitleSlide;
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
import * as z from "zod";
|
||||
|
||||
import { ImageSchema, IconSchema } from "../defaultSchemes";
|
||||
|
||||
export const layoutName = "What We Believe Slide";
|
||||
export const layoutId = "what-we-believe-slide";
|
||||
export const layoutDescription =
|
||||
"A slide that describes the organization's vision and mission";
|
||||
|
||||
// Schema definition
|
||||
export const Schema = z.object({
|
||||
sectionTitle: z.string().min(3).max(30).default("OUR VISION & MISSION").meta({
|
||||
description:
|
||||
"Main section heading - can be 'Our Values', 'What We Believe', 'Our Philosophy', or similar",
|
||||
}),
|
||||
|
||||
sectionSubtitle: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(60)
|
||||
.default("GUIDING PRINCIPLES AND CORE BELIEFS")
|
||||
.meta({
|
||||
description:
|
||||
"Supporting subtitle that introduces the organization's foundational concepts",
|
||||
}),
|
||||
|
||||
visionStatement: z
|
||||
.string()
|
||||
.min(30)
|
||||
.max(200)
|
||||
.default(
|
||||
"We envision a future where innovative solutions transform challenges into opportunities, creating sustainable value for all stakeholders."
|
||||
)
|
||||
.meta({
|
||||
description:
|
||||
"Vision statement describing the organization's aspirational future goals and impact",
|
||||
}),
|
||||
|
||||
missionContent: z
|
||||
.object({
|
||||
missionTitle: z.string().min(3).max(30).default("Our Mission"),
|
||||
missionDescription: z
|
||||
.string()
|
||||
.min(50)
|
||||
.max(300)
|
||||
.default(
|
||||
"To deliver exceptional value through strategic innovation, collaborative partnerships, and unwavering commitment to excellence. We believe in empowering organizations with the tools, insights, and support needed to achieve sustainable growth and meaningful impact in their communities."
|
||||
),
|
||||
})
|
||||
.default({
|
||||
missionTitle: "Our Mission",
|
||||
missionDescription:
|
||||
"To deliver exceptional value through strategic innovation, collaborative partnerships, and unwavering commitment to excellence. We believe in empowering organizations with the tools, insights, and support needed to achieve sustainable growth and meaningful impact in their communities.",
|
||||
})
|
||||
.meta({
|
||||
description:
|
||||
"Mission section with title and detailed description of organizational purpose and approach",
|
||||
}),
|
||||
|
||||
supportingVisual: ImageSchema.default({
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1522202176988-66273c2fd55f?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
|
||||
__image_prompt__:
|
||||
"Diverse team collaborating and planning together in modern workspace",
|
||||
}).meta({
|
||||
description:
|
||||
"Visual that represents collaboration, vision, or organizational culture",
|
||||
}),
|
||||
|
||||
showVisualAccents: z.boolean().default(true).meta({
|
||||
description: "Whether to display decorative visual accent elements",
|
||||
}),
|
||||
|
||||
showColorBlocks: z.boolean().default(true).meta({
|
||||
description:
|
||||
"Whether to show colored background sections for visual hierarchy",
|
||||
}),
|
||||
});
|
||||
|
||||
// Type inference
|
||||
type SchemaType = z.infer<typeof Schema>;
|
||||
|
||||
// Component definition
|
||||
const WhatWeBelieveSlide = ({ data }: { data: Partial<SchemaType> }) => {
|
||||
const {
|
||||
sectionTitle,
|
||||
sectionSubtitle,
|
||||
visionStatement,
|
||||
missionContent,
|
||||
supportingVisual,
|
||||
showVisualAccents,
|
||||
showColorBlocks,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
|
||||
{/* Main Content Area */}
|
||||
<div className="h-full flex">
|
||||
{/* Left Side - Image */}
|
||||
<div className="w-2/5 relative">
|
||||
{supportingVisual?.__image_url__ && (
|
||||
<div className="absolute inset-8 shadow-lg">
|
||||
<img
|
||||
src={supportingVisual.__image_url__}
|
||||
alt={supportingVisual.__image_prompt__}
|
||||
className="w-full h-full object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Visual Accents */}
|
||||
{showVisualAccents && (
|
||||
<>
|
||||
{/* Decorative circles */}
|
||||
<div className="absolute top-4 right-4 w-6 h-6 bg-teal-600 rounded-full opacity-70 z-20"></div>
|
||||
<div className="absolute bottom-8 left-8 w-4 h-4 bg-yellow-300 rounded-full z-20"></div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Side - Content */}
|
||||
<div className="w-3/5 relative bg-white px-16 py-12 flex flex-col justify-start">
|
||||
{/* Title Section */}
|
||||
<div className="mb-8">
|
||||
{sectionTitle && (
|
||||
<h1 className="text-3xl lg:text-4xl font-black text-teal-700 leading-tight mb-4">
|
||||
{sectionTitle}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{sectionSubtitle && (
|
||||
<p className="text-base font-semibold text-gray-800 tracking-wide mb-8">
|
||||
{sectionSubtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Vision Section */}
|
||||
{visionStatement && (
|
||||
<div className="mb-8">
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-4">Vision</h2>
|
||||
<p className="text-base leading-relaxed text-gray-700">
|
||||
{visionStatement}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mission Section with Teal Background */}
|
||||
{missionContent && (
|
||||
<div className="bg-teal-600 px-8 py-6 rounded-lg">
|
||||
{missionContent.missionTitle && (
|
||||
<h2 className="text-xl font-bold text-white mb-4">
|
||||
{missionContent.missionTitle}
|
||||
</h2>
|
||||
)}
|
||||
{missionContent.missionDescription && (
|
||||
<p className="text-base leading-relaxed text-gray-100">
|
||||
{missionContent.missionDescription}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Color blocks for visual hierarchy */}
|
||||
{showColorBlocks && (
|
||||
<>
|
||||
{/* Bottom accent strip */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-2 bg-teal-600"></div>
|
||||
{/* Left accent */}
|
||||
<div className="absolute top-0 left-0 bottom-0 w-2 bg-yellow-300"></div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WhatWeBelieveSlide;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"description": "Professional presentation layouts with clean design and flexible content fields. Suitable for business pitches, organizational overviews, product presentations, and various corporate communications.",
|
||||
"ordered": false,
|
||||
"default": false
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
import React from 'react'
|
||||
import * as z from 'zod'
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
Legend,
|
||||
LineChart,
|
||||
Line,
|
||||
PieChart,
|
||||
Pie,
|
||||
Cell,
|
||||
} from 'recharts'
|
||||
|
||||
const layoutId = 'chart-left-text-right-layout'
|
||||
const layoutName = 'Chart Left Text Right'
|
||||
const layoutDescription = 'A slide with header label, a left-side inline bar chart, and right-side title with paragraph.'
|
||||
|
||||
const ChartDatumSchema = z.object({
|
||||
label: z.string().min(1).max(12).default('A').meta({ description: 'Category label' }),
|
||||
value: z.number().min(0).max(100).default(60).meta({ description: 'Value 0–100' }),
|
||||
})
|
||||
|
||||
const Schema = z.object({
|
||||
|
||||
title: z
|
||||
.string()
|
||||
.min(16)
|
||||
.max(64)
|
||||
.default('Insights At A Glance')
|
||||
.meta({ description: 'Main heading (max ~7 words)' }),
|
||||
paragraph: z
|
||||
.string()
|
||||
.min(50)
|
||||
.max(200)
|
||||
.default(
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
|
||||
)
|
||||
.meta({ description: 'Supporting description' }),
|
||||
chart: z
|
||||
.object({
|
||||
type: z.enum(['bar', 'horizontalBar', 'line', 'pie']).default('line'),
|
||||
data: z.array(ChartDatumSchema).min(3).max(8).default([
|
||||
{ label: 'A', value: 60 },
|
||||
{ label: 'B', value: 42 },
|
||||
{ label: 'C', value: 75 },
|
||||
{ label: 'D', value: 30 },
|
||||
]),
|
||||
primaryColor: z.string().default('#1B8C2D'),
|
||||
gridColor: z.string().default('#E5E7EB'),
|
||||
pieColors: z.array(z.string()).min(1).max(10).default(['#1B8C2D', '#3b82f6', '#f59e0b', '#10b981', '#ef4444']),
|
||||
showLabels: z.boolean().default(true),
|
||||
})
|
||||
.default({
|
||||
type: 'line',
|
||||
data: [
|
||||
{ label: 'A', value: 60 },
|
||||
{ label: 'B', value: 42 },
|
||||
{ label: 'C', value: 75 },
|
||||
{ label: 'D', value: 30 },
|
||||
],
|
||||
primaryColor: '#1B8C2D',
|
||||
gridColor: '#E5E7EB',
|
||||
pieColors: ['#1B8C2D', '#3b82f6', '#f59e0b', '#10b981', '#ef4444'],
|
||||
showLabels: true,
|
||||
}),
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const dynamicSlideLayout: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const data = slideData?.chart?.data || []
|
||||
const type = slideData?.chart?.type || 'bar'
|
||||
const primaryColor = slideData?.chart?.primaryColor || '#1B8C2D'
|
||||
const gridColor = slideData?.chart?.gridColor || '#E5E7EB'
|
||||
const pieColors = slideData?.chart?.pieColors || ['#1B8C2D']
|
||||
const showLabels = slideData?.chart?.showLabels !== false
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;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"
|
||||
style={{ fontFamily:"var(--heading-font-family,Playfair Display)", backgroundColor: 'var(--card-background-color, #FFFFFF)' }}
|
||||
>
|
||||
<div className="w-full flex items-center justify-between px-10 pt-6">
|
||||
<div className="flex items-center gap-5">
|
||||
{ (slideData as any)?.__companyName__ && <span className="text-[18px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || 'Pitchdeck'}
|
||||
</span>}
|
||||
<svg className="w-[220px] h-[2px]" viewBox="0 0 220 2" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="220" height="2" style={{ fill: 'var(--text-heading-color, #111827)' }}></rect>
|
||||
</svg>
|
||||
</div>
|
||||
{/* page number intentionally omitted */}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 h-[calc(100%-64px)]">
|
||||
{/* Left: Recharts visualization */}
|
||||
<div className="h-full px-10 pt-8">
|
||||
<div className="w-full h-full flex items-center">
|
||||
<div className="w-full" style={{ height: 320 }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
{type === 'bar' ? (
|
||||
<BarChart data={data} margin={{ top: 10, right: 20, bottom: 10, left: 0 }}>
|
||||
<CartesianGrid stroke={gridColor} strokeDasharray="3 3" />
|
||||
<XAxis dataKey="label" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="value" fill={primaryColor} radius={[6, 6, 0, 0]} label={showLabels ? { position: 'top', fill: '#111827', fontSize: 12 } : false} />
|
||||
</BarChart>
|
||||
) : type === 'horizontalBar' ? (
|
||||
<BarChart data={data} layout="vertical" margin={{ top: 10, right: 20, bottom: 10, left: 20 }}>
|
||||
<CartesianGrid stroke={gridColor} strokeDasharray="3 3" />
|
||||
<XAxis type="number" />
|
||||
<YAxis type="category" dataKey="label" />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="value" fill={primaryColor} radius={[0, 6, 6, 0]} label={showLabels ? { position: 'right', fill: '#111827', fontSize: 12 } : false} />
|
||||
</BarChart>
|
||||
) : type === 'line' ? (
|
||||
<LineChart data={data} margin={{ top: 10, right: 20, bottom: 10, left: 0 }}>
|
||||
<CartesianGrid stroke={gridColor} strokeDasharray="3 3" />
|
||||
<XAxis dataKey="label" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line type="monotone" dataKey="value" stroke={primaryColor} strokeWidth={3} dot={{ r: 3 }} label={showLabels ? { position: 'top', fill: '#111827', fontSize: 12 } : false} />
|
||||
</LineChart>
|
||||
) : (
|
||||
<PieChart>
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Pie data={data} dataKey="value" nameKey="label" cx="50%" cy="50%" outerRadius={100} label={showLabels}>
|
||||
{data.map((_, i) => (
|
||||
<Cell key={i} fill={pieColors[i % pieColors.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
)}
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Text */}
|
||||
<div className="h-full px-12 flex flex-col justify-center">
|
||||
<h1 className="text-[64px] leading-[1.05] tracking-tight font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{slideData?.title || 'Insights At A Glance'}
|
||||
</h1>
|
||||
<p className="mt-6 text-[16px] leading-[28px]" style={{ color: 'var(--text-body-color, #6B7280)' }}>
|
||||
{slideData?.paragraph ||
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default dynamicSlideLayout
|
||||
|
||||
|
||||
204
servers/nextjs/presentation-templates/standard/ContactLayout.tsx
Normal file
204
servers/nextjs/presentation-templates/standard/ContactLayout.tsx
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
|
||||
const ImageSchema = z.object({
|
||||
__image_url__: z.string().url().default("https://images.pexels.com/photos/31527637/pexels-photo-31527637.jpeg").meta({
|
||||
description: "URL to image",
|
||||
}),
|
||||
__image_prompt__: z.string().min(10).max(120).default("Muted cover background image with subtle subject suitable for contact slide").meta({
|
||||
description: "Prompt used to generate the image. Max 24 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const IconSchema = z.object({
|
||||
__icon_url__: z.string().default("data:svg+xml,placeholder").meta({
|
||||
description: "URL to icon",
|
||||
}),
|
||||
__icon_query__: z.string().min(3).max(24).default("photo placeholder").meta({
|
||||
description: "Query used to search the icon. Max 5 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const layoutId = "header-left-media-contact-info-slide"
|
||||
const layoutName = "ContactLayout"
|
||||
const layoutDescription = "A slide with a top bar, left media with overlay and bottom bar, and right content with header and text blocks. This should only be used for contact information."
|
||||
|
||||
const Schema = z.object({
|
||||
metaMaxWords: z.number().default(24).meta({
|
||||
description: "Maximum number of words in any single text field",
|
||||
}),
|
||||
|
||||
topBar: z.object({
|
||||
|
||||
pageNumber: z.string().min(1).max(3).default("9").meta({
|
||||
description: "Top-right number text. Max 1 word",
|
||||
}),
|
||||
}).default({
|
||||
|
||||
pageNumber: "9",
|
||||
}),
|
||||
|
||||
leftPanel: z.object({
|
||||
backgroundImage: ImageSchema.default({
|
||||
__image_url__: "https://images.pexels.com/photos/31527637/pexels-photo-31527637.jpeg",
|
||||
__image_prompt__: "Muted cover background image with subtle subject suitable for contact slide",
|
||||
}),
|
||||
centerIcon: IconSchema.default({
|
||||
__icon_url__: "data:svg+xml,placeholder",
|
||||
__icon_query__: "photo placeholder",
|
||||
}),
|
||||
websiteBar: z.object({
|
||||
websiteText: z.string().min(12).max(30).default("www.yourwebsite.com").meta({
|
||||
description: "Website text in bottom green bar. Max 4 words",
|
||||
}),
|
||||
actionIcon: IconSchema.default({
|
||||
__icon_url__: "data:svg+xml,plus-arrow",
|
||||
__icon_query__: "plus arrow",
|
||||
}),
|
||||
}).default({
|
||||
websiteText: "www.yourwebsite.com",
|
||||
actionIcon: {
|
||||
__icon_url__: "data:svg+xml,plus-arrow",
|
||||
__icon_query__: "plus arrow",
|
||||
},
|
||||
}),
|
||||
}).default({
|
||||
backgroundImage: {
|
||||
__image_url__: "https://images.pexels.com/photos/326576/pexels-photo-326576.jpeg",
|
||||
__image_prompt__: "Muted cover background image with subtle subject suitable for contact slide",
|
||||
},
|
||||
centerIcon: {
|
||||
__icon_url__: "data:svg+xml,placeholder",
|
||||
__icon_query__: "photo placeholder",
|
||||
},
|
||||
websiteBar: {
|
||||
websiteText: "www.yourwebsite.com",
|
||||
actionIcon: {
|
||||
__icon_url__: "data:svg+xml,plus-arrow",
|
||||
__icon_query__: "plus arrow",
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
rightContent: z.object({
|
||||
title: z.string().min(18).max(40).default("Let’s Get in\nTouch with Us").meta({
|
||||
description: "Main heading. Max 6 words",
|
||||
}),
|
||||
sections: z.array(z.object({
|
||||
label: z.string().min(4).max(10).default("Label").meta({
|
||||
description: "Section label text. Max 2 words",
|
||||
}),
|
||||
value: z.string().min(8).max(50).default("Value text").meta({
|
||||
description: "Section value text. Max 100 characters",
|
||||
}),
|
||||
showDivider: z.boolean().default(true).meta({
|
||||
description: "Whether to show bottom divider",
|
||||
}),
|
||||
})).min(1).max(3).default([
|
||||
{
|
||||
label: "Address",
|
||||
value: "Boston, Downtown Main Street 233, New York, US",
|
||||
showDivider: true,
|
||||
},
|
||||
{
|
||||
label: "Phone",
|
||||
value: "+1234 2345 1234",
|
||||
showDivider: true,
|
||||
},
|
||||
{
|
||||
label: "E-mail:",
|
||||
value: "mail@company.com",
|
||||
showDivider: false,
|
||||
},
|
||||
]).meta({
|
||||
description: "List of content sections",
|
||||
}),
|
||||
}).default({
|
||||
title: "Let’s Get in\nTouch with Us",
|
||||
sections: [
|
||||
{
|
||||
label: "Address",
|
||||
value: "Boston, Downtown Main Street 233, New York, US",
|
||||
showDivider: true,
|
||||
},
|
||||
{
|
||||
label: "Phone",
|
||||
value: "+1234 2345 1234",
|
||||
showDivider: true,
|
||||
},
|
||||
{
|
||||
label: "E-mail:",
|
||||
value: "mail@company.com",
|
||||
showDivider: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const dynamicSlideLayout: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const sections = slideData?.rightContent?.sections || []
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;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" style={{ fontFamily:"var(--heading-font-family,Playfair Display)", backgroundColor: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
<div className="w-full flex items-center justify-between px-10 pt-6">
|
||||
<div className="flex items-center gap-6">
|
||||
{(slideData as any)?.__companyName__ && <div className="text-[18px] font-semibold tracking-wide" style={{ color: 'var(--primary-accent-color, #1B8C2D)' }}>
|
||||
{(slideData as any)?.__companyName__ || "Pitchdeck"}
|
||||
</div>}
|
||||
<div className="h-[2px] w-[220px] rounded-full" style={{ backgroundColor: 'var(--primary-accent-color, #1B8C2D)' }}></div>
|
||||
</div>
|
||||
{/* page number removed */}
|
||||
</div>
|
||||
|
||||
<div className="px-10 pt-4 pb-8 h-[calc(100%-72px)]">
|
||||
<div className="grid grid-cols-[640px_1fr] gap-x-16 h-full">
|
||||
<div className="relative h-full overflow-hidden" style={{ backgroundColor: 'var(--tertiary-accent-color, #E5E7EB)' }}>
|
||||
<img
|
||||
src={slideData?.leftPanel?.backgroundImage?.__image_url__ || ""}
|
||||
alt={slideData?.leftPanel?.backgroundImage?.__image_prompt__ || "cover"}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="h-full flex flex-col">
|
||||
<h1 className=" text-[64px] leading-[1.12] font-semibold mb-8 whitespace-pre-line" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{slideData?.rightContent?.title || "Let’s Get in\nTouch with Us"}
|
||||
</h1>
|
||||
|
||||
<div className="mb-6">
|
||||
{sections.map((sec, idx) => (
|
||||
<div key={idx} className={idx < sections.length - 1 ? "mb-6" : "mt-2"}>
|
||||
<div className="text-[24px] font-semibold mb-2" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{sec.label}
|
||||
</div>
|
||||
<div className="text-[18px] " style={{ color: 'var(--text-body-color, #6B7280)' }}>
|
||||
{sec.value}
|
||||
</div>
|
||||
{sec.showDivider ? <div className="border-b border-gray-300 mt-6"></div> : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default dynamicSlideLayout
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
import React from 'react'
|
||||
// charts removed
|
||||
import * as z from "zod";
|
||||
|
||||
const ImageSchema = z.object({
|
||||
__image_url__: z.string().url().default("https://images.pexels.com/photos/31527637/pexels-photo-31527637.jpeg").meta({
|
||||
description: "URL to image",
|
||||
}),
|
||||
__image_prompt__: z.string().min(10).max(150).default("Small decorative photo partially behind the card showing a business theme").meta({
|
||||
description: "Prompt used to generate the image. Max 30 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const IconSchema = z.object({
|
||||
__icon_url__: z.string().default("").meta({
|
||||
description: "URL to icon",
|
||||
}),
|
||||
__icon_query__: z.string().min(3).max(30).default("").meta({
|
||||
description: "Query used to search the icon. Max 5 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const layoutId = "split-left-strip-header-title-subtitle-cards-slide"
|
||||
const layoutName = "Heading Bullet Image Description"
|
||||
const layoutDescription = "A slide with a left strip, top label with rule, right header, right description, floating small image, and a centered card with ...cards."
|
||||
|
||||
const Schema = z.object({
|
||||
metaMaxWords: z.number().default(200).meta({
|
||||
description: "Maximum number of words the text areas can handle collectively.",
|
||||
}),
|
||||
pageNumber: z.string().min(1).max(3).default("7").meta({
|
||||
description: "Top-right page number text. Max 3 chars",
|
||||
}),
|
||||
|
||||
heading: z.string().min(16).max(38).default("A Blueprint for\nSuccess").meta({
|
||||
description: "Main heading across up to 2 lines. Max 7 words",
|
||||
}),
|
||||
subheading: z.string().min(60).max(200).default("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna").meta({
|
||||
description: "Supporting paragraph under the heading. Max 35 words",
|
||||
}),
|
||||
smallImage: ImageSchema.default({
|
||||
__image_url__: "https://images.pexels.com/photos/327533/pexels-photo-327533.jpeg",
|
||||
__image_prompt__: "A small landscape image suitable for a business slide"
|
||||
}).meta({
|
||||
description: "Small image partially behind the main card",
|
||||
}),
|
||||
cards: z.array(z.object({
|
||||
title: z.string().min(8).max(16).default("Strategy 01").meta({
|
||||
description: "Card ribbon title. Max 3 words",
|
||||
}),
|
||||
body: z.string().min(60).max(160).default("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor").meta({
|
||||
description: "Card body text. Max 28 words",
|
||||
}),
|
||||
})).min(1).max(4).default([
|
||||
{ title: "Strategy 01", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" },
|
||||
{ title: "Strategy 02", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" },
|
||||
{ title: "Strategy 03", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" },
|
||||
{ title: "Strategy 04", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" },
|
||||
]).meta({
|
||||
description: "Array of strategy cards",
|
||||
}),
|
||||
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const dynamicSlideLayout: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const cards = slideData?.cards || []
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;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" style={{ fontFamily:"var(--heading-font-family,Playfair Display)", backgroundColor: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
{/* page number removed */}
|
||||
|
||||
<div className="grid grid-cols-2 h-full">
|
||||
<div className="relative bg-[#efefef]">
|
||||
{slideData?.smallImage?.__image_url__ ? (
|
||||
<>
|
||||
<img
|
||||
src={slideData.smallImage.__image_url__}
|
||||
alt={slideData.smallImage.__image_prompt__ || "image"}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
{/* overlay removed */}
|
||||
</>
|
||||
) : null}
|
||||
<div className="pt-6 pl-10 pr-6 relative z-[1]">
|
||||
<div className="flex items-center gap-6">
|
||||
{ (slideData as any)?.__companyName__ && <span className="text-[18px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || "Pitchdeck"}
|
||||
</span>}
|
||||
<div className="h-[2px] w-[220px] rounded-full" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-0 left-0 w-full h-[88px] bg-gradient-to-t from-black/20 to-transparent z-[1]"></div>
|
||||
</div>
|
||||
|
||||
<div className="relative px-12 pt-16">
|
||||
<h1 className=" leading-[72px] text-[64px] tracking-tight whitespace-pre-line font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{slideData?.heading || "A Blueprint for\nSuccess"}
|
||||
</h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute left-10 right-10 top-[320px] z-10">
|
||||
<div className="w-fit max-w-full rounded-md shadow-[0_20px_60px_rgba(0,0,0,0.12)] ml-auto" style={{ backgroundColor: 'var(--secondary-accent-color, #F3F4F6)' }}>
|
||||
<div className="px-8 py-10">
|
||||
<div className="grid grid-flow-col auto-cols-max gap-6">
|
||||
{cards.map((card, idx) => (
|
||||
<div key={idx} className="flex flex-col items-center">
|
||||
<div className="w-[240px] h-[64px] rounded-sm text-white flex items-center justify-center text-[22px]" style={{ backgroundColor: 'var(--primary-accent-color, #1B8C2D)', color: 'var(--text-heading-color, #FFFFFF)' }}>
|
||||
{card.title}
|
||||
</div>
|
||||
<p className="mt-6 text-center text-[16px] leading-[28px] max-w-[240px]" style={{ color: 'var(--text-body-color, #6B7280)' }}>
|
||||
{card.body}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default dynamicSlideLayout
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
import { RemoteSvgIcon } from '@/app/hooks/useRemoteSvgIcon';
|
||||
import React from 'react'
|
||||
// charts removed
|
||||
import * as z from "zod";
|
||||
|
||||
|
||||
const ImageSchema = z.object({
|
||||
__image_url__: z.string().url().default("https://images.pexels.com/photos/31527637/pexels-photo-31527637.jpeg").meta({
|
||||
description: "URL to image",
|
||||
}),
|
||||
__image_prompt__: z.string().min(10).max(180).default("Decorative abstract office scene photo placed at lower right on the band").meta({
|
||||
description: "Prompt used to generate the image. Max 30 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const IconSchema = z.object({
|
||||
__icon_url__: z.string().default("https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/fediverse-logo-bold.svg").meta({
|
||||
description: "URL to icon",
|
||||
}),
|
||||
__icon_query__: z.string().min(2).max(20).default("info icon").meta({
|
||||
description: "Query used to search the icon. Max 3 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const layoutId = "header-bullets-title-description-image-slide"
|
||||
const layoutName = "Icon Bullet Description"
|
||||
const layoutDescription = "A slide with a small header label and number, a left card of ...cards with round symbols and titles with descriptions, a large heading with supporting text, and a decorative image on a mid-page band"
|
||||
|
||||
const Schema = z.object({
|
||||
metaMaxWords: z.number().default(240).meta({
|
||||
description: "Maximum number of words any single text field can handle in this layout",
|
||||
}),
|
||||
|
||||
|
||||
headerNumber: z.string().min(1).max(3).default("6").meta({
|
||||
description: "Small header number text. Max 3 characters",
|
||||
}),
|
||||
rightTitle: z.string().min(24).max(72).default("Disrupting the\nIndustry").meta({
|
||||
description: "Large heading on the right. Max 8 words",
|
||||
}),
|
||||
rightDescription: z.string().min(120).max(240).default("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna").meta({
|
||||
description: "Supporting paragraph under the large heading. Max 40 words",
|
||||
}),
|
||||
// decorative image removed
|
||||
cards: z.array(z.object({
|
||||
symbolText: z.string().min(1).max(1).default("i").meta({
|
||||
description: "Single-character symbol inside the round badge",
|
||||
}),
|
||||
symbolIcon: IconSchema.default({
|
||||
__icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/fediverse-logo-bold.png",
|
||||
__icon_query__: "info icon",
|
||||
}).meta({
|
||||
description: "Optional icon representation for the round symbol",
|
||||
}),
|
||||
title: z.string().min(16).max(38).default("Visionary Leadership").meta({
|
||||
description: "Title for the card item. Max 4 words",
|
||||
}),
|
||||
description: z.string().min(50).max(100).default("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor").meta({
|
||||
description: "Description for the card item. Max 15 words.",
|
||||
}),
|
||||
})).min(1).max(4).default([
|
||||
{
|
||||
symbolText: "i",
|
||||
symbolIcon: { __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/fediverse-logo-bold.svg", __icon_query__: "info icon" },
|
||||
title: "Visionary Leadership",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor",
|
||||
},
|
||||
{
|
||||
symbolText: "i",
|
||||
symbolIcon: { __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/video-bold.png", __icon_query__: "info icon" },
|
||||
title: "Innovation at the Core",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor",
|
||||
},
|
||||
{
|
||||
symbolText: "i",
|
||||
symbolIcon: { __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/receipt-x-bold.png", __icon_query__: "info icon" },
|
||||
title: "Customer-Centric Disruption",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor",
|
||||
},
|
||||
{
|
||||
symbolText: "i",
|
||||
symbolIcon: { __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/users-four-bold.png", __icon_query__: "info icon" },
|
||||
title: "Customer-Centric Disruption",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor",
|
||||
}
|
||||
]).meta({
|
||||
description: "Array of ...cards with a round symbol, title and description. Max 6 items",
|
||||
}),
|
||||
// chart and diagram removed
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const dynamicSlideLayout: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const cards = slideData?.cards || []
|
||||
// charts removed
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;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" style={{ fontFamily:"var(--heading-font-family,Playfair Display)", backgroundColor: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
|
||||
<div className="relative z-10 flex items-center justify-between px-[40px] pt-[24px]">
|
||||
<div className="flex items-center gap-[24px]">
|
||||
{ (slideData as any)?.__companyName__ && <span className=" text-[18px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>{(slideData as any)?.__companyName__ || "Pitchdeck"}</span>}
|
||||
<svg className="hidden md:block" width="220" height="2" viewBox="0 0 220 2" fill="none" aria-hidden="true">
|
||||
<rect width="220" height="2" rx="1" style={{ fill: 'var(--text-heading-color, #111827)' }}></rect>
|
||||
</svg>
|
||||
</div>
|
||||
{/* page number removed */}
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 grid grid-cols-[600px_1fr] gap-[64px] px-[80px] pt-[12px]">
|
||||
<div className="w-[600px]">
|
||||
<div className="rounded-[10px] shadow-[0_20px_60px_rgba(0,0,0,0.08)] px-[36px] py-[28px]" style={{ backgroundColor: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
<ul className="flex flex-col gap-[28px]">
|
||||
{cards.map((item, idx) => (
|
||||
<li key={idx} className="flex items-start gap-[24px]">
|
||||
<div className="w-[88px] h-[88px] rounded-full flex items-center justify-center overflow-hidden select-none" style={{ backgroundColor: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
{item.symbolIcon?.__icon_url__ ? (
|
||||
<RemoteSvgIcon
|
||||
url={item.symbolIcon.__icon_url__}
|
||||
strokeColor={"currentColor"}
|
||||
className="w-14 h-14"
|
||||
color="var(--text-heading-color, #111827)"
|
||||
title={item.symbolIcon.__icon_query__}
|
||||
/>
|
||||
) : (
|
||||
<span className="text-[34px] font-semibold" style={{ color: 'var(--primary-accent-color, #1B8C2D)' }}>{item.symbolText}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className=" text-[24px] font-semibold leading-[1.15]" style={{ color: 'var(--text-heading-color, #111827)' }}>{item.title}</h3>
|
||||
<p className=" text-[16px] leading-[1.55] mt-[8px]" style={{ color: 'var(--text-body-color, #6B7280)' }}>{item.description}</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-[28px]">
|
||||
<h1 className="font-['Playfair Display'] text-[64px] leading-[1.05] tracking-[-0.01em] max-w-[600px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }} dangerouslySetInnerHTML={{ __html: (slideData?.rightTitle || "Disrupting the\nIndustry").replace(/\n/g, "<br/>") }}></h1>
|
||||
<div className="mt-[24px] inline-block rounded-md px-6 py-4" style={{ backgroundColor: 'var(--secondary-accent-color, #F3F4F6)' }}>
|
||||
<p className="font-['Playfair Display'] text-[16px] leading-[1.6] max-w-[620px]" style={{ color: 'var(--text-body-color, #6B7280)' }}>
|
||||
{slideData?.rightDescription || "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* charts removed */}
|
||||
|
||||
{/* flowchart removed */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* decorative image removed */}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default dynamicSlideLayout
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
import { RemoteSvgIcon } from '@/app/hooks/useRemoteSvgIcon';
|
||||
import React from 'react'
|
||||
// Charts removed
|
||||
import * as z from "zod";
|
||||
|
||||
|
||||
|
||||
const ImageSchema = z.object({
|
||||
__image_url__: z.string().url().default("https://via.placeholder.com/900x500").meta({
|
||||
description: "URL to image",
|
||||
}),
|
||||
__image_prompt__: z.string().min(10).max(200).default("Wide landscape placeholder representing a slide image area").meta({
|
||||
description: "Prompt used to generate the image. Max 30 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const IconSchema = z.object({
|
||||
__icon_url__: z.string().default("https://via.placeholder.com/60").meta({
|
||||
description: "URL to icon",
|
||||
}),
|
||||
__icon_query__: z.string().min(3).max(30).default("camera landscape placeholder").meta({
|
||||
description: "Query used to search the icon. Max 3 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const layoutId = "header-title-card-slide"
|
||||
const layoutName = "Icon Image Description"
|
||||
const layoutDescription = "A slide with a top bar, centered title, placeholder icon area, and a colored card with circular icon, heading, and paragraph"
|
||||
|
||||
const Schema = z.object({
|
||||
meta: z.object({
|
||||
maxWords: z.number().default(56),
|
||||
}).default({ maxWords: 56 }),
|
||||
topBar: z.object({
|
||||
|
||||
pageNumber: z.string().min(1).max(3).default("3").meta({
|
||||
description: "Page number text. Max 1 word",
|
||||
}),
|
||||
}).default({
|
||||
|
||||
pageNumber: "3",
|
||||
}),
|
||||
title: z.string().min(24).max(56).default("Transforming Ideas into\nReality").meta({
|
||||
description: "Main title split across up to two lines. Max 10 words",
|
||||
}),
|
||||
backgroundImage: ImageSchema.default({
|
||||
__image_url__: "https://images.unsplash.com/photo-1650831432942-aa352df4e9b4?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||
__image_prompt__: "Background image covering the entire section behind the card",
|
||||
}).meta({
|
||||
description: "Full-bleed background image behind card area",
|
||||
}),
|
||||
card: z.object({
|
||||
circleIcon: IconSchema.default({
|
||||
__icon_url__: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 24 24' fill='%230A8016' aria-hidden='true'><path d='M10 6V7H7C5.895 7 5 7.895 5 9V18C5 19.105 5.895 20 7 20H17C18.105 20 19 19.105 19 18V9C19 7.895 18.105 7 17 7H14V6C14 4.895 13.105 4 12 4H12C10.895 4 10 4.895 10 6ZM12 6C12.552 6 13 6.448 13 7H11C11 6.448 11.448 6 12 6ZM7 9H17V11H7V9Z'/></svg>",
|
||||
__icon_query__: "badge document icon",
|
||||
}),
|
||||
heading: z.string().min(22).max(70).default("Idea Generation and Validation").meta({
|
||||
description: "Card heading text. Max 10 words",
|
||||
}),
|
||||
body: z.string().min(140).max(450).default("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.").meta({
|
||||
description: "Card body paragraph. Max 80 words",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
__image_url__: "https://via.placeholder.com/1200x600",
|
||||
__image_prompt__: "Optional supporting image inside card area",
|
||||
}).meta({
|
||||
description: "Optional supporting image for the card. Max 30 words",
|
||||
}),
|
||||
}).default({
|
||||
circleIcon: {
|
||||
__icon_url__: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 24 24' fill='%230A8016' aria-hidden='true'><path d='M10 6V7H7C5.895 7 5 7.895 5 9V18C5 19.105 5.895 20 7 20H17C18.105 20 19 19.105 19 18V9C19 7.895 18.105 7 17 7H14V6C14 4.895 13.105 4 12 4H12C10.895 4 10 4.895 10 6ZM12 6C12.552 6 13 6.448 13 7H11C11 6.448 11.448 6 12 6ZM7 9H17V11H7V9Z'/></svg>",
|
||||
__icon_query__: "badge document icon",
|
||||
},
|
||||
heading: "Idea Generation and Validation",
|
||||
body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||
image: {
|
||||
__image_url__: "https://via.placeholder.com/1200x600",
|
||||
__image_prompt__: "Optional supporting image inside card area",
|
||||
},
|
||||
}),
|
||||
// charts removed
|
||||
// diagram removed
|
||||
}).default({
|
||||
meta: { maxWords: 56 },
|
||||
topBar: { pageNumber: "3" },
|
||||
title: "Transforming Ideas into\nReality",
|
||||
backgroundImage: {
|
||||
__image_url__: "https://images.unsplash.com/photo-1650831432942-aa352df4e9b4?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||
__image_prompt__: "Background image covering the entire section behind the card",
|
||||
},
|
||||
card: {
|
||||
circleIcon: {
|
||||
__icon_url__: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 24 24' fill='%230A8016' aria-hidden='true'><path d='M10 6V7H7C5.895 7 5 7.895 5 9V18C5 19.105 5.895 20 7 20H17C18.105 20 19 19.105 19 18V9C19 7.895 18.105 7 17 7H14V6C14 4.895 13.105 4 12 4H12C10.895 4 10 4.895 10 6ZM12 6C12.552 6 13 6.448 13 7H11C11 6.448 11.448 6 12 6ZM7 9H17V11H7V9Z'/></svg>",
|
||||
__icon_query__: "badge document icon",
|
||||
},
|
||||
heading: "Idea Generation and Validation",
|
||||
body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||
image: { __image_url__: "https://via.placeholder.com/1200x600", __image_prompt__: "Optional supporting image inside card area" },
|
||||
},
|
||||
// charts removed
|
||||
// diagram removed
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const dynamicSlideLayout: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;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:"var(--heading-font-family,Playfair Display)", backgroundColor: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
<div className="flex items-center justify-between px-10 pt-6">
|
||||
<div className="flex items-center gap-6">
|
||||
{ (slideData as any)?.__companyName__ && <div className="text-[18px] leading-none" style={{ fontFamily: "Playfair Display", color: 'var(--text-heading-color, #111827)' }}>{(slideData as any)?.__companyName__ || "Pitchdeck"}</div>}
|
||||
<div className="h-[3px] w-[230px] rounded-full" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}></div>
|
||||
</div>
|
||||
{/* page number removed */}
|
||||
</div>
|
||||
|
||||
<div className="px-10 pt-5 pb-6">
|
||||
<h1 className="text-[64px] leading-[1.05] text-center font-semibold" style={{ fontFamily: "Playfair Display", color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData?.title || "").split("\n").map((line, idx) => (
|
||||
<span key={idx}>
|
||||
{line}
|
||||
{idx === 0 ? <br /> : null}
|
||||
</span>
|
||||
))}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="relative flex-1 flex items-center justify-center">
|
||||
<img
|
||||
src={slideData?.backgroundImage?.__image_url__ || ""}
|
||||
alt={slideData?.backgroundImage?.__image_prompt__ || "background"}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0" style={{ backgroundColor: 'rgba(255,255,255,0.6)' }}></div>
|
||||
<div className="relative w-[900px] max-w-[92%] rounded-md shadow-[0_30px_90px_rgba(0,0,0,0.18)]" style={{ backgroundColor: 'var(--primary-accent-color, #1B8C2D)' }}>
|
||||
<div className="p-10 grid grid-cols-[72px,1fr] gap-6">
|
||||
<div className="flex">
|
||||
<div className="w-16 h-16 rounded-full flex items-center justify-center" style={{ backgroundColor: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
{slideData?.card?.circleIcon?.__icon_url__ ? (
|
||||
<RemoteSvgIcon
|
||||
url={slideData?.card?.circleIcon.__icon_url__}
|
||||
strokeColor={"currentColor"}
|
||||
className="w-14 h-14"
|
||||
color="var(--text-heading-color, #111827)"
|
||||
title={slideData?.card?.circleIcon.__icon_query__}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="min-w-0">
|
||||
<div className="text-white text-[28px] leading-[34px] font-semibold" style={{ fontFamily: "Playfair Display", color: 'var(--text-heading-color, #FFFFFF)' }}>
|
||||
{slideData?.card?.heading}
|
||||
</div>
|
||||
<p className="mt-3 text-white/95 text-[16px] leading-[28px]" style={{ fontFamily: "Playfair Display", color: 'var(--text-body-color, #FFFFFF)' }}>
|
||||
{slideData?.card?.body}
|
||||
</p>
|
||||
|
||||
{/* Chart section removed */}
|
||||
|
||||
{/* Diagram removed */}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default dynamicSlideLayout
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
|
||||
const ImageSchema = z.object({
|
||||
__image_url__: z.string().url().default("https://images.pexels.com/photos/31527637/pexels-photo-31527637.jpeg").meta({
|
||||
description: "URL to image",
|
||||
}),
|
||||
__image_prompt__: z.string().min(10).max(160).default("Portrait of a professional team member with subtle background, soft light, business attire").meta({
|
||||
description: "Prompt used to generate the image. Max 30 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const IconSchema = z.object({
|
||||
__icon_url__: z.string().url().default("https://static.thenounproject.com/png/1137401-200.png").meta({
|
||||
description: "URL to icon",
|
||||
}),
|
||||
__icon_query__: z.string().min(3).max(30).default("photo image placeholder").meta({
|
||||
description: "Query used to search the icon. Max 5 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const layoutId = "header-smallbar-title-team-cards-slide"
|
||||
const layoutName = "Image List With Description"
|
||||
const layoutDescription = "A slide with a top utility bar, centered title, and a grid of cards with names, roles, and background images."
|
||||
|
||||
const Schema = z.object({
|
||||
utilityBar: z.object({
|
||||
|
||||
pageNumber: z.string().min(1).max(2).default("8").meta({
|
||||
description: "Page number text. 1-2 digits",
|
||||
}),
|
||||
decorativeLine: IconSchema.default({
|
||||
__icon_url__: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='2'%3E%3Crect width='220' height='2' fill='%231FA34A'/%3E%3C/svg%3E",
|
||||
__icon_query__: "green line separator",
|
||||
}).meta({
|
||||
description: "Decorative line representation.",
|
||||
}),
|
||||
}).default({
|
||||
|
||||
pageNumber: "8",
|
||||
decorativeLine: {
|
||||
__icon_url__: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='2'%3E%3Crect width='220' height='2' fill='%231FA34A'/%3E%3C/svg%3E",
|
||||
__icon_query__: "green line separator",
|
||||
},
|
||||
}),
|
||||
title: z.string().min(10).max(50).default("Our Professional Team").meta({
|
||||
description: "Centered main title. Max 5 words",
|
||||
}),
|
||||
cards: z.array(z.object({
|
||||
name: z.string().min(3).max(30).default("Sam Rawlings").meta({
|
||||
description: "Member name. Up to 3 words.",
|
||||
}),
|
||||
role: z.string().min(20).max(50).default("Marketing specialist with brand and growth experience").meta({
|
||||
description: "Short description under name. Up to 10 words",
|
||||
}),
|
||||
photo: ImageSchema,
|
||||
})).min(1).max(4).default([
|
||||
{
|
||||
name: "Sam Rawlings",
|
||||
role: "Marketing specialist with brand and growth experience",
|
||||
photo: {
|
||||
__image_url__: "https://images.pexels.com/photos/1181695/pexels-photo-1181695.jpeg",
|
||||
__image_prompt__: "Portrait of a professional team member with subtle background, soft light, business attire",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Sam Rawlings",
|
||||
role: "Marketing specialist with brand and growth experience",
|
||||
photo: {
|
||||
__image_url__: "https://images.pexels.com/photos/450214/pexels-photo-450214.jpeg",
|
||||
__image_prompt__: "Portrait of a professional team member with subtle background, soft light, business attire",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Sam Rawlings",
|
||||
role: "Marketing specialist with brand and growth experience",
|
||||
photo: {
|
||||
__image_url__: "https://images.pexels.com/photos/756484/pexels-photo-756484.jpeg",
|
||||
__image_prompt__: "Portrait of a professional team member with subtle background, soft light, business attire",
|
||||
},
|
||||
},
|
||||
|
||||
]).meta({
|
||||
description: "Grid of member cards with name, role, and image. Up to 4 items",
|
||||
}),
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const dynamicSlideLayout: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const cards = slideData?.cards || []
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;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" style={{ fontFamily:"var(--heading-font-family,Playfair Display)", backgroundColor: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
<div className="px-10 pt-6 flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
{ (slideData as any)?.__companyName__ && <span className="text-[18px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || "Pitchdeck"}
|
||||
</span>}
|
||||
<svg className="ml-6" width="220" height="2" viewBox="0 0 220 2" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="220" height="2" style={{ fill: 'var(--text-heading-color, #111827)' }}></rect>
|
||||
</svg>
|
||||
</div>
|
||||
{/* page number removed */}
|
||||
</div>
|
||||
|
||||
<h1 className="mt-4 text-center text-[64px] leading-[1.05] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{slideData?.title || "Our Professional Team"}
|
||||
</h1>
|
||||
|
||||
<div className="px-10 mt-8">
|
||||
<div className="mx-auto w-fit">
|
||||
<div className="grid grid-flow-col auto-cols-[280px] gap-8">
|
||||
{cards.map((card, idx) => (
|
||||
<div key={idx} className="w-[280px] flex flex-col rounded-md border border-transparent shadow-[0_12px_36px_rgba(0,0,0,0.08)] overflow-hidden" style={{ backgroundColor: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
<div className="px-8 pt-10 pb-6 text-center">
|
||||
<div className="text-[24px] leading-tight " style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{card.name}
|
||||
</div>
|
||||
<div className="mt-3 text-[14px] leading-[22px] " style={{ color: 'var(--text-body-color, #6B7280)' }}>
|
||||
{card.role}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex-1 min-h-[300px]" style={{ backgroundColor: 'var(--tertiary-accent-color, #E5E7EB)' }}>
|
||||
<img src={card.photo.__image_url__} alt={card.photo.__image_prompt__} className="absolute inset-0 w-full h-full object-cover" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default dynamicSlideLayout
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
const ImageSchema = z.object({
|
||||
__image_url__: z.string().url().default("https://images.pexels.com/photos/31527637/pexels-photo-31527637.jpeg").meta({
|
||||
description: "URL to image",
|
||||
}),
|
||||
__image_prompt__: z.string().min(10).max(150).default("High-quality illustrative image for the left panel of a pitch deck cover").meta({
|
||||
description: "Prompt used to generate the image. Max 30 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const IconSchema = z.object({
|
||||
__icon_url__: z.string().default("https://static.thenounproject.com/png/5563447-200.png").meta({
|
||||
description: "URL to icon",
|
||||
}),
|
||||
__icon_query__: z.string().min(3).max(40).default("image placeholder icon").meta({
|
||||
description: "Query used to search the icon. Max 3 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const layoutId = "header-counter-two-column-image-text-slide"
|
||||
const layoutName = "Intro Slide"
|
||||
const layoutDescription = "A slide with a header row containing label, separator, and counter, followed by a two-column layout with a media area and stacked text blocks. If used as the endig slide then it shoudn't have the intro card."
|
||||
|
||||
const Schema = z.object({
|
||||
header: z.object({
|
||||
|
||||
separatorIcon: IconSchema.default({
|
||||
__icon_url__: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='230' height='2' viewBox='0 0 230 2'%3E%3Crect width='230' height='2' fill='%2322863A'/%3E%3C/svg%3E",
|
||||
__icon_query__: "green line",
|
||||
}).meta({
|
||||
description: "Graphic separator element",
|
||||
}),
|
||||
counter: z.string().min(1).max(3).default("1").meta({
|
||||
description: "Small counter text. Max 1 word",
|
||||
}),
|
||||
}).default({
|
||||
|
||||
separatorIcon: {
|
||||
__icon_url__: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='230' height='2' viewBox='0 0 230 2'%3E%3Crect width='230' height='2' fill='%2322863A'/%3E%3C/svg%3E",
|
||||
__icon_query__: "green line",
|
||||
},
|
||||
counter: "1",
|
||||
}),
|
||||
|
||||
media: z.object({
|
||||
type: z.enum(["image"]).default("image").meta({
|
||||
description: "Choose media type for left panel",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
__image_url__: "https://images.unsplash.com/photo-1557426272-fc759fdf7a8d?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||
__image_prompt__: "Abstract gradient image suitable for a presentation left panel",
|
||||
}),
|
||||
}).default({
|
||||
type: "image",
|
||||
image: {
|
||||
__image_url__: "https://images.unsplash.com/photo-1557426272-fc759fdf7a8d?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||
__image_prompt__: "Abstract gradient image suitable for a presentation left panel",
|
||||
},
|
||||
}),
|
||||
|
||||
title: z.string().min(12).max(30).default("Introduction Our Pitchdeck").meta({
|
||||
description: "Main title, supports a line break. Max 6 words",
|
||||
}),
|
||||
titleBreakAfter: z.number().min(1).max(25).default(12).meta({
|
||||
description: "Character index to insert a line break in title",
|
||||
}),
|
||||
|
||||
paragraph: z.string().min(50).max(200).default("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris").meta({
|
||||
description: "Paragraph text block. Max 20 words",
|
||||
}),
|
||||
|
||||
introCard: z.object({
|
||||
enabled: z.boolean().default(false).meta({ description: "Show intro card with name and date" }),
|
||||
initials: z.string().min(2).max(3).default("PDT").meta({ description: "Initials inside the circle" }),
|
||||
name: z.string().min(3).max(40).default("Pitch Deck Team").meta({ description: "Display name" }),
|
||||
date: z.string().min(6).max(40).default("December 22, 2025").meta({ description: "Display date string" }),
|
||||
}).default({
|
||||
enabled: true,
|
||||
initials: "PDT",
|
||||
name: "Pitch Deck Team",
|
||||
date: "December 22, 2025",
|
||||
}),
|
||||
}).meta({
|
||||
maxWords: 460,
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface LayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const dynamicSlideLayout: React.FC<LayoutProps> = ({ data: slideData }) => {
|
||||
const title = slideData?.title || "Introduction Our Pitchdeck"
|
||||
const brIndex = typeof slideData?.titleBreakAfter === "number" ? slideData?.titleBreakAfter as number : 12
|
||||
const titleFirst = title.slice(0, brIndex)
|
||||
const titleSecond = title.slice(brIndex)
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;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" style={{ fontFamily:"var(--heading-font-family,Playfair Display)", backgroundColor: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
<div className="w-full flex items-center justify-between px-10 pt-6">
|
||||
<div className="flex items-center gap-5">
|
||||
{ (slideData as any)?.__companyName__ && <span className="text-[18px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>{(slideData as any)?.__companyName__ || "Pitchdeck"}</span>}
|
||||
<svg className="w-[220px] h-[2px]" viewBox="0 0 220 2" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="220" height="2" style={{ fill: 'var(--text-heading-color, #111827)' }}></rect>
|
||||
</svg>
|
||||
</div>
|
||||
{/* page number removed */}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 h-[calc(100%-64px)]">
|
||||
<div className="relative h-full overflow-hidden" style={{ backgroundColor: 'var(--tertiary-accent-color, #E5E7EB)' }}>
|
||||
{slideData?.media?.type === "image" ? (
|
||||
<img
|
||||
src={slideData?.media?.image?.__image_url__ || ""}
|
||||
alt={slideData?.media?.image?.__image_prompt__ || "left media"}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
) : null }
|
||||
</div>
|
||||
|
||||
<div className="h-full" style={{ backgroundColor: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
<div className="px-14 pt-16 max-w-[640px]">
|
||||
<h1 className=" text-[64px] leading-[1.06] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{titleFirst}
|
||||
<br />
|
||||
{titleSecond}
|
||||
</h1>
|
||||
|
||||
<p className="mt-8 text-[16px] leading-[28px] " style={{ color: 'var(--text-body-color, #6B7280)' }}>
|
||||
{slideData?.paragraph || "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris"}
|
||||
</p>
|
||||
|
||||
{slideData?.introCard?.enabled ? (
|
||||
<div className="mt-10 inline-flex items-center gap-4 border px-5 py-3 shadow-[0_10px_24px_rgba(0,0,0,0.08)] min-w-[400px]" style={{backgroundColor: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
<div className="w-16 h-16 rounded-full flex items-center justify-center" style={{ backgroundColor: 'var(--primary-accent-color, #1B8C2D)' }}>
|
||||
<span className="text-white text-[22px] font-bold tracking-wide" style={{ color: 'var(--text-heading-color, #FFFFFF)' }}>{slideData?.introCard?.initials}</span>
|
||||
</div>
|
||||
<div className="leading-tight">
|
||||
<div className="text-[22px] font-semibold" style={{ fontFamily: 'Playfair Display', color: 'var(--text-heading-color, #111827)' }}>{slideData?.introCard?.name}</div>
|
||||
<div className="text-[15px]" style={{ fontFamily: 'Playfair Display', color: 'var(--text-body-color, #1B8C2D)' }}>{slideData?.introCard?.date}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default dynamicSlideLayout
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
import React from 'react'
|
||||
// charts removed
|
||||
import * as z from "zod";
|
||||
|
||||
const ImageSchema = z.object({
|
||||
__image_url__: z.string().url().default("https://images.unsplash.com/photo-1503264116251-35a269479413?q=80&w=1200&auto=format&fit=crop").meta({
|
||||
description: "URL to image",
|
||||
}),
|
||||
__image_prompt__: z.string().min(10).max(200).default("Elegant abstract green themed background for a presentation slide, minimal shapes, soft lighting").meta({
|
||||
description: "Prompt used to generate the image. Max 40 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const IconSchema = z.object({
|
||||
__icon_url__: z.string().default("https://static.thenounproject.com/png/1783767-200.png").meta({
|
||||
description: "URL to icon",
|
||||
}),
|
||||
__icon_query__: z.string().min(3).max(40).default("leaf growth").meta({
|
||||
description: "Query used to search the icon. Max 6 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const layoutId = "header-tagline-cards-grid-slide"
|
||||
const layoutName = "Metrics Description"
|
||||
const layoutDescription = "A slide with a top utility row, a header, a tagline, and a grid of cards each with a number block and text"
|
||||
|
||||
const CardSchema = z.object({
|
||||
number: z.string().min(1).max(5).default("45").meta({
|
||||
description: "Main number text inside number block. 1 to 3 digits",
|
||||
}),
|
||||
numberSymbol: z.string().min(0).max(3).default("%").meta({
|
||||
description: "Optional symbol next to the number. Single character",
|
||||
}),
|
||||
subtitle: z.string().min(8).max(28).default("Subtitle Here").meta({
|
||||
description: "Card subtitle. Max 5 words",
|
||||
}),
|
||||
body: z.string().min(20).max(100).default("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do.").meta({
|
||||
description: "Card body text. Max 100 characters",
|
||||
}),
|
||||
icon: IconSchema.default({
|
||||
__icon_url__: "https://static.thenounproject.com/png/1783767-200.png",
|
||||
__icon_query__: "progress indicator",
|
||||
}).meta({
|
||||
description: "Optional icon for the card header area",
|
||||
}),
|
||||
})
|
||||
|
||||
const Schema = z.object({
|
||||
|
||||
title: z.string().min(12).max(70).default("Scaling New Heights Together").meta({
|
||||
description: "Main title. Single line up to ~34 chars or two lines up to ~70 chars. Max 9 words",
|
||||
}),
|
||||
tagline: z.string().min(40).max(120).default("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna").meta({
|
||||
description: "Subtitle/tagline under title. Max 20 words",
|
||||
}),
|
||||
decorativeLine: ImageSchema.default({
|
||||
__image_url__: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='2' viewBox='0 0 220 2'><rect width='220' height='2' rx='1' fill='%230B8E26'/></svg>",
|
||||
__image_prompt__: "Thin green horizontal line divider, 220x2, rounded ends",
|
||||
}).meta({
|
||||
description: "SVG decorative line asset",
|
||||
}),
|
||||
cards: z.array(CardSchema).min(1).max(6).default([
|
||||
{
|
||||
number: "87",
|
||||
numberSymbol: "%",
|
||||
subtitle: "Customer Satisfaction",
|
||||
body: "Our customers consistently rate their experience with our products and services as excellent.",
|
||||
icon: { __icon_url__: "https://static.thenounproject.com/png/1783767-200.png", __icon_query__: "happy customer icon" },
|
||||
},
|
||||
{
|
||||
number: "2.5",
|
||||
numberSymbol: "M",
|
||||
subtitle: "Active Users Monthly",
|
||||
body: "Growing user base actively engaging with our platform across multiple regions worldwide.",
|
||||
icon: { __icon_url__: "https://static.thenounproject.com/png/1783767-200.png", __icon_query__: "users group icon" },
|
||||
},
|
||||
{
|
||||
number: "99",
|
||||
numberSymbol: "%",
|
||||
subtitle: "System Uptime",
|
||||
body: "Maintaining exceptional reliability with industry-leading system availability and performance.",
|
||||
icon: { __icon_url__: "https://static.thenounproject.com/png/1783767-200.png", __icon_query__: "server uptime icon" },
|
||||
},
|
||||
{
|
||||
number: "142",
|
||||
numberSymbol: "+",
|
||||
subtitle: "Global Partners",
|
||||
body: "Strategic partnerships driving innovation and market expansion across key industry sectors.",
|
||||
icon: { __icon_url__: "https://static.thenounproject.com/png/1783767-200.png", __icon_query__: "handshake deal icon" },
|
||||
},
|
||||
{
|
||||
number: "32",
|
||||
numberSymbol: "x",
|
||||
subtitle: "Revenue Growth",
|
||||
body: "Year-over-year growth demonstrating strong market position and business model scalability.",
|
||||
icon: { __icon_url__: "https://static.thenounproject.com/png/1783767-200.png", __icon_query__: "growth chart icon" },
|
||||
},
|
||||
{
|
||||
number: "500",
|
||||
numberSymbol: "K",
|
||||
subtitle: "Carbon Offset",
|
||||
body: "Committed to sustainability through significant carbon reduction and environmental initiatives.",
|
||||
icon: { __icon_url__: "https://static.thenounproject.com/png/1783767-200.png", __icon_query__: "leaf sustainability icon" },
|
||||
},
|
||||
]).meta({
|
||||
description: "Grid of cards with number block, subtitle, and body (<=100 chars)",
|
||||
}),
|
||||
// chart and diagram removed
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const dynamicSlideLayout: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const cards = slideData?.cards || []
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;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" style={{ fontFamily:"var(--heading-font-family,Playfair Display)", backgroundColor: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
<div className="h-full flex flex-col px-10 pt-6 pb-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
{ (slideData as any)?.__companyName__ && <span className="text-[18px] " style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || "Pitchdeck"}
|
||||
</span>}
|
||||
<div className="block w-[220px] h-[2px]" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}></div>
|
||||
</div>
|
||||
{/* page number removed */}
|
||||
</div>
|
||||
|
||||
<h1 className="mt-4 text-[64px] leading-[1.06] tracking-tight font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{slideData?.title || "Scaling New Heights Together"}
|
||||
</h1>
|
||||
|
||||
<p className="mt-3 text-[16px] " style={{ color: 'var(--text-body-color, #6B7280)' }}>
|
||||
{slideData?.tagline || "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna"}
|
||||
</p>
|
||||
|
||||
<div className="mt-8 grid grid-cols-2 gap-x-10 gap-y-6">
|
||||
{cards.map((card, idx) => (
|
||||
<div key={idx} className="rounded-md shadow-sm px-5 py-4" style={{ backgroundColor: 'var(--primary-accent-color, #1B8C2D)' , color: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex items-baseline shrink-0">
|
||||
<span className="text-white text-[48px] leading-none" style={{ color: 'var(--text-heading-color, #FFFFFF)' }}>
|
||||
{card.number}
|
||||
</span>
|
||||
<span className="ml-1 text-white text-[24px] leading-none" style={{ color: 'var(--text-body-color, #FFFFFF)' }}>
|
||||
{card.numberSymbol}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-white text-[24px]" style={{ color: 'var(--text-heading-color, #FFFFFF)' }}>
|
||||
{card.subtitle}
|
||||
</h3>
|
||||
<p className="mt-1 text-white/95 text-[16px] leading-[1.55]" style={{ color: 'var(--text-body-color, #FFFFFF)' }} >
|
||||
{card.body}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* chart and flowchart removed */}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default dynamicSlideLayout
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
import React from 'react'
|
||||
// charts removed
|
||||
import * as z from "zod";
|
||||
|
||||
const ImageSchema = z.object({
|
||||
__image_url__: z.string().url().default("https://images.pexels.com/photos/31527637/pexels-photo-31527637.jpeg").meta({
|
||||
description: "URL to image",
|
||||
}),
|
||||
__image_prompt__: z.string().min(10).max(90).default("Professional business meeting scene for roadmap presentation image").meta({
|
||||
description: "Prompt used to generate the image. Max 18 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const IconSchema = z.object({
|
||||
__icon_url__: z.string().default("").meta({
|
||||
description: "URL to icon",
|
||||
}),
|
||||
__icon_query__: z.string().min(3).max(30).default("").meta({
|
||||
description: "Query used to search the icon. Max 6 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const layoutId = "header-bullets-image-split-slide"
|
||||
const layoutName = "Numbered Bullet Single Image"
|
||||
const layoutDescription = "A slide with a top bar, left column with numbered bullets, middle image panel, and right stacked heading with paragraph"
|
||||
|
||||
const Schema = z.object({
|
||||
metaMaxWords: z.number().default(18).meta({
|
||||
description: "Maximum number of words allowed in any prompt/description metadata fields.",
|
||||
}),
|
||||
topBar: z.object({
|
||||
|
||||
lineIcon: IconSchema.default({
|
||||
__icon_url__: "",
|
||||
__icon_query__: "thin green line",
|
||||
}).meta({
|
||||
description: "Decorative line representation with query only",
|
||||
}),
|
||||
}).default({
|
||||
|
||||
lineIcon: {
|
||||
__icon_url__: "",
|
||||
__icon_query__: "thin green line",
|
||||
},
|
||||
}),
|
||||
leftBullets: z.array(
|
||||
z.object({
|
||||
numberText: z.string().min(2).max(2).default("01").meta({
|
||||
description: "Two-digit bullet number. Max 2 chars",
|
||||
}),
|
||||
title: z.string().min(10).max(36).default("Strategic Execution").meta({
|
||||
description: "Bullet title text. Designed for 24px. Max ~36 chars",
|
||||
}),
|
||||
body: z.string().min(60).max(100).default("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.").meta({
|
||||
description: "Bullet body text. Max ~100 chars",
|
||||
}),
|
||||
})
|
||||
).min(1).max(4).default([
|
||||
{
|
||||
numberText: "01",
|
||||
title: "Strategic Execution",
|
||||
body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.",
|
||||
},
|
||||
{
|
||||
numberText: "02",
|
||||
title: "Building a Strong Team",
|
||||
body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.",
|
||||
},
|
||||
{
|
||||
numberText: "03",
|
||||
title: "Market Expansion Strategy",
|
||||
body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.",
|
||||
},
|
||||
{
|
||||
numberText: "04",
|
||||
title: "Innovation Pipeline",
|
||||
body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.",
|
||||
}
|
||||
]).meta({
|
||||
description: "List of numbered bullets. Max 5 items",
|
||||
}),
|
||||
middleImage: ImageSchema.default({
|
||||
__image_url__: "https://images.unsplash.com/photo-1515623959088-7617915baa1e?q=80&w=687&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||
__image_prompt__: "Professional business meeting scene for roadmap presentation image",
|
||||
}).meta({
|
||||
description: "Image displayed in the middle column",
|
||||
}),
|
||||
rightHeader: z.object({
|
||||
heading: z.string().min(6).max(30).default("Our Journey").meta({
|
||||
description: "Right column heading. Max ~30 chars",
|
||||
}),
|
||||
paragraph: z.string().min(80).max(200).default("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.").meta({
|
||||
description: "Right paragraph text. Max 200 chars",
|
||||
}),
|
||||
}).default({
|
||||
heading: "Our Journey",
|
||||
paragraph: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.",
|
||||
}),
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const dynamicSlideLayout: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const bullets = slideData?.leftBullets || []
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;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" style={{ fontFamily:"var(--heading-font-family,Playfair Display)", backgroundColor: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
<div className="flex items-center justify-between px-10 pt-6">
|
||||
<div className="flex items-center gap-4">
|
||||
{ (slideData as any)?.__companyName__ && <span className="text-[18px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{(slideData as any)?.__companyName__ || "Pitchdeck"}
|
||||
</span>}
|
||||
<svg className="shrink-0" width="220" height="2" viewBox="0 0 220 2" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 1H220" stroke="var(--text-heading-color, #111827)" strokeWidth="2" />
|
||||
</svg>
|
||||
</div>
|
||||
{/* page number removed */}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-[34%_33%_33%] h-[calc(100%-64px)] mt-1">
|
||||
<div className="pl-10 pr-5 pt-8">
|
||||
<ul className="flex flex-col gap-5">
|
||||
{bullets.map((b, i) => (
|
||||
<li key={i} className="flex items-center justify-between">
|
||||
<div className="w-[85%]">
|
||||
<h3 className="text-[24px] leading-tight font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{b.title}
|
||||
</h3>
|
||||
<p className="mt-2 text-[16px] leading-relaxed " style={{ color: 'var(--text-body-color, #6B7280)' }}>
|
||||
{b.body}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-12 h-12 rounded-full text-white flex items-center justify-center text-[16px] shadow-[0_12px_30px_rgba(0,0,0,0.12)]" style={{ backgroundColor: 'var(--primary-accent-color, #1B8C2D)' , color: 'var(--text-body-color, #FFFFFF)' }}>
|
||||
{b.numberText}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="relative" style={{ backgroundColor: 'var(--tertiary-accent-color, #E5E7EB)' }}>
|
||||
<img
|
||||
src={slideData?.middleImage?.__image_url__ || ""}
|
||||
alt={slideData?.middleImage?.__image_prompt__ || "image"}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
{/* overlay removed */}
|
||||
</div>
|
||||
|
||||
<div className="pl-10 pr-12 pt-16">
|
||||
<div className="max-w-[560px] mx-auto">
|
||||
<h1 className="text-[64px] leading-[0.95] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
<span className="block">{slideData?.rightHeader?.heading || "Our Journey"}</span>
|
||||
</h1>
|
||||
<p className="mt-6 text-[16px] leading-relaxed " style={{ color: 'var(--text-body-color, #6B7280)' }}>
|
||||
{slideData?.rightHeader?.paragraph || "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default dynamicSlideLayout
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
|
||||
const layoutId = "table-of-contents-layout"
|
||||
const layoutName = "Table Of Contents"
|
||||
const layoutDescription = "Header with brand marker, title, optional description, and a two-column table of contents list"
|
||||
|
||||
const ToCItemSchema = z
|
||||
.object({
|
||||
title: z.string().min(4).max(50).default("Introduction").meta({
|
||||
description: "Section title. Max 50 characters",
|
||||
}),
|
||||
})
|
||||
.default({
|
||||
title: "Introduction",
|
||||
})
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
topBar: z
|
||||
.object({
|
||||
|
||||
marker: z.string().min(1).max(3).default("2").meta({
|
||||
description: "Numeric marker on the top bar. Up to 3 digits",
|
||||
}),
|
||||
})
|
||||
.default({ marker: "2" }),
|
||||
|
||||
title: z
|
||||
.string()
|
||||
.min(12)
|
||||
.max(68)
|
||||
.default("Table Of Contents")
|
||||
.meta({ description: "Main slide title. Max 10 words" }),
|
||||
|
||||
description: z
|
||||
.string()
|
||||
.min(0)
|
||||
.max(200)
|
||||
.default(
|
||||
"Use this as a quick guide to navigate the presentation sections."
|
||||
)
|
||||
.meta({ description: "Lead paragraph. Optional. Max 35 words" }),
|
||||
|
||||
items: z
|
||||
.array(ToCItemSchema)
|
||||
.min(3)
|
||||
.max(10)
|
||||
.default([
|
||||
{ title: "Introduction" },
|
||||
{ title: "Problem Statement" },
|
||||
{ title: "Solution" },
|
||||
{ title: "Market" },
|
||||
{ title: "Business Model" },
|
||||
{ title: "Roadmap" },
|
||||
{ title: "Team" },
|
||||
{ title: "Go-To-Market" },
|
||||
{ title: "Financials" },
|
||||
{ title: "Ask" },
|
||||
])
|
||||
.meta({ description: "List of contents (3-10)" }),
|
||||
})
|
||||
.default({
|
||||
topBar: { marker: "2" },
|
||||
title: "Table Of Contents",
|
||||
description:
|
||||
"Use this as a quick guide to navigate the presentation sections.",
|
||||
items: [
|
||||
{ title: "Introduction" },
|
||||
{ title: "Problem Statement" },
|
||||
{ title: "Solution" },
|
||||
{ title: "Market" },
|
||||
{ title: "Business Model" },
|
||||
{ title: "Roadmap" },
|
||||
{ title: "Team" },
|
||||
{ title: "Go-To-Market" },
|
||||
{ title: "Financials" },
|
||||
{ title: "Ask" },
|
||||
],
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const dynamicSlideLayout: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;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"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Playfair Display)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-6">
|
||||
{ (slideData as any)?.__companyName__ && <span
|
||||
className="text-[18px] font-semibold "
|
||||
style={{ color: "var(--primary-accent-color, #1B8C2D)" }}
|
||||
>
|
||||
{(slideData as any)?.__companyName__ || "Pitchdeck"}
|
||||
</span>}
|
||||
<div
|
||||
className="h-[2px] w-[220px]"
|
||||
style={{ backgroundColor: "var(--primary-accent-color, #1B8C2D)" }}
|
||||
></div>
|
||||
</div>
|
||||
{/* page number intentionally omitted */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-12">
|
||||
<h1
|
||||
className="text-[64px] leading-[1.05] tracking-tight font-semibold mt-2"
|
||||
style={{ color: "var(--text-heading-color, #111827)" }}
|
||||
>
|
||||
{slideData?.title}
|
||||
</h1>
|
||||
{slideData?.description && (
|
||||
<p
|
||||
className="mt-5 text-[16px] leading-[1.6] max-w-[1020px] "
|
||||
style={{ color: "var(--text-body-color, #6B7280)" }}
|
||||
>
|
||||
{slideData?.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="px-10 mt-10">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{items.map((item, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="rounded-sm border shadow-[0_8px_24px_rgba(0,0,0,0.06)] px-4 py-3 flex items-center gap-4"
|
||||
style={{
|
||||
backgroundColor: "var(--primary-accent-color, #FFFFFF)",
|
||||
borderColor: "rgba(0,0,0,0.06)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="w-8 h-8 rounded-full flex items-center justify-center text-[16px] font-semibold"
|
||||
style={{
|
||||
border: "2px solid var(--primary-accent-color, #1B8C2D)",
|
||||
color: "var(--text-heading-color,, #111827)",
|
||||
}}
|
||||
>
|
||||
{idx + 1}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div
|
||||
className="text-[18px] leading-tight font-semibold truncate"
|
||||
style={{ color: "var(--text-heading-color, #111827)" }}
|
||||
>
|
||||
{item.title}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default dynamicSlideLayout
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
import React from 'react'
|
||||
// charts trimmed to donut only (inline SVG)
|
||||
import * as z from "zod";
|
||||
|
||||
|
||||
const ImageSchema = z.object({
|
||||
__image_url__: z.string().url().default("https://images.unsplash.com/photo-1522199755839-a2bacb67c546?q=80&w=1200&auto=format&fit=crop").meta({
|
||||
description: "URL to image",
|
||||
}),
|
||||
__image_prompt__: z.string().min(10).max(150).default("Abstract light background for slide header area").meta({
|
||||
description: "Prompt used to generate the image. Max 30 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const IconSchema = z.object({
|
||||
__icon_url__: z.string().url().default("https://cdn.jsdelivr.net/gh/tabler/tabler-icons/icons/placeholder.svg").meta({
|
||||
description: "URL to icon",
|
||||
}),
|
||||
__icon_query__: z.string().min(3).max(30).default("progress ring placeholder").meta({
|
||||
description: "Query used to search the icon. Max 3 words",
|
||||
}),
|
||||
})
|
||||
|
||||
const layoutId = "visual-metrics"
|
||||
const layoutName = "Visual Metrics"
|
||||
const layoutDescription = "A slide with a header bar, numeric marker, title, description, and grid of cards with headings, circular metrics, and texts"
|
||||
|
||||
const CardSchema = z.object({
|
||||
title: z.string().min(6).max(18).default("Research").meta({
|
||||
description: "Card heading. Max 3 words",
|
||||
}),
|
||||
value: z.number().min(0).max(9999).default(67).meta({
|
||||
description: "Numeric value to display",
|
||||
}),
|
||||
unit: z.string().min(0).max(2).default("K").meta({
|
||||
description: "Unit for the value, e.g., %, K, M",
|
||||
}),
|
||||
description: z.string().min(1).max(50).default("Lorem ipsum dolor sit amet, consectetur").meta({
|
||||
description: "Card supporting text. Max 50 characters",
|
||||
}),
|
||||
}).default({
|
||||
title: "Research",
|
||||
value: 67,
|
||||
unit: "K",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur",
|
||||
})
|
||||
|
||||
const Schema = z.object({
|
||||
topBar: z.object({
|
||||
|
||||
marker: z.string().min(1).max(3).default("2").meta({
|
||||
description: "Numeric marker on the top bar. Up to 3 digits",
|
||||
}),
|
||||
}).default({
|
||||
|
||||
marker: "2",
|
||||
}),
|
||||
title: z.string().min(20).max(68).default("Our Vision And Strategy For Excellence").meta({
|
||||
description: "Main slide title. Max 10 words",
|
||||
}),
|
||||
description: z.string().min(70).max(200).default("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation").meta({
|
||||
description: "Lead paragraph. Max 35 words",
|
||||
}),
|
||||
cards: z.array(CardSchema).min(1).max(4).default([
|
||||
{
|
||||
title: "Research",
|
||||
value: 6700,
|
||||
unit: "K",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur",
|
||||
|
||||
},
|
||||
{
|
||||
title: "Development",
|
||||
value: 80,
|
||||
unit: "K",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur",
|
||||
|
||||
},
|
||||
{
|
||||
title: "Research",
|
||||
value: 67,
|
||||
unit: "%",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur",
|
||||
|
||||
},
|
||||
{ title: "Development", value: 80, unit: "K", description: "Lorem ipsum dolor sit amet, consectetur" },
|
||||
]).meta({
|
||||
description: "Grid of cards with heading, circular metric, and text",
|
||||
}),
|
||||
chartPalette: z.array(z.string().min(4).max(20)).min(2).max(6).default(["var(--primary-accent-color, #1B8C2D)", "var(--tertiary-accent-color, #E5E7EB)", "#f59e0b", "#3b82f6"]).meta({
|
||||
description: "Palette for charts",
|
||||
}),
|
||||
}).default({
|
||||
topBar: { marker: "2" },
|
||||
title: "Our Vision And Strategy For Excellence",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation",
|
||||
cards: [
|
||||
{ title: "Research", value: 67, unit: "K", description: "Lorem ipsum dolor sit amet, consectetur" },
|
||||
{ title: "Ops", value: 42, unit: "M", description: "Lorem ipsum dolor sit amet, consectetur" },
|
||||
{ title: "Efficiency", value: 67, unit: "%", description: "Lorem ipsum dolor sit amet, consectetur" },
|
||||
{ title: "Development", value: 80, unit: "K", description: "Lorem ipsum dolor sit amet, consectetur" },
|
||||
],
|
||||
chartPalette: ["var(--primary-accent-color, #1B8C2D)", "var(--tertiary-accent-color, #E5E7EB)", "#f59e0b", "#3b82f6"],
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
// removed external chart components; using inline SVG ring only
|
||||
|
||||
const dynamicSlideLayout: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const cards = slideData?.cards || []
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;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" style={{ fontFamily:"var(--heading-font-family,Playfair Display)", backgroundColor: 'var(--card-background-color, #FFFFFF)' }}>
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-6">
|
||||
{ (slideData as any)?.__companyName__ && <span className="text-[18px] font-semibold " style={{ color: 'var(--text-heading-color, #111827)' }}>{(slideData as any)?.__companyName__ || "Pitchdeck"}</span>}
|
||||
<div className="h-[2px] w-[220px]" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}></div>
|
||||
</div>
|
||||
{/* page number removed */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-12">
|
||||
<h1 className="text-[64px] leading-[1.05] tracking-tight font-semibold mt-2" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{slideData?.title}
|
||||
</h1>
|
||||
<p className="mt-5 text-[16px] leading-[1.6] max-w-[1020px] " style={{ color: 'var(--text-body-color, #6B7280)' }}>
|
||||
{slideData?.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="px-10 mt-10">
|
||||
<div className="grid grid-cols-4 gap-8">
|
||||
{cards.map((card, idx) => {
|
||||
const radius = 80
|
||||
const circumference = 2 * Math.PI * radius
|
||||
const dasharray = circumference
|
||||
return (
|
||||
<div key={idx} className="rounded-xl border shadow-[0_24px_60px_rgba(0,0,0,0.08)]" style={{ backgroundColor: 'var(--card-background-color, #FFFFFF)', borderColor: 'rgba(0,0,0,0.06)' }}>
|
||||
<div className="px-8 pt-8 pb-7 flex flex-col items-center text-center">
|
||||
<h3 className="text-[24px] leading-tight font-semibold " style={{ color: 'var(--text-heading-color, #111827)' }}>{card.title}</h3>
|
||||
<div className="mt-6 relative w-[180px] h-[180px]">
|
||||
{/* donut ring retained */}
|
||||
<svg viewBox="0 0 200 200" className="w-full h-full">
|
||||
<circle cx="100" cy="100" r="80" fill="none" stroke="var(--tertiary-accent-color, #E5E7EB)" strokeWidth="16"></circle>
|
||||
<circle
|
||||
cx="100"
|
||||
cy="100"
|
||||
r="80"
|
||||
fill="none"
|
||||
stroke={'var(--primary-accent-color, #1B8C2D)'}
|
||||
strokeWidth="16"
|
||||
strokeLinecap="round"
|
||||
strokeDasharray={dasharray}
|
||||
strokeDashoffset={0}
|
||||
transform="rotate(-90 100 100)"
|
||||
></circle>
|
||||
<circle cx="100" cy="100" r="62" fill="none" stroke="rgba(0,0,0,0.06)" strokeWidth="10"></circle>
|
||||
</svg>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<span className="text-[32px] font-extrabold " style={{ color: 'var(--text-heading-color, #111827)' }}>{card.value}{card.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-6 text-[16px] leading-[1.6] " style={{ color: 'var(--text-body-color, #6B7280)' }}>
|
||||
{card.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* mermaid removed */}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default dynamicSlideLayout
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"description": "Standard layouts for presentations",
|
||||
"ordered": false,
|
||||
"default": false
|
||||
}
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
|
||||
import { IconSchema, ImageSchema } from '@/presentation-templates/defaultSchemes';
|
||||
import { RemoteSvgIcon } from "@/app/hooks/useRemoteSvgIcon";
|
||||
|
||||
const layoutId = "bullet-with-icons-title-description"
|
||||
const layoutName = "Bullet With Icons Title Description"
|
||||
const layoutDescription = "Bullet with icons with title and description and title and description for whole"
|
||||
|
||||
const ItemSchema = z
|
||||
.object({
|
||||
icon: IconSchema,
|
||||
title: z.string().min(3).max(40).default("Lorem ipsum dolor"),
|
||||
description: z
|
||||
.string()
|
||||
.min(0)
|
||||
.max(160)
|
||||
.default(
|
||||
"Short supporting description that fits under the icon title."
|
||||
),
|
||||
})
|
||||
.default({
|
||||
icon: {
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/graduation-cap-bold.svg",
|
||||
__icon_query__: "feature icon",
|
||||
},
|
||||
title: "Lorem ipsum dolor",
|
||||
description: "Short supporting description that fits under the icon title.",
|
||||
})
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
title: z
|
||||
.string()
|
||||
.min(3)
|
||||
.max(60)
|
||||
.default("Our Infographic"),
|
||||
sideHeading: z.string().min(0).max(60).default("Lorem ipsum dolor sit amet,"),
|
||||
sideParagraph: z
|
||||
.string()
|
||||
.min(0)
|
||||
.max(300)
|
||||
.default(
|
||||
"Concise paragraph describing context. Keep it short and readable across one or two lines."
|
||||
),
|
||||
items: z
|
||||
.array(ItemSchema)
|
||||
.min(3)
|
||||
.max(4)
|
||||
.default([
|
||||
{
|
||||
icon: {
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/graduation-cap-bold.svg",
|
||||
__icon_query__: "feature icon",
|
||||
},
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the first icon explaining the point.",
|
||||
},
|
||||
{
|
||||
icon: {
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/user-bold.svg",
|
||||
__icon_query__: "feature icon",
|
||||
},
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the second icon explaining the point.",
|
||||
},
|
||||
{
|
||||
icon: {
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/calendar-blank-bold.svg",
|
||||
__icon_query__: "feature icon",
|
||||
},
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the third icon explaining the point.",
|
||||
},
|
||||
{
|
||||
icon: {
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/x-bold.svg",
|
||||
__icon_query__: "feature icon",
|
||||
},
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the fourth icon explaining the point.",
|
||||
},
|
||||
]),
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
})
|
||||
.default({
|
||||
title: "Our Infographic",
|
||||
sideHeading: "Lorem ipsum dolor sit amet,",
|
||||
sideParagraph:
|
||||
"Concise paragraph describing context. Keep it short and readable across one or two lines.",
|
||||
items: [
|
||||
{
|
||||
icon: { __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/graduation-cap-bold.svg", __icon_query__: "feature icon" },
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the first icon explaining the point.",
|
||||
},
|
||||
{
|
||||
icon: { __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/user-bold.svg", __icon_query__: "feature icon" },
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the second icon explaining the point.",
|
||||
},
|
||||
{
|
||||
icon: { __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/calendar-blank-bold.svg", __icon_query__: "feature icon" },
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the third icon explaining the point.",
|
||||
},
|
||||
{
|
||||
icon: { __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/x-bold.svg", __icon_query__: "feature icon" },
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the fourth icon explaining the point.",
|
||||
},
|
||||
],
|
||||
website: "www.yourwebsite.com",
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const InfographicFourIcons: React.FC<SlideLayoutProps> = ({ data }) => {
|
||||
const slideData = data || {}
|
||||
const items = slideData.items || []
|
||||
|
||||
const renderTitle = (title?: string) => {
|
||||
if (!title) return null
|
||||
const parts = title.split("\n")
|
||||
return (
|
||||
<>
|
||||
{parts.map((p, i) => (
|
||||
<div key={i}>{p}</div>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;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"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
{(slideData as any )?.__companyName__ && <span className="text-[16px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{(slideData as any)?.__companyName__}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Title + right paragraph */}
|
||||
<div className="px-12 pt-2">
|
||||
<div className="grid grid-cols-[1.2fr_1fr] gap-10 items-start">
|
||||
<div className="text-[56px] leading-[1.05] font-semibold" style={{ color: "var(--text-heading-color, #111827)" }}>
|
||||
{renderTitle(slideData.title)}
|
||||
</div>
|
||||
<div>
|
||||
{slideData.sideHeading && (
|
||||
<div className="text-[16px] mb-2 font-semibold" style={{ color: "var(--text-heading-color, #111827)" }}>
|
||||
{slideData.sideHeading}
|
||||
</div>
|
||||
)}
|
||||
{slideData.sideParagraph && (
|
||||
<div className="text-[14px] leading-[1.6]" style={{ color: "var(--text-body-color, #6B7280)" }}>
|
||||
{slideData.sideParagraph}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Icons row */}
|
||||
<div className="px-12 pt-12">
|
||||
<div className="grid grid-flow-col auto-cols-[260px] gap-8 justify-center">
|
||||
{items.slice(0, 4).map((item, idx) => (
|
||||
<div key={idx} className="flex flex-col items-center text-center">
|
||||
<div className="relative">
|
||||
<div
|
||||
className="w-40 h-40 rounded-full flex items-center justify-center shadow"
|
||||
style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}
|
||||
>
|
||||
{/* Icon */}
|
||||
<RemoteSvgIcon
|
||||
url={item.icon.__icon_url__}
|
||||
strokeColor={"currentColor"}
|
||||
className="w-14 h-14"
|
||||
color="var(--text-heading-color, #111827)"
|
||||
title={item.icon.__icon_query__}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 text-[16px] font-semibold" style={{ color: "var(--text-heading-color, #111827)" }}>
|
||||
{item.title}
|
||||
</div>
|
||||
<div className="mt-2 text-[13px] leading-[1.6] max-w-[260px]" style={{ color: "var(--text-body-color, #6B7280)" }}>
|
||||
{item.description}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer (standardized like IntroSlideLayout) */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default InfographicFourIcons
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
import { RemoteSvgIcon } from "@/app/hooks/useRemoteSvgIcon";
|
||||
|
||||
const layoutId = "icon-bullet-list-description-slide"
|
||||
const layoutName = "Icon Bullet List Description"
|
||||
const layoutDescription = "Bullet list with title, description, and icon"
|
||||
|
||||
const IconSchema = z
|
||||
.object({
|
||||
__icon_url__: z
|
||||
.string()
|
||||
.default(
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/file-text-bold.svg"
|
||||
),
|
||||
__icon_query__: z.string().min(0).max(80).default("feature icon"),
|
||||
})
|
||||
.default({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/file-text-bold.svg",
|
||||
__icon_query__: "feature icon",
|
||||
})
|
||||
|
||||
const FeatureSchema = z
|
||||
.object({
|
||||
title: z.string().min(4).max(28).default("Customizable Workflows"),
|
||||
body: z
|
||||
.string()
|
||||
.min(20)
|
||||
.max(140)
|
||||
.default("Lorem ipsum dolor sit amet, dolor sit amet."),
|
||||
icon: IconSchema,
|
||||
})
|
||||
.default({
|
||||
title: "Customizable Workflows",
|
||||
body: "Lorem ipsum dolor sit amet, dolor sit amet.",
|
||||
icon: IconSchema.parse({}),
|
||||
})
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
|
||||
title: z
|
||||
.string()
|
||||
.min(8)
|
||||
.max(48)
|
||||
.default("Key Product Features"),
|
||||
description: z
|
||||
.string()
|
||||
.min(30)
|
||||
.max(200)
|
||||
.default(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor."
|
||||
),
|
||||
features: z
|
||||
.array(FeatureSchema)
|
||||
.min(3)
|
||||
.max(4)
|
||||
.default([
|
||||
FeatureSchema.parse({}),
|
||||
{
|
||||
title: "Multi-Device Access",
|
||||
body: "Lorem ipsum dolor sit amet.",
|
||||
icon: IconSchema.parse({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/devices-bold.svg",
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "Scalable Architecture",
|
||||
body: "Lorem ipsum dolor sit amet.",
|
||||
icon: IconSchema.parse({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/chart-line-up-bold.svg",
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "Detailed Reports",
|
||||
body: "Lorem ipsum dolor sit amet.",
|
||||
icon: IconSchema.parse({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/checks-bold.svg",
|
||||
}),
|
||||
},
|
||||
]),
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
})
|
||||
.default({
|
||||
|
||||
title: "Key Product Features",
|
||||
description:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor.",
|
||||
features: [
|
||||
FeatureSchema.parse({}),
|
||||
{
|
||||
title: "Multi-Device Access",
|
||||
body: "Lorem ipsum dolor sit amet.",
|
||||
icon: IconSchema.parse({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/devices-bold.svg",
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "Scalable Architecture",
|
||||
body: "Lorem ipsum dolor sit amet.",
|
||||
icon: IconSchema.parse({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/chart-line-up-bold.svg",
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "Detailed Reports",
|
||||
body: "Lorem ipsum dolor sit amet.",
|
||||
icon: IconSchema.parse({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/checks-bold.svg",
|
||||
}),
|
||||
},
|
||||
],
|
||||
website: "www.yourwebsite.com",
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const FeatureCards: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const features = slideData?.features || []
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;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"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
{(slideData as any)?.__companyName__ && <span className="text-[16px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{(slideData as any)?.__companyName__}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Decorative right image area removed to keep imagery-driven design */}
|
||||
|
||||
<div className="px-12 pt-3">
|
||||
<h1 className="text-[48px] leading-[1.1] font-semibold" style={{ color: "var(--text-heading-color, #111827)" }}>{slideData?.title}</h1>
|
||||
<p className="mt-3 text-[16px] max-w-[760px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.description}</p>
|
||||
</div>
|
||||
|
||||
{/* Cyan band */}
|
||||
<div className="absolute left-0 right-0 bottom-20 h-[160px]" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}></div>
|
||||
|
||||
{/* Feature cards */}
|
||||
<div className="relative px-12 mt-8">
|
||||
<div className="grid grid-flow-col auto-cols-[260px] gap-6 justify-center">
|
||||
{features.slice(0,4).map((f, i) => (
|
||||
<div key={i} className="rounded-[22px] shadow-[0_16px_40px_rgba(0,0,0,0.08)] overflow-hidden" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}>
|
||||
<div className="px-6 py-5">
|
||||
<div className="w-10 h-10 rounded-sm flex items-center justify-center" style={{ backgroundColor: 'var(--secondary-accent-color, #FFFFFF)' }}>
|
||||
<RemoteSvgIcon
|
||||
url={f.icon.__icon_url__}
|
||||
strokeColor={"currentColor"}
|
||||
className="w-6 h-6"
|
||||
color="var(--text-heading-color, #111827)"
|
||||
title={f.icon.__icon_query__}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 text-[18px] font-semibold whitespace-pre-line" style={{ color: 'var(--text-heading-color, #111827)' }}>{f.title}</div>
|
||||
<p className="mt-3 text-[14px] leading-[1.7]" style={{ color: 'var(--text-body-color, #6B7280)' }}>{f.body}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer (standardized like IntroSlideLayout) */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default FeatureCards
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
|
||||
const layoutId = "image-list-description-slide"
|
||||
const layoutName = "Image List Description"
|
||||
const layoutDescription = "List of Images with subtitle and description with one description for the entire page"
|
||||
|
||||
const ImageSchema = z
|
||||
.object({
|
||||
__image_url__: z
|
||||
.string()
|
||||
.url()
|
||||
.default(
|
||||
"https://images.unsplash.com/photo-1522199710521-72d69614c702?w=1200&q=80&auto=format&fit=crop"
|
||||
),
|
||||
__image_prompt__: z
|
||||
.string()
|
||||
.min(0)
|
||||
.max(120)
|
||||
.default("abstract gradient background"),
|
||||
})
|
||||
.default({
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1522199710521-72d69614c702?w=1200&q=80&auto=format&fit=crop",
|
||||
__image_prompt__: "abstract gradient background",
|
||||
})
|
||||
|
||||
const ItemSchema = z
|
||||
.object({
|
||||
title: z.string().min(2).max(40).default("Sample Title"),
|
||||
description: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(140)
|
||||
.default("Short description for the image or item."),
|
||||
image: ImageSchema,
|
||||
})
|
||||
.default({
|
||||
title: "Sample Title",
|
||||
description: "Short description for the image or item.",
|
||||
image: ImageSchema.parse({}),
|
||||
})
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
titleLine1: z.string().min(3).max(24).default("Meet Our"),
|
||||
titleLine2: z.string().min(3).max(24).default("Team"),
|
||||
description: z
|
||||
.string()
|
||||
.min(20)
|
||||
.max(200)
|
||||
.default(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
),
|
||||
items: z
|
||||
.array(ItemSchema)
|
||||
.min(3)
|
||||
.max(6)
|
||||
.default([
|
||||
ItemSchema.parse({}),
|
||||
ItemSchema.parse({ title: "Another Item", description: "Concise supporting text.", image: ImageSchema.parse({}) }),
|
||||
ItemSchema.parse({ title: "Third Item", description: "Concise supporting text.", image: ImageSchema.parse({}) }),
|
||||
]),
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
})
|
||||
.default({
|
||||
|
||||
titleLine1: "Meet Our",
|
||||
titleLine2: "Team",
|
||||
description:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
items: [
|
||||
ItemSchema.parse({}),
|
||||
ItemSchema.parse({ title: "Another Item", description: "Concise supporting text.", image: ImageSchema.parse({}) }),
|
||||
ItemSchema.parse({ title: "Third Item", description: "Concise supporting text.", image: ImageSchema.parse({}) }),
|
||||
],
|
||||
website: "www.yourwebsite.com",
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const TeamMembers: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;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"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header: diamond + business name */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="w-3 h-3 rotate-45"
|
||||
style={{ backgroundColor: "var(--text-heading-color, #111827)" }}
|
||||
></div>
|
||||
{ (slideData as any)?.__companyName__ && <span
|
||||
className="text-[16px]"
|
||||
style={{ color: "var(--text-body-color, #6B7280)" }}
|
||||
>
|
||||
{(slideData as any)?.__companyName__}
|
||||
</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-12 pt-6 grid grid-cols-[36%_64%] gap-8 items-start">
|
||||
{/* Left text stack */}
|
||||
<div>
|
||||
<div
|
||||
className="text-[56px] leading-[1.05] font-semibold"
|
||||
style={{ color: "var(--text-heading-color, #111827)" }}
|
||||
>
|
||||
{slideData?.titleLine1}
|
||||
<br />
|
||||
{slideData?.titleLine2}
|
||||
</div>
|
||||
<p
|
||||
className="mt-8 text-[16px] leading-[1.8] max-w-[300px]"
|
||||
style={{ color: "var(--text-body-color, #6B7280)" }}
|
||||
>
|
||||
{slideData?.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right generic image cards */}
|
||||
<div className="grid grid-cols-3 gap-10">
|
||||
{items.slice(0, 3).map((it, i) => (
|
||||
<div key={i} className="flex flex-col items-stretch">
|
||||
{/* Photo block uses provided image */}
|
||||
<div className="h-[180px] rounded-t-md overflow-hidden">
|
||||
<img src={it.image.__image_url__} alt={it.image.__image_prompt__} className="w-full h-full object-cover" />
|
||||
</div>
|
||||
{/* Cyan details panel */}
|
||||
<div className="relative -mt-[1px] rounded-b-[28px] px-6 pt-6 pb-7" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}>
|
||||
<div className="text-[20px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>{it.title}</div>
|
||||
<p className="mt-3 text-[14px] leading-[1.7]" style={{ color: 'var(--text-body-color, #6B7280)' }}>{it.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer line with website and end diamond */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "#E5E7EB" }}></div>
|
||||
</div>
|
||||
|
||||
{/* Big bottom-right diamond */}
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default TeamMembers
|
||||
|
||||
|
||||
177
servers/nextjs/presentation-templates/swift/IntroSlideLayout.tsx
Normal file
177
servers/nextjs/presentation-templates/swift/IntroSlideLayout.tsx
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
|
||||
const layoutId = "IntroSlideLayout"
|
||||
const layoutName = "Intro Slide Layout"
|
||||
const layoutDescription = "Intro slide with header, title, subtitle, body, image. If used for last slide, then intro card should be disabled."
|
||||
|
||||
const ImageSchema = z
|
||||
.object({
|
||||
__image_url__: z.string().url().default("https://images.unsplash.com/photo-1522199710521-72d69614c702?w=1200&q=80&auto=format&fit=crop"),
|
||||
__image_prompt__: z.string().min(0).max(120).default("abstract gradient background"),
|
||||
})
|
||||
.default({
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1522199710521-72d69614c702?w=1200&q=80&auto=format&fit=crop",
|
||||
__image_prompt__: "abstract gradient background",
|
||||
})
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
|
||||
title: z
|
||||
.string()
|
||||
.min(12)
|
||||
.max(68)
|
||||
.default("Pitch Deck")
|
||||
.meta({ description: "Main slide title" }),
|
||||
|
||||
subtitlePrefix: z.string().min(3).max(40).default("Presentation"),
|
||||
subtitleAccent: z.string().min(3).max(40).default("Template"),
|
||||
|
||||
paragraph: z
|
||||
.string()
|
||||
.min(40)
|
||||
.max(200)
|
||||
.default(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
),
|
||||
|
||||
website: z
|
||||
.string()
|
||||
.min(6)
|
||||
.max(60)
|
||||
.default("www.yourwebsite.com"),
|
||||
|
||||
introCard: z
|
||||
.object({
|
||||
enabled: z.boolean().default(false),
|
||||
name: z.string().min(3).max(40).default("John Doe"),
|
||||
date: z.string().min(4).max(40).default("Jan 1, 2025"),
|
||||
})
|
||||
.default({ enabled: true, name: "John Doe", date: "Jan 1, 2025" }),
|
||||
|
||||
media: z
|
||||
.object({
|
||||
type: z.literal("image").default("image"),
|
||||
image: ImageSchema,
|
||||
})
|
||||
.default({ type: "image", image: ImageSchema.parse({}) }),
|
||||
})
|
||||
.default({
|
||||
title: "Pitch Deck",
|
||||
subtitlePrefix: "Presentation",
|
||||
subtitleAccent: "Template",
|
||||
paragraph:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
website: "www.yourwebsite.com",
|
||||
introCard: { enabled: true, name: "John Doe", date: "Jan 1, 2025" },
|
||||
media: { type: "image", image: ImageSchema.parse({}) },
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const IntroSlideLayout: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;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"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header: diamond + business name */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="w-3 h-3 rotate-45"
|
||||
style={{ backgroundColor: "var(--text-heading-color, #111827)" }}
|
||||
></div>
|
||||
{ (slideData as any)?.__companyName__ && <span
|
||||
className="text-[16px] "
|
||||
style={{ color: "var(--text-body-color, #6B7280)" }}
|
||||
>
|
||||
{(slideData as any)?.__companyName__}
|
||||
</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right panel image (replaces dark gradient box) */}
|
||||
<div className="absolute top-0 right-0 h-[520px] w-[36%] overflow-hidden">
|
||||
<img
|
||||
src={slideData?.media?.image?.__image_url__}
|
||||
alt={slideData?.media?.image?.__image_prompt__}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Vertical diamond decorations */}
|
||||
<div className="absolute top-8 right-[38%] flex flex-col items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-[58%_42%] gap-10 px-12 pt-4 pb-10 items-start">
|
||||
<div className="pt-4">
|
||||
<h1
|
||||
className="text-[64px] leading-[1.05] tracking-tight font-semibold"
|
||||
style={{ color: "var(--text-heading-color, #111827)" }}
|
||||
>
|
||||
{slideData?.title}
|
||||
</h1>
|
||||
<p className="mt-5 text-[16px] leading-[1.6] max-w-[620px] " style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.paragraph}</p>
|
||||
|
||||
{slideData?.introCard?.enabled && (
|
||||
<div
|
||||
className="mt-6 inline-flex items-center gap-4 rounded-sm border px-4 py-3 "
|
||||
style={{
|
||||
borderColor: 'var(--primary-accent-color, #BFF4FF)',
|
||||
backgroundColor: 'var(--primary-accent-color, #BFF4FF)'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="w-2 h-6"
|
||||
style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}
|
||||
></div>
|
||||
<div className="flex items-baseline gap-3">
|
||||
<span className="text-[16px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{slideData?.introCard?.name}
|
||||
</span>
|
||||
<span className="text-[14px]" style={{ color: 'var(--text-body-color, #6B7280)' }}>
|
||||
{slideData?.introCard?.date}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative h-[420px]"></div>
|
||||
</div>
|
||||
|
||||
{/* Footer line with website and end diamond */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px] " style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-body-color, #E5E7EB)" }}></div>
|
||||
</div>
|
||||
|
||||
{/* Big bottom-right diamond */}
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default IntroSlideLayout
|
||||
|
||||
|
||||
178
servers/nextjs/presentation-templates/swift/MetricsNumbers.tsx
Normal file
178
servers/nextjs/presentation-templates/swift/MetricsNumbers.tsx
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
|
||||
const layoutId = "MetricsNumbers"
|
||||
const layoutName = "Metrics Numbers"
|
||||
const layoutDescription = "Swift: Our Impact in Numbers with three stacked metric cards"
|
||||
|
||||
const MetricSchema = z
|
||||
.object({
|
||||
value: z.string().min(1).max(8).default("10K+"),
|
||||
line1: z.string().min(2).max(22).default("Total"),
|
||||
line2: z.string().min(0).max(22).default("Users"),
|
||||
description: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(140)
|
||||
.default("active users across multiple industries"),
|
||||
})
|
||||
.default({
|
||||
value: "10K+",
|
||||
line1: "Total",
|
||||
line2: "Users",
|
||||
description: "active users across multiple industries",
|
||||
})
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
title: z
|
||||
.string()
|
||||
.min(8)
|
||||
.max(60)
|
||||
.default("Our Impact in Numbers"),
|
||||
leftTitle: z
|
||||
.string()
|
||||
.min(6)
|
||||
.max(40)
|
||||
.default("Proven Results\nThrough Data"),
|
||||
leftBody: z
|
||||
.string()
|
||||
.min(30)
|
||||
.max(220)
|
||||
.default(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
),
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
metrics: z
|
||||
.array(MetricSchema)
|
||||
.min(1)
|
||||
.max(4)
|
||||
.default([
|
||||
MetricSchema.parse({
|
||||
value: "10K+",
|
||||
line1: "Total",
|
||||
line2: "Users",
|
||||
description: "active users across multiple industries",
|
||||
}),
|
||||
MetricSchema.parse({
|
||||
value: "150%",
|
||||
line1: "Revenue",
|
||||
line2: "Growth",
|
||||
description: "year-over-year revenue growth",
|
||||
}),
|
||||
MetricSchema.parse({
|
||||
value: "95%",
|
||||
line1: "Customer",
|
||||
line2: "Satisfaction",
|
||||
description: "retention rate with an average rating of 4.8/5",
|
||||
}),
|
||||
]),
|
||||
})
|
||||
.default({
|
||||
title: "Our Impact in Numbers",
|
||||
leftTitle: "Proven Results\nThrough Data",
|
||||
leftBody: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
website: "www.yourwebsite.com",
|
||||
metrics: [
|
||||
MetricSchema.parse({
|
||||
value: "10K+",
|
||||
line1: "Total",
|
||||
line2: "Users",
|
||||
description: "active users across multiple industries",
|
||||
}),
|
||||
MetricSchema.parse({
|
||||
value: "150%",
|
||||
line1: "Revenue",
|
||||
line2: "Growth",
|
||||
description: "year-over-year revenue growth",
|
||||
}),
|
||||
MetricSchema.parse({
|
||||
value: "95%",
|
||||
line1: "Customer",
|
||||
line2: "Satisfaction",
|
||||
description: "retention rate with an average rating of 4.8/5",
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const MetricsNumbers: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const metrics = slideData?.metrics || []
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;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"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
{ (slideData as any)?.__companyName__ && <span className="text-[16px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{(slideData as any)?.__companyName__}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Separator line like the reference */}
|
||||
<div className="absolute top-0 left-1/2 w-[1px] h-full" style={{ backgroundColor: "rgba(0,0,0,0.1)" }}></div>
|
||||
|
||||
<div className="px-12 pt-3 grid grid-cols-[42%_58%] gap-8 items-start">
|
||||
{/* Left content */}
|
||||
<div>
|
||||
<h1 className="text-[48px] leading-[1.1] font-semibold max-w-[420px]" style={{ color: "var(--text-heading-color, #111827)" }}>{slideData?.title}</h1>
|
||||
<div className="mt-8 inline-flex items-center gap-3">
|
||||
<div className="w-5 h-5 rounded-full" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
<div>
|
||||
<div className="text-[20px] font-semibold whitespace-pre-line" style={{ color: "var(--text-heading-color, #111827)" }}>{slideData?.leftTitle}</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-5 text-[16px] leading-[1.8] max-w-[360px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.leftBody}</p>
|
||||
</div>
|
||||
|
||||
{/* Right stacked metric cards */}
|
||||
<div className="relative">
|
||||
{/* decorative circle on the right */}
|
||||
<div className="absolute top-6 -right-24 w-[220px] h-[220px] rounded-full border" style={{ borderColor: "rgba(0,0,0,0.2)" }}></div>
|
||||
|
||||
<div className="flex flex-col gap-6">
|
||||
{metrics.slice(0,3).map((m, i) => (
|
||||
<div key={i} className="rounded-[18px] px-6 py-5 grid grid-cols-[38%_62%] items-start shadow-[0_16px_40px_rgba(0,0,0,0.08)]" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}>
|
||||
<div className="text-[40px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>{m.value}</div>
|
||||
<div>
|
||||
<div className="text-[16px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>{m.line1}</div>
|
||||
{m.line2 && <div className="-mt-1 text-[16px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>{m.line2}</div>}
|
||||
<p className="mt-3 text-[12px] leading-[1.6]" style={{ color: 'var(--text-body-color, #6B7280)' }}>{m.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer (standardized like IntroSlideLayout) */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default MetricsNumbers
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
|
||||
const layoutId = "simple-bullet-points-layout"
|
||||
const layoutName = "Simple Bullet Points"
|
||||
const layoutDescription = "Bullet Points with title and description"
|
||||
|
||||
const PointSchema = z
|
||||
.object({
|
||||
title: z.string().min(6).max(60).default("Your Title Here"),
|
||||
body: z
|
||||
.string()
|
||||
.min(30)
|
||||
.max(220)
|
||||
.default(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa."
|
||||
),
|
||||
})
|
||||
.default({ title: "Your Title Here", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa." })
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
title: z.string().min(4).max(36).default("Our Commitment"),
|
||||
statement: z
|
||||
.string()
|
||||
.min(20)
|
||||
.max(260)
|
||||
.default(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
),
|
||||
points: z
|
||||
.array(PointSchema)
|
||||
.min(1)
|
||||
.max(4)
|
||||
.default([PointSchema.parse({}), PointSchema.parse({}), PointSchema.parse({}), PointSchema.parse({ title: "Your Title Here", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa." })]),
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
})
|
||||
.default({
|
||||
title: "Our Commitment to Innovation",
|
||||
statement: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
points: [
|
||||
{ title: "Your Title Here", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa." },
|
||||
{ title: "Your Title Here", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa." },
|
||||
{ title: "Your Title Here", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa." },
|
||||
{ title: "Your Title Here", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa." },
|
||||
|
||||
],
|
||||
website: "www.yourwebsite.com",
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const CommitmentTwoPoints: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const points = slideData?.points || []
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;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"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
{ (slideData as any)?.__companyName__ && <span className="text-[16px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{(slideData as any)?.__companyName__}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Subtle background motif */}
|
||||
<div
|
||||
className="pointer-events-none absolute left-1/2 -translate-x-1/2 bottom-[140px] w-[900px] h-[260px]"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(closest-side, rgba(17,24,39,0.06), transparent 70%)",
|
||||
filter: "blur(0.2px)",
|
||||
}}
|
||||
></div>
|
||||
|
||||
{/* Content grid */}
|
||||
<div className="px-12 pt-4 grid grid-cols-[48%_52%] gap-10 items-start">
|
||||
{/* Left heading and statement */}
|
||||
<div>
|
||||
<div className="text-[56px] leading-[1.05] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{slideData?.title}
|
||||
</div>
|
||||
|
||||
<p className="mt-10 text-[16px] leading-[1.8] max-w-[520px]" style={{ color: 'var(--text-body-color, #6B7280)' }}>
|
||||
{slideData?.statement}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right numbered points (up to 4) */}
|
||||
<div className="flex flex-col gap-6">
|
||||
{points.slice(0, 4).map((p, i) => (
|
||||
<div key={i}>
|
||||
<div className="text-[24px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>{p.title}</div>
|
||||
<p className="mt-3 text-[16px] leading-[1.8]" style={{ color: 'var(--text-body-color, #6B7280)' }}>{p.body}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer (align with other Swift layouts) */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default CommitmentTwoPoints
|
||||
|
||||
|
||||
144
servers/nextjs/presentation-templates/swift/TableOfContents.tsx
Normal file
144
servers/nextjs/presentation-templates/swift/TableOfContents.tsx
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
|
||||
const layoutId = "SwiftTableOfContents"
|
||||
const layoutName = "Table Of Contents"
|
||||
const layoutDescription = "Swift: Table of contents with up to 10 items (title + description)"
|
||||
|
||||
const ToCItemSchema = z
|
||||
.object({
|
||||
title: z.string().min(3).max(40).default("Introduction"),
|
||||
description: z
|
||||
.string()
|
||||
.min(0)
|
||||
.max(60)
|
||||
.default("A brief overview of the section."),
|
||||
})
|
||||
.default({ title: "Introduction", description: "A brief overview of the section." })
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
title: z
|
||||
.string()
|
||||
.min(3)
|
||||
.max(60)
|
||||
.default("Table of Contents"),
|
||||
items: z
|
||||
.array(ToCItemSchema)
|
||||
.min(1)
|
||||
.max(10)
|
||||
.default([
|
||||
{ title: "Introduction", description: "A brief description of our company and goals." },
|
||||
{ title: "Our Team", description: "Leadership and core contributors." },
|
||||
{ title: "Timeline", description: "High-level execution plan and milestones." },
|
||||
{ title: "Recommendations", description: "Key suggestions based on initial requirements." },
|
||||
{ title: "Solution", description: "What we propose and why it works." },
|
||||
{ title: "Market", description: "Audience, segments, and opportunity size." },
|
||||
{ title: "Business Model", description: "How we create and capture value." },
|
||||
{ title: "Conclusion", description: "Closing notes and next steps." },
|
||||
{ title: "Business Model", description: "How we create and capture value." },
|
||||
{ title: "Conclusion", description: "Closing notes and next steps." },
|
||||
]),
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
})
|
||||
.default({
|
||||
title: "Table of Contents",
|
||||
items: [
|
||||
{ title: "Introduction", description: "A brief description of our company and goals." },
|
||||
{ title: "Our Team", description: "Leadership and core contributors." },
|
||||
{ title: "Timeline", description: "High-level execution plan and milestones." },
|
||||
{ title: "Recommendations", description: "Key suggestions based on initial requirements." },
|
||||
{ title: "Solution", description: "What we propose and why it works." },
|
||||
{ title: "Market", description: "Audience, segments, and opportunity size." },
|
||||
{ title: "Business Model", description: "How we create and capture value." },
|
||||
{ title: "Conclusion", description: "Closing notes and next steps." },
|
||||
{ title: "Business Model", description: "How we create and capture value." },
|
||||
{ title: "Conclusion", description: "Closing notes and next steps." },
|
||||
|
||||
],
|
||||
website: "www.yourwebsite.com",
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const TableOfContents: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;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"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
{ (slideData as any)?.__companyName__ && <span className="text-[16px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{(slideData as any)?.__companyName__}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-12 pt-3">
|
||||
<h1 className="text-[48px] leading-[1.1] font-semibold" style={{ color: "var(--text-heading-color, #111827)" }}>{slideData?.title}</h1>
|
||||
</div>
|
||||
|
||||
{/* List */}
|
||||
<div className="px-12 pt-8">
|
||||
<div className="grid grid-cols-2 gap-x-12 gap-y-6 max-w-[1180px]">
|
||||
{items.slice(0, 10).map((item, idx) => (
|
||||
<div key={idx} className="relative">
|
||||
<div className="flex items-start gap-6">
|
||||
<div className="flex-none">
|
||||
<div
|
||||
className="leading-none font-semibold"
|
||||
style={{
|
||||
fontSize: 48,
|
||||
color: "var(--text-heading-color, #BFF4FF)",
|
||||
}}
|
||||
>
|
||||
{String(idx + 1).padStart(2, "0")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 pt-1">
|
||||
<div className="text-[22px] leading-[1.2] font-semibold" style={{ color: "var(--text-heading-color, #111827)" }}>
|
||||
{item.title}
|
||||
</div>
|
||||
{item.description && (
|
||||
<div className="mt-2 text-[14px] leading-[1.6]" style={{ color: "var(--text-body-color, #6B7280)" }}>
|
||||
{item.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 h-px" style={{ backgroundColor: "rgba(59,130,246,0.15)" }}></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer (standardized like IntroSlideLayout) */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default TableOfContents
|
||||
|
||||
|
||||
279
servers/nextjs/presentation-templates/swift/TableorChart.tsx
Normal file
279
servers/nextjs/presentation-templates/swift/TableorChart.tsx
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
Legend,
|
||||
LineChart,
|
||||
Line,
|
||||
PieChart,
|
||||
Pie,
|
||||
Cell,
|
||||
} from "recharts"
|
||||
|
||||
const layoutId = "tableorChart"
|
||||
const layoutName = "Table Or Chart"
|
||||
const layoutDescription = "Swift: Generic data table with option to render a chart (bar, horizontalBar, line, pie)"
|
||||
|
||||
const ChartDatumSchema = z.object({
|
||||
label: z.string().min(1).max(12).default("A"),
|
||||
value: z.number().min(0).max(1000000).default(60),
|
||||
})
|
||||
|
||||
const TableRowSchema = z.object({
|
||||
cells: z
|
||||
.array(z.string().min(0).max(200))
|
||||
.min(2)
|
||||
.max(10)
|
||||
.default(["Row 1", "Value", "Value"])
|
||||
.meta({ description: "Row cells; count should match columns length" }),
|
||||
})
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
title: z.string().min(6).max(60).default("Data Table or Chart"),
|
||||
description: z
|
||||
.string()
|
||||
.min(20)
|
||||
.max(220)
|
||||
.default(
|
||||
"Present structured information in a flexible table or visualize it with a chart."
|
||||
),
|
||||
|
||||
mode: z.enum(["table", "chart"]).default("table"),
|
||||
|
||||
// Table configuration (generic)
|
||||
columns: z
|
||||
.array(z.string().min(1).max(40))
|
||||
.min(2)
|
||||
.max(10)
|
||||
.default(["Column 1", "Column 2", "Column 3"]),
|
||||
rows: z
|
||||
.array(TableRowSchema)
|
||||
.min(1)
|
||||
.max(30)
|
||||
.default([
|
||||
{ cells: ["Row A", "✓", "-"] },
|
||||
{ cells: ["Row B", "Text", "123"] },
|
||||
{ cells: ["Row C", "More text", "456"] },
|
||||
]),
|
||||
|
||||
// Chart configuration (parity with @standard ChartLeftTextRightLayout)
|
||||
chart: z
|
||||
.object({
|
||||
type: z.enum(["bar", "horizontalBar", "line", "pie"]).default("line"),
|
||||
data: z.array(ChartDatumSchema).min(3).max(12).default([
|
||||
{ label: "A", value: 60 },
|
||||
{ label: "B", value: 42 },
|
||||
{ label: "C", value: 75 },
|
||||
{ label: "D", value: 30 },
|
||||
]),
|
||||
primaryColor: z.string().default("var(--text-heading-color, #111827)"),
|
||||
gridColor: z.string().default("var(--primary-accent-color, #BFF4FF)"),
|
||||
pieColors: z
|
||||
.array(z.string())
|
||||
.min(1)
|
||||
.max(10)
|
||||
.default(["var(--text-heading-color, #111827)", "#3b82f6", "#f59e0b", "#10b981", "#ef4444"]),
|
||||
showLabels: z.boolean().default(true),
|
||||
})
|
||||
.default({
|
||||
type: "line",
|
||||
data: [
|
||||
{ label: "A", value: 60 },
|
||||
{ label: "B", value: 42 },
|
||||
{ label: "C", value: 75 },
|
||||
{ label: "D", value: 30 },
|
||||
],
|
||||
primaryColor: "#1B8C2D",
|
||||
gridColor: "#E5E7EB",
|
||||
pieColors: ["#1B8C2D", "#3b82f6", "#f59e0b", "#10b981", "#ef4444"],
|
||||
showLabels: true,
|
||||
}),
|
||||
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
})
|
||||
.default({
|
||||
title: "Data Table or Chart",
|
||||
description:
|
||||
"Present structured information in a flexible table or visualize it with a chart.",
|
||||
mode: "table",
|
||||
columns: ["Column 1", "Column 2", "Column 3"],
|
||||
rows: [
|
||||
{ cells: ["Row A", "✓", "-"] },
|
||||
{ cells: ["Row B", "Text", "123"] },
|
||||
{ cells: ["Row C", "More text", "456"] },
|
||||
],
|
||||
chart: {
|
||||
type: "line",
|
||||
data: [
|
||||
{ label: "A", value: 60 },
|
||||
{ label: "B", value: 42 },
|
||||
{ label: "C", value: 75 },
|
||||
{ label: "D", value: 30 },
|
||||
],
|
||||
primaryColor: "#1B8C2D",
|
||||
gridColor: "#E5E7EB",
|
||||
pieColors: ["#1B8C2D", "#3b82f6", "#f59e0b", "#10b981", "#ef4444"],
|
||||
showLabels: true,
|
||||
},
|
||||
website: "www.yourwebsite.com",
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const TableOrChart: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const mode = slideData?.mode || "table"
|
||||
const columns = slideData?.columns || []
|
||||
const rows = slideData?.rows || []
|
||||
|
||||
const cData = slideData?.chart?.data || []
|
||||
const type = slideData?.chart?.type || "bar"
|
||||
const primaryColor = slideData?.chart?.primaryColor || "var(--text-heading-color, #111827)"
|
||||
const gridColor = slideData?.chart?.gridColor || "var(--primary-accent-color, #BFF4FF)"
|
||||
const pieColors = slideData?.chart?.pieColors || ["var(--text-heading-color, #111827)"]
|
||||
const showLabels = slideData?.chart?.showLabels !== false
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;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"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
{(slideData as any)?.__companyName__ && <span className="text-[16px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{(slideData as any)?.__companyName__}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Title and description */}
|
||||
<div className="px-12 pt-3">
|
||||
<h1 className="text-[48px] leading-[1.1] font-semibold" style={{ color: "var(--text-heading-color, #111827)" }}>{slideData?.title}</h1>
|
||||
<p className="mt-3 text-[16px] max-w-[900px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.description}</p>
|
||||
</div>
|
||||
|
||||
{/* Content area: Table or Chart */}
|
||||
<div className="px-12 pt-6">
|
||||
{mode === "table" ? (
|
||||
<div className="rounded-xl p-5" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}>
|
||||
<div className="overflow-x-auto rounded-lg bg-white ring-1" style={{ borderColor: 'rgba(0,0,0,0.08)' }}>
|
||||
<table className="w-full border-separate border-spacing-0">
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.map((col, idx) => (
|
||||
<th
|
||||
key={idx}
|
||||
className="text-left text-[14px] font-semibold px-4 py-3 border-b first:rounded-tl-md last:rounded-tr-md"
|
||||
style={{
|
||||
color: 'var(--text-heading-color, #111827)',
|
||||
borderColor: 'var(--secondary-accent-color, rgba(0,0,0,0.12))',
|
||||
backgroundColor: 'var(--primary-accent-color, #BFF4FF)'
|
||||
}}
|
||||
>
|
||||
{col}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row, rIdx) => (
|
||||
<tr key={rIdx} className="align-top">
|
||||
{columns.map((_, cIdx) => (
|
||||
<td
|
||||
key={cIdx}
|
||||
className={`text-[14px] px-4 py-3 border-t ${rIdx === rows.length - 1 ? 'first:rounded-bl-md last:rounded-br-md' : ''}`}
|
||||
style={{
|
||||
color: 'var(--text-body-color, #374151)',
|
||||
borderColor: 'rgba(0,0,0,0.08)',
|
||||
backgroundColor: rIdx % 2 === 0 ? 'var(--primary-accent-color, #BFF4FF)' : 'var(--tertiary-accent-color, #E5E7EB)'
|
||||
}}
|
||||
>
|
||||
{row.cells[cIdx] || ''}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full h-[360px] rounded-xl p-4" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
{type === 'bar' ? (
|
||||
<BarChart data={cData} margin={{ top: 10, right: 20, bottom: 10, left: 0 }}>
|
||||
<CartesianGrid stroke={gridColor} strokeDasharray="3 3" />
|
||||
<XAxis dataKey="label" tick={{ fill: 'var(--text-body-color, #6B7280)', fontWeight: 600 }} />
|
||||
<YAxis tick={{ fill: 'var(--text-body-color, #6B7280)', fontWeight: 600 }} />
|
||||
<Tooltip labelStyle={{ color: 'var(--text-body-color, #6B7280)' }} itemStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Legend wrapperStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Bar dataKey="value" fill={primaryColor} radius={[6, 6, 0, 0]} label={showLabels ? { position: 'top', fill: '#111827', fontSize: 12 } : false} />
|
||||
</BarChart>
|
||||
) : type === 'horizontalBar' ? (
|
||||
<BarChart data={cData} layout="vertical" margin={{ top: 10, right: 20, bottom: 10, left: 20 }}>
|
||||
<CartesianGrid stroke={gridColor} strokeDasharray="3 3" />
|
||||
<XAxis type="number" tick={{ fill: 'var(--text-body-color, #6B7280)', fontWeight: 600 }} />
|
||||
<YAxis type="category" dataKey="label" tick={{ fill: 'var(--text-body-color, #6B7280)', fontWeight: 600 }} />
|
||||
<Tooltip labelStyle={{ color: 'var(--text-body-color, #6B7280)' }} itemStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Legend wrapperStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Bar dataKey="value" fill={primaryColor} radius={[0, 6, 6, 0]} label={showLabels ? { position: 'right', fill: '#111827', fontSize: 12 } : false} />
|
||||
</BarChart>
|
||||
) : type === 'line' ? (
|
||||
<LineChart data={cData} margin={{ top: 10, right: 20, bottom: 10, left: 0 }}>
|
||||
<CartesianGrid stroke={gridColor} strokeDasharray="3 3" />
|
||||
<XAxis dataKey="label" tick={{ fill: 'var(--text-body-color, #6B7280)', fontWeight: 600 }} />
|
||||
<YAxis tick={{ fill: 'var(--text-body-color, #6B7280)', fontWeight: 600 }} />
|
||||
<Tooltip labelStyle={{ color: 'var(--text-body-color, #6B7280)' }} itemStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Legend wrapperStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Line type="monotone" dataKey="value" stroke={primaryColor} strokeWidth={3} dot={{ r: 3 }} label={showLabels ? { position: 'top', fill: '#111827', fontSize: 12 } : false} />
|
||||
</LineChart>
|
||||
) : (
|
||||
<PieChart>
|
||||
<Tooltip labelStyle={{ color: 'var(--text-body-color, #6B7280)' }} itemStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Legend wrapperStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Pie data={cData} dataKey="value" nameKey="label" cx="50%" cy="50%" outerRadius={120} label={showLabels ? { fill: 'var(--text-body-color, #6B7280)' } : false}>
|
||||
{cData.map((_, i) => (
|
||||
<Cell key={i} fill={primaryColor} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
)}
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer (standardized like IntroSlideLayout) */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default TableOrChart
|
||||
|
||||
|
||||
170
servers/nextjs/presentation-templates/swift/Timeline.tsx
Normal file
170
servers/nextjs/presentation-templates/swift/Timeline.tsx
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
|
||||
const layoutId = "Timeline"
|
||||
const layoutName = "Timeline"
|
||||
const layoutDescription = "Timeline of cards with title, subtitle banner"
|
||||
|
||||
const IconSchema = z
|
||||
.object({
|
||||
__icon_url__: z
|
||||
.string()
|
||||
.default(
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/clipboard-text-bold.svg"
|
||||
),
|
||||
__icon_query__: z.string().min(0).max(80).default("timeline icon"),
|
||||
})
|
||||
.default({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/clipboard-text-bold.svg",
|
||||
__icon_query__: "timeline icon",
|
||||
})
|
||||
|
||||
const ItemSchema = z
|
||||
.object({
|
||||
year: z.string().min(3).max(6).default("2018"),
|
||||
heading: z.string().min(3).max(28).default("Founded in 2020"),
|
||||
body: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(160)
|
||||
.default("Lorem ipsum dolor"),
|
||||
icon: IconSchema,
|
||||
})
|
||||
.default({ year: "2018", heading: "Founded in 2020", body: "Lorem ipsum dolor", icon: IconSchema.parse({}) })
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
title: z
|
||||
.string()
|
||||
.min(8)
|
||||
.max(60)
|
||||
.default("Our Journey at a Glance"),
|
||||
subtitle: z
|
||||
.string()
|
||||
.min(20)
|
||||
.max(200)
|
||||
.default(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa."
|
||||
),
|
||||
items: z
|
||||
.array(ItemSchema)
|
||||
.min(1)
|
||||
.max(4)
|
||||
.default([
|
||||
ItemSchema.parse({ year: "2018", heading: "Founded in 2020", body: "Lorem ipsum dolor" }),
|
||||
ItemSchema.parse({ year: "2019", heading: "First Product in 2021", body: "Lorem ipsum dolor" }),
|
||||
ItemSchema.parse({ year: "2020", heading: "Key Milestone in 2022", body: "Lorem ipsum dolor" }),
|
||||
ItemSchema.parse({ year: "2021", heading: "Global Expansion in 2024", body: "Lorem ipsum dolor" }),
|
||||
]),
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
})
|
||||
.default({
|
||||
title: "Our Journey at a Glance",
|
||||
subtitle:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.",
|
||||
items: [
|
||||
ItemSchema.parse({ year: "2018", heading: "Founded in 2020", body: "Lorem ipsum dolor" }),
|
||||
ItemSchema.parse({ year: "2019", heading: "First Product in 2021", body: "Lorem ipsum dolor" }),
|
||||
ItemSchema.parse({ year: "2020", heading: "Key Milestone in 2022", body: "Lorem ipsum dolor" }),
|
||||
ItemSchema.parse({ year: "2021", heading: "Global Expansion in 2024", body: "Lorem ipsum dolor" }),
|
||||
],
|
||||
website: "www.yourwebsite.com",
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const Timeline: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;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"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Keep white background to match Swift layouts */}
|
||||
|
||||
{/* Header: diamond + business name */}
|
||||
<div className="relative px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
{(slideData as any)?.__companyName__ && <span className="text-[16px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{(slideData as any)?.__companyName__}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right vertical diamonds */}
|
||||
<div className="absolute top-16 right-6 flex flex-col items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<div className="relative px-12 pt-3 flex flex-col items-center">
|
||||
<h1 className="text-[48px] leading-[1.1] font-semibold text-center" style={{ color: "var(--text-heading-color, #111827)" }}>{slideData?.title}</h1>
|
||||
{/* Subtitle banner */}
|
||||
<div className="mt-5 max-w-[720px] w-full rounded-md text-center px-6 py-3" style={{
|
||||
borderColor: "rgba(0,0,0,0.25)",
|
||||
color: "var(--text-body-color, #6B7280)",
|
||||
backgroundColor: "var(--primary-accent-color, #BFF4FF)",
|
||||
}}>
|
||||
<span className="text-[16px]">{slideData?.subtitle}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Horizontal timeline */}
|
||||
<div className="relative px-12 pt-10">
|
||||
{/* center line */}
|
||||
<div className="absolute left-12 right-12 top-1/2 h-[4px]" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}></div>
|
||||
|
||||
<div className="relative flex justify-center gap-10">
|
||||
{items.slice(0, 4).map((it, idx) => (
|
||||
<div key={idx} className="relative flex flex-col items-center">
|
||||
{/* year badge - placed close to the card */}
|
||||
<div className="mb-2 px-5 py-2 rounded-md text-white text-[16px] font-semibold" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}>{it.year}</div>
|
||||
|
||||
{/* connector dot */}
|
||||
<div className="w-5 h-5 rounded-full border-4" style={{ backgroundColor: 'var(--card-background-color, #FFFFFF)', borderColor: 'var(--primary-accent-color, #BFF4FF)' }}></div>
|
||||
|
||||
{/* card container */}
|
||||
<div className="mt-4">
|
||||
<div className="rounded-[16px] bg-white shadow-[0_16px_40px_rgba(0,0,0,0.08)] px-6 pt-6 pb-5 text-center w-[260px]">
|
||||
<div className="mx-auto w-12 h-12 rounded-full flex items-center justify-center mb-4" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}>
|
||||
<img src={it.icon.__icon_url__} alt={it.icon.__icon_query__} className="w-6 h-6 object-contain" />
|
||||
</div>
|
||||
<div className="text-[18px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>{it.heading}</div>
|
||||
<p className="mt-3 text-[14px]" style={{ color: 'var(--text-body-color, #6B7280)' }}>{it.body}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer (standardized like IntroSlideLayout) */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default Timeline
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"description": "Swift layouts for presentations",
|
||||
"ordered": false,
|
||||
"default": false
|
||||
}
|
||||
|
||||
Loading…
Add table
Reference in a new issue