feat(nextjs+fastapi): fixes presentation export

This commit is contained in:
sauravniraula 2025-07-24 00:56:46 +05:45
commit 23d0c7fb17
No known key found for this signature in database
GPG key ID: 60FCC1B5A5E83326
52 changed files with 3621 additions and 1613 deletions

View file

@ -166,8 +166,7 @@ class PptxPresentationCreator:
)
connector_shape.line.width = Pt(connector_model.thickness)
connector_shape.line.color.rgb = RGBColor.from_string(connector_model.color)
# Set line opacity using XML manipulation for better reliability
self.set_line_opacity(connector_shape, connector_model.opacity)
self.set_fill_opacity(connector_shape, connector_model.opacity)
def add_picture(self, slide: Slide, picture_model: PptxPictureBoxModel):
image_path = picture_model.picture.path

View file

@ -92,7 +92,7 @@ def get_google_llm_client():
def get_large_model():
selected_llm = get_llm_provider()
if selected_llm == LLMProvider.OPENAI:
return "o3"
return "gpt-4.1"
elif selected_llm == LLMProvider.GOOGLE:
return "gemini-2.0-flash"
elif selected_llm == LLMProvider.OLLAMA:

View file

@ -59,9 +59,11 @@ const TiptapText: React.FC<TiptapTextProps> = ({
}
return (
<div className="relative z-50 w-full">
<BubbleMenu editor={editor} tippyOptions={{ duration: 100 }}>
<div className="flex bg-white rounded-lg shadow-lg p-2 gap-1 border border-gray-200 z-50">
<>
<BubbleMenu editor={editor} className='z-50' tippyOptions={{ duration: 100 }}>
<div style={{
zIndex: 100
}} className="flex text-black bg-white rounded-lg shadow-lg p-2 gap-1 border border-gray-200 z-50">
<button
onClick={() => editor?.chain().focus().toggleBold().run()}
className={`p-1 rounded hover:bg-gray-100 transition-colors ${editor?.isActive("bold") ? "bg-blue-100 text-blue-600" : ""
@ -116,9 +118,10 @@ const TiptapText: React.FC<TiptapTextProps> = ({
fontFamily: 'inherit',
color: 'inherit',
textAlign: 'inherit',
}}
/>
</div>
</>
);
};

View file

@ -73,6 +73,25 @@ const TiptapTextReplacer: React.FC<TiptapTextReplacerProps> = ({
paddingBottom: computedStyles.paddingBottom,
paddingLeft: computedStyles.paddingLeft,
paddingRight: computedStyles.paddingRight,
borderRadius: computedStyles.borderRadius,
border: computedStyles.border,
backgroundColor: computedStyles.backgroundColor,
opacity: computedStyles.opacity,
zIndex: computedStyles.zIndex,
cursor: computedStyles.cursor,
boxShadow: computedStyles.boxShadow,
textShadow: computedStyles.textShadow,
textDecoration: computedStyles.textDecoration,
textTransform: computedStyles.textTransform,
letterSpacing: computedStyles.letterSpacing,
wordSpacing: computedStyles.wordSpacing,
textOverflow: computedStyles.textOverflow,
whiteSpace: computedStyles.whiteSpace,
wordBreak: computedStyles.wordBreak,
overflow: computedStyles.overflow,
textAlignLast: computedStyles.textAlignLast,
};
// Try to find matching data path
@ -99,8 +118,7 @@ const TiptapTextReplacer: React.FC<TiptapTextReplacerProps> = ({
const root = ReactDOM.createRoot(tiptapContainer);
root.render(
<TiptapText
key={JSON.stringify(slideData)}
content={dataPath.originalText}
content={trimmedText}
onContentChange={(content: string) => {
if (dataPath && onContentChange) {
onContentChange(content, dataPath.path, slideIndex);

View file

@ -36,6 +36,7 @@ export const useGroupLayouts = () => {
// Render slide content with group validation, automatic Tiptap text editing, and editable images/icons
const renderSlideContent = useMemo(() => {
return (slide: any, isEditMode: boolean) => {
console.time("renderSlideContent");
const Layout = getGroupLayout(slide.layout, slide.layout_group);
if (!Layout) {
return (
@ -75,8 +76,10 @@ export const useGroupLayouts = () => {
</EditableLayoutWrapper>
);
}
console.timeEnd("renderSlideContent");
return <Layout data={slide.content} />;
};
}, [getGroupLayout, dispatch]);
return {

View file

@ -162,15 +162,17 @@ const Header = ({
{/* Desktop Export Button with Popover */}
<div className="hidden lg:block">
<Popover open={open} onOpenChange={setOpen}>
<div style={{
zIndex: 100
}} className="hidden lg:block relative ">
<Popover open={open} onOpenChange={setOpen} >
<PopoverTrigger asChild>
<Button className={`border py-5 text-[#5146E5] font-bold rounded-[32px] transition-all duration-500 hover:border hover:bg-[#5146E5] hover:text-white w-full ${mobile ? "" : "bg-white"}`}>
<SquareArrowOutUpRight className="w-4 h-4 mr-1" />
Export
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="w-[250px] space-y-2 py-3 px-2">
<PopoverContent align="end" className="w-[250px] space-y-2 py-3 px-2 ">
<ExportOptions mobile={false} />
</PopoverContent>
</Popover>
@ -184,42 +186,47 @@ const Header = ({
);
return (
<div className="bg-[#5146E5] w-full shadow-lg sticky top-0 z-50">
<>
<OverlayLoader
show={showLoader}
text="Exporting presentation..."
showProgress={true}
duration={40}
/>
<Announcement />
<Wrapper className="flex items-center justify-between py-1">
<Link href="/dashboard" className="min-w-[162px]">
<img
className="h-16"
src="/logo-white.png"
alt="Presentation logo"
/>
</Link>
<div
{/* Desktop Menu */}
<div className="hidden lg:flex items-center gap-4 2xl:gap-6">
{isStreaming && (
<Loader2 className="animate-spin text-white font-bold w-6 h-6" />
)}
className="bg-[#5146E5] w-full shadow-lg sticky top-0 ">
<Announcement />
<Wrapper className="flex items-center justify-between py-1">
<Link href="/dashboard" className="min-w-[162px]">
<img
className="h-16"
src="/logo-white.png"
alt="Presentation logo"
/>
</Link>
{/* Desktop Menu */}
<div className="hidden lg:flex items-center gap-4 2xl:gap-6">
{isStreaming && (
<Loader2 className="animate-spin text-white font-bold w-6 h-6" />
)}
<MenuItems mobile={false} />
<HeaderNav />
</div>
<MenuItems mobile={false} />
<HeaderNav />
</div>
{/* Mobile Menu */}
<div className="lg:hidden flex items-center gap-4">
<HeaderNav />
{/* Mobile Menu */}
<div className="lg:hidden flex items-center gap-4">
<HeaderNav />
</div>
</Wrapper>
</div>
</Wrapper>
</div>
</div>
</>
);
};

View file

@ -1,5 +1,5 @@
"use client";
import React, { useState } from "react";
import React, { useCallback, useState } from "react";
import { useSelector } from "react-redux";
import { RootState } from "@/store/store";
import { Skeleton } from "@/components/ui/skeleton";
@ -20,6 +20,7 @@ import { PresentationPageProps } from "../types";
import LoadingState from "./LoadingState";
const PresentationPage: React.FC<PresentationPageProps> = ({ presentation_id }) => {
// State management
const [loading, setLoading] = useState(true);
const [selectedSlide, setSelectedSlide] = useState(0);
@ -45,20 +46,15 @@ const PresentationPage: React.FC<PresentationPageProps> = ({ presentation_id })
setLoading,
setError
);
const {
isPresentMode,
stream,
currentSlide,
handleSlideClick,
toggleFullscreen,
handlePresentExit,
handleSlideChange,
} = usePresentationNavigation(
presentation_id,
selectedSlide,
setSelectedSlide,
setIsFullscreen
);
} = usePresentationNavigation(presentation_id, selectedSlide, setSelectedSlide, setIsFullscreen);
// Initialize streaming
usePresentationStreaming(
@ -70,18 +66,18 @@ const PresentationPage: React.FC<PresentationPageProps> = ({ presentation_id })
);
const onSlideChange = (newSlide: number) => {
handleSlideChange(newSlide, presentationData);
};
// Presentation Mode View
if (isPresentMode) {
return (
<PresentationMode
slides={presentationData?.slides!}
currentSlide={currentSlide}
currentSlide={selectedSlide}
isFullscreen={isFullscreen}
onFullscreenToggle={toggleFullscreen}
onExit={handlePresentExit}
@ -122,7 +118,7 @@ const PresentationPage: React.FC<PresentationPageProps> = ({ presentation_id })
</div>
<Header presentation_id={presentation_id} currentSlide={currentSlide} />
<Header presentation_id={presentation_id} currentSlide={selectedSlide} />
<Help />
<div

View file

@ -58,7 +58,11 @@ const SidePanel = ({
}, [isMobilePanelOpen]);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(PointerSensor, {
activationConstraint: {
distance: 8, // Start drag after moving 8px
},
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
@ -263,8 +267,8 @@ const SidePanel = ({
className={` cursor-pointer ring-2 p-1 rounded-md transition-all duration-200 ${selectedSlide === index ? ' ring-[#5141e5]' : 'ring-gray-200'
}`}
>
<div className=" bg-white relative overflow-hidden aspect-video">
<div className="absolute bg-gray-100/5 z-40 top-0 left-0 w-full h-full" />
<div className=" bg-white pointer-events-none relative overflow-hidden aspect-video">
<div className="absolute bg-gray-100/5 z-50 top-0 left-0 w-full h-full" />
<div className="transform scale-[0.2] flex justify-center items-center origin-top-left w-[500%] h-[500%]">
{renderSlideContent(slide, false)}
</div>
@ -274,7 +278,7 @@ const SidePanel = ({
) : (
<SortableContext
items={
presentationData?.slides.map((slide: any) => slide.id!) || []
presentationData?.slides.map((slide: any) => slide.id || `${slide.index}`) || []
}
strategy={verticalListSortingStrategy}
>

View file

@ -1,7 +1,7 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Slide } from '../../types/slide';
import { useState } from 'react';
import { useRef } from 'react';
interface SortableListItemProps {
slide: Slide;
@ -11,7 +11,7 @@ interface SortableListItemProps {
}
export function SortableListItem({ slide, index, selectedSlide, onSlideClick }: SortableListItemProps) {
const [mouseDownTime, setMouseDownTime] = useState(0);
const lastClickTime = useRef(0);
const {
attributes,
@ -28,16 +28,17 @@ export function SortableListItem({ slide, index, selectedSlide, onSlideClick }:
opacity: isDragging ? 0.5 : 1
};
const handleMouseDown = () => {
setMouseDownTime(Date.now());
};
const handleClick = (e: React.MouseEvent) => {
const now = Date.now();
const handleMouseUp = () => {
const mouseUpTime = Date.now();
const timeDiff = mouseUpTime - mouseDownTime;
// Debounce clicks - only allow one click every 300ms
if (now - lastClickTime.current < 300) {
return;
}
// If the mouse was down for less than 200ms, consider it a click
if (timeDiff < 200 && !isDragging) {
// Only trigger click if not dragging
if (!isDragging) {
lastClickTime.current = now;
onSlideClick(slide.index);
}
};
@ -48,8 +49,7 @@ export function SortableListItem({ slide, index, selectedSlide, onSlideClick }:
style={style}
{...attributes}
{...listeners}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onClick={handleClick}
className={`p-3 cursor-pointer ring-0 border-[3px] rounded-lg slide-box
${selectedSlide === index
? ' border-[#5141e5] '

View file

@ -1,7 +1,7 @@
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Slide } from '../../types/slide';
import { useState } from 'react';
import { useRef } from 'react';
interface SortableSlideProps {
slide: Slide;
@ -12,7 +12,7 @@ interface SortableSlideProps {
}
export function SortableSlide({ slide, index, selectedSlide, onSlideClick, renderSlideContent }: SortableSlideProps) {
const [mouseDownTime, setMouseDownTime] = useState(0);
const lastClickTime = useRef(0);
const {
attributes,
@ -21,7 +21,7 @@ export function SortableSlide({ slide, index, selectedSlide, onSlideClick, rende
transform,
transition,
isDragging
} = useSortable({ id: slide.id! });
} = useSortable({ id: slide.id || `${slide.index}` });
const style = {
transform: CSS.Transform.toString(transform),
@ -29,16 +29,17 @@ export function SortableSlide({ slide, index, selectedSlide, onSlideClick, rende
opacity: isDragging ? 0.5 : 1
};
const handleMouseDown = () => {
setMouseDownTime(Date.now());
};
const handleClick = (e: React.MouseEvent) => {
const now = Date.now();
const handleMouseUp = () => {
const mouseUpTime = Date.now();
const timeDiff = mouseUpTime - mouseDownTime;
// Debounce clicks - only allow one click every 300ms
if (now - lastClickTime.current < 300) {
return;
}
// If the mouse was down for less than 200ms, consider it a click
if (timeDiff < 200 && !isDragging) {
// Only trigger click if not dragging
if (!isDragging) {
lastClickTime.current = now;
onSlideClick(slide.index);
}
};
@ -49,14 +50,13 @@ export function SortableSlide({ slide, index, selectedSlide, onSlideClick, rende
style={style}
{...attributes}
{...listeners}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
className={` cursor-pointer border-[3px] p-1 shadow-lg rounded-md transition-all duration-200 ${selectedSlide === index ? ' border-[#5141e5]' : 'border-gray-300'
onClick={handleClick}
className={` cursor-pointer border-[3px] relative p-1 shadow-lg rounded-md transition-all duration-200 ${selectedSlide === index ? ' border-[#5141e5]' : 'border-gray-300'
}`}
>
<div className=" slide-box relative overflow-hidden aspect-video">
<div className="absolute bg-transparent z-40 top-0 left-0 w-full h-full" />
<div className="transform scale-[0.2] flex justify-center items-center origin-top-left w-[500%] h-[500%]">
<div className=" slide-box relative z-50 overflow-hidden aspect-video">
<div className="absolute bg-transparent z-50 top-0 left-0 w-full h-full" />
<div className="transform scale-[0.2] flex pointer-events-none justify-center items-center origin-top-left w-[500%] h-[500%]">
{renderSlideContent(slide, false)}
</div>
</div>

View file

@ -25,7 +25,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
}
[data-sonner-toast][data-type="success"] [data-title] {
color: rgb(15 23 42) !important; /* slate-900 */
font-weight: 600 !important;
font-weight: 500 !important;
}
[data-sonner-toast][data-type="success"] [data-description] {
color: rgb(71 85 105) !important; /* slate-600 */
@ -39,7 +39,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
}
[data-sonner-toast][data-type="error"] [data-title] {
color: rgb(15 23 42) !important; /* slate-900 */
font-weight: 600 !important;
font-weight: 500 !important;
}
[data-sonner-toast][data-type="error"] [data-description] {
color: rgb(71 85 105) !important; /* slate-600 */
@ -53,7 +53,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
}
[data-sonner-toast][data-type="info"] [data-title] {
color: rgb(15 23 42) !important; /* slate-900 */
font-weight: 600 !important;
font-weight: 500 !important;
}
[data-sonner-toast][data-type="info"] [data-description] {
color: rgb(71 85 105) !important; /* slate-600 */
@ -67,7 +67,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
}
[data-sonner-toast][data-type="warning"] [data-title] {
color: rgb(15 23 42) !important; /* slate-900 */
font-weight: 600 !important;
font-weight: 500 !important;
}
[data-sonner-toast][data-type="warning"] [data-description] {
color: rgb(71 85 105) !important; /* slate-600 */
@ -81,7 +81,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
}
[data-sonner-toast][data-type="loading"] [data-title] {
color: rgb(15 23 42) !important; /* slate-900 */
font-weight: 600 !important;
font-weight: 500 !important;
}
[data-sonner-toast][data-type="loading"] [data-description] {
color: rgb(71 85 105) !important; /* slate-600 */

View file

@ -108,17 +108,12 @@ export const Schema = type10SlideSchema
export type Type10SlideData = z.infer<typeof type10SlideSchema>
interface Type10SlideLayoutProps {
data?: Partial<Type10SlideData>
data: Partial<Type10SlideData>
}
const Type10SlideLayout: React.FC<Type10SlideLayoutProps> = ({ data: slideData }) => {
const chartData = slideData?.data || [];
const chartType = slideData?.chartType || 'line';
const color = slideData?.color || '#3b82f6';
const dataKey = slideData?.dataKey || 'value';
const categoryKey = slideData?.categoryKey || 'name';
const showLegend = slideData?.showLegend || false;
const showTooltip = slideData?.showTooltip || true;
const { title, items, chartData, chartType = 'line', color = '#3b82f6', dataKey = 'value', categoryKey = 'name', showLegend = false, showTooltip = true } = slideData;
const renderChart = () => {
const commonProps = {
data: chartData,
@ -188,7 +183,7 @@ const Type10SlideLayout: React.FC<Type10SlideLayoutProps> = ({ data: slideData }
dataKey={dataKey}
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
>
{chartData.map((entry, index) => (
{chartData.map((entry: any, index: number) => (
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
))}
</Pie>
@ -219,9 +214,9 @@ const Type10SlideLayout: React.FC<Type10SlideLayoutProps> = ({ data: slideData }
>
<div className='w-full flex flex-col items-start justify-start'>
<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">
{slideData?.title || 'Chart Analysis'}
</h1>
{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 `}>
@ -234,7 +229,7 @@ const Type10SlideLayout: React.FC<Type10SlideLayoutProps> = ({ data: slideData }
</div>
<div className="lg:w-1/2 relative">
<div className="space-y-3 lg:space-y-6">
{slideData?.items?.map((item, index) => (
{items && items.map((item, index) => (
<div
key={index}
style={{
@ -245,20 +240,20 @@ const Type10SlideLayout: React.FC<Type10SlideLayoutProps> = ({ data: slideData }
<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">
<img
{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">
<h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
{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>
<p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
</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>
</p>}
</div>
</div>
</div>

View file

@ -26,10 +26,11 @@ export const Schema = type1SlideSchema
export type Type1SlideData = z.infer<typeof type1SlideSchema>
interface Type1SlideLayoutProps {
data?: Partial<Type1SlideData>
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"
@ -38,23 +39,23 @@ const Type1SlideLayout: React.FC<Type1SlideLayoutProps> = ({ data: slideData })
<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 */}
<h1 className=" text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
{slideData?.title || ' This is the title of slide'}
</h1>
{title && <h1 className=" 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">
{slideData?.description || '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.'}
</p>
{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]">
<img
src={slideData?.image?.__image_url__ || ''}
alt={slideData?.image?.__image_prompt__ || slideData?.title || ''}
{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>

View file

@ -39,18 +39,18 @@ export const Schema = type2NumberedSlideSchema
export type Type2NumberedSlideData = z.infer<typeof type2NumberedSlideSchema>
interface Type2NumberedSlideLayoutProps {
data?: Partial<Type2NumberedSlideData>
data: Partial<Type2NumberedSlideData>
}
const Type2NumberedSlideLayout: React.FC<Type2NumberedSlideLayoutProps> = ({ data: slideData }) => {
const items = slideData?.items || []
const isGridLayout = items.length >= 4
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) => (
{items?.map((item, index) => (
<div
key={index}
style={{
@ -63,12 +63,12 @@ const Type2NumberedSlideLayout: React.FC<Type2NumberedSlideLayoutProps> = ({ dat
{numberTranslations[index] || `0${index + 1}`}
</div>
<div className="space-y-2">
<h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
{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>
<p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
</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>
</p>}
</div>
</div>
</div>
@ -80,7 +80,7 @@ const Type2NumberedSlideLayout: React.FC<Type2NumberedSlideLayoutProps> = ({ dat
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) => (
{items?.map((item, index) => (
<div
key={index}
style={{
@ -92,12 +92,12 @@ const Type2NumberedSlideLayout: React.FC<Type2NumberedSlideLayoutProps> = ({ dat
{numberTranslations[index] || `0${index + 1}`}
</div>
<div className="space-y-2 lg:space-y-4">
<h3 className=" text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
{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>
<p className=" text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
</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>
</p>}
</div>
</div>
))}
@ -111,9 +111,9 @@ const Type2NumberedSlideLayout: React.FC<Type2NumberedSlideLayoutProps> = ({ dat
>
<div className="text-center lg:pb-8 w-full">
<h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
{slideData?.title || 'Main Title'}
</h1>
{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()}

View file

@ -39,29 +39,29 @@ export const Schema = type2SlideSchema
export type Type2SlideData = z.infer<typeof type2SlideSchema>
interface Type2SlideLayoutProps {
data?: Partial<Type2SlideData>
data: Partial<Type2SlideData>
}
const Type2SlideLayout: React.FC<Type2SlideLayoutProps> = ({ data: slideData }) => {
const items = slideData?.items || []
const isGridLayout = items.length >= 4
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) => (
{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">
<h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
{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>
<p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
</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>
</p>}
</div>
</div>
))}
@ -72,19 +72,19 @@ const Type2SlideLayout: React.FC<Type2SlideLayoutProps> = ({ data: slideData })
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) => (
{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">
<h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
{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>
<p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
</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>
</p>}
</div>
</div>
))}
@ -98,9 +98,9 @@ const Type2SlideLayout: React.FC<Type2SlideLayoutProps> = ({ data: slideData })
>
<div className="text-center lg:pb-8 w-full">
<h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
{slideData?.title || 'Main Title'}
</h1>
{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()}

View file

@ -39,12 +39,11 @@ export const Schema = type2TimelineSlideSchema
export type Type2TimelineSlideData = z.infer<typeof type2TimelineSlideSchema>
interface Type2TimelineSlideLayoutProps {
data?: Partial<Type2TimelineSlideData>
data: Partial<Type2TimelineSlideData>
}
const Type2TimelineSlideLayout: React.FC<Type2TimelineSlideLayoutProps> = ({ data: slideData }) => {
const items = slideData?.items || []
const numberTranslations: string[] = ['01', '02', '03', '04', '05', '06']
const { title, items } = slideData;
const renderTimelineContent = () => {
return (
@ -55,27 +54,27 @@ const Type2TimelineSlideLayout: React.FC<Type2TimelineSlideLayoutProps> = ({ dat
<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.map((_, index) => (
{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>{numberTranslations[index] || `0${index + 1}`}</span>
<span> `0${index + 1}`</span>
</div>
))}
</div>
{/* Timeline Content */}
<div className="flex justify-between gap-8">
{items.map((item, index) => (
{items && items.map((item, index) => (
<div key={index} className="flex-1 text-center relative">
<div className="space-y-4">
<h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
{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>
<p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
</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>
</p>}
</div>
</div>
))}
@ -89,9 +88,9 @@ const Type2TimelineSlideLayout: React.FC<Type2TimelineSlideLayoutProps> = ({ dat
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">
<h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
{slideData?.title || 'Main Title'}
</h1>
{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()}

View file

@ -55,11 +55,11 @@ export const Schema = type3SlideSchema
export type Type3SlideData = z.infer<typeof type3SlideSchema>
interface Type3SlideLayoutProps {
data?: Partial<Type3SlideData>
data: Partial<Type3SlideData>
}
const Type3SlideLayout: React.FC<Type3SlideLayoutProps> = ({ data: slideData }) => {
const items = slideData?.items || []
const { title, items } = slideData;
const getGridCols = (length: number) => {
switch (length) {
@ -77,13 +77,13 @@ const Type3SlideLayout: React.FC<Type3SlideLayoutProps> = ({ data: slideData })
>
<div className="text-center mb-4 lg:mb-16 w-full">
<h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
{slideData?.title || 'Featured Content'}
</h1>
{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)} gap-3 lg:gap-6 w-full`}>
{items.map((item, index) => (
<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={{
@ -102,12 +102,12 @@ const Type3SlideLayout: React.FC<Type3SlideLayoutProps> = ({ data: slideData })
{/* Content */}
<div className="space-y-2 p-3 lg:p-6">
<h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
{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>
<p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
</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>
</p>}
</div>
</div>
))}

View file

@ -1,6 +1,6 @@
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 { 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'
@ -77,17 +77,14 @@ export const Schema = type4SlideSchema
export type Type4SlideData = z.infer<typeof type4SlideSchema>
interface Type4SlideLayoutProps {
data?: Partial<Type4SlideData>
data: Partial<Type4SlideData>
}
const Type4SlideLayout: React.FC<Type4SlideLayoutProps> = ({ data: slideData }) => {
const chartData = slideData?.data || [];
const chartType = slideData?.chartType || 'line';
const color = slideData?.color || '#3b82f6';
const dataKey = slideData?.dataKey || 'value';
const categoryKey = slideData?.categoryKey || 'name';
const showLegend = slideData?.showLegend || false;
const showTooltip = slideData?.showTooltip || true;
const { title, description, data, dataKey, categoryKey, color, showLegend = false, showTooltip = true, chartType = 'bar' } = slideData;
const chartData = data || [];
const renderChart = () => {
const commonProps = {
data: chartData,
@ -103,7 +100,7 @@ const Type4SlideLayout: React.FC<Type4SlideLayoutProps> = ({ data: slideData })
<YAxis />
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
<Bar dataKey={dataKey} fill={color} radius={[4, 4, 0, 0]} />
<Bar dataKey={dataKey || 'value'} fill={color} radius={[4, 4, 0, 0]} />
</BarChart>
);
@ -117,7 +114,7 @@ const Type4SlideLayout: React.FC<Type4SlideLayoutProps> = ({ data: slideData })
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
<Line
type="monotone"
dataKey={dataKey}
dataKey={dataKey || 'value'}
stroke={color}
strokeWidth={3}
dot={{ fill: color, strokeWidth: 2, r: 4 }}
@ -135,7 +132,7 @@ const Type4SlideLayout: React.FC<Type4SlideLayoutProps> = ({ data: slideData })
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
<Area
type="monotone"
dataKey={dataKey}
dataKey={dataKey || 'value'}
stroke={color}
fill={color}
fillOpacity={0.6}
@ -154,7 +151,7 @@ const Type4SlideLayout: React.FC<Type4SlideLayoutProps> = ({ data: slideData })
cy="40%"
outerRadius={70}
fill={color}
dataKey={dataKey}
dataKey={dataKey || 'value'}
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
>
{chartData.map((entry, index) => (
@ -172,7 +169,7 @@ const Type4SlideLayout: React.FC<Type4SlideLayoutProps> = ({ data: slideData })
<YAxis dataKey="y" type="number" />
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
<Scatter dataKey="value" fill={color} />
<Scatter dataKey={dataKey || 'value'} fill={color} />
</ScatterChart>
);
@ -186,9 +183,9 @@ const Type4SlideLayout: React.FC<Type4SlideLayoutProps> = ({ data: slideData })
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"
>
<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">
{slideData?.title || 'Chart Analysis'}
</h1>
{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">
@ -199,9 +196,9 @@ const Type4SlideLayout: React.FC<Type4SlideLayoutProps> = ({ data: slideData })
</div>
</div>
<div className="w-full text-center">
<p className={`text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal`}>
{slideData?.description || 'This chart shows important data trends and insights that help understand the current situation and make informed decisions.'}
</p>
{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>

View file

@ -42,11 +42,12 @@ export const Schema = type5SlideSchema
export type Type5SlideData = z.infer<typeof type5SlideSchema>
interface Type5SlideLayoutProps {
data?: Partial<Type5SlideData>
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"
@ -55,13 +56,13 @@ const Type5SlideLayout: React.FC<Type5SlideLayoutProps> = ({ data: slideData })
<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 ">
<h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
{slideData?.title || 'Key Points'}
</h1>
{title && <h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
{title}
</h1>}
<p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
{slideData?.description || 'Here is the main description that provides context and introduction to the numbered points on the right side.'}
</p>
{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 */}
@ -80,12 +81,12 @@ const Type5SlideLayout: React.FC<Type5SlideLayoutProps> = ({ data: slideData })
{`0${index + 1}`}
</div>
<div className="space-y-1">
<h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
{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>
<p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
</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>
</p>}
</div>
</div>
</div>

View file

@ -61,12 +61,12 @@ export const Schema = type6SlideSchema
export type Type6SlideData = z.infer<typeof type6SlideSchema>
interface Type6SlideLayoutProps {
data?: Partial<Type6SlideData>
data: Partial<Type6SlideData>
}
const Type6SlideLayout: React.FC<Type6SlideLayoutProps> = ({ data: slideData }) => {
const items = slideData?.items || []
const isGridLayout = items.length >= 4
const { title, items } = slideData;
const isGridLayout = items && items.length >= 4
const getGridCols = (length: number) => {
switch (length) {
@ -82,8 +82,8 @@ const Type6SlideLayout: React.FC<Type6SlideLayoutProps> = ({ data: slideData })
const renderGridContent = () => {
return (
<div className={`grid grid-cols-1 ${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.map((item, index) => (
<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={{
@ -114,8 +114,8 @@ const Type6SlideLayout: React.FC<Type6SlideLayoutProps> = ({ data: slideData })
const renderHorizontalContent = () => {
return (
<div className={`grid grid-cols-1 sm:grid-cols-2 ${getGridCols(items.length)} w-full gap-3 lg:gap-8 mt-4 lg:mt-12`}>
{items.map((item, index) => (
<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={{
@ -129,12 +129,12 @@ const Type6SlideLayout: React.FC<Type6SlideLayoutProps> = ({ data: slideData })
</div>
</div>
<div className="lg:space-y-4 mt-2 lg:mt-4">
<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 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold text-center">
{item.heading}
</h3>
<p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal text-center">
</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>
</p>}
</div>
</div>
))}
@ -148,9 +148,9 @@ const Type6SlideLayout: React.FC<Type6SlideLayoutProps> = ({ data: slideData })
>
<div className="text-center sm:pb-2 lg:pb-8 w-full">
<h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
{slideData?.title || 'Our Services'}
</h1>
{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()}

View file

@ -66,12 +66,12 @@ export const Schema = type7SlideSchema
export type Type7SlideData = z.infer<typeof type7SlideSchema>
interface Type7SlideLayoutProps {
data?: Partial<Type7SlideData>
data: Partial<Type7SlideData>
}
const Type7SlideLayout: React.FC<Type7SlideLayoutProps> = ({ data: slideData }) => {
const items = slideData?.items || []
const isGridLayout = items.length >= 4
const { title, items } = slideData;
const isGridLayout = items && items.length >= 4
const getGridCols = (length: number) => {
switch (length) {
@ -87,8 +87,8 @@ const Type7SlideLayout: React.FC<Type7SlideLayoutProps> = ({ data: slideData })
const renderGridContent = () => {
return (
<div className={`grid grid-cols-1 ${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.map((item, index) => (
<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={{
@ -107,12 +107,12 @@ const Type7SlideLayout: React.FC<Type7SlideLayoutProps> = ({ data: slideData })
</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 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">
</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>
</p>}
</div>
</div>
</div>
@ -123,8 +123,8 @@ const Type7SlideLayout: React.FC<Type7SlideLayoutProps> = ({ data: slideData })
const renderHorizontalContent = () => {
return (
<div className={`grid grid-cols-1 sm:grid-cols-2 ${getGridCols(items.length)} w-full gap-3 lg:gap-8 mt-4 lg:mt-12`}>
{items.map((item, index) => (
<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={{
@ -134,20 +134,20 @@ const Type7SlideLayout: React.FC<Type7SlideLayoutProps> = ({ data: slideData })
>
<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">
<img
{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">
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight text-center">
{item.heading && <h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight text-center">
{item.heading}
</h3>
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed text-center">
</h3>}
{item.description && <p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed text-center">
{item.description}
</p>
</p>}
</div>
</div>
))}
@ -160,9 +160,9 @@ const Type7SlideLayout: React.FC<Type7SlideLayoutProps> = ({ data: slideData })
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">
<h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
{slideData?.title || 'Our Services'}
</h1>
{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()}

View file

@ -61,18 +61,18 @@ export const Schema = type8SlideSchema
export type Type8SlideData = z.infer<typeof type8SlideSchema>
interface Type8SlideLayoutProps {
data?: Partial<Type8SlideData>
data: Partial<Type8SlideData>
}
const Type8SlideLayout: React.FC<Type8SlideLayoutProps> = ({ data: slideData }) => {
const items = slideData?.items || []
const { title, description, items } = slideData;
const renderItems = () => {
if (items.length === 2) {
if (items && items.length === 2) {
// Vertical stacked layout for 2 items
return (
<div className="space-y-4 lg:space-y-8">
{items.map((item, index) => (
{items && items.map((item, index) => (
<div
key={index}
style={{
@ -82,20 +82,20 @@ const Type8SlideLayout: React.FC<Type8SlideLayoutProps> = ({ data: slideData })
>
<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">
<img
{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">
<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 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold text-center">
{item.heading}
</h3>
<p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal text-center">
</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>
</p>}
</div>
</div>
))}
@ -105,7 +105,7 @@ const Type8SlideLayout: React.FC<Type8SlideLayoutProps> = ({ data: slideData })
// Horizontal layout with side icons for 3+ items
return (
<div className="space-y-4 lg:space-y-8">
{items.map((item, index) => (
{items && items.map((item, index) => (
<div
key={index}
style={{
@ -116,20 +116,20 @@ const Type8SlideLayout: React.FC<Type8SlideLayoutProps> = ({ data: slideData })
<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">
<img
{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">
<h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
{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>
<p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
</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>
</p>}
</div>
</div>
</div>
@ -146,13 +146,13 @@ const Type8SlideLayout: React.FC<Type8SlideLayoutProps> = ({ data: slideData })
<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">
<h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
{slideData?.title || 'Key Features'}
</h1>
{title && <h1 className="text-gray-900 text-xl sm:text-2xl lg:text-[40px] leading-[36px] lg:leading-[48px] font-bold">
{title}
</h1>}
<p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
{slideData?.description || 'Here is the main description that provides context and introduces the key features outlined on the right side.'}
</p>
{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 */}

View file

@ -97,17 +97,11 @@ export const Schema = type9SlideSchema
export type Type9SlideData = z.infer<typeof type9SlideSchema>
interface Type9SlideLayoutProps {
data?: Partial<Type9SlideData>
data: Partial<Type9SlideData>
}
const Type9SlideLayout: React.FC<Type9SlideLayoutProps> = ({ data: slideData }) => {
const chartData = slideData?.data || [];
const chartType = slideData?.chartType || 'line';
const color = slideData?.color || '#3b82f6';
const dataKey = slideData?.dataKey || 'value';
const categoryKey = slideData?.categoryKey || 'name';
const showLegend = slideData?.showLegend || false;
const showTooltip = slideData?.showTooltip || true;
const { title, items, chartData, chartType = 'line', color = '#3b82f6', dataKey = 'value', categoryKey = 'name', showLegend = false, showTooltip = true } = slideData;
const renderChart = () => {
const commonProps = {
data: chartData,
@ -177,7 +171,7 @@ const Type9SlideLayout: React.FC<Type9SlideLayoutProps> = ({ data: slideData })
dataKey={dataKey}
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
>
{chartData.map((entry, index) => (
{chartData.map((entry: any, index: number) => (
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
))}
</Pie>
@ -206,9 +200,9 @@ const Type9SlideLayout: React.FC<Type9SlideLayoutProps> = ({ data: slideData })
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"
>
<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">
{slideData?.title || 'Chart Analysis'}
</h1>
{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">
@ -220,7 +214,7 @@ const Type9SlideLayout: React.FC<Type9SlideLayoutProps> = ({ data: slideData })
</div>
<div className="lg:w-1/2 relative">
<div className="space-y-3 lg:space-y-6">
{slideData?.items?.map((item, index) => (
{items && items.map((item, index) => (
<div
key={index}
style={{
@ -233,12 +227,12 @@ const Type9SlideLayout: React.FC<Type9SlideLayoutProps> = ({ data: slideData })
{`0${index + 1}`}
</div>
<div className="space-y-1">
<h3 className="text-gray-900 text-base sm:text-lg lg:text-[24px] leading-[26px] lg:leading-[32px] font-bold">
{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>
<p className="text-gray-700 text-sm sm:text-base lg:text-[20px] leading-[20px] lg:leading-[30px] font-normal">
</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>
</p>}
</div>
</div>
</div>

View file

@ -0,0 +1,118 @@
import React from "react";
import * as z from "zod";
export const layoutId = "intro-pitchdeck-slide";
export const layoutName = "Intro Pitch Deck Slide";
export const layoutDescription =
"A visually appealing introduction slide for a pitch deck, featuring a large title, company name, date, and contact information with a modern design.";
const introPitchDeckSchema = z.object({
title: z.string().min(2).max(15).default("Pitch Deck and badu").meta({
description: "Main title of the slide",
}),
description: z.string().default("").meta({
description: "Empty description as per the design",
}),
contactNumber: z.string().default("+123-456-7890").meta({
description: "Contact phone number displayed in footer",
}),
contactAddress: z
.string()
.default("123 Anywhere St., Any City, ST 123")
.meta({
description: "Contact address displayed in footer",
}),
contactWebsite: z.string().default("www.reallygreatsite.com").meta({
description: "Contact website URL displayed in footer",
}),
companyName: z.string().default("presenton").meta({
description: "Company name displayed in header",
}),
date: z.string().default("June 13, 2038").meta({
description: "Date of the presentation",
}),
});
export const Schema = introPitchDeckSchema;
export type IntroPitchDeckData = z.infer<typeof introPitchDeckSchema>;
interface IntroSlideLayoutProps {
data: Partial<IntroPitchDeckData>;
}
const IntroPitchDeckSlide: React.FC<IntroSlideLayoutProps> = ({
data: slideData,
}) => {
const { title, description, contactNumber, contactAddress, contactWebsite, companyName, date } = slideData;
return (
<>
{/* Montserrat Font */}
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<div
className="w-full max-w-[1280px] bg-white aspect-video mx-auto relative overflow-hidden rounded-md"
style={{
fontFamily: "Montserrat, sans-serif",
backgroundSize: "cover",
backgroundPosition: "center",
}}
>
{/* Top Header */}
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
<span>{slideData?.companyName}</span>
<span>{slideData?.date}</span>
</div>
{/* Main Title */}
<div
className="absolute left-10"
style={{
top: "50%",
transform: "translateY(-50%)",
}}
>
{title && <div className="relative inline-block">
<h1
className="text-7xl font-bold text-[#1E4CD9] leading-none"
id="pitchdeck-title"
>
{title}
</h1>
{/* Blue underline */}
<span
className="block bg-[#1E4CD9] h-[4px] absolute left-0"
style={{
width: "100%",
bottom: "-0.5em",
transition: "width 0.3s",
}}
/>
</div>}
</div>
{/* Bottom Contact Row */}
<div className="absolute bottom-8 left-10 right-10 flex flex-wrap items-center gap-10 text-[#1E4CD9] text-sm font-medium">
{contactNumber && <div className="flex items-center gap-2">
<span className="text-lg">📞</span>
<span>{contactNumber}</span>
</div>}
{contactAddress && <div className="flex items-center gap-2">
<span className="text-lg">📍</span>
<span>{contactAddress}</span>
</div>}
{contactWebsite && <div className="flex items-center gap-2">
<span className="text-lg">🌐</span>
<span>{contactWebsite}</span>
</div>}
{description && <div className="flex items-center gap-2">
<span className="text-lg">💬</span>
<span>{description}</span>
</div>}
</div>
</div>
</>
);
};
export default IntroPitchDeckSlide;

View file

@ -0,0 +1,140 @@
import React from "react";
import * as z from "zod";
import { ImageSchema, IconSchema } from "@/presentation-layouts/defaultSchemes";
export const layoutId = "about-company-slide";
export const layoutName = "About Our Company Slide";
export const layoutDescription =
"A slide layout providing an overview of the company, its background, and key information.";
const aboutCompanySlideSchema = z.object({
title: z.string().min(3).max(30).default("About Our Company").meta({
description: "Main title of the slide",
}),
content: z
.string()
.min(50)
.max(500)
.default(
"In the presentation session, the background/introduction can be filled with information that is arranged systematically and effectively with respect to an interesting topic to be used as material for discussion at the opening of the presentation session. The introduction can provide a general overview for those who are listening to your presentation so that the key words on the topic of discussion are emphasized during this background/introductory presentation session.",
)
.meta({
description: "Main content text describing the company or topic",
}),
companyName: z.string().min(2).max(50).default("presenton").meta({
description: "Company name displayed in header",
}),
date: z.string().min(5).max(30).default("June 13, 2038").meta({
description: "Date displayed in header",
}),
image: ImageSchema.optional().meta({
description:
"Optional supporting image for the slide (building, office, etc.)",
}),
});
export const Schema = aboutCompanySlideSchema;
export type AboutCompanySlideData = z.infer<typeof aboutCompanySlideSchema>;
interface AboutCompanySlideLayoutProps {
data?: Partial<AboutCompanySlideData>;
}
const AboutCompanySlideLayout: React.FC<AboutCompanySlideLayoutProps> = ({
data: slideData,
}) => {
return (
<>
{/* Import fonts */}
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<div
className="w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video bg-white relative z-20 mx-auto overflow-hidden"
style={{
fontFamily: "Montserrat, sans-serif",
}}
>
{/* Header */}
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
<span>{slideData?.companyName}</span>
<span>{slideData?.date}</span>
</div>
{/* Main content area */}
<div className="flex h-full px-16 pb-16">
{/* Left side - Image */}
<div className="flex-1 pr-16 flex items-center pt-8">
<div className="w-full h-96 overflow-hidden">
{slideData?.image ? (
<img
src={slideData.image.__image_url__}
alt={slideData.image.__image_prompt__}
className="w-full h-full object-cover"
/>
) : (
/* Default building facade */
<div className="w-full h-full bg-gray-200 relative">
{/* Building structure simulation */}
<div className="absolute inset-0 bg-gray-300"></div>
{/* Horizontal lines (building floors) */}
<div className="absolute inset-0">
{[...Array(12)].map((_, i) => (
<div
key={i}
className="absolute w-full border-t border-gray-400 opacity-60"
style={{ top: `${(i + 1) * 8}%` }}
></div>
))}
</div>
{/* Vertical lines (building columns) */}
<div className="absolute inset-0">
{[...Array(6)].map((_, i) => (
<div
key={i}
className="absolute h-full border-l border-gray-400 opacity-40"
style={{ left: `${(i + 1) * 16}%` }}
></div>
))}
</div>
{/* Windows */}
<div className="absolute inset-0 grid grid-cols-4 gap-2 p-4">
{[...Array(32)].map((_, i) => (
<div
key={i}
className="bg-blue-100 opacity-60 rounded-sm border border-gray-300"
></div>
))}
</div>
{/* Building edge highlight */}
<div className="absolute right-0 top-0 w-1 h-full bg-white opacity-80"></div>
</div>
)}
</div>
</div>
{/* Right side - Content */}
<div className="flex-1 pl-16 flex flex-col justify-center">
<h2 className="text-6xl font-bold text-blue-600 mb-12 leading-tight">
{slideData?.title || "About Our Company"}
</h2>
<div className="text-lg text-blue-600 leading-relaxed font-normal max-w-lg">
{slideData?.content ||
"In the presentation session, the background/introduction can be filled with information that is arranged systematically and effectively with respect to an interesting topic to be used as material for discussion at the opening of the presentation session. The introduction can provide a general overview for those who are listening to your presentation so that the key words on the topic of discussion are emphasized during this background/introductory presentation session."}
</div>
</div>
</div>
</div>
</>
);
};
export default AboutCompanySlideLayout;

View file

@ -0,0 +1,181 @@
import React from "react";
import * as z from "zod";
import { ImageSchema, IconSchema } from "@/presentation-layouts/defaultSchemes";
export const layoutId = "problem-statement-slide";
export const layoutName = "Problem Statement Slide";
export const layoutDescription =
"A slide layout designed to present a clear problem statement, including categories of problems, company information, and an optional image.";
const problemStatementSlideSchema = z.object({
title: z.string().min(3).max(20).default("Problem").meta({
description: "Main title of the problem statement slide",
}),
description: z
.string()
.min(50)
.max(200)
.default(
"A problem needs to be discussed further and in detail because this problem is the main foundation in the initial development of a product, service, and decision making. Without a well-defined problem, it will have an impact on a job that is unfocused, unmanaged, and less relevant.",
)
.meta({
description: "Main content text describing the problem statement",
}),
problemCategories: z
.array(
z.object({
title: z.string().min(3).max(30).meta({
description: "Title of the problem category",
}),
description: z.string().min(20).max(100).meta({
description: "Description of the problem category",
}),
icon: IconSchema.optional().meta({
description: "Optional icon for the problem category",
}),
}),
)
.min(2)
.max(4)
.default([
{
title: "Inefficiency",
description:
"Businesses struggle to find digital tools that meet their needs, causing operational slowdowns.",
icon: {
__icon_url__:
"https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/alert-triangle.js",
__icon_query__: "warning alert inefficiency",
},
},
{
title: "High Costs",
description:
"Outdated systems increase expenses, while small businesses struggle to expand their market reach.",
icon: {
__icon_url__:
"https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/trending-up.js",
__icon_query__: "trending up costs chart",
},
},
{
title: "Inefficiency",
description:
"Businesses struggle to find digital tools that meet their needs, causing operational slowdowns.",
icon: {
__icon_url__:
"https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/alert-triangle.js",
__icon_query__: "warning alert inefficiency",
},
},
{
title: "Inefficiency",
description:
"Businesses struggle to find digital tools that meet their needs, causing operational slowdowns.",
icon: {
__icon_url__:
"https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/alert-triangle.js",
__icon_query__: "warning alert inefficiency",
},
},
])
.meta({
description:
"List of problem categories with titles, descriptions, and optional icons",
}),
companyName: z.string().min(2).max(50).default("Rimberio").meta({
description: "Company name displayed in header",
}),
date: z.string().min(5).max(30).default("June 13, 2038").meta({
description: "Date displayed in header",
}),
});
export const Schema = problemStatementSlideSchema;
export type ProblemStatementSlideData = z.infer<
typeof problemStatementSlideSchema
>;
interface ProblemStatementSlideLayoutProps {
data?: Partial<ProblemStatementSlideData>;
}
const ProblemStatementSlideLayout: React.FC<
ProblemStatementSlideLayoutProps
> = ({ data: slideData }) => {
const problemCategories = slideData?.problemCategories || [];
return (
<>
{/* Import fonts */}
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<div
className="w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video bg-blue-600 relative z-20 mx-auto overflow-hidden text-white"
style={{
fontFamily: "Montserrat, sans-serif",
}}
>
{/* Header */}
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-white text-sm font-semibold">
<span>{slideData?.companyName}</span>
<span>{slideData?.date}</span>
</div>
{/* Main content area */}
<div className="flex h-full px-16 pb-16">
{/* Left side - Main Problem */}
<div className="flex-1 pr-16 flex flex-col justify-center">
<div className="flex flex-col items-start justify-center h-full">
<h2 className="text-5xl font-bold text-white mb-8 leading-tight text-left">
{slideData?.title}
</h2>
<div className="text-lg text-white leading-relaxed font-normal mb-12 max-w-lg text-left">
{slideData?.description}
</div>
</div>
</div>
{/* Right side - Problem Categories with Icons */}
<div className="flex-1 pl-16 flex flex-col justify-center">
<div className="w-full max-w-xl mx-auto grid grid-cols-1 gap-8">
{problemCategories.map((category, index) => (
<div
key={index}
className="flex items-start gap-5 bg-white bg-opacity-5 rounded-lg p-5"
>
<div className="flex-shrink-0">
<img
src={category.icon?.__icon_url__}
alt={category.icon?.__icon_query__}
className="w-12 h-12"
style={{ filter: "invert(1)" }}
/>
</div>
<div>
<h3 className="text-xl font-semibold text-white mb-1">
{category.title}
</h3>
<p className="text-sm text-blue-100 leading-relaxed max-w-md">
{category.description}
</p>
</div>
</div>
))}
</div>
</div>
</div>
{/* Bottom border line */}
<div className="absolute bottom-0 left-0 w-full h-1 bg-white"></div>
</div>
</>
);
};
export default ProblemStatementSlideLayout;

View file

@ -0,0 +1,169 @@
import React from "react";
import * as z from "zod";
import { IconSchema } from "@/presentation-layouts/defaultSchemes";
export const layoutId = "solution-slide";
export const layoutName = "Solution Slide";
export const layoutDescription =
"A slide layout designed to present a solution to previously identified problems, showcasing key aspects of the solution with sections and icons.";
const solutionSlideSchema = z.object({
companyName: z.string().min(2).max(50).default("presenton").meta({
description: "Company name displayed in header",
}),
date: z.string().min(5).max(50).default("June 13, 2038").meta({
description: "Date displayed in header",
}),
title: z.string().min(3).max(25).default("Businesses struggle").meta({
description: "Main title of the slide",
}),
mainDescription: z
.string()
.min(20)
.max(300)
.default(
"Show that we offer a solution that solves the problems previously described and identified. Make sure that the solutions we offer uphold the values of effectiveness, efficiency, and are highly relevant to the market situation and societyshiva raj badu is here and what is hsd sdksdf klfdslkf lkflkfsldkf.",
)
.meta({
description: "Main content text describing the solution",
}),
sections: z
.array(
z.object({
title: z.string().min(3).max(30).meta({
description: "Section title",
}),
description: z.string().min(5).max(80).meta({
description: "Section description",
}),
icon: IconSchema.optional().meta({
description: "Icon for the section",
}),
}),
)
.min(2)
.max(4)
.default([
{
title: "Market",
description: "Innovative and widely accepted.",
icon: {
__icon_query__: "market innovation",
__icon_url__:
"https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/globe.js",
},
},
{
title: "Industry",
description: "Based on sound market decisions.",
icon: {
__icon_query__: "industry building",
__icon_url__:
"https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/building.js",
},
},
{
title: "SEM",
description: "Driven by precise data and analysis.",
icon: {
__icon_query__: "SEM data analysis",
__icon_url__:
"https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/chart-bar.js",
},
},
{
title: "End User",
description: "Focused on real user impact.",
icon: {
__icon_query__: "end user impact",
__icon_url__:
"https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/user.js",
},
},
])
.meta({
description:
"List of solution sections with titles, descriptions, and optional icons",
}),
});
export const Schema = solutionSlideSchema;
export type SolutionSlideData = z.infer<typeof solutionSlideSchema>;
interface SolutionSlideLayoutProps {
data?: Partial<SolutionSlideData>;
}
const SolutionSlideLayout: React.FC<SolutionSlideLayoutProps> = ({
data: slideData,
}) => {
const sections = slideData?.sections || [];
return (
<>
{/* Import Google Fonts */}
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<div
className="w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video bg-white relative z-20 mx-auto overflow-hidden border-2 border-gray-800"
style={{
fontFamily: "Montserrat, sans-serif",
}}
>
{/* Header */}
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
<span>{slideData?.companyName}</span>
<span>{slideData?.date}</span>
</div>
{/* Main Content */}
<div className="flex justify-center items-center h-full px-16 pb-16 gap-4">
{/* Title and Description */}
<div className="w-full flex flex-col items-start mb-4">
<h1 className="text-6xl font-bold text-blue-600 mb-8 leading-tight text-left">
{slideData?.title}
</h1>
<p className="text-blue-600 text-lg leading-relaxed font-normal mb-12 max-w-lg text-left">
{slideData?.mainDescription}
</p>
</div>
{/* Four Small Boxes in a Row */}
<div className="grid grid-cols-2 gap-4 w-full max-w-5xl">
{sections.map((section, idx) => (
<div
key={idx}
className="flex flex-col items-center text-center bg-[#F5F8FE] rounded-lg shadow px-3 py-4 min-h-[140px] max-h-[160px]"
>
<div className="mb-2">
{section?.icon?.__icon_url__ && (
<img
src={section.icon.__icon_url__}
alt={section.icon.__icon_query__}
className="w-12 h-12 mb-2"
/>
)}
</div>
<h2 className="text-lg font-semibold text-blue-600 mb-1">
{section.title}
</h2>
<div className="w-8 h-1 bg-blue-600 mb-2"></div>
<p className="text-blue-600 text-xs leading-snug">
{section.description}
</p>
</div>
))}
</div>
</div>
{/* Bottom Border */}
<div className="absolute bottom-0 left-0 right-0 h-1 bg-blue-600"></div>
</div>
</>
);
};
export default SolutionSlideLayout;

View file

@ -0,0 +1,301 @@
import React from "react";
import * as z from "zod";
import { ImageSchema } from "@/presentation-layouts/defaultSchemes";
export const layoutId = "product-overview-slide";
export const layoutName = "Product Overview Slide";
export const layoutDescription =
"A slide layout designed to showcase a company's products or services, highlighting their features and benefits in a structured format.";
const productOverviewSlideSchema = z.object({
companyName: z.string().min(2).max(50).default("presenton").meta({
description: "Company name displayed in header",
}),
date: z.string().min(5).max(50).default("June 13, 2038").meta({
description: "Date displayed in header",
}),
title: z.string().min(3).max(40).default("Product Overview").meta({
description: "Main title of the slide",
}),
mainDescription: z
.string()
.min(50)
.max(400)
.default(
"Provide an explanation of the general profile of the services we have. Arrange information about our products services in a systematic and fact-based manner. Also express our pride in the service that we have done well.",
)
.meta({
description: "Main content text describing the product overview",
}),
products: z
.array(
z.object({
title: z.string().min(3).max(50).meta({
description: "Product title",
}),
description: z.string().min(30).max(140).meta({
description: "Product description",
}),
image: ImageSchema.meta({
description: "Product image",
}),
isBlueBackground: z.boolean().default(false).meta({
description: "Whether the product box has a blue background",
}),
}),
)
.min(2)
.max(2)
.default([
{
title: "Internet of Things",
description:
"Detail and explain each product. Our examination of community and market issues increases with additional products/services.",
image: {
__image_url__:
"https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=300&h=200&fit=crop",
__image_prompt__: "Person working on electronics with headphones",
},
isBlueBackground: true,
},
{
title: "Smart Home Platform",
description:
"Our alternate product category is available. Our products must work together to solve social and economic issues.",
image: {
__image_url__:
"https://images.unsplash.com/photo-1573164713988-8665fc963095?w=300&h=200&fit=crop",
__image_prompt__:
"Woman working at computer with technical equipment",
},
isBlueBackground: true,
},
])
.meta({
description: "List of products or services to showcase",
}),
});
export const Schema = productOverviewSlideSchema;
export type ProductOverviewSlideData = z.infer<
typeof productOverviewSlideSchema
>;
interface ProductOverviewSlideLayoutProps {
data?: Partial<ProductOverviewSlideData>;
}
const ProductOverviewSlideLayout: React.FC<ProductOverviewSlideLayoutProps> = ({
data: slideData,
}) => {
const products = slideData?.products || [];
// Make the product boxes smaller
const PRODUCT_BOX_HEIGHT = 400; // px (smaller than before)
const PRODUCT_BOX_WIDTH = 200; // px (smaller than before)
const TEXT_SECTION_HEIGHT = Math.round(PRODUCT_BOX_HEIGHT * 0.56); // ~190px
const IMAGE_SECTION_HEIGHT = PRODUCT_BOX_HEIGHT - TEXT_SECTION_HEIGHT; // ~150px
return (
<>
{/* Import Google Fonts */}
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<div
className="w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video bg-white relative z-20 mx-auto overflow-hidden"
style={{
fontFamily: "Montserrat, sans-serif",
}}
>
{/* Header */}
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
<span>{slideData?.companyName}</span>
<span>{slideData?.date}</span>
</div>
{/* Main Content */}
<div className="flex h-full px-16 pb-16">
{/* Title and Description on the left */}
<div className="flex flex-col items-start justify-center w-[48%] pr-8">
<h1 className="text-6xl font-bold text-blue-600 mb-8 leading-tight text-left">
{slideData?.title}
</h1>
<p className="text-blue-600 text-lg leading-relaxed font-normal mb-12 max-w-lg text-left">
{slideData?.mainDescription}
</p>
</div>
{/* Product Grid on the right */}
<div className="flex flex-row items-center justify-end w-[62%] gap-8">
{/* First Column: Normal order (Text above, Image below) */}
<div className="flex flex-col items-center gap-4 justify-center h-full">
{products[0] && (
<div
className="flex flex-col items-stretch"
style={{
width: `${PRODUCT_BOX_WIDTH + 40}px`,
height: `${PRODUCT_BOX_HEIGHT + 60}px`,
}}
>
{/* Top Section - Blue background with text */}
<div
className={`${products[0].isBlueBackground ? "bg-blue-600" : "bg-gray-100"} p-5 flex flex-col justify-center text-center rounded-t-md`}
style={{ height: `${TEXT_SECTION_HEIGHT + 32}px` }}
>
<h2
className={`text-xl font-semibold mb-3 ${products[0].isBlueBackground ? "text-white" : "text-blue-600"}`}
>
{products[0].title}
</h2>
<p
className={`text-sm leading-relaxed ${products[0].isBlueBackground ? "text-white" : "text-blue-600"}`}
>
{products[0].description}
</p>
</div>
{/* Bottom Section - Image */}
<div
className="rounded-b-md overflow-hidden"
style={{ height: `${IMAGE_SECTION_HEIGHT + 28}px` }}
>
<img
src={products[0].image.__image_url__}
alt={
products[0].image.__image_prompt__ || products[0].title
}
className="w-full h-full object-cover"
/>
</div>
</div>
)}
{products[2] && (
<div
className="flex flex-col items-stretch"
style={{
width: `${PRODUCT_BOX_WIDTH + 40}px`,
height: `${PRODUCT_BOX_HEIGHT + 60}px`,
}}
>
<div
className={`${products[2].isBlueBackground ? "bg-blue-600" : "bg-gray-100"} p-5 flex flex-col justify-center text-center rounded-t-md`}
style={{ height: `${TEXT_SECTION_HEIGHT + 32}px` }}
>
<h2
className={`text-xl font-semibold mb-3 ${products[2].isBlueBackground ? "text-white" : "text-blue-600"}`}
>
{products[2].title}
</h2>
<p
className={`text-sm leading-relaxed ${products[2].isBlueBackground ? "text-white" : "text-blue-600"}`}
>
{products[2].description}
</p>
</div>
<div
className="rounded-b-md overflow-hidden"
style={{ height: `${IMAGE_SECTION_HEIGHT + 28}px` }}
>
<img
src={products[2].image.__image_url__}
alt={
products[2].image.__image_prompt__ || products[2].title
}
className="w-full h-full object-cover"
/>
</div>
</div>
)}
</div>
{/* Second Column: Reverse order (Image above, Text below) */}
<div className="flex flex-col items-center gap-4 justify-center h-full">
{products[1] && (
<div
className="flex flex-col items-stretch"
style={{
width: `${PRODUCT_BOX_WIDTH + 40}px`,
height: `${PRODUCT_BOX_HEIGHT + 60}px`,
}}
>
{/* Top Section - Image */}
<div
className="rounded-t-md overflow-hidden"
style={{ height: `${IMAGE_SECTION_HEIGHT + 28}px` }}
>
<img
src={products[1].image.__image_url__}
alt={
products[1].image.__image_prompt__ || products[1].title
}
className="w-full h-full object-cover"
/>
</div>
{/* Bottom Section - Blue background with text */}
<div
className={`${products[1].isBlueBackground ? "bg-blue-600" : "bg-gray-100"} p-5 flex flex-col justify-center text-center rounded-b-md`}
style={{ height: `${TEXT_SECTION_HEIGHT + 32}px` }}
>
<h2
className={`text-xl font-semibold mb-3 ${products[1].isBlueBackground ? "text-white" : "text-blue-600"}`}
>
{products[1].title}
</h2>
<p
className={`text-sm leading-relaxed ${products[1].isBlueBackground ? "text-white" : "text-blue-600"}`}
>
{products[1].description}
</p>
</div>
</div>
)}
{products[3] && (
<div
className="flex flex-col items-stretch"
style={{
width: `${PRODUCT_BOX_WIDTH + 40}px`,
height: `${PRODUCT_BOX_HEIGHT + 60}px`,
}}
>
<div
className="rounded-t-md overflow-hidden"
style={{ height: `${IMAGE_SECTION_HEIGHT + 28}px` }}
>
<img
src={products[3].image.__image_url__}
alt={
products[3].image.__image_prompt__ || products[3].title
}
className="w-full h-full object-cover"
/>
</div>
<div
className={`${products[3].isBlueBackground ? "bg-blue-600" : "bg-gray-100"} p-5 flex flex-col justify-center text-center rounded-b-md`}
style={{ height: `${TEXT_SECTION_HEIGHT + 32}px` }}
>
<h2
className={`text-xl font-semibold mb-3 ${products[3].isBlueBackground ? "text-white" : "text-blue-600"}`}
>
{products[3].title}
</h2>
<p
className={`text-sm leading-relaxed ${products[3].isBlueBackground ? "text-white" : "text-blue-600"}`}
>
{products[3].description}
</p>
</div>
</div>
)}
</div>
</div>
</div>
{/* Bottom Border */}
<div className="absolute bottom-0 left-0 right-0 h-1 bg-blue-600"></div>
</div>
</>
);
};
export default ProductOverviewSlideLayout;

View file

@ -0,0 +1,153 @@
import React from "react";
import * as z from "zod";
import { ImageSchema } from "@/presentation-layouts/defaultSchemes";
export const layoutId = "market-size-pitchdeck-slide";
export const layoutName = "Market Size Pitch Deck Slide";
export const layoutDescription =
"A professional slide layout designed to present market size statistics, including TAM, SAM, and SOM, with a world map and key metrics.";
const marketSizeSlideSchema = z.object({
title: z.string().min(3).max(15).default("Market Size").meta({
description: "Main slide title",
}),
companyName: z.string().min(3).max(30).default("Rimberio").meta({
description: "Presenter's name",
}),
date: z.string().default("June 13, 2038").meta({
description: "Presentation date",
}),
mapImage: ImageSchema.default({
__image_url__:
"https://upload.wikimedia.org/wikipedia/commons/8/80/World_map_-_low_resolution.svg", // You can quickly find a world map image via a Google search or use a free resource like Wikimedia Commons
__image_prompt__: "World map with location pins or points",
}),
marketStats: z
.array(
z.object({
label: z.string().min(3).max(30),
value: z.string().min(3).max(30),
description: z.string().min(3).max(130),
}),
)
.min(1)
.max(3)
.default([
{
label: "Total Available Market (TAM)",
value: "1.4 Billion",
description:
"In the TAM Section, we can fill in the potential of any person who can buy an offer or the maximum amount of revenue a business can earn by selling their offer.",
},
{
label: "Serviceable Available Market (SAM)",
value: "194 Million",
description:
"It is a part of TAM that has the potential to become a target market for the company by considering the type of product, technology available and geographical conditions.",
},
{
label: "Serviceable Obtainable Market (SOM)",
value: "167 Million",
description:
"The SOM is a smaller fraction of the SAM that is the target of a serviceable and realistically achievable market in the short to medium term.",
},
]),
description: z
.string()
.default(
"Market size is the total amount of all sales and customers that can be seen directly by stakeholders. This technique is usually calculated at the end of the year, the market size can be used by companies to determine the potential of their market and business in the future. This is very useful, especially for new companies that will offer services to those who are interested in our services.",
)
.meta({
description: "Main description text for the slide",
}),
});
export const Schema = marketSizeSlideSchema;
export type MarketSizeSlideData = z.infer<typeof marketSizeSlideSchema>;
interface MarketSizeSlideProps {
data?: Partial<MarketSizeSlideData>;
}
const MarketSizeSlideLayout: React.FC<MarketSizeSlideProps> = ({
data: slideData,
}) => {
const stats = slideData?.marketStats || [];
return (
<>
{/* Montserrat Font */}
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<div
className="w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video bg-white relative z-20 mx-auto overflow-hidden"
style={{
fontFamily: "Montserrat, sans-serif",
}}
>
{/* Header */}
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
<span>{slideData?.companyName || "Rimberio"}</span>
<span>{slideData?.date || "June 13, 2038"}</span>
</div>
{/* Main Content */}
<div className="flex h-full px-16 pb-16">
{/* Title and Map on the left */}
<div className="flex flex-col items-center justify-center w-[48%] pr-8 h-full">
<div className="flex flex-col items-left justify-center h-full w-full">
{/* Move the title down to align with the top of the market stats */}
<h1
className="text-6xl font-bold text-blue-600 mb-8 leading-tight text-left"
style={{ marginTop: "112px" }} // 112px matches top-36 (9rem) of stats
>
{slideData?.title || "Market Size"}
</h1>
<div className="w-full bg-[#CBE3CC] rounded-md mb-8 flex items-center justify-center">
<img
src={
slideData?.mapImage?.__image_url__ ||
"https://upload.wikimedia.org/wikipedia/commons/8/80/World_map_-_low_resolution.svg"
}
alt="Market World Map with Points"
className="w-full object-contain rounded-md"
style={{ maxHeight: 220 }}
/>
</div>
<p className="text-blue-600 text-sm leading-relaxed font-normal mb-12 max-w-lg text-left">
{slideData?.description ||
"Market size is the total amount of all sales and customers that can be seen directly by stakeholders. This technique is usually calculated at the end of the year, the market size can be used by companies to determine the potential of their market and business in the future."}
</p>
</div>
</div>
{/* Market Stats on the right */}
<div className="flex flex-col items-start justify-center w-[52%] gap-8">
<div className="absolute top-36 right-10 w-[42%] space-y-10">
{stats.map((stat, index) => (
<div key={index}>
<div className="space-y-2">
<div className="bg-[#1E4CD9] text-white text-sm font-semibold px-3 py-1 inline-block rounded-sm">
<span className="text-sm">{stat.label}</span>
</div>
<div className="text-2xl font-bold text-[#1E4CD9]">
{stat.value}
</div>
</div>
<p className="text-sm text-gray-700 leading-snug">
{stat.description}
</p>
</div>
))}
</div>
</div>
</div>
</div>
</>
);
};
export default MarketSizeSlideLayout;

View file

@ -0,0 +1,193 @@
import React from "react";
import * as z from "zod";
import { Card } from "@/components/ui/card";
import { Table, TableHeader, TableBody } from "@/components/ui/table";
import { ChartContainer } from "@/components/ui/chart";
import { ImageSchema } from "@/presentation-layouts/defaultSchemes";
import { BarChart, Bar, XAxis, YAxis, Tooltip, Cell } from "recharts";
export const layoutId = "market-validation-slide";
export const layoutName = "Market Validation Slide";
export const layoutDescription =
"A slide layout designed to present market validation data, including flexible market validation metrics, comparisons, and an optional decorative image.";
// Make the schema generic: allow any label/value pairs for comparison
const marketValidationSchema = z.object({
companyName: z.string().min(2).max(50).default("presenton").meta({
description: "Company name in header",
}),
date: z.string().min(5).max(50).default("June 13, 2038").meta({
description: "Date in header",
}),
title: z.string().min(3).max(20).default("Market Validation").meta({
description: "Title of the slide",
}),
description: z
.string()
.min(50)
.max(400)
.default(
"Its a market testing stage to ensure that the products produced by the company can be accepted and effectively used by the broad market. For start-up companies, we can use data already achieved by similar products from other companies.",
)
.meta({
description:
"Main description text for the slide explaining market validation",
}),
// Generic comparisonData: label for row, label for metric, and value
comparisonData: z
.array(
z.object({
label: z.string().min(2).max(50).meta({
description:
"Name of comparison entity (e.g., company, product, etc.)",
}),
metricLabel: z.string().min(2).max(50).meta({
description:
"Label for the metric being compared (e.g., Users, Revenue, etc.)",
}),
value: z.number().min(0).meta({
description: "Numeric value for the metric",
}),
}),
)
.min(2)
.max(5)
.default([
{ label: "Thynk Unlimited", metricLabel: "Revenue ($K)", value: 2650 },
{ label: "Salford & Co.", metricLabel: "Revenue ($K)", value: 1850 },
{ label: "Liceria & Co.", metricLabel: "Revenue ($K)", value: 1010 },
])
.meta({
description: "Market benchmark data (generic metric)",
}),
image: ImageSchema.optional().meta({
description: "Optional decorative image",
}),
});
export const Schema = marketValidationSchema;
export type MarketValidationSlideData = z.infer<typeof marketValidationSchema>;
interface MarketValidationSlideLayoutProps {
data?: Partial<MarketValidationSlideData>;
}
const MarketValidationSlideLayout: React.FC<
MarketValidationSlideLayoutProps
> = ({ data: slideData }) => {
const comparisonData = slideData?.comparisonData || [];
// Chart color palette (shadcn blue and gray)
const chartColors = ["#2563eb", "#1e40af", "#60a5fa", "#93c5fd", "#dbeafe"];
// Determine the metric label to use (assume all rows use the same metricLabel)
const metricLabel =
comparisonData.length > 0 ? comparisonData[0].metricLabel : "Metric";
return (
<>
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<div
className="w-full max-w-[1280px] max-h-[720px] aspect-video bg-white mx-auto rounded shadow-lg overflow-hidden relative z-20"
style={{
fontFamily: "Montserrat, sans-serif",
}}
>
{/* Header */}
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
<span>{slideData?.companyName}</span>
<span>{slideData?.date}</span>
</div>
{/* Main Content */}
<div className="px-16 py-16 flex h-full gap-8">
{/* Left Column */}
<div className="flex-1 pr-12 flex flex-col justify-center">
<h1 className="text-6xl font-bold text-blue-600 mb-8 leading-tight text-left">
{slideData?.title}
</h1>
<p className="text-blue-600 text-sm leading-relaxed font-normal mb-12 max-w-lg text-left">
{slideData?.description}
</p>
</div>
{/* Right Column - Chart on top, Table on bottom */}
<div className="flex-1 flex flex-col justify-center items-center gap-6">
{/* Bar Chart */}
<Card className="w-full p-4 flex flex-col items-center">
<div className="w-full h-64">
<ChartContainer
config={{
value: { label: metricLabel, color: "#2563eb" },
}}
>
<BarChart
data={comparisonData}
layout="vertical"
margin={{ left: 32, right: 16, top: 16, bottom: 16 }}
>
<XAxis type="number" hide />
<YAxis
dataKey="label"
type="category"
width={120}
tick={{ fill: "#1e40af", fontWeight: 600 }}
/>
<Tooltip />
{/* Legend removed */}
<Bar
dataKey="value"
name={metricLabel}
radius={[8, 8, 8, 8]}
>
{comparisonData.map((entry, idx) => (
<Cell
key={entry.label}
fill={chartColors[idx % chartColors.length]}
/>
))}
</Bar>
</BarChart>
</ChartContainer>
</div>
</Card>
{/* Table of comparison data */}
<Card className="w-full">
<Table>
<TableHeader>
<tr>
<th className="text-left px-4 py-2 text-blue-700">
{comparisonData.length > 0 ? "Name" : "Name"}
</th>
<th className="text-left px-4 py-2 text-blue-700">
{metricLabel}
</th>
</tr>
</TableHeader>
<TableBody>
{comparisonData.map((entry) => (
<tr key={entry.label}>
<td className="px-4 py-2">{entry.label}</td>
<td className="px-4 py-2">
{entry.value.toLocaleString()}
</td>
</tr>
))}
</TableBody>
</Table>
</Card>
</div>
</div>
<div className="absolute bottom-0 left-0 right-0 h-1 bg-blue-600" />
</div>
</>
);
};
export default MarketValidationSlideLayout;

View file

@ -0,0 +1,269 @@
import React from "react";
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
} from "recharts";
import * as z from "zod";
export const layoutId = "company-traction-slide";
export const layoutName = "Company Traction Slide";
export const layoutDescription =
"A slide layout designed to present company traction data, including growth statistics over the years, a chart visualization, and key metrics in a visually appealing format.";
// growthStats: list of dicts, each dict is { year: string, <metric1>: number, <metric2>: number, ... }
const tractionSchema = z.object({
companyName: z.string().default("presention"),
date: z.string().default("June 13, 2038"),
title: z.string().default("Company Traction"),
description: z
.string()
.min(3)
.max(200)
.default(
"Traction is a period where the company is feeling momentum during its development period. If traction momentum is not harnessed, sales figures can decline and the customer base can shrink. In general, companies will judge success by the amount of revenue and new customers they receive.",
),
// growthStats is a list of objects, each with a 'year' and any number of metric keys (all numbers)
growthStats: z
.array(
z
.object({
year: z.string(),
})
.catchall(z.number()),
)
.min(1)
.max(20)
.default([
{
year: "2020",
artificialIntelligence: 5,
internetOfThings: 10,
others: 8,
},
{
year: "2021",
artificialIntelligence: 10,
internetOfThings: 20,
others: 15,
},
{
year: "2022",
artificialIntelligence: 20,
internetOfThings: 30,
others: 22,
},
{
year: "2023",
artificialIntelligence: 28,
internetOfThings: 38,
others: 29,
},
{
year: "2024",
artificialIntelligence: 35,
internetOfThings: 45,
others: 34,
},
{
year: "2025",
artificialIntelligence: 45,
internetOfThings: 53,
others: 42,
},
{
year: "2026",
artificialIntelligence: 55,
internetOfThings: 65,
others: 52,
},
{
year: "2029",
artificialIntelligence: 55,
internetOfThings: 65,
others: 52,
},
]),
});
export const Schema = tractionSchema;
export type CompanyTractionData = z.infer<typeof tractionSchema>;
interface Props {
data?: Partial<CompanyTractionData>;
}
// Helper: assign colors to series
const defaultColors = [
"#1E4CD9",
"#3b82f6",
"#f59e0b",
"#10b981",
"#ef4444",
"#a21caf",
"#6366f1",
"#f43f5e",
"#fbbf24",
"#14b8a6",
];
function getSeriesKeys(
growthStats: Array<Record<string, string | number>>,
): string[] {
if (!growthStats.length) return [];
// Exclude 'year' or any non-numeric keys
const first = growthStats[0];
return Object.keys(first).filter(
(key) => key !== "year" && typeof first[key] === "number",
);
}
// Compute stats for right column, generic for all series
function computeStats(
growthStats: Array<Record<string, string | number>>,
seriesKeys: string[],
) {
if (!growthStats.length) return [];
const first = growthStats[0];
const last = growthStats[growthStats.length - 1];
return seriesKeys.map((key) => {
const start = typeof first[key] === "number" ? (first[key] as number) : 0;
const end = typeof last[key] === "number" ? (last[key] as number) : 0;
const growth = start === 0 ? 0 : ((end - start) / Math.abs(start)) * 100;
return {
label: key
.replace(/([A-Z])/g, " $1")
.replace(/^./, (str) => str.toUpperCase()),
value: `${growth >= 0 ? "+" : ""}${Math.round(growth)}% growth`,
description: `${key
.replace(/([A-Z])/g, " $1")
.replace(/^./, (str) => str.toUpperCase())} growth over the period.`,
};
});
}
const CompanyTractionSlideLayout: React.FC<Props> = ({ data }) => {
const growthStats = data?.growthStats || [];
// Dynamically determine series keys
const seriesKeys = getSeriesKeys(growthStats);
// Prepare stats for the right column, generic for all series
const stats = computeStats(growthStats, seriesKeys);
return (
<>
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<div
className="w-full max-w-[1280px] max-h-[720px] aspect-video bg-white mx-auto rounded shadow-lg overflow-hidden relative z-20"
style={{
fontFamily: "Montserrat, sans-serif",
}}
>
{/* Header */}
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
<span>{data?.companyName}</span>
<span>{data?.date}</span>
</div>
{/* Main Content */}
<div className="px-16 py-16 flex h-full gap-8">
{/* Left Column - Chart with Title Below */}
<div className="flex-1 pr-12 flex flex-col justify-center">
<h1 className="text-6xl font-bold text-blue-600 mb-4 leading-tight text-left">
{data?.title}
</h1>
<div className="bg-white rounded-lg shadow p-4 mb-8">
<div className="w-full h-64">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={growthStats}>
<CartesianGrid stroke="#e5eafe" />
<XAxis
dataKey="year"
stroke="#1E4CD9"
tick={{ fill: "#1E4CD9", fontWeight: 600 }}
/>
<YAxis
stroke="#1E4CD9"
tick={{ fill: "#1E4CD9", fontWeight: 600 }}
/>
<Tooltip
contentStyle={{
backgroundColor: "#1E4CD9",
border: "none",
color: "#fff",
}}
labelStyle={{ color: "#fff" }}
itemStyle={{ color: "#fff" }}
/>
<Legend
wrapperStyle={{ color: "#1E4CD9", fontWeight: 600 }}
iconType="circle"
/>
{seriesKeys.map((key, idx) => (
<Line
key={key}
type="monotone"
dataKey={key}
stroke={defaultColors[idx % defaultColors.length]}
strokeWidth={3}
name={key
.replace(/([A-Z])/g, " $1")
.replace(/^./, (str) => str.toUpperCase())}
dot={{
r: 4,
fill: defaultColors[idx % defaultColors.length],
}}
activeDot={{
r: 6,
fill: defaultColors[idx % defaultColors.length],
}}
/>
))}
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
{/* Right Column - Description and Stats */}
<div className="flex flex-col items-start justify-center w-[52%] gap-8">
<p className="text-blue-600 text-base leading-relaxed font-normal mb-6 max-w-xl text-left">
{data?.description ||
"Traction is a period where the company is feeling momentum during its development period. If traction momentum is not harnessed, sales figures can decline and the customer base can shrink. In general, companies will judge success by the amount of revenue and new customers they receive."}
</p>
<div className="flex flex-row w-full gap-6">
{stats.map((stat, index) => (
<div
key={index}
className="flex-1 bg-[#f5f8ff] rounded-lg shadow-sm px-5 py-4 flex flex-col items-start"
>
<div className="bg-[#1E4CD9] text-white text-xs font-semibold px-3 py-1 rounded-sm mb-2">
{stat.label}
</div>
<div className="text-2xl font-bold text-[#1E4CD9] mb-1">
{stat.value}
</div>
<p className="text-sm text-gray-700 leading-snug">
{stat.description}
</p>
</div>
))}
</div>
</div>
</div>
<div className="absolute bottom-0 left-0 right-0 h-1 bg-blue-600" />
</div>
</>
);
};
export default CompanyTractionSlideLayout;

View file

@ -0,0 +1,140 @@
import React from "react";
import * as z from "zod";
import {
BarChart,
Bar,
CartesianGrid,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
Legend,
} from "recharts";
export const layoutId = "business-model-slide";
export const layoutName = "Business Model Slide";
export const layoutDescription =
"A business model presentation slide displaying CAC metrics and monetization strategy.";
const businessModelSchema = z.object({
companyName: z.string().default("presenton"),
date: z.string().default("June 13, 2038"),
title: z.string().min(3).max(20).default("Business Model"),
description: z
.string()
.default(
"Describe how you monetize, who your customers are, your distribution channels or fee structure. The goal is to give an idea of how this business will sustain your product or service and explain how your company will make money and achieve its goals. This can be shown with graphs, statistics, or charts. Use the Lifetime Value (LTV) and Customer Acquisition Cost (CAC) metrics to provide a clearer picture.",
),
cacChart: z
.array(
z.object({
label: z.string().min(3).max(20),
percentage: z.number().min(0).max(100),
}),
)
.default([
{ label: "Internet of Things", percentage: 70 },
{ label: "Artificial Intelligence", percentage: 60 },
]),
});
export const Schema = businessModelSchema;
export type BusinessModelData = z.infer<typeof businessModelSchema>;
interface Props {
data?: Partial<BusinessModelData>;
}
const BusinessModelSlide: React.FC<Props> = ({ data }) => {
const cacChartData =
data?.cacChart && Array.isArray(data.cacChart) && data.cacChart.length > 0
? data.cacChart
: [
{ label: "Internet of Things", percentage: 70 },
{ label: "Artificial Intelligence", percentage: 60 },
];
return (
<>
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<div
className="w-full max-w-[1280px] max-h-[720px] aspect-video bg-white mx-auto rounded shadow-lg overflow-hidden relative z-20"
style={{
fontFamily: "Montserrat, sans-serif",
}}
>
{/* Header */}
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
<span>{data?.companyName}</span>
<span>{data?.date}</span>
</div>
{/* Main Content */}
<div className="px-16 py-16 flex h-full gap-8">
{/* Left Column - Chart with Title Below */}
<div className="flex-1 pr-12 flex flex-col justify-center">
<h1 className="text-6xl font-bold text-blue-600 mb-4 leading-tight text-left">
{data?.title}
</h1>
<div className="bg-white rounded-lg shadow p-4 mb-8">
<div className="w-full h-64">
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={cacChartData}
margin={{ top: 20, right: 30, left: 10, bottom: 20 }}
barCategoryGap="30%"
>
<CartesianGrid stroke="#e5eafe" />
<XAxis
dataKey="label"
tick={{ fill: "#1E4CD9", fontWeight: 600 }}
/>
<YAxis
tick={{ fill: "#1E4CD9", fontWeight: 600 }}
domain={[0, 100]}
ticks={[0, 20, 40, 60, 80, 100]}
width={40}
/>
<Tooltip
contentStyle={{
backgroundColor: "#1E4CD9",
border: "none",
color: "#fff",
}}
labelStyle={{ color: "#fff" }}
itemStyle={{ color: "#fff" }}
/>
<Legend
wrapperStyle={{ color: "#1E4CD9", fontWeight: 600 }}
iconType="circle"
/>
<Bar
dataKey="percentage"
fill="#3b82f6"
name="CAC %"
maxBarSize={48}
radius={[8, 8, 0, 0]}
/>
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>
{/* Right Column - Description and Optional Image */}
<div className="flex flex-col items-start justify-center w-[52%] gap-8">
<p className="text-blue-600 text-base leading-relaxed font-normal mb-6 max-w-xl text-left">
{data?.description}
</p>
</div>
</div>
<div className="absolute bottom-0 left-0 right-0 h-1 bg-blue-600" />
</div>
</>
);
};
export default BusinessModelSlide;

View file

@ -0,0 +1,5 @@
{
"description": "Modern white and blue business pitch deck layouts with clean, professional design",
"ordered": false,
"isDefault": false
}

View file

@ -0,0 +1,199 @@
import React from "react";
import * as z from "zod";
import { ImageSchema } from "@/presentation-layouts/defaultSchemes";
export const layoutId = "modern-team-slide";
export const layoutName = "Modern Team Slide";
export const layoutDescription =
"A clean modern team slide showcasing team members with professional profiles and blue-white design.";
const teamMemberSchema = z.object({
name: z.string().min(2).max(50).meta({
description: "Team member's full name",
}),
position: z.string().min(2).max(50).meta({
description: "Job title or position",
}),
description: z.string().min(20).max(120).meta({
description: "Brief professional description of the team member",
}),
image: ImageSchema,
linkedIn: z.string().optional().meta({
description: "LinkedIn profile URL (optional)",
}),
});
const modernTeamSlideSchema = z.object({
title: z.string().min(3).max(20).default("Our Team").meta({
description: "Main title of the slide",
}),
subtitle: z.string().min(10).max(120).optional().meta({
description: "Optional subtitle describing the team",
}),
teamMembers: z
.array(teamMemberSchema)
.min(2)
.max(4)
.default([
{
name: "Sarah Johnson",
position: "CEO & Founder",
description:
"Strategic leader with 15+ years experience in technology and business development. Former VP at Fortune 500 company.",
image: {
__image_url__:
"https://plus.unsplash.com/premium_photo-1661589856899-6dd0871f9db6?fm=jpg&q=60&w=3000&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NXx8YnVzaW5lc3N3b21lbnxlbnwwfHwwfHx8MA%3D%3D",
__image_prompt__: "Professional businesswoman CEO headshot",
},
},
{
name: "Michael Chen",
position: "CTO",
description:
"Technology expert specializing in scalable architecture and AI solutions. PhD in Computer Science from MIT.",
image: {
__image_url__:
"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80",
__image_prompt__: "Professional businessman CTO headshot",
},
},
{
name: "Emily Rodriguez",
position: "VP of Sales",
description:
"Sales leader with proven track record of building high-performing teams and driving revenue growth in B2B markets.",
image: {
__image_url__:
"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80",
__image_prompt__: "Professional businesswoman VP headshot",
},
},
{
name: "David Kim",
position: "Head of Product",
description:
"Product strategist focused on user experience and market-driven solutions. Former product manager at leading tech companies.",
image: {
__image_url__:
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80",
__image_prompt__: "Professional businessman product manager headshot",
},
},
])
.meta({
description: "List of team members with their information",
}),
companyName: z.string().default("presenton").meta({
description: "Company name to display in the header",
}),
date: z.string().default("June 13, 2038").meta({
description: "Date to display in the header",
}),
});
export const Schema = modernTeamSlideSchema;
export type ModernTeamSlideData = z.infer<typeof modernTeamSlideSchema>;
interface ModernTeamSlideLayoutProps {
data?: Partial<ModernTeamSlideData>;
}
const ModernTeamSlideLayout: React.FC<ModernTeamSlideLayoutProps> = ({
data: slideData,
}) => {
return (
<>
{/* Import Montserrat Font */}
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<div
className="w-full max-w-[1280px] max-h-[720px] aspect-video bg-white mx-auto rounded shadow-lg overflow-hidden relative z-20"
style={{
fontFamily: "Montserrat, sans-serif",
}}
>
{/* Header */}
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-[#1E4CD9] text-sm font-semibold">
<span>{slideData?.companyName}</span>
<span>{slideData?.date}</span>
</div>
{/* Main Content */}
<div className="relative z-10 flex flex-col items-start justify-center h-full px-16 pt-24 pb-10">
{/* Title */}
<h1
className="text-7xl font-bold text-blue-600 mb-4 leading-tight text-left"
style={{ letterSpacing: "-0.03em" }}
>
{slideData?.title}
</h1>
{/* Subtitle */}
<p className="text-blue-600 text-lg leading-relaxed font-normal mb-12 max-w-lg text-left">
{slideData?.subtitle}
</p>
{/* Team Members Row */}
<div className="flex flex-row w-full justify-between items-start gap-6 mt-2">
{slideData?.teamMembers?.map((member, idx) => (
<div
key={idx}
className="flex flex-col items-center bg-[#f7f9fc] rounded-lg shadow-md px-6 pt-6 pb-4 w-1/4 min-w-[210px] max-w-[240px] mx-auto"
style={{ minHeight: 340 }}
>
{/* Photo */}
<div className="relative w-28 h-28 mb-4 rounded overflow-hidden bg-white border-2 border-blue-100 flex items-center justify-center">
<img
src={member.image.__image_url__ || ""}
alt={member.image.__image_prompt__ || member.name}
className="w-full h-full object-cover"
/>
</div>
{/* Name */}
<div className="text-lg font-bold text-blue-700 mb-1">
{member.name}
</div>
{/* Position Badge */}
<div className="bg-blue-600 text-white text-xs font-semibold px-3 py-1 rounded-sm mb-2 uppercase tracking-wide">
{member.position}
</div>
{/* Description */}
<div className="text-sm text-gray-700 text-center mb-2 min-h-[48px]">
{member.description}
</div>
{/* LinkedIn Link (if provided) */}
{member.linkedIn && (
<a
href={member.linkedIn}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center text-xs text-blue-600 hover:text-blue-800 transition-colors duration-200 mt-1"
>
<svg
className="w-4 h-4 mr-1"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M16.338 16.338H13.67V12.16c0-.995-.017-2.277-1.387-2.277-1.39 0-1.601 1.086-1.601 2.207v4.248H8.014v-8.59h2.559v1.174h.037c.356-.675 1.227-1.387 2.526-1.387 2.703 0 3.203 1.778 3.203 4.092v4.711zM5.005 6.575a1.548 1.548 0 11-.003-3.096 1.548 1.548 0 01.003 3.096zm-1.337 9.763H6.34v-8.59H3.667v8.59zM17.668 1H2.328C1.595 1 1 1.581 1 2.298v15.403C1 18.418 1.595 19 2.328 19h15.34c.734 0 1.332-.582 1.332-1.299V2.298C19 1.581 18.402 1 17.668 1z"
clipRule="evenodd"
/>
</svg>
LinkedIn
</a>
)}
</div>
))}
</div>
</div>
{/* Bottom Divider */}
<div className="absolute bottom-0 left-0 right-0 h-1 bg-blue-600" />
</div>
</>
);
};
export default ModernTeamSlideLayout;

View file

@ -0,0 +1,140 @@
import React from "react";
import * as z from "zod";
export const layoutId = "thank-you-slide";
export const layoutName = "Thank You Slide";
export const layoutDescription =
"A simple, plain thank you slide for closing presentations.";
const thankYouSlideSchema = z.object({
title: z.string().min(3).max(40).default("Thank You!").meta({
description: "Main thank you message",
}),
subtitle: z.string().min(0).max(100).default("").meta({
description: "Optional subtitle or closing remark",
}),
companyName: z.string().min(2).max(30).default("Rimberio").meta({
description: "Company name displayed in header",
}),
date: z.string().min(5).max(30).default("June 13, 2038").meta({
description: "Date displayed in header",
}),
address: z
.string()
.min(5)
.max(100)
.default("123 Anywhere St., Any City, ST 12345")
.meta({
description: "Company address for contact section",
}),
phone: z.string().min(5).max(30).default("+123-456-7890").meta({
description: "Company phone number for contact section",
}),
website: z.string().min(5).max(100).default("www.reallygreatsite.com").meta({
description: "Company website for contact section",
}),
email: z.string().default("info@reallygreatsite.com").meta({
description: "Company email address for contact section",
}),
});
export const Schema = thankYouSlideSchema;
export type ThankYouSlideData = z.infer<typeof thankYouSlideSchema>;
interface ThankYouSlideLayoutProps {
data?: Partial<ThankYouSlideData>;
}
const ThankYouSlideLayout: React.FC<ThankYouSlideLayoutProps> = ({ data }) => {
return (
<>
{/* Import fonts */}
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<div
className="w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video relative z-20 mx-auto overflow-hidden flex flex-col"
style={{
fontFamily: "Montserrat, sans-serif",
backgroundColor: "#1E4CD9", // blue background
}}
>
{/* Header */}
<div className="absolute top-8 left-10 right-10 flex justify-between items-center text-white text-sm font-semibold">
<span>{data?.companyName || "Rimberio"}</span>
<span>{data?.date || "June 13, 2038"}</span>
</div>
{/* Main content area */}
<div className="flex flex-1 flex-col px-16 pb-16 justify-between">
{/* Thank You and description */}
<div className="flex flex-col items-start w-full pt-16">
<h1
className="font-bold text-8xl text-white mb-6 mt-8 text-left w-full"
>
{data?.title || "Thank You!"}
</h1>
{data?.subtitle && (
<div className="text-xl text-blue-100 font-normal text-left w-full mb-2">
{data.subtitle}
</div>
)}
</div>
{/* Footer area */}
<div className="flex w-full items-end justify-between mt-auto">
{/* Left: We are ready to assist you */}
<div className="flex flex-col">
<div
className="font-bold text-white text-left mb-3"
style={{
fontSize: "2rem",
marginBottom: 0,
}}
>
We are ready to assist you
</div>
</div>
{/* Right: Contacts */}
<div className="flex flex-col items-end text-white text-sm space-y-2 min-w-[220px]">
<div className="flex items-center gap-2">
<span role="img" aria-label="address">
📍
</span>
<span>{data?.address}</span>
</div>
<div className="flex items-center gap-2">
<span role="img" aria-label="phone">
📞
</span>
<span>{data?.phone}</span>
</div>
<div className="flex items-center gap-2">
<span role="img" aria-label="website">
🌐
</span>
<span>{data?.website}</span>
</div>
<div className="flex items-center gap-2">
<span role="img" aria-label="email">
</span>
<span>{data?.email}</span>
</div>
</div>
</div>
</div>
{/* Bottom border line */}
<div className="absolute bottom-0 left-0 w-full h-1 bg-white"></div>
</div>
</>
);
};
export default ThankYouSlideLayout;

View file

@ -1,76 +1,83 @@
import * as z from "zod";
// Note:
// If you want to use Image and Icon Must Use these Schemas.
// Image and icons are only media support for PDF and PPTX.
import { ImageSchema, IconSchema } from "../defaultSchemes";
// Schema definition
export const Schema = z.object({
// Note:
// Schema fields
// Each fields must have a default value (this is important for Layout-preview)
// Each fields must have a meta description
// Each fields must have a min and max length, length must support the layout design.
// Each Array fields must have a min and max length
mainTitle: z.string()
.min(5)
.max(15)
sectionTitle: z.string()
.min(3)
.max(25)
.default("ABOUT US")
.meta({
description: "Main title for the about us section",
description: "Main section heading - can be used for any organizational introduction",
}),
subtitle: z.string()
.min(10)
.max(20)
sectionSubtitle: z.string()
.min(5)
.max(40)
.default("GET TO KNOW US BETTER")
.meta({
description: "Subtitle describing the section",
description: "Supporting subtitle that invites audience engagement and builds connection",
}),
companyDescription: z.string()
.min(100)
.max(200)
.default("At our company, we believe in the transformative power of compelling storytelling, data-driven strategies, and cutting-edge creativity. Our mission is simple: to empower businesses with strategic marketing solutions that not only elevate brand visibility but also drive tangible growth and success.")
.meta({
description: "Main company description paragraph",
}),
additionalText: z.string()
organizationDescription: z.string()
.min(50)
.max(200)
.default("What sets us apart is not just our expertise but our commitment to understanding the unique DNA of each client.")
.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: "Additional descriptive text",
description: "Primary description of the organization's mission, values, and approach",
}),
businessImage: ImageSchema.default({
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__: "Business person analyzing charts and graphs with pen in hand"
__image_prompt__: "Professional business team analyzing data and working collaboratively"
}).meta({
description: "Business analytics image showing data analysis",
description: "Primary visual that represents the organization's work or environment",
}),
showDecoCircle: z.boolean()
showVisualAccents: z.boolean()
.default(true)
.meta({
description: "Show decorative circle element",
description: "Whether to display decorative visual accent elements",
}),
showTealAccent: z.boolean()
showColorBlocks: z.boolean()
.default(true)
.meta({
description: "Show teal accent areas",
description: "Whether to show colored background blocks for visual hierarchy",
}),
showBeigSquare: z.boolean()
showAccentSquare: z.boolean()
.default(true)
.meta({
description: "Show beige square decoration",
description: "Whether to display the accent square decoration element",
}),
})
// Type inference
type SchemaType = z.infer<typeof Schema>;
// Component definition
// Component definitionz
const AboutUsSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { mainTitle, subtitle, companyDescription, additionalText, businessImage, showDecoCircle, showTealAccent, showBeigSquare } = data;
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">
@ -80,15 +87,15 @@ const AboutUsSlide = ({ data }: { data: Partial<SchemaType> }) => {
<div className="w-3/5 relative bg-white px-16 py-12 flex flex-col justify-center">
{/* Title Section */}
<div className="mb-0">
{mainTitle && (
{sectionTitle && (
<h1 className="text-3xl lg:text-4xl font-black text-teal-700 leading-tight mb-4">
{mainTitle}
{sectionTitle}
</h1>
)}
{subtitle && (
{sectionSubtitle && (
<p className="text-base font-semibold text-gray-800 tracking-wide mb-6">
{subtitle}
{sectionSubtitle}
</p>
)}
@ -97,18 +104,18 @@ const AboutUsSlide = ({ data }: { data: Partial<SchemaType> }) => {
</div>
{/* Description Text */}
{companyDescription && (
{organizationDescription && (
<p className="text-base leading-relaxed text-gray-700 max-w-xl">
{companyDescription}
{organizationDescription}
</p>
)}
{/* Additional Text */}
{additionalText && (
{additionalContext && (
<div>
<p className="text-base leading-relaxed text-gray-700 max-w-xl">
{additionalText}
{additionalContext}
</p>
</div>
)}
@ -117,28 +124,28 @@ const AboutUsSlide = ({ data }: { data: Partial<SchemaType> }) => {
{/* Right Side - Image and Decorative Elements */}
<div className="w-2/5 relative">
{/* Yellow Square - Top Right */}
{showBeigSquare && (
<div className="absolute bottom-0 right-0 w-24 h-24 bg-yellow-300 z-20"></div>
{showAccentSquare && (
<div className="absolute bottom-0 right-0 w-24 h-24 bg-yellow-300 z-10"></div>
)}
{/* Decorative Circle - On Yellow Square */}
{showDecoCircle && (
<div className="absolute top-6 right-6 w-6 h-6 border-2 border-teal-600 rounded-full z-30"></div>
{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 */}
{businessImage?.__image_url__ && (
{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={businessImage.__image_url__}
alt={businessImage.__image_prompt__}
src={featuredImage.__image_url__}
alt={featuredImage.__image_prompt__}
className="w-full h-full object-cover"
/>
</div>
)}
{/* Teal Accent Areas */}
{showTealAccent && (
{showColorBlocks && (
<>
{/* Vertical Teal Strip - Center */}
<div className="absolute top-0 right-0 bottom-16 h-full w-[300px] bg-teal-600 z-10"></div>

View file

@ -1,67 +1,66 @@
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, ResponsiveContainer } from "recharts";
import { BarChart, Bar, XAxis, YAxis, CartesianGrid } from "recharts";
// Schema definition
export const Schema = z.object({
mainTitle: z.string()
.min(5)
.max(15)
sectionTitle: z.string()
.min(3)
.max(30)
.default("BUSINESS MODEL")
.meta({
description: "Main title for the business model section",
description: "Main section heading - adapt to presentation topic (e.g., 'Revenue Strategy', 'Funding Model', 'Implementation Plan', 'Solution Framework')",
}),
subtitle: z.string()
sectionSubtitle: z.string()
.min(10)
.max(25)
.default("OUR BUSINESS MODEL")
.max(60)
.default("SUSTAINABLE REVENUE AND VALUE CREATION")
.meta({
description: "Subtitle describing the business model",
description: "Supporting subtitle that describes the approach - adapt to topic (e.g., 'Carbon Reduction Strategy', 'Healthcare Delivery Model', 'Educational Framework')",
}),
description: z.string()
modelDescription: z.string()
.min(50)
.max(300)
.default("Our business model thrives on delivering value through strategic innovation, client-centric solutions, and a dynamic blend of creativity and analytics.")
.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: "Description of the business model",
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.",
}),
businessImage: ImageSchema.default({
__image_url__: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?ixlib=rb-4.0.3&auto=format&fit=crop&w=1200&q=80",
__image_prompt__: "Business professionals analyzing charts and data on tablet and computer"
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: "Business analytics header image",
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({
name: z.string(),
series1: z.number(),
series2: z.number(),
series3: z.number()
})).min(3).max(6).default([
{ name: "Item 1", series1: 5, series2: 4, series3: 3 },
{ name: "Item 2", series1: 8, series2: 7, series3: 4 },
{ name: "Item 3", series1: 15, series2: 10, series3: 5 },
{ name: "Item 4", series1: 18, series2: 15, series3: 8 },
{ name: "Item 5", series1: 22, series2: 20, series3: 8 }
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: "Chart data for business metrics",
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.",
}),
showYellowAccent: z.boolean()
showChart: z.boolean()
.default(true)
.meta({
description: "Show yellow accent decoration",
description: "Whether to display the data visualization - typically keep true for data-driven presentations",
}),
showTealAccents: z.boolean()
showVisualAccents: z.boolean()
.default(true)
.meta({
description: "Show teal accent decorations",
description: "Whether to display decorative visual accent elements",
}),
})
@ -87,134 +86,100 @@ type SchemaType = z.infer<typeof Schema>;
// Component definition
const BusinessModelSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { mainTitle, subtitle, description, businessImage, chartData, showYellowAccent, showTealAccents } = data;
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 with Teal Decorative Elements */}
<div className="relative h-1/3">
{/* Business Image */}
{businessImage?.__image_url__ && (
<div className="absolute inset-0">
<img
src={businessImage.__image_url__}
alt={businessImage.__image_prompt__}
className="w-full h-full object-cover"
/>
</div>
)}
{/* 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>
)}
{/* Teal Decorative Accents */}
{showTealAccents && (
<>
{/* Top left teal block */}
<div className="absolute top-0 left-0 w-20 h-full bg-teal-600 z-10"></div>
{/* Top right teal block */}
<div className="absolute top-0 right-0 w-32 h-full bg-teal-600 z-10"></div>
</>
)}
{/* Yellow Accent */}
{showYellowAccent && (
<div className="absolute top-0 right-32 w-24 h-16 bg-yellow-300 z-15"></div>
)}
</div>
{/* Content Section */}
<div className="h-2/3 flex">
{/* Left Side - Title and Description */}
<div className="w-1/2 px-16 py-8 flex flex-col justify-center bg-gray-50">
{/* 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">
{mainTitle && (
<h1 className="text-4xl lg:text-5xl font-black text-teal-700 leading-tight mb-4">
{mainTitle}
{sectionTitle && (
<h1 className="text-3xl lg:text-4xl font-black text-teal-700 leading-tight mb-4">
{sectionTitle}
</h1>
)}
{subtitle && (
{sectionSubtitle && (
<p className="text-base font-semibold text-gray-800 tracking-wide mb-6">
{subtitle}
{sectionSubtitle}
</p>
)}
</div>
{/* Description */}
{description && (
{/* Model Description */}
{modelDescription && (
<div>
<p className="text-base leading-relaxed text-gray-700">
{description}
{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 bg-white">
{/* 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-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-yellow-200 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-green-400 rounded-full"></div>
<span className="text-sm text-gray-600">Series 3</span>
</div>
</div>
<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>
{/* Chart Container */}
{chartData && chartData.length > 0 && (
<div className="flex-1">
<ChartContainer config={chartConfig} className="h-full w-full">
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={chartData}
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={20}
/>
<Bar
dataKey="series2"
fill="#E8F4B8"
radius={[2, 2, 0, 0]}
barSize={20}
/>
<Bar
dataKey="series3"
fill="#A8C97F"
radius={[2, 2, 0, 0]}
barSize={20}
/>
</BarChart>
</ResponsiveContainer>
</ChartContainer>
</div>
)}
</div>
</div>
{/* Bottom accent strip */}
<div className="absolute bottom-0 left-0 right-0 h-2 bg-teal-600"></div>
</div>
);
};

View file

@ -5,57 +5,56 @@ import { ImageSchema, IconSchema } from "../defaultSchemes";
// Schema definition
export const Schema = z.object({
mainTitle: z.string()
sectionTitle: z.string()
.min(3)
.max(30)
.default("WELCOME!")
.meta({
description: "Main greeting or welcome message - can be 'Hello!', 'Welcome!', 'Greetings!', or similar",
}),
sectionSubtitle: z.string()
.min(10)
.max(60)
.default("WE'RE EXCITED TO SHARE OUR STORY")
.meta({
description: "Supporting message that sets the tone and builds connection with the audience",
}),
welcomeMessage: z.string()
.min(30)
.max(200)
.default("Thank you for joining us today. We're thrilled to have this opportunity to connect with you and share our journey, insights, and vision for the future.")
.meta({
description: "Main welcome or introductory message that engages the audience personally",
}),
callToActionText: z.string()
.min(5)
.max(15)
.default("HELLO FRIENDS!")
.max(25)
.default("Let's Get Started")
.meta({
description: "Main greeting title",
description: "Action button text that encourages audience engagement or progression",
}),
subtitle: z.string()
.min(5)
.max(20)
.default("GREETING FROM US")
.meta({
description: "Subtitle for the greeting",
}),
welcomeText: z.string()
.min(50)
.max(300)
.default("Ladies and gentlemen, a warm welcome to our business pitch deck presentation. Your time and attention are greatly appreciated. Today, we're excited to share our vision, accomplishments, and the exciting roadmap ahead. Let's embark on this journey together, and thank you for considering an investment in our innovative venture.")
.meta({
description: "Main welcome message text",
}),
officeImage: ImageSchema.default({
__image_url__: "https://images.unsplash.com/photo-1497366216548-37526070297c?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
__image_prompt__: "Modern office workspace with desk, computer, and large window view"
speakerImage: ImageSchema.default({
__image_url__: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
__image_prompt__: "Professional presenter or team representative in business setting"
}).meta({
description: "Office workspace image in circular frame",
description: "Image of the presenter, team representative, or welcoming figure",
}),
buttonText: z.string()
.min(3)
.max(20)
.default("Thank you")
.meta({
description: "Text for the call-to-action button",
}),
showSpeechBubble: z.boolean()
showDecorations: z.boolean()
.default(true)
.meta({
description: "Show decorative speech bubble",
description: "Whether to display decorative visual elements like underlines and accents",
}),
showDecoCircle: z.boolean()
showCallToAction: z.boolean()
.default(true)
.meta({
description: "Show decorative circle element",
description: "Whether to show the call-to-action button",
}),
})
// Type inference
@ -64,7 +63,7 @@ type SchemaType = z.infer<typeof Schema>;
// Component definition
const HelloFriendsSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { mainTitle, subtitle, welcomeText, officeImage, buttonText, showSpeechBubble, showDecoCircle } = data;
const { sectionTitle, sectionSubtitle, welcomeMessage, callToActionText, speakerImage, showDecorations, showCallToAction } = data;
return (
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
@ -73,26 +72,7 @@ const HelloFriendsSlide = ({ data }: { data: Partial<SchemaType> }) => {
{/* Left Side - Teal Background */}
<div className="w-1/3 relative bg-teal-600">
{/* Speech Bubble */}
{showSpeechBubble && (
<div className="absolute top-16 left-16 z-20">
<div className="w-16 h-10 bg-yellow-200 rounded-2xl relative">
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex space-x-1">
<div className="w-2 h-2 bg-teal-600 rounded-full"></div>
<div className="w-2 h-2 bg-teal-600 rounded-full"></div>
<div className="w-2 h-2 bg-teal-600 rounded-full"></div>
</div>
{/* Speech bubble tail */}
<div className="absolute bottom-0 left-6 transform translate-y-full">
<div className="w-0 h-0 border-l-4 border-r-4 border-t-8 border-l-transparent border-r-transparent border-t-yellow-200"></div>
</div>
</div>
</div>
)}
{/* Decorative Circle */}
{showDecoCircle && (
<div className="absolute bottom-16 left-16 w-8 h-8 border-4 border-white rounded-full z-20"></div>
)}
</div>
{/* Right Side - White Background */}
@ -101,36 +81,38 @@ const HelloFriendsSlide = ({ data }: { data: Partial<SchemaType> }) => {
<div className="pl-32 pr-16 py-12 h-full flex flex-col justify-center">
{/* Title Section */}
<div className="mb-8">
{mainTitle && (
{sectionTitle && (
<h1 className="text-4xl lg:text-5xl font-black text-teal-700 leading-tight mb-4">
{mainTitle}
{sectionTitle}
</h1>
)}
{subtitle && (
{sectionSubtitle && (
<p className="text-base font-semibold text-gray-800 tracking-wide mb-6">
{subtitle}
{sectionSubtitle}
</p>
)}
{/* Decorative underline */}
<div className="w-32 h-1 bg-yellow-300 mb-8"></div>
{showDecorations && (
<div className="w-32 h-1 bg-yellow-300 mb-8"></div>
)}
</div>
{/* Welcome Text */}
{welcomeText && (
{welcomeMessage && (
<div className="mb-8">
<p className="text-base leading-relaxed text-gray-700">
{welcomeText}
{welcomeMessage}
</p>
</div>
)}
{/* Thank You Button */}
{buttonText && (
{showCallToAction && (
<div>
<button className="bg-teal-600 hover:bg-teal-700 text-white font-semibold px-8 py-3 rounded-full transition-colors duration-200">
{buttonText}
{callToActionText}
</button>
</div>
)}
@ -139,12 +121,12 @@ const HelloFriendsSlide = ({ data }: { data: Partial<SchemaType> }) => {
</div>
{/* Overlapping Circular Office Image */}
{officeImage?.__image_url__ && (
<div className="absolute top-1/2 left-72 transform -translate-x-1/2 -translate-y-1/2 z-30">
{speakerImage?.__image_url__ && (
<div className="absolute top-1/2 left-72 transform -translate-x-1/2 -translate-y-1/2 ">
<div className="w-96 h-96 rounded-full overflow-hidden bg-white p-2 shadow-2xl">
<img
src={officeImage.__image_url__}
alt={officeImage.__image_prompt__}
src={speakerImage.__image_url__}
alt={speakerImage.__image_prompt__}
className="w-full h-full object-cover rounded-full"
/>
</div>

View file

@ -5,65 +5,63 @@ import { ImageSchema, IconSchema } from "../defaultSchemes";
// Schema definition
export const Schema = z.object({
mainTitle: z.string()
.min(5)
.max(15)
.default("MARKET SIZE")
sectionTitle: z.string()
.min(3)
.max(30)
.default("MARKET ANALYSIS")
.meta({
description: "Main title for the market size section",
description: "Main section heading - can be 'Market Size', 'Market Opportunity', 'Industry Overview', or similar",
}),
subtitle: z.string()
sectionSubtitle: z.string()
.min(10)
.max(25)
.default("OUR CLIENTS COME FROM EVERYWHERE")
.max(60)
.default("UNDERSTANDING THE OPPORTUNITY LANDSCAPE")
.meta({
description: "Subtitle describing global reach",
description: "Supporting subtitle that frames the market discussion and opportunity scope",
}),
globalDescription: z.string()
.min(50)
.max(200)
.default("With a global perspective, our marketing agency has proudly served multinational clients, delivering tailored strategies that transcend borders and cultures, ensuring consistent brand success on a worldwide scale.")
.meta({
description: "Description of global market presence",
}),
worldMapImage: ImageSchema.default({
__image_url__: "https://images.unsplash.com/photo-1516245834210-c4c142787335?ixlib=rb-4.0.3&auto=format&fit=crop&w=1200&q=80",
__image_prompt__: "World map with location pins showing global business presence"
}).meta({
description: "World map image showing global reach",
}),
marketDefinitions: z.array(z.object({
acronym: z.string().min(2).max(10),
fullName: z.string().min(10).max(50),
description: z.string().min(50).max(300)
})).min(3).max(3).default([
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(4).default([
{
acronym: "TAM",
fullName: "Total Available Market (TAM)",
description: "The Total Available Market (TAM) represents the entire potential demand for our product or service, reflecting the vast landscape of opportunities awaiting exploration and market capture."
marketType: "Total Addressable Market (TAM)",
marketDescription: "The overall revenue opportunity available if we achieved 100% market share across all segments and geographies.",
marketValue: "$50B"
},
{
acronym: "SAM",
fullName: "Serviceable Available Market (SAM)",
description: "The Serviceable Available Market (SAM) represents the specific segment of the Total Available Market where our product or service can be realistically and effectively offered, defining the target audience for our strategic market approach."
marketType: "Serviceable Addressable Market (SAM)",
marketDescription: "The portion of TAM targeted by our products and services within our geographic reach.",
marketValue: "$15B"
},
{
acronym: "SOM",
fullName: "Serviceable Obtainable Market (SOM)",
description: "The Serviceable Obtainable Market (SOM) signifies the realistic and achievable portion of the Serviceable Available Market where our business aims to capture market share, emphasizing our practical and strategic approach to market penetration."
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: "Market size definitions for TAM, SAM, and SOM",
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: "Show yellow decorative underline",
description: "Whether to display the decorative yellow underline accent",
}),
showVisualAccents: z.boolean()
.default(true)
.meta({
description: "Whether to display decorative visual accent elements",
}),
})
@ -73,81 +71,84 @@ type SchemaType = z.infer<typeof Schema>;
// Component definition
const MarketSizeSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { mainTitle, subtitle, globalDescription, worldMapImage, marketDefinitions, showYellowUnderline } = data;
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 - Teal Background with Map */}
<div className="w-1/2 relative bg-teal-600 px-16 py-12 flex flex-col text-white">
{/* 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">
{mainTitle && (
<h1 className="text-5xl lg:text-6xl font-black leading-tight mb-4">
{mainTitle}
{sectionTitle && (
<h1 className="text-3xl lg:text-4xl font-black leading-tight mb-4">
{sectionTitle}
</h1>
)}
{subtitle && (
{sectionSubtitle && (
<p className="text-base font-semibold tracking-wide mb-4">
{subtitle}
{sectionSubtitle}
</p>
)}
{/* Yellow Decorative Underline */}
{showYellowUnderline && (
<div className="w-24 h-1 bg-yellow-300 "></div>
<div className="w-24 h-1 bg-yellow-300 mb-8"></div>
)}
</div>
{/* World Map Image */}
{worldMapImage?.__image_url__ && (
<div className="flex-1 flex items-center justify-center">
<div className="w-full max-w-md">
<img
src={worldMapImage.__image_url__}
alt={worldMapImage.__image_prompt__}
className="w-full h-auto object-contain opacity-90"
/>
</div>
</div>
)}
{/* Global Description */}
{globalDescription && (
<div>
<p className="text-base leading-relaxed">
{globalDescription}
</p>
</div>
)}
</div>
{/* Right Side - White Background with Market Definitions */}
<div className="w-1/2 relative bg-white px-16 py-12 flex flex-col justify-center">
{/* Market Definitions */}
{marketDefinitions && marketDefinitions.length >= 3 && (
<div className="space-y-8">
{marketDefinitions.slice(0, 3).map((definition, index) => (
<div key={index} className="mb-8">
{/* Header with rounded background */}
<div className="bg-teal-600 text-white px-6 py-3 rounded-full mb-4">
<h3 className="text-lg font-bold text-center">
{definition.fullName}
{/* 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>
{/* Description */}
<p className="text-base leading-relaxed text-gray-700 px-2">
{definition.description}
<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>
);
};

View file

@ -5,62 +5,39 @@ import { ImageSchema, IconSchema } from "../defaultSchemes";
// Schema definition
export const Schema = z.object({
mainTitle: z.string()
.min(5)
.max(10)
.default("OUR SERVICE")
sectionTitle: z.string()
.min(3)
.max(30)
.default("OUR SERVICES")
.meta({
description: "Main title for the service section",
description: "Main section heading - can be 'Our Services', 'What We Offer', 'Service Portfolio', or similar",
}),
services: z.array(z.object({
title: z.string().min(5).max(40),
description: z.string().min(30).max(100),
image: ImageSchema
})).min(3).max(3).default([
{
title: "Strategic Brand Development",
description: "Our agency specializes in strategic brand development, ensuring that your brand not only resonates with your target audience but also stands out in a crowded market.",
image: {
__image_url__: "https://images.unsplash.com/photo-1553877522-43269d4ea984?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
__image_prompt__: "Business team working on brand strategy with documents and tablet"
}
},
{
title: "Data-Driven Marketing",
description: "Our data-driven approach ensures that every campaign is backed by insights, maximizing ROI and driving tangible results.",
image: {
__image_url__: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
__image_prompt__: "Business analytics with charts, graphs and keyboard on desk"
}
},
{
title: "Creative Content Production",
description: "Content is king, and our agency excels in producing creative, engaging, and impactful content that resonates with your audience.",
image: {
__image_url__: "https://images.unsplash.com/photo-1542744094-3a31f272c490?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
__image_prompt__: "Creative professional working on laptop with design and content"
}
}
]).meta({
description: "Three main services with titles, descriptions and images",
}),
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("Company Name")
sectionSubtitle: z.string()
.min(10)
.max(60)
.default("COMPREHENSIVE SOLUTIONS TAILORED FOR SUCCESS")
.meta({
description: "Company name for branding",
description: "Supporting subtitle that describes the service approach or value proposition",
}),
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",
}),
})
@ -70,74 +47,60 @@ type SchemaType = z.infer<typeof Schema>;
// Component definition
const OurServiceSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { mainTitle, services, companyLogo, companyName } = data;
const { sectionTitle, sectionSubtitle, serviceHighlight, showVisualAccents, showColorBlocks } = data;
return (
<div className="aspect-video max-w-[1280px] w-full bg-gray-100 relative overflow-hidden">
{/* Header with Title and Company Branding */}
<div className="h-full flex flex-col">
{/* Top Section */}
<div className="flex ">
{/* Left - Title */}
<div className="w-2/3 px-16 py-12 bg-gray-100">
{mainTitle && (
<h1 className="text-4xl lg:text-5xl font-black text-teal-700 leading-tight mb-4">
{mainTitle}
</h1>
)}
</div>
<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>
)}
{/* Right - Teal Background with Company Branding */}
<div className="w-1/3 bg-teal-600 px-16 py-12 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>
{sectionSubtitle && (
<p className="text-base font-semibold text-gray-800 tracking-wide">
{sectionSubtitle}
</p>
)}
{/* 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>
{/* Bottom Section - Services Grid */}
<div className="flex-1 px-16 py-8 bg-white">
{services && services.length >= 3 && (
<div className="grid grid-cols-3 gap-8 h-full">
{services.slice(0, 3).map((service, index) => (
<div key={index} className="flex flex-col">
{/* Service Image */}
<div className="h-48 w-full mb-6">
<img
src={service.image.__image_url__}
alt={service.image.__image_prompt__}
className="w-full h-full object-cover rounded-lg"
/>
</div>
{/* Service Content */}
<div className="flex-1">
<h3 className="text-xl font-bold text-teal-700 mb-4 leading-tight">
{service.title}
</h3>
<p className="text-base leading-relaxed text-gray-700">
{service.description}
</p>
</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>
);
};

View file

@ -5,63 +5,63 @@ import { ImageSchema, IconSchema } from "../defaultSchemes";
// Schema definition
export const Schema = z.object({
mainTitle: z.string()
.min(5)
.max(20)
.default("PROBLEMS")
.meta({
description: "Main title for the problems section",
}),
subtitle: z.string()
.min(10)
sectionTitle: z.string()
.min(3)
.max(25)
.default("WE WILL SOLVE THE PROBLEMS")
.default("CHALLENGES")
.meta({
description: "Subtitle describing the section",
description: "Main section heading - can be 'Problems', 'Challenges', 'Issues', or similar",
}),
problems: z.array(z.object({
number: z.string().min(1).max(3),
title: z.string().min(5).max(40),
description: z.string().min(20).max(200)
})).min(2).max(3).default([
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(4).default([
{
number: "01",
title: "Lack of Brand Visibility",
description: "Many businesses struggle with gaining visibility in a saturated market. Our solution involves a comprehensive analysis of your brand, audience, and competitors, leading to the development of a strategic branding."
itemNumber: "01",
challengeTitle: "Inefficient Processes",
challengeDescription: "Current workflows and systems lack optimization, leading to wasted resources and reduced productivity across all operational areas."
},
{
number: "02",
title: "Ineffective Digital Presence",
description: "Weak online presence can hinder business growth. Our agency offers an integrated approach to digital marketing, covering SEO optimization, social media management, content marketing, and more."
itemNumber: "02",
challengeTitle: "Limited Scalability",
challengeDescription: "Existing infrastructure and methodologies cannot accommodate growth, creating bottlenecks that hinder expansion and progress."
},
{
number: "03",
title: "Lack of Targeted Lead Generation",
description: "Many businesses struggle with generating quality leads that convert into customers. Our solution involves a meticulous understanding of your target audience, allowing us to develop highly targeted lead generation campaigns."
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 problems with numbers, titles and descriptions",
description: "List of key challenges or problems with numbered identification and detailed descriptions",
}),
workspaceImage: ImageSchema.default({
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__: "Clean modern workspace with laptop, plant, and documents on desk"
__image_prompt__: "Professional workspace showing analysis and problem-solving activities"
}).meta({
description: "Workspace image showing business environment",
description: "Visual that supports the problem discussion - could show analysis, challenges, or work environment",
}),
showDecoCircle: z.boolean()
showVisualAccents: z.boolean()
.default(true)
.meta({
description: "Show decorative circle element",
description: "Whether to display decorative visual accent elements",
}),
showTealAccent: z.boolean()
showColorBlocks: z.boolean()
.default(true)
.meta({
description: "Show teal accent block",
description: "Whether to show colored accent blocks for visual hierarchy",
}),
})
@ -71,7 +71,7 @@ type SchemaType = z.infer<typeof Schema>;
// Component definition
const ProblemsSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { mainTitle, subtitle, problems, workspaceImage, showDecoCircle, showTealAccent } = data;
const { sectionTitle, sectionSubtitle, challengeItems, supportingVisual, showVisualAccents, showColorBlocks } = data;
return (
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
@ -81,38 +81,38 @@ const ProblemsSlide = ({ data }: { data: Partial<SchemaType> }) => {
<div className="w-3/5 relative bg-white px-16 py-12 flex flex-col justify-start">
{/* Title Section */}
<div className="mb-12">
{mainTitle && (
<h1 className="text-4xl lg:text-5xl font-black text-teal-700 leading-tight mb-4">
{mainTitle}
{sectionTitle && (
<h1 className="text-3xl lg:text-4xl font-black text-teal-700 leading-tight mb-4">
{sectionTitle}
</h1>
)}
{subtitle && (
{sectionSubtitle && (
<p className="text-base font-semibold text-gray-800 tracking-wide">
{subtitle}
{sectionSubtitle}
</p>
)}
</div>
{/* Problems List */}
{problems && problems.length > 0 && (
{/* Challenge Items List */}
{challengeItems && challengeItems.length > 0 && (
<div className="space-y-8">
{problems.map((problem, index) => (
{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">
{problem.number}
{item.itemNumber}
</span>
</div>
{/* Content */}
<div className="flex-1">
<h3 className="text-xl font-bold text-gray-900 mb-3">
{problem.title}
{item.challengeTitle}
</h3>
<p className="text-base leading-relaxed text-gray-700">
{problem.description}
{item.challengeDescription}
</p>
</div>
</div>
@ -124,23 +124,23 @@ const ProblemsSlide = ({ data }: { data: Partial<SchemaType> }) => {
{/* Right Side - Image and Decorative Elements */}
<div className="w-2/5 relative">
{/* Decorative Circle */}
{showDecoCircle && (
{showVisualAccents && (
<div className="absolute top-12 left-8 w-8 h-8 border-4 border-teal-600 rounded-full z-20"></div>
)}
{/* Workspace Image */}
{workspaceImage?.__image_url__ && (
<div className="absolute top-8 right-8 bottom-20 left-4 z-15 shadow-lg">
{/* Supporting Visual */}
{supportingVisual?.__image_url__ && (
<div className="absolute top-8 right-8 bottom-20 left-4 shadow-lg">
<img
src={workspaceImage.__image_url__}
alt={workspaceImage.__image_prompt__}
src={supportingVisual.__image_url__}
alt={supportingVisual.__image_prompt__}
className="w-full h-full object-cover rounded-lg"
/>
</div>
)}
{/* Teal Accent Block - Right Edge */}
{showTealAccent && (
{showColorBlocks && (
<div className="absolute top-0 right-0 bottom-0 w-16 bg-teal-600 z-10"></div>
)}
</div>

View file

@ -5,72 +5,76 @@ import { ImageSchema, IconSchema } from "../defaultSchemes";
// Schema definition
export const Schema = z.object({
mainTitle: z.string()
.min(5)
.max(15)
.default("SOLUTIONS")
.meta({
description: "Main title for the solutions section",
}),
subtitle: z.string()
.min(10)
sectionTitle: z.string()
.min(3)
.max(25)
.default("SOLUTIONS OF THE PROBLEMS")
.default("OUR SOLUTIONS")
.meta({
description: "Subtitle describing the section",
description: "Main section heading - can be 'Solutions', 'Our Approach', 'How We Help', or similar",
}),
solutions: z.array(z.object({
number: z.string().min(1).max(3),
title: z.string().min(5).max(40),
description: z.string().min(20).max(300)
})).min(2).max(3).default([
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(4).default([
{
number: "01",
title: "Lack of Brand Visibility",
description: "By defining your unique value proposition and creating a consistent brand identity, we ensure your business stands out and remains memorable in the minds of your target audience."
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."
},
{
number: "02",
title: "Ineffective Digital Presence",
description: "Through data-driven insights, we tailor strategies to maximize online visibility, engage your audience, and drive meaningful interactions, converting online engagements into tangible business outcomes."
itemNumber: "02",
solutionTitle: "Scalable Infrastructure",
solutionDescription: "Build robust, flexible systems and methodologies that can grow with your organization, eliminating bottlenecks and supporting expansion efforts."
},
{
number: "03",
title: "Lack of Targeted Lead Generation",
description: "By leveraging strategic content, paid advertising, and personalized engagement tactics, we ensure that your marketing efforts are focused on reaching and converting the right audience."
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 solutions with numbers, titles and descriptions",
description: "List of key solutions or approaches with numbered identification and detailed descriptions",
}),
workspaceImages: ImageSchema.default({
__image_url__: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
__image_prompt__: "Business person working on laptop with charts and analytics"
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: "Two workspace images for left side",
description: "Primary visual representing teamwork, strategy, or solution implementation",
}),
companyLogo: ImageSchema.default({
__image_url__: "https://via.placeholder.com/40x40/1D9A8A/FFFFFF?text=C",
__image_prompt__: "Clean modern company logo icon"
brandingVisual: ImageSchema.default({
__image_url__: "https://via.placeholder.com/150x80/22C55E/FFFFFF?text=LOGO",
__image_prompt__: "Organization logo or brand mark"
}).meta({
description: "Company logo icon",
description: "Logo or branding element to maintain visual identity",
}),
companyName: z.string()
.min(2)
.max(25)
.default("Company Name")
.meta({
description: "Company name for branding",
}),
showYellowUnderline: z.boolean()
.default(true)
.meta({
description: "Show yellow decorative underline",
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",
}),
})
@ -80,25 +84,50 @@ type SchemaType = z.infer<typeof Schema>;
// Component definition
const SolutionsSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { mainTitle, subtitle, solutions, workspaceImages, companyLogo, companyName, showYellowUnderline } = data;
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-1/2 relative bg-gray-100 px-16 py-12 flex flex-col">
<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">
{mainTitle && (
<h1 className="text-4xl lg:text-5xl font-black text-teal-700 leading-tight mb-4">
{mainTitle}
{sectionTitle && (
<h1 className="text-3xl lg:text-4xl font-black text-white leading-tight mb-4">
{sectionTitle}
</h1>
)}
{subtitle && (
<p className="text-base font-semibold text-gray-800 tracking-wide mb-4">
{subtitle}
{sectionSubtitle && (
<p className="text-base font-semibold text-gray-100 tracking-wide mb-4">
{sectionSubtitle}
</p>
)}
@ -108,70 +137,52 @@ const SolutionsSlide = ({ data }: { data: Partial<SchemaType> }) => {
)}
</div>
{/* Images */}
{workspaceImages && (
<div className="flex-1 space-y-6">
<div className="h-full w-full">
<img
src={workspaceImages.__image_url__}
alt={workspaceImages.__image_prompt__}
className="w-full h-full object-cover rounded-lg shadow-md"
/>
</div>
</div>
)}
{/* Company Branding */}
<div className="mt-8 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-teal-700">
{companyName}
</span>
)}
</div>
</div>
{/* Right Side - Teal Background with Solutions */}
<div className="w-1/2 relative bg-teal-600 px-16 py-12 flex flex-col justify-center">
{/* Solutions List */}
{solutions && solutions.length > 0 && (
{/* Solution Items List */}
{solutionItems && solutionItems.length > 0 && (
<div className="space-y-8">
{solutions.map((solution, index) => (
<div key={index} className="flex items-start space-x-4">
{solutionItems.map((item, index) => (
<div key={index} className="flex items-start space-x-6">
{/* Number Circle */}
<div className="w-12 h-12 bg-yellow-200 rounded-full flex items-center justify-center flex-shrink-0">
<span className="text-teal-700 font-bold text-lg">
{solution.number}
<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">
{solution.title}
{item.solutionTitle}
</h3>
<p className="text-base leading-relaxed text-white">
{solution.description}
<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>
);
};

View file

@ -4,31 +4,38 @@ import { ImageSchema, IconSchema } from "../defaultSchemes";
// Schema definition
export const Schema = z.object({
mainTitle: z.string()
.min(5)
.max(15)
.default("STATISTIC")
sectionTitle: z.string()
.min(3)
.max(20)
.default("CLIENT SATISFACTION")
.meta({
description: "Main title for the statistic section",
description: "Main section heading - adapt to presentation topic (e.g., 'Climate Progress', 'Treatment Success', 'Learning Achievement', 'Project Completion')",
}),
subtitle: z.string()
sectionSubtitle: z.string()
.min(10)
.max(25)
.default("CLIENT'S SATISFACTION")
.max(35)
.default("MEASURING OUR IMPACT AND SUCCESS")
.meta({
description: "Subtitle describing the statistic focus",
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(100)
.max(400)
.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: "Description of client satisfaction approach",
description: "Name of the organization or entity being measured",
}),
circularMetric: z.object({
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)
@ -37,7 +44,7 @@ export const Schema = z.object({
label: "CLIENT'S REPEAT ORDER",
percentage: "90%"
}).meta({
description: "Main circular chart metric",
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({
@ -56,7 +63,7 @@ export const Schema = z.object({
backgroundColor: "beige"
}
]).meta({
description: "Two statistic blocks with percentages and descriptions",
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({
@ -69,7 +76,7 @@ export const Schema = z.object({
companyName: z.string()
.min(2)
.max(25)
.default("Company Name")
.default("Deskpro")
.meta({
description: "Company name for branding",
}),
@ -81,7 +88,7 @@ type SchemaType = z.infer<typeof Schema>;
// Component definition
const StatisticCircularSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { mainTitle, subtitle, description, circularMetric, statisticBlocks, companyLogo, companyName } = data;
const { sectionTitle, sectionSubtitle, description, brandLogo, satisfactionRate, statisticBlocks, companyLogo, companyName } = data;
const getBackgroundClass = (bg: string) => {
switch (bg) {
@ -95,7 +102,7 @@ const StatisticCircularSlide = ({ data }: { data: Partial<SchemaType> }) => {
const radius = 150;
const circumference = 2 * Math.PI * radius;
const strokeDasharray = circumference;
const strokeDashoffset = circumference - (circumference * (circularMetric?.value || 90) / 100);
const strokeDashoffset = circumference - (circumference * (satisfactionRate?.value || 90) / 100);
return (
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
@ -105,15 +112,15 @@ const StatisticCircularSlide = ({ data }: { data: Partial<SchemaType> }) => {
<div className="flex h-32">
{/* Left - Title */}
<div className="w-1/2 px-16 py-8 bg-gray-100 flex flex-col justify-center">
{mainTitle && (
{sectionTitle && (
<h1 className="text-4xl lg:text-5xl font-black text-teal-700 leading-tight mb-2">
{mainTitle}
{sectionTitle}
</h1>
)}
{subtitle && (
{sectionSubtitle && (
<p className="text-base font-semibold text-gray-800 tracking-wide">
{subtitle}
{sectionSubtitle}
</p>
)}
</div>
@ -172,14 +179,14 @@ const StatisticCircularSlide = ({ data }: { data: Partial<SchemaType> }) => {
{/* Center Content */}
<div className="absolute inset-0 flex flex-col items-center justify-center">
{circularMetric?.label && (
{satisfactionRate?.label && (
<p className="text-sm font-semibold text-gray-800 mb-4 text-center max-w-32 leading-tight">
{circularMetric.label}
{satisfactionRate.label}
</p>
)}
{circularMetric?.percentage && (
{satisfactionRate?.percentage && (
<span className="text-7xl font-black text-teal-700">
{circularMetric.percentage}
{satisfactionRate.percentage}
</span>
)}
</div>

View file

@ -1,34 +1,34 @@
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, ResponsiveContainer } from "recharts";
import { BarChart, Bar, AreaChart, Area, XAxis, YAxis, CartesianGrid } from "recharts";
// Schema definition
export const Schema = z.object({
mainTitle: z.string()
.min(5)
.max(15)
.default("STATISTIC")
sectionTitle: z.string()
.min(3)
.max(30)
.default("PERFORMANCE METRICS")
.meta({
description: "Main title for the statistic section",
description: "Main section heading - adapt to presentation topic (e.g., 'Climate Analysis', 'Health Outcomes', 'Research Data', 'Impact Assessment')",
}),
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()
organizationName: z.string()
.min(2)
.max(25)
.default("Company Name")
.max(30)
.default("Your Organization")
.meta({
description: "Company name for branding",
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(),
@ -41,7 +41,7 @@ export const Schema = z.object({
{ name: "Item 4", series1: 18, series2: 14, series3: 22 },
{ name: "Item 5", series1: 22, series2: 20, series3: 8 }
]).meta({
description: "Bar chart data for customer satisfaction",
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({
@ -56,7 +56,7 @@ export const Schema = z.object({
{ name: "Item 4", series1: 50, series2: 45, series3: 85 },
{ name: "Item 5", series1: 80, series2: 75, series3: 120 }
]).meta({
description: "Area chart data for repeat order rate",
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()
@ -64,7 +64,7 @@ export const Schema = z.object({
.max(40)
.default("Our Customer's Satisfaction")
.meta({
description: "Title for the left chart",
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()
@ -72,7 +72,7 @@ export const Schema = z.object({
.max(200)
.default("An impressive client satisfaction rate underscores our unwavering commitment to delivering exceptional service and exceeding expectations.")
.meta({
description: "Description for the left chart",
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()
@ -80,7 +80,7 @@ export const Schema = z.object({
.max(40)
.default("Repeat Order Rate")
.meta({
description: "Title for the right chart",
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()
@ -88,7 +88,7 @@ export const Schema = z.object({
.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: "Description for the right chart",
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.",
}),
})
@ -114,43 +114,33 @@ type SchemaType = z.infer<typeof Schema>;
// Component definition
const StatisticDualChartSlide = ({ data }: { data: Partial<SchemaType> }) => {
const {
mainTitle,
companyLogo,
companyName,
barChartData,
areaChartData,
leftChartTitle,
leftChartDescription,
rightChartTitle,
rightChartDescription
} = data;
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 */}
{mainTitle && (
{sectionTitle && (
<h1 className="text-4xl font-black text-white">
{mainTitle}
{sectionTitle}
</h1>
)}
{/* Company Branding */}
<div className="flex items-center space-x-3">
{companyLogo?.__image_url__ && (
{brandLogo?.__image_url__ && (
<div className="w-8 h-8">
<img
src={companyLogo.__image_url__}
alt={companyLogo.__image_prompt__}
src={brandLogo.__image_url__}
alt={brandLogo.__image_prompt__}
className="w-full h-full object-contain"
/>
</div>
)}
{companyName && (
{organizationName && (
<span className="text-lg font-bold text-white">
{companyName}
{organizationName}
</span>
)}
</div>
@ -180,45 +170,45 @@ const StatisticDualChartSlide = ({ data }: { data: Partial<SchemaType> }) => {
{barChartData && barChartData.length > 0 && (
<div className="flex-1 mb-6">
<ChartContainer config={chartConfig} className="h-full w-full">
<ResponsiveContainer width="100%" height="100%">
<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>
</ResponsiveContainer>
<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>
)}
@ -260,50 +250,50 @@ const StatisticDualChartSlide = ({ data }: { data: Partial<SchemaType> }) => {
{areaChartData && areaChartData.length > 0 && (
<div className="flex-1 mb-6">
<ChartContainer config={chartConfig} className="h-full w-full">
<ResponsiveContainer width="100%" height="100%">
<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>
</ResponsiveContainer>
<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>
)}

View file

@ -1,40 +1,57 @@
import * as z from "zod";
import { ImageSchema, IconSchema } from "../defaultSchemes";
import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from "recharts";
import { LineChart, Line, XAxis, YAxis, CartesianGrid } from "recharts";
// Schema definition
export const Schema = z.object({
mainTitle: z.string()
.min(5)
.max(12)
.default("STATISTIC")
sectionTitle: z.string()
.min(3)
.max(30)
.default("KEY STATISTICS")
.meta({
description: "Main title for the statistic section",
description: "Main section heading - adapt to presentation topic (e.g., 'Climate Data', 'Health Metrics', 'Performance Stats', 'Research Findings')",
}),
subtitle: z.string()
sectionSubtitle: z.string()
.min(10)
.max(18)
.default("CLIENT'S SATISFACTION")
.max(60)
.default("DATA-DRIVEN INSIGHTS AND PERFORMANCE")
.meta({
description: "Subtitle describing the statistic focus",
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')",
}),
description: z.string()
.min(80)
.max(200)
.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.")
statisticValue: z.string()
.min(1)
.max(15)
.default("85%")
.meta({
description: "Description of client satisfaction approach",
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.",
}),
businessImage: ImageSchema.default({
__image_url__: "https://images.unsplash.com/photo-1552664730-d307ca884978?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
__image_prompt__: "Business team meeting discussing charts and documents at table"
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: "Business meeting image showing team collaboration",
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({
@ -43,42 +60,26 @@ export const Schema = z.object({
series2: z.number(),
series3: z.number()
})).min(5).max(5).default([
{ name: "Item 1", series1: 18, series2: 0, series3: 0 },
{ name: "Item 2", series1: 30, series2: 12, series3: 8 },
{ name: "Item 3", series1: 26, series2: 38, series3: 20 },
{ name: "Item 4", series1: 40, series2: 30, series3: 35 },
{ name: "Item 5", series1: 42, series2: 45, series3: 32 }
{ 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: "Line chart data for satisfaction metrics",
}),
bulletPoints: z.array(z.object({
text: z.string().min(20).max(100),
color: z.enum(["teal", "beige", "light"])
})).min(3).max(3).default([
{
text: "From brand positioning and messaging to visual identity, we guide you through every step.",
color: "teal"
},
{
text: "Navigate the path to increased sales with our insightful report and offering a strategic roadmap.",
color: "beige"
},
{
text: "Amplify your revenue streams, engage customers, and unlock the full potential of your business.",
color: "light"
}
]).meta({
description: "Three bullet points with different colored indicators",
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: "Show yellow decorative underline",
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
@ -103,73 +104,72 @@ type SchemaType = z.infer<typeof Schema>;
// Component definition
const StatisticSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { mainTitle, subtitle, description, businessImage, chartData, bulletPoints, showYellowUnderline } = data;
const getColorClass = (color: string) => {
switch (color) {
case "teal": return "bg-teal-600";
case "beige": return "bg-yellow-300";
case "light": return "bg-gray-300";
default: return "bg-gray-300";
}
};
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 with Content and Image */}
<div className="w-1/2 relative bg-teal-600">
{/* Top Content Section */}
<div className="px-16 pt-12 text-white flex-1">
{/* Title Section */}
<div className="mb-4">
{mainTitle && (
<h1 className="text-4xl lg:text-5xl font-black leading-tight mb-1">
{mainTitle}
</h1>
)}
{/* 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>
)}
{subtitle && (
<p className="text-base font-semibold tracking-wide mb-1">
{subtitle}
</p>
)}
{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-4"></div>
)}
</div>
{/* Yellow Decorative Underline */}
{showYellowUnderline && (
<div className="w-24 h-1 bg-yellow-300 mb-8"></div>
)}
</div>
{/* Description */}
{description && (
<div>
<p className="text-base leading-relaxed">
{description}
</p>
{/* Large Statistic Display */}
<div className="mb-8">
{statisticValue && (
<div className="text-8xl font-black text-yellow-300 mb-4">
{statisticValue}
</div>
)}
</div>
{/* Bottom Business Image */}
<div className="relative h-80 mx-16 mt-10">
{businessImage?.__image_url__ && (
<img
src={businessImage.__image_url__}
alt={businessImage.__image_prompt__}
className="max-w-full w-full h-full object-cover"
/>
{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 - Chart and Bullet Points */}
<div className="w-1/2 relative bg-white">
{/* 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 */}
@ -192,47 +192,47 @@ const StatisticSlide = ({ data }: { data: Partial<SchemaType> }) => {
{chartData && chartData.length > 0 && (
<div className="h-64">
<ChartContainer config={chartConfig} className="h-full w-full">
<ResponsiveContainer width="100%" height="100%">
<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>
</ResponsiveContainer>
<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>
)}
@ -242,19 +242,28 @@ const StatisticSlide = ({ data }: { data: Partial<SchemaType> }) => {
<div className="px-8 pb-6 space-y-4 mt-10">
{bulletPoints && bulletPoints.length > 0 && (
<>
{bulletPoints.map((point, index) => (
<div key={index} className="flex items-start space-x-4">
<div className={`w-6 h-6 ${getColorClass(point.color)} rounded-full flex-shrink-0 mt-1`}></div>
<p className="text-base leading-relaxed text-gray-700">
{point.text}
</p>
</div>
))}
{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>
);
};

View file

@ -5,63 +5,78 @@ import { ImageSchema, IconSchema } from "../defaultSchemes";
// Schema definition
export const Schema = z.object({
mainTitle: z.string()
.min(5)
.max(20)
.default("TABLE OF CONTENT")
sectionTitle: z.string()
.min(3)
.max(30)
.default("TABLE OF CONTENTS")
.meta({
description: "Main title for the table of contents",
description: "Main heading for the content overview - can be 'Agenda', 'Overview', 'Contents', or similar",
}),
subtitle: z.string()
sectionSubtitle: z.string()
.min(10)
.max(25)
.default("PITCH DECK PRESENTATION")
.max(60)
.default("PRESENTATION OVERVIEW AND AGENDA")
.meta({
description: "Subtitle describing the presentation type",
description: "Supporting subtitle that explains what the audience will learn or see",
}),
contentItems: z.array(z.object({
number: z.string().min(1).max(3),
title: z.string().min(3).max(30)
})).min(4).max(8).default([
{ number: "01", title: "Hello Friends!" },
{ number: "02", title: "About Us" },
{ number: "03", title: "What We Believe" },
{ number: "04", title: "Problems & Solutions" },
{ number: "05", title: "Market Size" },
{ number: "06", title: "Statistic" }
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 content items with numbers and titles",
description: "List of presentation sections with numbered sequence and brief descriptions",
}),
heroImage: ImageSchema.default({
__image_url__: "https://images.unsplash.com/photo-1498050108023-c5249f4df085?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
__image_prompt__: "Modern laptop and office workspace on wooden desk"
brandingVisual: ImageSchema.default({
__image_url__: "https://via.placeholder.com/200x100/22C55E/FFFFFF?text=BRAND",
__image_prompt__: "Organization logo or brand visual element"
}).meta({
description: "Hero image showing professional workspace",
description: "Logo or branding element displayed prominently for visual identity",
}),
companyLogo: ImageSchema.default({
__image_url__: "https://via.placeholder.com/40x40/1D9A8A/FFFFFF?text=C",
__image_prompt__: "Clean modern company logo icon"
}).meta({
description: "Company logo icon",
}),
companyName: z.string()
.min(2)
.max(25)
.default("Company Name")
.meta({
description: "Company name for branding",
}),
decorativeCircle: z.boolean()
showDecorations: z.boolean()
.default(true)
.meta({
description: "Show decorative circle element",
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",
}),
})
@ -71,45 +86,56 @@ type SchemaType = z.infer<typeof Schema>;
// Component definition
const TableOfContentsSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { mainTitle, subtitle, contentItems, heroImage, companyLogo, companyName, decorativeCircle } = data;
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-center">
<div className="w-3/5 px-16 py-12 flex flex-col justify-start">
{/* Title Section */}
<div className="mb-12">
{mainTitle && (
<h1 className="text-4xl lg:text-5xl font-black text-teal-700 leading-tight mb-4">
{mainTitle}
{sectionTitle && (
<h1 className="text-3xl lg:text-4xl font-black text-teal-700 leading-tight mb-4">
{sectionTitle}
</h1>
)}
{/* Decorative underline */}
<div className="w-32 h-1 bg-yellow-300 mb-6"></div>
{showDecorations && (
<div className="w-32 h-1 bg-yellow-300 mb-6"></div>
)}
{subtitle && (
{sectionSubtitle && (
<p className="text-base font-medium text-gray-700 tracking-wide">
{subtitle}
{sectionSubtitle}
</p>
)}
</div>
{/* Content Items */}
{contentItems && contentItems.length > 0 && (
<div className="grid grid-cols-2 gap-6">
<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-center space-x-4">
<div className="w-12 h-12 bg-yellow-200 rounded-full flex items-center justify-center flex-shrink-0">
<span className="text-teal-700 font-bold text-lg">
{item.number}
<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>
<div className="text-gray-900 font-medium text-lg">
{item.title}
{/* 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>
))}
@ -117,43 +143,33 @@ const TableOfContentsSlide = ({ data }: { data: Partial<SchemaType> }) => {
)}
</div>
{/* Right Side - Image and Branding */}
<div className="w-2/5 relative bg-teal-600">
{/* Hero Image */}
{heroImage?.__image_url__ && (
<div className="absolute top-0 left-0 right-0 h-1/2">
{/* 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={heroImage.__image_url__}
alt={heroImage.__image_prompt__}
className="w-full h-full object-cover"
src={brandingVisual.__image_url__}
alt={brandingVisual.__image_prompt__}
className="max-w-64 max-h-32 object-contain mx-auto"
/>
</div>
)}
{/* Decorative Circle */}
{decorativeCircle && (
<div className="absolute bottom-24 left-1/2 transform -translate-x-1/2 w-32 h-32 bg-yellow-200 rounded-full"></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>
</>
)}
{/* Company Branding */}
<div className="absolute bottom-8 left-8 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-white font-bold text-xl">
{companyName}
</span>
)}
</div>
</div>
</div>
{/* Bottom accent strip */}
<div className="absolute bottom-0 left-0 right-0 h-2 bg-teal-600"></div>
</div>
);
};

View file

@ -1,72 +1,89 @@
import * as z from "zod";
import { ImageSchema, IconSchema } from "../defaultSchemes";
// Schema definition
export const Schema = z.object({
mainTitle: z.string()
.min(5)
.max(15)
.default("TESTIMONIAL")
sectionTitle: z.string()
.min(3)
.max(30)
.default("CLIENT TESTIMONIALS")
.meta({
description: "Main title for the testimonial section",
description: "Main section heading - can be 'Testimonials', 'Client Feedback', 'Reviews', or similar",
}),
companyLogo: ImageSchema.default({
__image_url__: "https://via.placeholder.com/40x40/FFFFFF/1D9A8A?text=C",
__image_prompt__: "Clean modern company logo icon in white"
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: "Company logo icon",
description: "Logo or brand mark representing the organization",
}),
companyName: z.string()
.min(2)
.max(25)
.default("Company Name")
.meta({
description: "Company name for branding",
}),
testimonials: z.array(z.object({
text: z.string().min(30).max(200),
clientName: z.string().min(2).max(30),
clientPhoto: ImageSchema,
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),
backgroundColor: z.enum(["beige", "teal", "light"])
})).min(3).max(3).default([
clientPhoto: ImageSchema
})).min(2).max(4).default([
{
text: "In a world flooded with marketing noise, this company stands out as a beacon of creativity and effectiveness.",
clientName: "Benjamin Shah",
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 headshot of smiling businessman"
},
rating: 5,
backgroundColor: "beige"
__image_prompt__: "Professional businessman headshot"
}
},
{
text: "The level of expertise and personalized attention to our unique needs has made them an invaluable partner.",
clientName: "Murad Naser",
clientPhoto: {
__image_url__: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80",
__image_prompt__: "Professional headshot of confident businessman with beard"
},
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,
backgroundColor: "teal"
},
{
text: "I've been thoroughly impressed with the exceptional level of service and creativity they bring to the table.",
clientName: "Drew Feig",
clientPhoto: {
__image_url__: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80",
__image_prompt__: "Professional headshot of smiling young businessman"
},
rating: 5,
backgroundColor: "light"
__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: "Three client testimonials with photos and ratings",
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
@ -75,101 +92,104 @@ type SchemaType = z.infer<typeof Schema>;
// Component definition
const TestimonialSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { mainTitle, companyLogo, companyName, testimonials } = data;
const { sectionTitle, organizationName, brandLogo, testimonialItems, showRatings, showClientPhotos } = data;
const getBackgroundClass = (bg: string) => {
switch (bg) {
case "teal": return "bg-teal-600 text-white";
case "beige": return "bg-yellow-100 text-gray-900";
case "light": return "bg-gray-100 text-gray-900";
default: return "bg-gray-100 text-gray-900";
}
};
const getStarColor = (bg: string) => {
return bg === "teal" ? "text-yellow-400" : "text-yellow-500";
};
const renderStars = (rating: number, backgroundColor: string) => {
return (
<div className={`flex space-x-1 mb-6 ${getStarColor(backgroundColor)}`}>
{[...Array(5)].map((_, index) => (
<svg
key={index}
className="w-6 h-6 fill-current"
viewBox="0 0 24 24"
>
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
))}
</div>
);
// 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-gray-50 relative overflow-hidden">
<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 */}
{mainTitle && (
<h1 className="text-4xl font-black text-white">
{mainTitle}
{sectionTitle && (
<h1 className="text-2xl font-black text-white">
{sectionTitle}
</h1>
)}
{/* Company Branding */}
<div className="flex items-center space-x-3">
{companyLogo?.__image_url__ && (
{brandLogo?.__image_url__ && (
<div className="w-8 h-8">
<img
src={companyLogo.__image_url__}
alt={companyLogo.__image_prompt__}
src={brandLogo.__image_url__}
alt={brandLogo.__image_prompt__}
className="w-full h-full object-contain"
/>
</div>
)}
{companyName && (
<span className="text-lg font-bold text-white">
{companyName}
{organizationName && (
<span className="text-base font-bold text-white">
{organizationName}
</span>
)}
</div>
</div>
{/* Testimonials Section */}
<div className="flex-1 h-[calc(100%-80px)] px-16 py-12 flex items-center justify-center">
{testimonials && testimonials.length > 0 && (
<div className="grid grid-cols-3 gap-8 w-full max-w-6xl">
{testimonials.map((testimonial, index) => (
<div
key={index}
className={`${getBackgroundClass(testimonial.backgroundColor)} rounded-3xl p-8 flex flex-col items-center text-center h-full`}
>
{/* Stars */}
{renderStars(testimonial.rating, testimonial.backgroundColor)}
{/* 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];
{/* Testimonial Text */}
<p className="text-base leading-relaxed mb-8 flex-1 flex items-center">
{testimonial.text}
</p>
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>
)}
{/* Client Photo */}
{testimonial.clientPhoto?.__image_url__ && (
<div className="w-20 h-20 rounded-full overflow-hidden mb-4 border-4 border-white shadow-lg">
<img
src={testimonial.clientPhoto.__image_url__}
alt={testimonial.clientPhoto.__image_prompt__}
className="w-full h-full object-cover"
/>
{/* 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>
)}
{/* Client Name */}
<h3 className="text-lg font-bold">
{testimonial.clientName}
</h3>
</div>
))}
</div>
);
})}
</div>
)}
</div>

View file

@ -5,68 +5,73 @@ import { ImageSchema, IconSchema } from "../defaultSchemes";
// Schema definition
export const Schema = z.object({
companyName: z.string()
organizationName: z.string()
.min(2)
.max(15)
.default("Company Name")
.max(30)
.default("Your Organization")
.meta({
description: "Company name displayed prominently",
description: "Name of the organization, company, or entity presenting",
}),
mainTitle: z.string()
.min(5)
.max(20)
primaryMessage: z.string()
.min(3)
.max(25)
.default("THANK YOU")
.meta({
description: "Main thank you title in large bold letters",
description: "Main closing message - can be 'Thank You', 'Questions?', 'Let's Connect', or similar",
}),
subtitle: z.string()
.min(10)
.max(40)
.default("FOR YOUR NICE ATTENTION")
secondaryMessage: z.string()
.min(5)
.max(60)
.default("FOR YOUR TIME AND ATTENTION")
.meta({
description: "Subtitle thanking the audience",
description: "Supporting message that completes the primary message or adds context",
}),
companyLogo: ImageSchema.default({
__image_url__: "https://via.placeholder.com/40x40/22C55E/FFFFFF?text=C",
__image_prompt__: "Company logo - geometric green icon"
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: "Company logo icon",
description: "Logo or brand mark representing the presenting organization",
}),
contactInfo: z.object({
telephone: z.string().min(10).max(20).default("+123-456-7890"),
address: z.string().min(10).max(50).default("123 Anywhere St., Any City, ST 12345"),
website: z.string().min(10).max(30).default("www.reallygreatsite.com")
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({
telephone: "+123-456-7890",
address: "123 Anywhere St., Any City, ST 12345",
website: "www.reallygreatsite.com"
phoneNumber: "+1-234-567-8900",
physicalAddress: "123 Business Ave, City, State 12345",
websiteUrl: "www.yourorganization.com"
}).meta({
description: "Company contact information",
description: "Contact information for follow-up communication and connection",
}),
presentationDate: z.string()
.min(5)
.min(3)
.max(20)
.default("December 2023")
.default("Current Month Year")
.meta({
description: "Date of the presentation",
description: "Date when the presentation was given or document was created",
}),
decorativeCircle: z.boolean()
showDecorations: z.boolean()
.default(true)
.meta({
description: "Show decorative circle element",
description: "Whether to display decorative visual elements like background shapes",
}),
arrowButton: z.boolean()
showNavigationArrow: z.boolean()
.default(true)
.meta({
description: "Show navigation arrow button",
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",
}),
})
@ -76,7 +81,7 @@ type SchemaType = z.infer<typeof Schema>;
// Component definition
const ThankYouSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { companyName, mainTitle, subtitle, companyLogo, contactInfo, presentationDate, decorativeCircle, arrowButton } = data;
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">
@ -84,24 +89,24 @@ const ThankYouSlide = ({ data }: { data: Partial<SchemaType> }) => {
<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">
{companyLogo?.__image_url__ && (
{brandLogo?.__image_url__ && (
<div className="w-10 h-10">
<img
src={companyLogo.__image_url__}
alt={companyLogo.__image_prompt__}
src={brandLogo.__image_url__}
alt={brandLogo.__image_prompt__}
className="w-full h-full object-contain"
/>
</div>
)}
{companyName && (
{organizationName && (
<span className="text-2xl font-bold text-gray-900">
{companyName}
{organizationName}
</span>
)}
</div>
{/* Arrow Button */}
{arrowButton && (
{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" />
@ -111,7 +116,7 @@ const ThankYouSlide = ({ data }: { data: Partial<SchemaType> }) => {
</div>
{/* Decorative Circle */}
{decorativeCircle && (
{showDecorations && (
<div className="absolute top-20 right-16 w-96 h-96 bg-yellow-100 rounded-full opacity-60 z-10"></div>
)}
@ -119,18 +124,18 @@ const ThankYouSlide = ({ data }: { data: Partial<SchemaType> }) => {
<div className="relative z-15 h-full flex flex-col justify-center px-16">
<div className="max-w-4xl">
{/* Main Title */}
{mainTitle && (
{primaryMessage && (
<h1 className="text-8xl lg:text-9xl font-black text-teal-700 leading-none tracking-tight mb-4">
{mainTitle}
{primaryMessage}
</h1>
)}
{/* Subtitle with Circle Bullet */}
{subtitle && (
{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">
{subtitle}
{secondaryMessage}
</h2>
</div>
)}
@ -138,44 +143,46 @@ const ThankYouSlide = ({ data }: { data: Partial<SchemaType> }) => {
</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 */}
{contactInfo?.telephone && (
<div>
<div className="font-semibold text-gray-900 mb-1">Telephone</div>
<div>{contactInfo.telephone}</div>
</div>
)}
{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 */}
{contactInfo?.address && (
<div>
<div className="font-semibold text-gray-900 mb-1">Address</div>
<div>{contactInfo.address}</div>
</div>
)}
{/* Address */}
{contactDetails?.physicalAddress && (
<div>
<div className="font-semibold text-gray-900 mb-1">Address</div>
<div>{contactDetails.physicalAddress}</div>
</div>
)}
{/* Website */}
{contactInfo?.website && (
<div>
<div className="font-semibold text-gray-900 mb-1">Website</div>
<div>{contactInfo.website}</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>
{/* Presentation Date */}
{presentationDate && (
<div className="text-right">
<div className="text-lg font-bold text-gray-900">
{presentationDate}
</div>
</div>
)}
</div>
</div>
)}
</div>
);
};

View file

@ -5,68 +5,67 @@ import { ImageSchema, IconSchema } from "../defaultSchemes";
// Schema definition
export const Schema = z.object({
companyName: z.string()
organizationName: z.string()
.min(2)
.max(15)
.default("Thynk Unlimited")
.max(25)
.default("Your Organization")
.meta({
description: "Company name displayed prominently",
description: "Name of the organization, company, or entity presenting",
}),
mainTitle: z.string()
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(15)
.default("PITCH DECK")
.max(50)
.default("PROFESSIONAL PRESENTATION")
.meta({
description: "Main title in large bold letters",
description: "Subtitle that provides context about the presentation type or purpose",
}),
subtitle: z.string()
.min(10)
.max(40)
.default("BUSINESS PRESENTATION")
.meta({
description: "Subtitle describing the presentation type",
}),
companyLogo: ImageSchema.default({
__image_url__: "https://via.placeholder.com/40x40/22C55E/FFFFFF?text=T",
__image_prompt__: "Thynk Unlimited logo - geometric green icon"
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: "Company logo icon",
description: "Logo or brand mark representing the presenting organization",
}),
contactInfo: z.object({
telephone: z.string().min(10).max(20).default("+123-456-7890"),
address: z.string().min(10).max(50).default("123 Anywhere St., Any City, ST 12345"),
website: z.string().min(10).max(30).default("www.reallygreatsite.com")
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({
telephone: "+123-456-7890",
address: "123 Anywhere St., Any City, ST 12345",
website: "www.reallygreatsite.com"
phoneNumber: "+1-234-567-8900",
physicalAddress: "123 Business Ave, City, State 12345",
websiteUrl: "www.yourorganization.com"
}).meta({
description: "Company contact information",
description: "Contact information including phone, address, and website for follow-up communication",
}),
presentationDate: z.string()
.min(5)
.min(3)
.max(20)
.default("December 2023")
.default("Current Month Year")
.meta({
description: "Date of the presentation",
description: "Date when the presentation is being given or was created",
}),
decorativeCircle: z.boolean()
showDecorations: z.boolean()
.default(true)
.meta({
description: "Show decorative circle element",
description: "Whether to display decorative visual elements like background shapes",
}),
arrowButton: z.boolean()
showNavigationArrow: z.boolean()
.default(true)
.meta({
description: "Show navigation arrow button",
description: "Whether to show a navigation arrow button for presentation flow",
}),
})
@ -77,7 +76,7 @@ type SchemaType = z.infer<typeof Schema>;
const ThynkTitleSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { companyName, mainTitle, subtitle, companyLogo, contactInfo, presentationDate, decorativeCircle, arrowButton } = data;
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">
@ -85,24 +84,24 @@ const ThynkTitleSlide = ({ data }: { data: Partial<SchemaType> }) => {
<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">
{companyLogo?.__image_url__ && (
{brandLogo?.__image_url__ && (
<div className="w-10 h-10">
<img
src={companyLogo.__image_url__}
alt={companyLogo.__image_prompt__}
src={brandLogo.__image_url__}
alt={brandLogo.__image_prompt__}
className="w-full h-full object-contain"
/>
</div>
)}
{companyName && (
{organizationName && (
<span className="text-2xl font-bold text-gray-900">
{companyName}
{organizationName}
</span>
)}
</div>
{/* Arrow Button */}
{arrowButton && (
{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" />
@ -112,26 +111,26 @@ const ThynkTitleSlide = ({ data }: { data: Partial<SchemaType> }) => {
</div>
{/* Decorative Circle */}
{decorativeCircle && (
{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="relative h-full flex flex-col justify-center px-16">
<div className="">
{/* Main Title */}
{mainTitle && (
{primaryTitle && (
<h1 className="text-4xl lg:text-5xl font-black text-teal-700 leading-none tracking-tight mb-4">
{mainTitle}
{primaryTitle}
</h1>
)}
{/* Subtitle with Circle Bullet */}
{subtitle && (
{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">
{subtitle}
{secondaryTitle}
</h2>
</div>
)}
@ -143,26 +142,26 @@ const ThynkTitleSlide = ({ data }: { data: Partial<SchemaType> }) => {
<div className="flex justify-between items-center text-gray-700">
<div className="flex space-x-16 text-sm">
{/* Telephone */}
{contactInfo?.telephone && (
{contactDetails?.phoneNumber && (
<div>
<div className="font-semibold text-gray-900 mb-1">Telephone</div>
<div>{contactInfo.telephone}</div>
<div>{contactDetails.phoneNumber}</div>
</div>
)}
{/* Address */}
{contactInfo?.address && (
{contactDetails?.physicalAddress && (
<div>
<div className="font-semibold text-gray-900 mb-1">Address</div>
<div>{contactInfo.address}</div>
<div>{contactDetails.physicalAddress}</div>
</div>
)}
{/* Website */}
{contactInfo?.website && (
{contactDetails?.websiteUrl && (
<div>
<div className="font-semibold text-gray-900 mb-1">Website</div>
<div>{contactInfo.website}</div>
<div>{contactDetails.websiteUrl}</div>
</div>
)}
</div>

View file

@ -5,66 +5,57 @@ import { ImageSchema, IconSchema } from "../defaultSchemes";
// Schema definition
export const Schema = z.object({
mainTitle: z.string()
.min(5)
.max(15)
.default("WHAT WE BELIEVE")
.meta({
description: "Main title for the beliefs section",
}),
subtitle: z.string()
.min(10)
sectionTitle: z.string()
.min(3)
.max(30)
.default("ABOUT OUR VISION AND MISSION")
.default("OUR VISION & MISSION")
.meta({
description: "Subtitle describing the section",
description: "Main section heading - can be 'Our Values', 'What We Believe', 'Our Philosophy', or similar",
}),
visionTitle: z.string()
.min(3)
.max(15)
.default("VISION")
sectionSubtitle: z.string()
.min(10)
.max(60)
.default("GUIDING PRINCIPLES AND CORE BELIEFS")
.meta({
description: "Vision section title",
description: "Supporting subtitle that introduces the organization's foundational concepts",
}),
visionText: z.string()
.min(50)
.max(300)
.default("Our vision is to be the catalyst for transformative marketing solutions that redefine industry standards. We envision a future where brands not only captivate their audience but also inspire meaningful connections.")
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 text",
description: "Vision statement describing the organization's aspirational future goals and impact",
}),
missionTitle: z.string()
.min(3)
.max(15)
.default("MISSION")
.meta({
description: "Mission section title",
}),
missionText: z.string()
.min(50)
.max(400)
.default("Our mission is to deliver strategic and impactful marketing solutions that propel businesses to new heights of success. We are committed to leveraging our expertise in data-driven insights, creative storytelling, and cutting-edge technology to craft bespoke campaigns.")
.meta({
description: "Mission statement text",
}),
teamImage: 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__: "Business team collaboration meeting with documents and discussion"
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: "Team collaboration image",
description: "Mission section with title and detailed description of organizational purpose and approach",
}),
showYellowAccent: z.boolean()
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: "Show yellow accent block",
description: "Whether to display decorative visual accent elements",
}),
showColorBlocks: z.boolean()
.default(true)
.meta({
description: "Whether to show colored background sections for visual hierarchy",
}),
})
@ -74,84 +65,88 @@ type SchemaType = z.infer<typeof Schema>;
// Component definition
const WhatWeBelieveSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { mainTitle, subtitle, visionTitle, visionText, missionTitle, missionText, teamImage, showYellowAccent } = data;
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-1/2 relative">
{/* Team Image */}
{teamImage?.__image_url__ && (
<div className="absolute top-0 left-0 bottom-32 right-0">
<div className="w-2/5 relative">
{supportingVisual?.__image_url__ && (
<div className="absolute inset-8 shadow-lg">
<img
src={teamImage.__image_url__}
alt={teamImage.__image_prompt__}
className="w-full h-full object-cover"
src={supportingVisual.__image_url__}
alt={supportingVisual.__image_prompt__}
className="w-full h-full object-cover rounded-lg"
/>
</div>
)}
{/* Yellow Accent Block - Bottom Left */}
{showYellowAccent && (
<div className="absolute bottom-0 left-0 w-32 h-32 bg-yellow-300 z-10"></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>
</>
)}
{/* Teal Accent Block - Bottom */}
<div className="absolute bottom-0 left-32 right-0 h-32 bg-teal-600 z-10"></div>
</div>
{/* Right Side - Content */}
<div className="w-1/2 relative bg-white ">
<div className="px-16 py-12">
{/* Title Section */}
<div className="mb-8">
{mainTitle && (
<h1 className="text-4xl lg:text-5xl font-black text-teal-700 leading-tight mb-4">
{mainTitle}
</h1>
)}
{subtitle && (
<p className="text-base font-semibold text-gray-800 tracking-wide mb-8">
{subtitle}
</p>
)}
</div>
{/* Vision Section */}
<div className="mb-8">
{visionTitle && (
<h2 className="text-2xl font-bold text-teal-700 mb-4">
{visionTitle}
</h2>
)}
{visionText && (
<p className="text-base leading-relaxed text-gray-700">
{visionText}
</p>
)}
</div>
</div>
{/* Mission Section with Teal Background */}
<div className="bg-teal-600 p-6 absolute bottom-0 left-0 right-0 px-16 text-white">
{missionTitle && (
<h2 className="text-2xl font-bold mb-4">
{missionTitle}
</h2>
<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>
)}
{missionText && (
<p className="text-base leading-relaxed">
{missionText}
{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>
);
};

View file

@ -1,5 +1,5 @@
{
"description": "This is a new pitch deck layout with a focus on simplicity and clarity.",
"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,
"isDefault": false
}

View file

@ -87,10 +87,14 @@ function convertElementToPptxShape(
}
if (element.innerText && element.innerText.trim().length > 0) {
// Use AutoShape model if there's background color and border radius
if (element.background?.color && element.borderRadius && element.borderRadius.some(radius => radius > 0)) {
return convertToAutoShapeBox(element);
}
return convertToTextBox(element);
}
if (element.tagName === 'hr' || (element.className && typeof element.className === 'string' && (element.className.includes('connector') || element.className.includes('line')))) {
if (element.tagName === 'hr') {
return convertToConnector(element);
}
@ -177,6 +181,13 @@ function convertToAutoShapeBox(element: ElementAttributes): PptxAutoShapeBoxMode
const shapeType = element.borderRadius ? PptxShapeType.ROUNDED_RECTANGLE : PptxShapeType.RECTANGLE;
let borderRadius = undefined;
for (const eachCornerRadius of element.borderRadius ?? []) {
if (eachCornerRadius > 0) {
borderRadius = Math.max(borderRadius ?? 0, eachCornerRadius);
}
}
return {
type: shapeType,
margin: undefined,
@ -185,7 +196,7 @@ function convertToAutoShapeBox(element: ElementAttributes): PptxAutoShapeBoxMode
shadow,
position,
text_wrap: element.textWrap ?? true,
border_radius: element.borderRadius ? Math.round(element.borderRadius[0]) : undefined,
border_radius: borderRadius || undefined,
paragraphs
};
}