Merge branch 'feat/custom_schema_and_layout' of github.com:presenton/presenton into feat/custom_schema_and_layout
This commit is contained in:
commit
46a99e3198
32 changed files with 2518 additions and 1189 deletions
|
|
@ -107,7 +107,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
|
|||
!isInitialLoad.current &&
|
||||
presentationData.slides &&
|
||||
presentationData.slides.some(
|
||||
(slide) => slide.images && slide.images.length > 0
|
||||
(slide: any) => slide.images && slide.images.length > 0
|
||||
)
|
||||
) {
|
||||
|
||||
|
|
@ -245,7 +245,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
|
|||
};
|
||||
}, []);
|
||||
// Function to scroll to specific slide
|
||||
const handleSlideClick = (index: number) => {
|
||||
const handleSlideClick = (index: any) => {
|
||||
const slideElement = document.getElementById(`slide-${index}`);
|
||||
if (slideElement) {
|
||||
slideElement.scrollIntoView({
|
||||
|
|
@ -416,7 +416,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
|
|||
{presentationData &&
|
||||
presentationData.slides &&
|
||||
presentationData.slides.length > 0 &&
|
||||
presentationData.slides.map((slide, index) => (
|
||||
presentationData.slides.map((slide: any, index: number) => (
|
||||
<SlideContent
|
||||
key={`${slide.type}-${index}-${slide.index}}`}
|
||||
slide={slide}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ const SidePanel = ({
|
|||
const { currentTheme, currentColors } = useSelector(
|
||||
(state: RootState) => state.theme
|
||||
);
|
||||
console.log('presentationData', presentationData)
|
||||
const dispatch = useDispatch();
|
||||
const { getLayout } = useLayoutCache();
|
||||
|
||||
|
|
@ -90,10 +91,10 @@ const SidePanel = ({
|
|||
if (active.id !== over.id) {
|
||||
// Find the indices of the dragged and target items
|
||||
const oldIndex = presentationData?.slides.findIndex(
|
||||
(item) => item.id === active.id
|
||||
(item: any) => item.id === active.id
|
||||
);
|
||||
const newIndex = presentationData?.slides.findIndex(
|
||||
(item) => item.id === over.id
|
||||
(item: any) => item.id === over.id
|
||||
);
|
||||
|
||||
// Reorder the array
|
||||
|
|
@ -104,7 +105,7 @@ const SidePanel = ({
|
|||
);
|
||||
|
||||
// Update indices of all slides
|
||||
const updatedArray = reorderedArray.map((slide, index) => ({
|
||||
const updatedArray = reorderedArray.map((slide: any, index: number) => ({
|
||||
...slide,
|
||||
index: index,
|
||||
}));
|
||||
|
|
@ -128,66 +129,96 @@ const SidePanel = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
{/* Mobile Toggle Button */}
|
||||
<div className="md:hidden fixed top-20 left-4 z-40">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setIsMobilePanelOpen(true)}
|
||||
className="bg-white shadow-md"
|
||||
>
|
||||
<PanelRightOpen className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
{/* Desktop Toggle Button - Always visible when panel is closed */}
|
||||
{!isOpen && (
|
||||
<div className="hidden xl:block fixed left-4 top-1/2 -translate-y-1/2 z-50">
|
||||
<ToolTip content="Open Panel">
|
||||
<Button
|
||||
onClick={() => setIsOpen(true)}
|
||||
className="bg-white hover:bg-gray-50 shadow-lg"
|
||||
>
|
||||
<PanelRightOpen className="text-black" size={20} />
|
||||
</Button>
|
||||
</ToolTip>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Backdrop for mobile */}
|
||||
{isMobilePanelOpen && (
|
||||
<div
|
||||
className="md:hidden fixed inset-0 bg-black/50 z-30"
|
||||
onClick={() => setIsMobilePanelOpen(false)}
|
||||
/>
|
||||
{/* Mobile Toggle Button */}
|
||||
{!isMobilePanelOpen && (
|
||||
<div className="xl:hidden fixed left-4 bottom-4 z-50">
|
||||
<ToolTip content="Show Panel">
|
||||
<Button
|
||||
onClick={() => setIsMobilePanelOpen(true)}
|
||||
className="bg-[#5146E5] text-white p-3 rounded-full shadow-lg"
|
||||
>
|
||||
<PanelRightOpen className="text-white" size={20} />
|
||||
</Button>
|
||||
</ToolTip>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`${isOpen ? "w-72" : "w-0"} ${isMobilePanelOpen ? "translate-x-0" : "-translate-x-full"
|
||||
} md:translate-x-0 fixed md:relative left-0 top-0 h-full bg-white border-r border-gray-200 transition-all duration-300 ease-in-out z-40 md:z-auto flex-shrink-0`}
|
||||
style={{
|
||||
background: currentColors.background,
|
||||
}}
|
||||
className={`
|
||||
fixed xl:relative h-full z-50 xl:z-auto
|
||||
transition-all duration-300 ease-in-out
|
||||
${isOpen ? "ml-0" : "-ml-[300px]"}
|
||||
${isMobilePanelOpen
|
||||
? "translate-x-0"
|
||||
: "-translate-x-full xl:translate-x-0"
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div className="h-full flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="p-4 border-b border-color flex items-center justify-between">
|
||||
<h3 className="font-semibold slide-title">Slides</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<ToolTip content="List view">
|
||||
<Button
|
||||
variant={active === "list" ? "default" : "ghost"}
|
||||
size="sm"
|
||||
onClick={() => setActive("list")}
|
||||
className="p-2"
|
||||
>
|
||||
<ListTree className="w-4 h-4" />
|
||||
</Button>
|
||||
</ToolTip>
|
||||
<ToolTip content="Grid view">
|
||||
<Button
|
||||
variant={active === "grid" ? "default" : "ghost"}
|
||||
size="sm"
|
||||
onClick={() => setActive("grid")}
|
||||
className="p-2"
|
||||
>
|
||||
<LayoutList className="w-4 h-4" />
|
||||
</Button>
|
||||
</ToolTip>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
<div
|
||||
data-theme={currentTheme}
|
||||
style={{
|
||||
backgroundColor: currentColors.slideBg,
|
||||
}}
|
||||
className="min-w-[300px] max-w-[300px] h-[calc(100vh-120px)] rounded-[20px] hide-scrollbar overflow-hidden slide-theme shadow-xl"
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: currentColors.slideBg,
|
||||
}}
|
||||
className="sticky top-0 z-40 px-6 py-4"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center justify-start gap-4">
|
||||
<ToolTip content="Image Preview">
|
||||
<Button
|
||||
className={`${active === "grid"
|
||||
? "bg-[#5141e5] hover:bg-[#4638c7]"
|
||||
: "bg-white hover:bg-white"
|
||||
}`}
|
||||
onClick={() => setActive("grid")}
|
||||
>
|
||||
<LayoutList
|
||||
className={`${active === "grid" ? "text-white" : "text-black"
|
||||
}`}
|
||||
size={20}
|
||||
/>
|
||||
</Button>
|
||||
</ToolTip>
|
||||
<ToolTip content="List Preview">
|
||||
<Button
|
||||
className={`${active === "list"
|
||||
? "bg-[#5141e5] hover:bg-[#4638c7]"
|
||||
: "bg-white hover:bg-white"
|
||||
}`}
|
||||
onClick={() => setActive("list")}
|
||||
>
|
||||
<ListTree
|
||||
className={`${active === "list" ? "text-white" : "text-black"
|
||||
}`}
|
||||
size={20}
|
||||
/>
|
||||
</Button>
|
||||
</ToolTip>
|
||||
</div>
|
||||
<X
|
||||
onClick={handleClose}
|
||||
className="md:hidden p-2"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</Button>
|
||||
className="text-[#6c7081] cursor-pointer hover:text-gray-600"
|
||||
size={20}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -201,7 +232,7 @@ const SidePanel = ({
|
|||
<div className="p-4 overflow-y-auto hide-scrollbar h-[calc(100%-100px)]">
|
||||
{isStreaming ? (
|
||||
presentationData &&
|
||||
presentationData?.slides.map((slide, index) => (
|
||||
presentationData?.slides.map((slide: any, index: number) => (
|
||||
<div
|
||||
key={`${index}-${slide.type}-${slide.id}`}
|
||||
className={`p-3 cursor-pointer rounded-lg slide-box`}
|
||||
|
|
@ -217,13 +248,13 @@ const SidePanel = ({
|
|||
) : (
|
||||
<SortableContext
|
||||
items={
|
||||
presentationData?.slides.map((slide) => slide.id!) || []
|
||||
presentationData?.slides.map((slide: any) => slide.id!) || []
|
||||
}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<div className="space-y-2" id={`slide-${selectedSlide}`}>
|
||||
{presentationData &&
|
||||
presentationData?.slides.map((slide, index) => (
|
||||
presentationData?.slides.map((slide: any, index: number) => (
|
||||
<SortableListItem
|
||||
key={`${slide.id}-${index}`}
|
||||
slide={slide}
|
||||
|
|
@ -243,7 +274,7 @@ const SidePanel = ({
|
|||
<div className="p-4 overflow-y-auto hide-scrollbar h-[calc(100%-100px)] space-y-4">
|
||||
{isStreaming ? (
|
||||
presentationData &&
|
||||
presentationData?.slides.map((slide, index) => (
|
||||
presentationData?.slides.map((slide: any, index: number) => (
|
||||
<div
|
||||
key={`${slide.id}-${index}`}
|
||||
onClick={() => onSlideClick(index)}
|
||||
|
|
@ -261,12 +292,12 @@ const SidePanel = ({
|
|||
) : (
|
||||
<SortableContext
|
||||
items={
|
||||
presentationData?.slides.map((slide) => slide.id!) || []
|
||||
presentationData?.slides.map((slide: any) => slide.id!) || []
|
||||
}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{presentationData &&
|
||||
presentationData?.slides.map((slide, index) => (
|
||||
presentationData?.slides.map((slide: any, index: number) => (
|
||||
<SortableSlide
|
||||
key={`${slide.id}-${index}`}
|
||||
slide={slide}
|
||||
|
|
|
|||
|
|
@ -99,25 +99,38 @@ const SlideContent = ({
|
|||
|
||||
dispatch(addSlide({ slide: newSlide, index: index + 1 }));
|
||||
setShowNewSlideSelection(false);
|
||||
|
||||
// Scroll to the newly added slide after a short delay to ensure it's rendered
|
||||
setTimeout(() => {
|
||||
const newSlideElement = document.getElementById(`slide-${newSlide.id}`);
|
||||
if (newSlideElement) {
|
||||
newSlideElement.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// Scroll to the new slide when the presentationData is updated
|
||||
// useEffect(() => {
|
||||
// if (
|
||||
// presentationData &&
|
||||
// presentationData?.slides &&
|
||||
// presentationData.slides.length > 1 &&
|
||||
// isStreaming
|
||||
// ) {
|
||||
// const slideElement = document.getElementById(`slide-${index}`);
|
||||
// if (slideElement) {
|
||||
// slideElement.scrollIntoView({
|
||||
// behavior: "smooth",
|
||||
// block: "center",
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }, [presentationData?.slides, isStreaming]);
|
||||
// Scroll to the new slide when streaming and new slides are being generated
|
||||
useEffect(() => {
|
||||
if (
|
||||
presentationData &&
|
||||
presentationData?.slides &&
|
||||
presentationData.slides.length > 1 &&
|
||||
isStreaming
|
||||
) {
|
||||
// Scroll to the last slide (newly generated during streaming)
|
||||
const lastSlideIndex = presentationData.slides.length - 1;
|
||||
const slideElement = document.getElementById(`slide-${presentationData.slides[lastSlideIndex].id}`);
|
||||
if (slideElement) {
|
||||
slideElement.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [presentationData?.slides?.length, isStreaming]);
|
||||
|
||||
// Memoized slide content rendering to prevent unnecessary re-renders
|
||||
const slideContent = useMemo(() => {
|
||||
|
|
@ -127,7 +140,7 @@ const SlideContent = ({
|
|||
return (
|
||||
<>
|
||||
<div
|
||||
id={`slide-${isStreaming ? index : slide.index}`}
|
||||
id={`slide-${slide.id}`}
|
||||
className=" w-full max-w-[1280px] main-slide flex items-center max-md:mb-4 justify-center relative"
|
||||
>
|
||||
{isStreaming && (
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ interface SortableListItemProps {
|
|||
slide: Slide;
|
||||
index: number;
|
||||
selectedSlide: number;
|
||||
onSlideClick: (index: number) => void;
|
||||
onSlideClick: (index: any) => void;
|
||||
}
|
||||
|
||||
export function SortableListItem({ slide, index, selectedSlide, onSlideClick }: SortableListItemProps) {
|
||||
|
|
@ -38,7 +38,7 @@ export function SortableListItem({ slide, index, selectedSlide, onSlideClick }:
|
|||
|
||||
// If the mouse was down for less than 200ms, consider it a click
|
||||
if (timeDiff < 200 && !isDragging) {
|
||||
onSlideClick(slide.index);
|
||||
onSlideClick(slide.id);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ interface SortableSlideProps {
|
|||
slide: Slide;
|
||||
index: number;
|
||||
selectedSlide: number;
|
||||
onSlideClick: (index: number) => void;
|
||||
onSlideClick: (index: any) => void;
|
||||
renderSlideContent: (slide: any) => React.ReactElement;
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ export function SortableSlide({ slide, index, selectedSlide, onSlideClick, rende
|
|||
|
||||
// If the mouse was down for less than 200ms, consider it a click
|
||||
if (timeDiff < 200 && !isDragging) {
|
||||
onSlideClick(slide.index);
|
||||
onSlideClick(slide.id);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -121,8 +121,8 @@ interface ThemeState {
|
|||
}
|
||||
|
||||
const initialState: ThemeState = {
|
||||
currentTheme: ThemeType.Dark,
|
||||
currentColors: defaultColors.dark,
|
||||
currentTheme: ThemeType.Light,
|
||||
currentColors: defaultColors.light,
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
import { ImageSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'bullet-point-slide'
|
||||
export const layoutName = 'Bullet Point Slide'
|
||||
|
|
@ -26,7 +26,7 @@ const bulletPointSlideSchema = z.object({
|
|||
]).meta({
|
||||
description: "List of bullet points (2-8 items)",
|
||||
}),
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
}),
|
||||
})
|
||||
|
|
@ -37,36 +37,11 @@ export type BulletPointSlideData = z.infer<typeof bulletPointSlideSchema>
|
|||
|
||||
interface BulletPointSlideLayoutProps {
|
||||
data?: Partial<BulletPointSlideData>
|
||||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const BulletPointSlideLayout: React.FC<BulletPointSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
const BulletPointSlideLayout: React.FC<BulletPointSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
green: 'from-emerald-600 to-emerald-800',
|
||||
purple: 'from-violet-600 to-violet-800',
|
||||
orange: 'from-orange-600 to-orange-800',
|
||||
red: 'from-red-600 to-red-800'
|
||||
}
|
||||
|
||||
const bulletColors = {
|
||||
blue: 'bg-blue-600',
|
||||
green: 'bg-emerald-600',
|
||||
purple: 'bg-violet-600',
|
||||
orange: 'bg-orange-600',
|
||||
red: 'bg-red-600'
|
||||
}
|
||||
|
||||
const accentSolids = {
|
||||
blue: 'bg-blue-600',
|
||||
green: 'bg-emerald-600',
|
||||
purple: 'bg-violet-600',
|
||||
orange: 'bg-orange-600',
|
||||
red: 'bg-red-600'
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200"
|
||||
|
|
@ -76,76 +51,64 @@ const BulletPointSlideLayout: React.FC<BulletPointSlideLayoutProps> = ({ data: s
|
|||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
{/* Enhanced geometric background decoration */}
|
||||
<div className="absolute inset-0 opacity-[0.03]">
|
||||
<div className={`absolute top-0 right-0 w-96 h-96 ${accentSolids[accentColor]} rounded-full transform translate-x-32 -translate-y-32 blur-3xl`} />
|
||||
<div className={`absolute bottom-0 left-0 w-64 h-64 ${accentSolids[accentColor]} rounded-full transform -translate-x-16 translate-y-16 blur-2xl`} />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex flex-col h-full px-8 py-8">
|
||||
{/* Professional Header */}
|
||||
<header className="mb-6">
|
||||
<h1 className={`text-4xl md:text-5xl font-bold mb-3 tracking-tight leading-tight break-words ${slideData?.backgroundImage
|
||||
? 'text-white drop-shadow-lg'
|
||||
: 'text-slate-900'
|
||||
}`}>
|
||||
<span className={`bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent`}>
|
||||
{slideData?.title}
|
||||
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative z-10 h-full flex flex-col px-16 py-12">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-6xl font-black mb-8 leading-tight tracking-tight max-w-4xl mx-auto">
|
||||
<span className="text-gray-900">{slideData?.title?.split(' ').slice(0, -1).join(' ')}</span>{' '}
|
||||
<span className={`bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent`}>
|
||||
{slideData?.title?.split(' ').slice(-1)[0]}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
{slideData?.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData?.backgroundImage
|
||||
? 'text-slate-200 drop-shadow-md'
|
||||
: 'text-slate-600'
|
||||
}`}>
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
<div className="relative mb-8">
|
||||
<div className="relative flex justify-center">
|
||||
<div className={`w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
<p className="text-xl text-gray-600 font-light mt-6 max-w-2xl mx-auto leading-relaxed">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="relative mt-4">
|
||||
<div className={`w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full blur-sm opacity-50`} />
|
||||
{/* Corner accent */}
|
||||
<div className={`absolute top-0 right-0 w-24 h-24 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full`} />
|
||||
</div>
|
||||
|
||||
{/* Bullet Points */}
|
||||
<div className="flex-1 max-w-4xl mx-auto">
|
||||
<div className="grid gap-6">
|
||||
{slideData?.bulletPoints?.map((point, index) => (
|
||||
<div key={index} className="group flex items-start gap-6 p-6 rounded-2xl bg-white/80 backdrop-blur-sm hover:bg-white/90 transition-all duration-200 hover:shadow-lg">
|
||||
<div className="relative flex-shrink-0 mt-1">
|
||||
<div className={`bg-blue-600 w-4 h-4 rounded-full shadow-lg group-hover:scale-125 transition-all duration-200 relative z-10`} />
|
||||
<div className={`absolute inset-0 bg-blue-600 w-4 h-4 rounded-full blur-sm opacity-50 group-hover:opacity-75 transition-opacity duration-200`} />
|
||||
</div>
|
||||
<p className="text-lg text-gray-800 leading-relaxed font-medium">
|
||||
{point}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Enhanced Bullet Points */}
|
||||
<main className="flex-1 flex items-center justify-center">
|
||||
<div className="bg-white/95 backdrop-blur-lg rounded-2xl p-8 shadow-2xl border border-white/50 w-full max-w-5xl relative overflow-hidden">
|
||||
{/* Content background accent */}
|
||||
<div className={`absolute top-0 right-0 w-24 h-24 bg-gradient-to-bl ${accentColors[accentColor]} opacity-5 rounded-bl-full`} />
|
||||
|
||||
<ul className={`space-y-${slideData?.bulletPoints?.length && slideData?.bulletPoints?.length <= 4 ? '6' : slideData?.bulletPoints?.length && slideData?.bulletPoints?.length <= 6 ? '4' : '3'} relative z-10`}>
|
||||
{slideData?.bulletPoints?.map((point, index) => (
|
||||
<li key={index} className="flex items-start group hover:transform hover:translateX-2 transition-all duration-200">
|
||||
{/* Enhanced bullet point icon */}
|
||||
<div className="relative mr-6 mt-1 flex-shrink-0">
|
||||
<div className={`${bulletColors[accentColor]} w-4 h-4 rounded-full shadow-lg group-hover:scale-125 transition-all duration-200 relative z-10`} />
|
||||
<div className={`absolute inset-0 ${bulletColors[accentColor]} w-4 h-4 rounded-full blur-sm opacity-50 group-hover:opacity-75 transition-opacity duration-200`} />
|
||||
</div>
|
||||
|
||||
{/* Enhanced bullet text */}
|
||||
<span className={`text-lg md:text-xl leading-relaxed break-words font-medium ${slideData?.backgroundImage
|
||||
? 'text-slate-700'
|
||||
: 'text-slate-700'
|
||||
} group-hover:text-slate-900 transition-colors duration-200`}>
|
||||
{point}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enhanced decorative accent */}
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r ${accentColors[accentColor]} shadow-lg`}>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent" />
|
||||
<div className={`absolute inset-0 bg-gradient-to-r ${accentColors[accentColor]} blur-sm opacity-50`} />
|
||||
{/* Bottom accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0">
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r from-blue-600 to-blue-800 shadow-lg`}>
|
||||
{/* Glow effect */}
|
||||
<div className={`absolute inset-0 bg-gradient-to-r from-blue-600 to-blue-800 blur-sm opacity-50`} />
|
||||
</div>
|
||||
{/* Corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full`} />
|
||||
</div>
|
||||
|
||||
{/* Professional corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl ${accentColors[accentColor]} opacity-5 rounded-bl-full`} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
141
servers/nextjs/components/layouts/CardSlideLayout.tsx
Normal file
141
servers/nextjs/components/layouts/CardSlideLayout.tsx
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema, IconSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'card-slide'
|
||||
export const layoutName = 'Card Slide'
|
||||
export const layoutDescription = 'A professional slide featuring feature cards with icons, titles, and descriptions.'
|
||||
|
||||
const cardSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Our Features').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(10).max(200).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
cards: z.array(z.object({
|
||||
icon: IconSchema.default({
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'Default card icon'
|
||||
}).meta({
|
||||
description: "Icon for the card",
|
||||
}),
|
||||
title: z.string().min(2).max(50).meta({
|
||||
description: "Title for the card",
|
||||
}),
|
||||
description: z.string().min(10).max(150).meta({
|
||||
description: "Description of the feature",
|
||||
})
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'Lightning fast icon'
|
||||
},
|
||||
title: 'Lightning Fast',
|
||||
description: 'Optimized performance for quick results and seamless user experience'
|
||||
},
|
||||
{
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
|
||||
prompt: 'Secure and safe icon'
|
||||
},
|
||||
title: 'Secure & Safe',
|
||||
description: 'Enterprise-grade security with advanced encryption and protection'
|
||||
},
|
||||
{
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
|
||||
prompt: 'Precise targeting icon'
|
||||
},
|
||||
title: 'Precise Targeting',
|
||||
description: 'Advanced analytics to reach your exact audience with precision'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of feature cards (2-6 items)",
|
||||
}),
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = cardSlideSchema
|
||||
|
||||
export type CardSlideData = z.infer<typeof cardSlideSchema>
|
||||
|
||||
interface CardSlideLayoutProps {
|
||||
data?: Partial<CardSlideData>
|
||||
}
|
||||
|
||||
const CardSlideLayout: React.FC<CardSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.url})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
|
||||
|
||||
{/* Header section */}
|
||||
<div className="text-center px-12 py-8 print:px-8 print:py-6 relative z-10">
|
||||
<h1 className="text-4xl font-bold text-blue-600 mb-4 leading-tight print:text-3xl">
|
||||
{slideData?.title || 'Our Features'}
|
||||
</h1>
|
||||
{slideData?.subtitle && (
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto leading-relaxed print:text-base">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 w-24 h-1 bg-gradient-to-r from-blue-600 to-blue-800 mx-auto rounded-full"></div>
|
||||
</div>
|
||||
|
||||
{/* Cards section */}
|
||||
<div className="flex-1 px-12 pb-8 print:px-8 print:pb-6 relative z-10">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 h-full items-center">
|
||||
{slideData?.cards?.map((card, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="relative bg-white/95 backdrop-blur-sm rounded-2xl p-6 shadow-2xl border border-white/50 text-center group hover:transform hover:scale-105 transition-all duration-300 print:shadow-md print:p-4"
|
||||
>
|
||||
{/* Card accent */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800" />
|
||||
|
||||
{/* Icon */}
|
||||
<div className="mb-4 group-hover:scale-110 transition-transform duration-300">
|
||||
<div className="w-16 h-16 mx-auto bg-blue-100 rounded-xl flex items-center justify-center overflow-hidden print:w-12 print:h-12">
|
||||
<img
|
||||
src={card.icon?.url || ''}
|
||||
alt={card.icon?.prompt || card.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="text-xl font-bold text-blue-600 mb-3 leading-tight print:text-lg">
|
||||
{card.title}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-gray-700 leading-relaxed text-sm print:text-xs">
|
||||
{card.description}
|
||||
</p>
|
||||
|
||||
{/* Background decoration */}
|
||||
<div className="absolute bottom-0 right-0 w-16 h-16 bg-gradient-to-tl from-blue-600 to-blue-800 opacity-5 rounded-tl-full" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom decorative accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CardSlideLayout
|
||||
173
servers/nextjs/components/layouts/ComparisonSlideLayout.tsx
Normal file
173
servers/nextjs/components/layouts/ComparisonSlideLayout.tsx
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'comparison-slide'
|
||||
export const layoutName = 'Comparison Slide'
|
||||
export const layoutDescription = 'A professional slide for comparing features, options, or before/after scenarios.'
|
||||
|
||||
const comparisonSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Feature Comparison').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(10).max(200).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
leftColumn: z.object({
|
||||
title: z.string().min(2).max(50).meta({
|
||||
description: "Title for left column",
|
||||
}),
|
||||
icon: z.string().default('📊').meta({
|
||||
description: "Icon for left column",
|
||||
}),
|
||||
items: z.array(z.string().min(5).max(100)).min(2).max(8).meta({
|
||||
description: "List of items for left column",
|
||||
})
|
||||
}).default({
|
||||
title: 'Before',
|
||||
icon: '📊',
|
||||
items: [
|
||||
'Manual processes and workflows',
|
||||
'Limited scalability options',
|
||||
'Disconnected systems',
|
||||
'Time-consuming operations'
|
||||
]
|
||||
}).meta({
|
||||
description: "Left comparison column",
|
||||
}),
|
||||
rightColumn: z.object({
|
||||
title: z.string().min(2).max(50).meta({
|
||||
description: "Title for right column",
|
||||
}),
|
||||
icon: z.string().default('🚀').meta({
|
||||
description: "Icon for right column",
|
||||
}),
|
||||
items: z.array(z.string().min(5).max(100)).min(2).max(8).meta({
|
||||
description: "List of items for right column",
|
||||
})
|
||||
}).default({
|
||||
title: 'After',
|
||||
icon: '🚀',
|
||||
items: [
|
||||
'Automated intelligent workflows',
|
||||
'Unlimited scaling capabilities',
|
||||
'Seamlessly integrated ecosystem',
|
||||
'Lightning-fast performance'
|
||||
]
|
||||
}).meta({
|
||||
description: "Right comparison column",
|
||||
}),
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = comparisonSlideSchema
|
||||
|
||||
export type ComparisonSlideData = z.infer<typeof comparisonSlideSchema>
|
||||
|
||||
interface ComparisonSlideLayoutProps {
|
||||
data?: Partial<ComparisonSlideData>
|
||||
}
|
||||
|
||||
const ComparisonSlideLayout: React.FC<ComparisonSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.url})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
|
||||
{/* Header section */}
|
||||
<div className="text-center px-12 py-6 print:px-8 print:py-4 relative z-10">
|
||||
<h1 className="text-4xl font-bold text-blue-600 mb-4 leading-tight print:text-3xl">
|
||||
{slideData?.title || 'Feature Comparison'}
|
||||
</h1>
|
||||
{slideData?.subtitle && (
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto leading-relaxed print:text-base">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 w-24 h-1 bg-gradient-to-r from-blue-600 to-blue-800 mx-auto rounded-full"></div>
|
||||
</div>
|
||||
|
||||
{/* Comparison section */}
|
||||
<div className="flex-1 px-12 pb-8 print:px-8 print:pb-6 relative z-10">
|
||||
<div className="flex gap-8 h-full items-stretch">
|
||||
{/* Left Column */}
|
||||
<div className="flex-1 bg-white/90 backdrop-blur-sm rounded-2xl p-6 shadow-xl border border-white/50 relative overflow-hidden print:shadow-md print:p-4">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-6">
|
||||
<div className="text-4xl mb-3 print:text-3xl">
|
||||
{slideData?.leftColumn?.icon || '📊'}
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-gray-700 mb-2 print:text-xl">
|
||||
{slideData?.leftColumn?.title || 'Before'}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Items */}
|
||||
<div className="space-y-3">
|
||||
{slideData?.leftColumn?.items?.map((item, index) => (
|
||||
<div key={index} className="flex items-start gap-3">
|
||||
<div className="w-2 h-2 bg-gray-400 rounded-full mt-2 flex-shrink-0"></div>
|
||||
<p className="text-gray-700 leading-relaxed text-sm print:text-xs">
|
||||
{item}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Accent */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-gray-400 to-gray-600" />
|
||||
</div>
|
||||
|
||||
{/* VS Divider */}
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="w-16 h-16 bg-gradient-to-br from-blue-600 to-blue-800 rounded-full flex items-center justify-center shadow-2xl print:w-12 print:h-12">
|
||||
<span className="text-white font-bold text-lg print:text-base">VS</span>
|
||||
</div>
|
||||
<div className="w-0.5 h-12 bg-gradient-to-b from-blue-600 to-blue-800 mt-4 print:h-8"></div>
|
||||
</div>
|
||||
|
||||
{/* Right Column */}
|
||||
<div className="flex-1 bg-gradient-to-br from-blue-50 to-white rounded-2xl p-6 shadow-xl border border-blue-200 relative overflow-hidden print:shadow-md print:p-4">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-6">
|
||||
<div className="text-4xl mb-3 print:text-3xl">
|
||||
{slideData?.rightColumn?.icon || '🚀'}
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-blue-600 mb-2 print:text-xl">
|
||||
{slideData?.rightColumn?.title || 'After'}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Items */}
|
||||
<div className="space-y-3">
|
||||
{slideData?.rightColumn?.items?.map((item, index) => (
|
||||
<div key={index} className="flex items-start gap-3">
|
||||
<div className="w-2 h-2 bg-blue-600 rounded-full mt-2 flex-shrink-0"></div>
|
||||
<p className="text-gray-700 leading-relaxed text-sm print:text-xs">
|
||||
{item}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Accent */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom decorative accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ComparisonSlideLayout
|
||||
|
|
@ -1,250 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'conclusion-slide'
|
||||
export const layoutName = 'Conclusion Slide'
|
||||
export const layoutDescription = 'A slide with a title, subtitle, key takeaways, call to action, and contact information'
|
||||
|
||||
const conclusionSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Conclusion').meta({
|
||||
description: "Title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(3).max(150).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
keyTakeaways: z.array(z.string().min(5).max(200)).min(2).max(6).default([
|
||||
'Successfully achieved our primary objectives',
|
||||
'Demonstrated significant value and impact',
|
||||
'Established clear next steps for continued success',
|
||||
'Built strong foundation for future growth'
|
||||
]).meta({
|
||||
description: "Key takeaways or summary points (2-6 items)",
|
||||
}),
|
||||
callToAction: z.string().min(5).max(150).optional().meta({
|
||||
description: "Optional call to action or next steps",
|
||||
}),
|
||||
contactInfo: z.object({
|
||||
email: z.email().optional().meta({
|
||||
description: "Contact email",
|
||||
}),
|
||||
phone: z.string().min(5).max(50).optional().meta({
|
||||
description: "Contact phone number",
|
||||
}),
|
||||
website: z.string().url().optional().meta({
|
||||
description: "Website URL",
|
||||
})
|
||||
}).optional().meta({
|
||||
description: "Optional contact information",
|
||||
}),
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = conclusionSlideSchema
|
||||
|
||||
export type ConclusionSlideData = z.infer<typeof conclusionSlideSchema>
|
||||
|
||||
interface ConclusionSlideLayoutProps {
|
||||
data?: Partial<ConclusionSlideData>
|
||||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const ConclusionSlideLayout: React.FC<ConclusionSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
green: 'from-emerald-600 to-emerald-800',
|
||||
purple: 'from-violet-600 to-violet-800',
|
||||
orange: 'from-orange-600 to-orange-800',
|
||||
red: 'from-red-600 to-red-800'
|
||||
}
|
||||
|
||||
const bulletColors = {
|
||||
blue: 'bg-blue-600',
|
||||
green: 'bg-emerald-600',
|
||||
purple: 'bg-violet-600',
|
||||
orange: 'bg-orange-600',
|
||||
red: 'bg-red-600'
|
||||
}
|
||||
|
||||
const accentSolids = {
|
||||
blue: 'bg-blue-600',
|
||||
green: 'bg-emerald-600',
|
||||
purple: 'bg-violet-600',
|
||||
orange: 'bg-orange-600',
|
||||
red: 'bg-red-600'
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
{/* Enhanced geometric background decoration */}
|
||||
<div className="absolute inset-0 opacity-[0.03]">
|
||||
<div className={`absolute top-0 right-0 w-96 h-96 ${accentSolids[accentColor]} rounded-full transform translate-x-32 -translate-y-32 blur-3xl`} />
|
||||
<div className={`absolute bottom-0 left-0 w-64 h-64 ${accentSolids[accentColor]} rounded-full transform -translate-x-16 translate-y-16 blur-2xl`} />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex flex-col h-full px-8 py-8">
|
||||
{/* Professional Header */}
|
||||
<header className="mb-6">
|
||||
<h1 className={`text-4xl md:text-5xl font-bold mb-3 tracking-tight leading-tight break-words ${slideData?.backgroundImage
|
||||
? 'text-white drop-shadow-lg'
|
||||
: 'text-slate-900'
|
||||
}`}>
|
||||
<span className={`bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent`}>
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{slideData?.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData?.backgroundImage
|
||||
? 'text-slate-200 drop-shadow-md'
|
||||
: 'text-slate-600'
|
||||
}`}>
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="relative mt-4">
|
||||
<div className={`w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Enhanced Content Layout */}
|
||||
<main className="flex-1 grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{/* Key Takeaways - Takes up 2/3 of space */}
|
||||
<div className="lg:col-span-2">
|
||||
<div className="bg-white/95 backdrop-blur-lg rounded-2xl p-8 shadow-2xl border border-white/50 h-full relative overflow-hidden">
|
||||
{/* Content accent */}
|
||||
<div className={`absolute top-0 left-0 right-0 h-1 bg-gradient-to-r ${accentColors[accentColor]}`} />
|
||||
|
||||
<h2 className="text-2xl font-bold text-slate-900 mb-6 relative z-10">Key Takeaways</h2>
|
||||
|
||||
<ul className={`space-y-4 relative z-10`}>
|
||||
{slideData?.keyTakeaways?.map((takeaway, index) => (
|
||||
<li key={index} className="flex items-start group hover:transform hover:translateX-2 transition-all duration-200">
|
||||
{/* Enhanced bullet point */}
|
||||
<div className="relative mr-4 mt-1.5 flex-shrink-0">
|
||||
<div className={`${bulletColors[accentColor]} w-3 h-3 rounded-full shadow-lg group-hover:scale-125 transition-all duration-200 relative z-10`} />
|
||||
<div className={`absolute inset-0 ${bulletColors[accentColor]} w-3 h-3 rounded-full blur-sm opacity-50 group-hover:opacity-75 transition-opacity duration-200`} />
|
||||
</div>
|
||||
|
||||
<span className="text-lg leading-relaxed break-words font-medium text-slate-700 group-hover:text-slate-900 transition-colors duration-200">
|
||||
{takeaway}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{/* Background decoration */}
|
||||
<div className={`absolute bottom-0 right-0 w-16 h-16 bg-gradient-to-tl ${accentColors[accentColor]} opacity-5 rounded-tl-full`} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Call to Action & Contact Info - Takes up 1/3 of space */}
|
||||
<div className="space-y-6">
|
||||
{/* Call to Action */}
|
||||
{slideData?.callToAction && (
|
||||
<div className="bg-white/95 backdrop-blur-lg rounded-2xl p-6 shadow-2xl border border-white/50 text-center relative overflow-hidden">
|
||||
{/* CTA accent */}
|
||||
<div className={`absolute top-0 left-0 right-0 h-1 bg-gradient-to-r ${accentColors[accentColor]}`} />
|
||||
|
||||
<div className={`w-12 h-12 mx-auto mb-4 rounded-full ${accentSolids[accentColor]} flex items-center justify-center shadow-lg relative`}>
|
||||
<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="M13 7l5 5m0 0l-5 5m5-5H6" />
|
||||
</svg>
|
||||
<div className={`absolute inset-0 rounded-full ${accentSolids[accentColor]} blur-md opacity-50`} />
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-bold text-slate-900 mb-3">Next Steps</h3>
|
||||
<p className="text-sm text-slate-600 leading-relaxed break-words font-medium">
|
||||
{slideData?.callToAction}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Contact Information */}
|
||||
{slideData?.contactInfo && Object.values(slideData?.contactInfo).some(Boolean) && (
|
||||
<div className="bg-white/95 backdrop-blur-lg rounded-2xl p-6 shadow-2xl border border-white/50 relative overflow-hidden">
|
||||
{/* Contact accent */}
|
||||
<div className={`absolute top-0 left-0 right-0 h-1 bg-gradient-to-r ${accentColors[accentColor]}`} />
|
||||
|
||||
<h3 className="text-lg font-bold text-slate-900 mb-4 text-center">Get in Touch</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
{slideData?.contactInfo?.email && (
|
||||
<a
|
||||
href={`mailto:${slideData?.contactInfo?.email}`}
|
||||
className="flex items-center space-x-3 p-3 rounded-xl bg-slate-50 hover:bg-slate-100 transition-colors duration-200 group"
|
||||
>
|
||||
<div className={`w-8 h-8 rounded-full ${accentSolids[accentColor]} flex items-center justify-center group-hover:scale-110 transition-transform duration-200`}>
|
||||
<svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
|
||||
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-slate-700 break-all">{slideData?.contactInfo?.email}</span>
|
||||
</a>
|
||||
)}
|
||||
|
||||
{slideData?.contactInfo?.phone && (
|
||||
<a
|
||||
href={`tel:${slideData?.contactInfo?.phone}`}
|
||||
className="flex items-center space-x-3 p-3 rounded-xl bg-slate-50 hover:bg-slate-100 transition-colors duration-200 group"
|
||||
>
|
||||
<div className={`w-8 h-8 rounded-full ${accentSolids[accentColor]} flex items-center justify-center group-hover:scale-110 transition-transform duration-200`}>
|
||||
<svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-slate-700">{slideData.contactInfo.phone}</span>
|
||||
</a>
|
||||
)}
|
||||
|
||||
{slideData?.contactInfo?.website && (
|
||||
<a
|
||||
href={slideData?.contactInfo?.website}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center space-x-3 p-3 rounded-xl bg-slate-50 hover:bg-slate-100 transition-colors duration-200 group"
|
||||
>
|
||||
<div className={`w-8 h-8 rounded-full ${accentSolids[accentColor]} flex items-center justify-center group-hover:scale-110 transition-transform duration-200`}>
|
||||
<svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M4.083 9h1.946c.089-1.546.383-2.97.837-4.118A6.004 6.004 0 004.083 9zM10 2a8 8 0 100 16 8 8 0 000-16zm0 2c-.076 0-.232.032-.465.262-.238.234-.497.623-.737 1.182-.389.907-.673 2.142-.766 3.556h3.936c-.093-1.414-.377-2.649-.766-3.556-.24-.559-.5-.948-.737-1.182C10.232 4.032 10.076 4 10 4zm3.971 5c-.089-1.546-.383-2.97-.837-4.118A6.004 6.004 0 0115.917 9h-1.946zm-2.003 2H8.032c.093 1.414.377 2.649.766 3.556.24.559.5.948.737 1.182.233.23.389.262.465.262.076 0 .232-.032.465-.262.238-.234.498-.623.737-1.182.389-.907.673-2.142.766-3.556zm1.166 4.118c.454-1.147.748-2.572.837-4.118h1.946a6.004 6.004 0 01-2.783 4.118zm-6.268 0C6.412 13.97 6.118 12.546 6.03 11H4.083a6.004 6.004 0 002.783 4.118z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-slate-700 break-all">{slideData.contactInfo.website.replace(/^https?:\/\//, '')}</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Background decoration */}
|
||||
<div className={`absolute bottom-0 right-0 w-12 h-12 bg-gradient-to-tl ${accentColors[accentColor]} opacity-5 rounded-tl-full`} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{/* Enhanced decorative accent */}
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r ${accentColors[accentColor]} shadow-lg`}>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent" />
|
||||
<div className={`absolute inset-0 bg-gradient-to-r ${accentColors[accentColor]} blur-sm opacity-50`} />
|
||||
</div>
|
||||
|
||||
{/* Professional corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl ${accentColors[accentColor]} opacity-5 rounded-bl-full`} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConclusionSlideLayout
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
import { ImageSchema } from './defaultSchemes';
|
||||
|
||||
|
||||
export const layoutId = 'content-slide'
|
||||
|
|
@ -17,7 +17,7 @@ const contentSlideSchema = z.object({
|
|||
content: z.string().min(10).max(1000).default('Your slide content goes here. This is where you can add detailed information, explanations, or any other text content that supports your presentation.').meta({
|
||||
description: "Main content text",
|
||||
}),
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
|
@ -28,26 +28,9 @@ export type ContentSlideData = z.infer<typeof contentSlideSchema>
|
|||
|
||||
interface ContentSlideLayoutProps {
|
||||
data?: Partial<ContentSlideData>
|
||||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const ContentSlideLayout: React.FC<ContentSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
green: 'from-emerald-600 to-emerald-800',
|
||||
purple: 'from-violet-600 to-violet-800',
|
||||
orange: 'from-orange-600 to-orange-800',
|
||||
red: 'from-red-600 to-red-800'
|
||||
}
|
||||
|
||||
const accentSolids = {
|
||||
blue: 'bg-blue-600',
|
||||
green: 'bg-emerald-600',
|
||||
purple: 'bg-violet-600',
|
||||
orange: 'bg-orange-600',
|
||||
red: 'bg-red-600'
|
||||
}
|
||||
const ContentSlideLayout: React.FC<ContentSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -58,11 +41,7 @@ const ContentSlideLayout: React.FC<ContentSlideLayoutProps> = ({ data: slideData
|
|||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
{/* Enhanced geometric background decoration */}
|
||||
<div className="absolute inset-0 opacity-[0.03]">
|
||||
<div className={`absolute top-0 right-0 w-96 h-96 ${accentSolids[accentColor]} rounded-full transform translate-x-32 -translate-y-32 blur-3xl`} />
|
||||
<div className={`absolute bottom-0 left-0 w-64 h-64 ${accentSolids[accentColor]} rounded-full transform -translate-x-16 translate-y-16 blur-2xl`} />
|
||||
</div>
|
||||
|
||||
|
||||
{/* Grid overlay for professional look */}
|
||||
<div className="absolute inset-0 opacity-[0.015]" style={{
|
||||
|
|
@ -71,63 +50,57 @@ const ContentSlideLayout: React.FC<ContentSlideLayoutProps> = ({ data: slideData
|
|||
backgroundSize: '40px 40px'
|
||||
}} />
|
||||
|
||||
<div className="relative z-10 flex flex-col h-full px-8 py-8">
|
||||
{/* Professional Header */}
|
||||
<header className="mb-6">
|
||||
<h1 className={`text-4xl md:text-5xl font-bold mb-3 tracking-tight leading-tight break-words ${slideData?.backgroundImage
|
||||
? 'text-white drop-shadow-lg'
|
||||
: 'text-slate-900'
|
||||
}`}>
|
||||
<span className={`bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent`}>
|
||||
{slideData?.title}
|
||||
{/* Content */}
|
||||
<div className="relative z-10 h-full flex flex-col px-16 py-12">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-16">
|
||||
<h1 className="text-6xl font-black mb-8 leading-tight tracking-tight max-w-4xl mx-auto">
|
||||
<span className="text-gray-900">{slideData?.title?.split(' ').slice(0, -1).join(' ')}</span>{' '}
|
||||
<span className={`bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent`}>
|
||||
{slideData?.title?.split(' ').slice(-1)[0]}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
{slideData?.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData?.backgroundImage
|
||||
? 'text-slate-200 drop-shadow-md'
|
||||
: 'text-slate-600'
|
||||
}`}>
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
<div className="relative mb-8">
|
||||
<div className="relative flex justify-center">
|
||||
<div className={`w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
<p className="text-xl text-gray-600 font-light mt-6 max-w-2xl mx-auto leading-relaxed">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="relative mt-4">
|
||||
<div className={`w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
</header>
|
||||
{/* Corner accent */}
|
||||
<div className={`absolute top-0 right-0 w-24 h-24 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full`} />
|
||||
</div>
|
||||
|
||||
{/* Main Content with Enhanced Styling */}
|
||||
<main className="flex-1 flex items-center justify-center">
|
||||
<div className="bg-white/95 backdrop-blur-lg rounded-2xl p-8 shadow-2xl border border-white/50 max-w-5xl relative overflow-hidden">
|
||||
{/* Content background accent */}
|
||||
<div className={`absolute top-0 right-0 w-24 h-24 bg-gradient-to-bl ${accentColors[accentColor]} opacity-5 rounded-bl-full`} />
|
||||
|
||||
<div className={`text-lg md:text-xl leading-relaxed break-words relative z-10 ${slideData?.backgroundImage
|
||||
? 'text-slate-700'
|
||||
: 'text-slate-700'
|
||||
}`}>
|
||||
{/* Content */}
|
||||
<div className="flex-1 max-w-4xl mx-auto">
|
||||
<div className="prose prose-lg prose-gray max-w-none">
|
||||
<div className="text-lg text-gray-700 leading-relaxed">
|
||||
{slideData?.content?.split('\n').map((paragraph, index) => (
|
||||
paragraph.trim() && (
|
||||
<p key={index} className="mb-5 last:mb-0 font-medium leading-relaxed">
|
||||
{paragraph}
|
||||
</p>
|
||||
)
|
||||
<p key={index} className="mb-6">
|
||||
{paragraph}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enhanced decorative accent */}
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r ${accentColors[accentColor]} shadow-lg`}>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent" />
|
||||
<div className={`absolute inset-0 bg-gradient-to-r ${accentColors[accentColor]} blur-sm opacity-50`} />
|
||||
{/* Bottom accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0">
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r from-blue-600 to-blue-800 shadow-lg`}>
|
||||
{/* Glow effect */}
|
||||
<div className={`absolute inset-0 bg-gradient-to-r from-blue-600 to-blue-800 blur-sm opacity-50`} />
|
||||
</div>
|
||||
{/* Corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full`} />
|
||||
</div>
|
||||
|
||||
{/* Professional corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl ${accentColors[accentColor]} opacity-5 rounded-bl-full`} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
import { ImageSchema } from './defaultSchemes';
|
||||
|
||||
|
||||
export const layoutId = 'first-slide'
|
||||
|
|
@ -23,7 +23,7 @@ const firstSlideSchema = z.object({
|
|||
company: z.string().max(100).default('Company Name').optional().meta({
|
||||
description: "Company or organization name",
|
||||
}),
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
|
@ -34,28 +34,11 @@ export type FirstSlideData = z.infer<typeof firstSlideSchema>
|
|||
|
||||
interface FirstSlideLayoutProps {
|
||||
data?: Partial<FirstSlideData>
|
||||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const FirstSlideLayout: React.FC<FirstSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
const FirstSlideLayout: React.FC<FirstSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
green: 'from-emerald-600 to-emerald-800',
|
||||
purple: 'from-violet-600 to-violet-800',
|
||||
orange: 'from-orange-600 to-orange-800',
|
||||
red: 'from-red-600 to-red-800'
|
||||
}
|
||||
|
||||
const accentSolids = {
|
||||
blue: 'bg-blue-600',
|
||||
green: 'bg-emerald-600',
|
||||
purple: 'bg-violet-600',
|
||||
orange: 'bg-orange-600',
|
||||
red: 'bg-red-600'
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200"
|
||||
|
|
@ -65,83 +48,66 @@ const FirstSlideLayout: React.FC<FirstSlideLayoutProps> = ({ data: slideData, ac
|
|||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
{/* Enhanced geometric background decoration */}
|
||||
<div className="absolute inset-0 opacity-[0.03]">
|
||||
<div className={`absolute top-0 right-0 w-96 h-96 ${accentSolids[accentColor]} rounded-full transform translate-x-32 -translate-y-32 blur-3xl`} />
|
||||
<div className={`absolute bottom-0 left-0 w-64 h-64 ${accentSolids[accentColor]} rounded-full transform -translate-x-16 translate-y-16 blur-2xl`} />
|
||||
<div className={`absolute top-1/2 left-1/2 w-48 h-48 ${accentSolids[accentColor]} rounded-full transform -translate-x-1/2 -translate-y-1/2 blur-3xl opacity-50`} />
|
||||
</div>
|
||||
|
||||
{/* Grid overlay for professional look */}
|
||||
<div className="absolute inset-0 opacity-[0.02]" style={{
|
||||
backgroundImage: `linear-gradient(0deg, rgba(0,0,0,0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0,0,0,0.1) 1px, transparent 1px)`,
|
||||
backgroundSize: '60px 60px'
|
||||
}} />
|
||||
|
||||
<div className="relative z-10 flex flex-col h-full px-8 py-8 justify-center items-center text-center">
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 flex flex-col justify-center items-center max-w-5xl">
|
||||
{/* Content */}
|
||||
<div className="relative z-10 h-full flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex-1 flex flex-col justify-center items-center text-center px-16 py-12">
|
||||
{/* Title */}
|
||||
<h1 className={`text-5xl md:text-6xl font-black mb-6 tracking-tight leading-[0.9] break-words ${slideData?.backgroundImage
|
||||
? 'text-white drop-shadow-2xl'
|
||||
: 'text-slate-900'
|
||||
}`}>
|
||||
<span className={`bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent`}>
|
||||
{slideData?.title}
|
||||
<h1 className="text-7xl font-black mb-8 leading-tight tracking-tight max-w-4xl">
|
||||
<span className="text-gray-900">{slideData?.title?.split(' ').slice(0, -1).join(' ')}</span>{' '}
|
||||
<span className={`bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent`}>
|
||||
{slideData?.title?.split(' ').slice(-1)[0]}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
{slideData?.subtitle && (
|
||||
<p className={`text-xl md:text-2xl font-light leading-relaxed mb-8 break-words max-w-4xl ${slideData?.backgroundImage
|
||||
? 'text-slate-200 drop-shadow-lg'
|
||||
: 'text-slate-600'
|
||||
}`}>
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
<div className="relative mb-12">
|
||||
<div className="relative">
|
||||
<div className={`w-32 h-1.5 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1.5 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
<p className="text-2xl text-gray-600 font-light mt-6 max-w-2xl leading-relaxed">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Enhanced Accent Line */}
|
||||
<div className="relative mb-8">
|
||||
<div className={`w-32 h-1.5 bg-gradient-to-r ${accentColors[accentColor]} rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1.5 bg-gradient-to-r ${accentColors[accentColor]} rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
{/* Date */}
|
||||
{slideData?.date && (
|
||||
<p className={`text-base font-medium break-words from-blue-600 to-blue-800 bg-gradient-to-r bg-clip-text text-transparent`}>
|
||||
{slideData.date}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Professional Metadata Container */}
|
||||
<div className="bg-white/90 backdrop-blur-md rounded-2xl px-8 py-6 shadow-xl border border-white/40">
|
||||
<div className="space-y-3">
|
||||
{/* Footer with Author and Company */}
|
||||
<div className="px-16 pb-8">
|
||||
<div className="flex justify-between items-end">
|
||||
<div className="text-left">
|
||||
{slideData?.author && (
|
||||
<p className={`text-lg font-semibold break-words text-slate-800`}>
|
||||
{slideData?.author}
|
||||
</p>
|
||||
<p className="text-lg font-semibold text-gray-800 mb-1">{slideData.author}</p>
|
||||
)}
|
||||
|
||||
{slideData?.company && (
|
||||
<p className={`text-base font-medium break-words ${accentColors[accentColor]} bg-gradient-to-r bg-clip-text text-transparent`}>
|
||||
{slideData?.company}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{slideData?.date && (
|
||||
<p className={`text-sm break-words text-slate-600 font-medium tracking-wide`}>
|
||||
{slideData?.date}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">{slideData.company}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enhanced decorative accent with glow effect */}
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r ${accentColors[accentColor]} shadow-lg`}>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent" />
|
||||
<div className={`absolute inset-0 bg-gradient-to-r ${accentColors[accentColor]} blur-sm opacity-50`} />
|
||||
{/* Bottom accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0">
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r from-blue-600 to-blue-800 shadow-lg`}>
|
||||
{/* Glow effect */}
|
||||
<div className={`absolute inset-0 bg-gradient-to-r from-blue-600 to-blue-800 blur-sm opacity-50`} />
|
||||
</div>
|
||||
{/* Corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full`} />
|
||||
<div className={`absolute bottom-0 left-0 w-24 h-24 bg-gradient-to-tr from-blue-600 to-blue-800 opacity-5 rounded-tr-full`} />
|
||||
</div>
|
||||
|
||||
{/* Professional corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl ${accentColors[accentColor]} opacity-5 rounded-bl-full`} />
|
||||
<div className={`absolute bottom-0 left-0 w-24 h-24 bg-gradient-to-tr ${accentColors[accentColor]} opacity-5 rounded-tr-full`} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
109
servers/nextjs/components/layouts/IconSlideLayout.tsx
Normal file
109
servers/nextjs/components/layouts/IconSlideLayout.tsx
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'icon-slide'
|
||||
export const layoutName = 'Icon Slide'
|
||||
export const layoutDescription = 'A professional slide featuring a prominent icon with title, description, and key points.'
|
||||
|
||||
|
||||
|
||||
const iconSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Key Features').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(10).max(200).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
icon: IconSchema.default({
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'A beautiful road in the mountains'
|
||||
}).meta({
|
||||
description: "Main slide icon",
|
||||
}),
|
||||
features: z.array(z.string().min(10).max(150)).min(2).max(6).default([
|
||||
'Advanced analytics and reporting capabilities',
|
||||
'Seamless integration with existing systems',
|
||||
'Real-time collaboration and communication',
|
||||
'Enhanced security and data protection'
|
||||
]).meta({
|
||||
description: "List of key features (2-6 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = iconSlideSchema
|
||||
|
||||
export type IconSlideData = z.infer<typeof iconSlideSchema>
|
||||
|
||||
interface IconSlideLayoutProps {
|
||||
data?: Partial<IconSlideData>
|
||||
}
|
||||
|
||||
const IconSlideLayout: React.FC<IconSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
|
||||
return (
|
||||
<div className="relative w-full aspect-[16/9] flex bg-white overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300">
|
||||
{/* Subtle background pattern for print compatibility */}
|
||||
<div className="absolute inset-0 opacity-[0.02] print:opacity-[0.01]">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-gray-200 to-transparent transform rotate-45 scale-150"></div>
|
||||
</div>
|
||||
|
||||
{/* Left side - Icon and title */}
|
||||
<div className="flex-1 flex flex-col justify-center items-center p-12 relative">
|
||||
{/* Icon container */}
|
||||
<div className="relative mb-8 p-8 rounded-3xl bg-white border-2 shadow-xl print:shadow-md">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-600 to-blue-800 opacity-5 rounded-3xl"></div>
|
||||
<img
|
||||
src={slideData?.icon?.url || ''}
|
||||
alt={slideData?.icon?.prompt || ''}
|
||||
className="w-24 h-24 object-contain relative z-10 print:w-20 print:h-20"
|
||||
/>
|
||||
<div className="absolute -bottom-2 -right-2 w-6 h-6 bg-gradient-to-br from-blue-600 to-blue-800 rounded-full opacity-80"></div>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h1 className="text-4xl font-bold text-center text-blue-700 mb-4 max-w-sm leading-tight print:text-3xl">
|
||||
{slideData?.title || 'Key Features'}
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
{slideData?.subtitle && (
|
||||
<p className="text-lg text-gray-600 text-center max-w-md leading-relaxed print:text-base">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Decorative element */}
|
||||
<div className="mt-6 w-16 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full"></div>
|
||||
</div>
|
||||
|
||||
{/* Right side - Content */}
|
||||
<div className="flex-1 flex flex-col justify-center p-12 bg-white/50 print:bg-transparent">
|
||||
{/* Features */}
|
||||
<div className="space-y-4">
|
||||
{slideData?.features?.map((feature, index) => (
|
||||
<div key={index} className="flex items-start space-x-4">
|
||||
<div className="flex-shrink-0 w-8 h-8 bg-gradient-to-br from-blue-600 to-blue-800 rounded-full flex items-center justify-center mt-1 print:w-6 print:h-6">
|
||||
<span className="text-white font-semibold text-sm print:text-xs">
|
||||
{index + 1}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-700 leading-relaxed print:text-sm">
|
||||
{feature}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Bottom accent line */}
|
||||
<div className="mt-8 w-full h-0.5 bg-gradient-to-r from-blue-600 to-blue-800 opacity-20"></div>
|
||||
</div>
|
||||
|
||||
{/* Top decorative border */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconSlideLayout
|
||||
115
servers/nextjs/components/layouts/ImageSlideLayout.tsx
Normal file
115
servers/nextjs/components/layouts/ImageSlideLayout.tsx
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'image-slide'
|
||||
export const layoutName = 'Image Slide'
|
||||
export const layoutDescription = 'A professional slide featuring a prominent image with overlaid text content and call-to-action elements.'
|
||||
|
||||
|
||||
|
||||
const imageSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Our Vision').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(10).max(200).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
description: z.string().min(20).max(500).default('Transform your ideas into reality with innovative solutions that drive success and growth.').meta({
|
||||
description: "Main description text",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'A beautiful road in the mountains'
|
||||
}).meta({
|
||||
description: "Main slide image",
|
||||
}),
|
||||
buttonText: z.string().max(50).default('Learn More').optional().meta({
|
||||
description: "Optional button text",
|
||||
}),
|
||||
buttonUrl: z.string().url().optional().meta({
|
||||
description: "Optional button URL",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = imageSlideSchema
|
||||
|
||||
export type ImageSlideData = z.infer<typeof imageSlideSchema>
|
||||
|
||||
interface ImageSlideLayoutProps {
|
||||
data?: Partial<ImageSlideData>
|
||||
}
|
||||
|
||||
const ImageSlideLayout: React.FC<ImageSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div className="relative w-full aspect-[16/9] flex bg-gradient-to-br from-slate-50 to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300">
|
||||
{/* Left panel - Image */}
|
||||
<div className="flex-1 relative">
|
||||
<img
|
||||
src={slideData?.image?.url || ''}
|
||||
alt={slideData?.image?.prompt || ''}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
{/* Overlay gradient */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-black/70 via-black/50 to-transparent"></div>
|
||||
|
||||
{/* Content overlay */}
|
||||
<div className="absolute inset-0 flex flex-col justify-center p-12 text-white print:p-8">
|
||||
{/* Title */}
|
||||
<h1 className="text-5xl font-bold mb-6 leading-tight print:text-4xl">
|
||||
{slideData?.title || 'Our Vision'}
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
{slideData?.subtitle && (
|
||||
<p className="text-xl text-gray-200 mb-6 max-w-xl leading-relaxed print:text-lg">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-lg text-gray-100 mb-8 max-w-2xl leading-relaxed print:text-base">
|
||||
{slideData?.description || 'Transform your ideas into reality with innovative solutions that drive success and growth.'}
|
||||
</p>
|
||||
|
||||
{/* Button */}
|
||||
{slideData?.buttonText && (
|
||||
<div className="inline-flex">
|
||||
<button className="px-8 py-3 bg-gradient-to-r from-blue-600 to-blue-800 text-white font-semibold rounded-lg shadow-lg print:shadow-md">
|
||||
{slideData.buttonText}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Decorative line */}
|
||||
<div className="mt-8 w-24 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right panel for additional context */}
|
||||
<div className="w-80 bg-white/95 backdrop-blur-sm flex flex-col justify-center p-8 print:w-64 print:bg-white print:p-6">
|
||||
{/* Feature card */}
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center space-x-4 p-4 rounded-xl bg-white shadow-md print:shadow-sm">
|
||||
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center flex-shrink-0 print:w-10 print:h-10">
|
||||
<span className="text-blue-600 font-bold text-lg print:text-base">✓</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900 text-sm print:text-xs">Professional Quality</h3>
|
||||
<p className="text-gray-600 text-xs print:text-xs">Excellence in every detail</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom accent line */}
|
||||
<div className="mt-8 w-full h-2 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full opacity-20"></div>
|
||||
</div>
|
||||
|
||||
{/* Top decorative border */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ImageSlideLayout
|
||||
114
servers/nextjs/components/layouts/NumberBoxSlideLayout.tsx
Normal file
114
servers/nextjs/components/layouts/NumberBoxSlideLayout.tsx
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'number-box-slide'
|
||||
export const layoutName = 'Number Box Slide'
|
||||
export const layoutDescription = 'A professional slide featuring numbered content boxes with titles, descriptions, and clean typography.'
|
||||
|
||||
|
||||
|
||||
const numberBoxSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Key Metrics').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(10).max(200).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
numberBoxes: z.array(z.object({
|
||||
number: z.string().min(1).max(20).meta({
|
||||
description: "Number or statistic to display",
|
||||
}),
|
||||
title: z.string().min(2).max(50).meta({
|
||||
description: "Title for the number",
|
||||
}),
|
||||
description: z.string().min(10).max(100).meta({
|
||||
description: "Brief description of the metric",
|
||||
})
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
number: '150+',
|
||||
title: 'Projects Completed',
|
||||
description: 'Successfully delivered across various industries'
|
||||
},
|
||||
{
|
||||
number: '98%',
|
||||
title: 'Client Satisfaction',
|
||||
description: 'Consistently exceeding expectations'
|
||||
},
|
||||
{
|
||||
number: '24/7',
|
||||
title: 'Support Available',
|
||||
description: 'Round-the-clock assistance for all clients'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of number boxes (2-6 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = numberBoxSlideSchema
|
||||
|
||||
export type NumberBoxSlideData = z.infer<typeof numberBoxSlideSchema>
|
||||
|
||||
interface NumberBoxSlideLayoutProps {
|
||||
data?: Partial<NumberBoxSlideData>
|
||||
}
|
||||
|
||||
const NumberBoxSlideLayout: React.FC<NumberBoxSlideLayoutProps> = ({ data: slideData }) => {
|
||||
// Parse and validate data with defaults
|
||||
// const data = numberBoxSlideSchema.parse(slideData || {})
|
||||
|
||||
return (
|
||||
<div className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300">
|
||||
{/* Subtle background pattern */}
|
||||
<div className="absolute inset-0 opacity-[0.02] print:opacity-[0.01]">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-gray-200 to-transparent transform -rotate-45 scale-150"></div>
|
||||
</div>
|
||||
|
||||
{/* Header section */}
|
||||
<div className="text-center px-12 py-8 print:px-8 print:py-6">
|
||||
<h1 className="text-4xl font-bold text-blue-600 mb-4 leading-tight print:text-3xl">
|
||||
{slideData?.title || 'Key Metrics'}
|
||||
</h1>
|
||||
{slideData?.subtitle && (
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto leading-relaxed print:text-base">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 w-24 h-1 bg-gradient-to-r from-blue-600 to-blue-800 mx-auto rounded-full"></div>
|
||||
</div>
|
||||
|
||||
{/* Content section */}
|
||||
<div className="flex-1 px-12 pb-8 print:px-8 print:pb-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{slideData?.numberBoxes?.map((box, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="relative bg-white rounded-2xl p-6 border-blue-200 border-2 shadow-blue-200 shadow-lg print:shadow-md print:p-4"
|
||||
>
|
||||
{/* Number display */}
|
||||
<div className="text-center mb-4">
|
||||
<div className="text-4xl font-black text-blue-600 mb-2 print:text-3xl">
|
||||
{box.number}
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-blue-600 mb-3 leading-tight print:text-lg">
|
||||
{box.title}
|
||||
</h3>
|
||||
<p className="text-gray-700 leading-relaxed text-sm print:text-xs">
|
||||
{box.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Decorative corner accent */}
|
||||
<div className="absolute bottom-0 right-0 w-12 h-12 bg-gradient-to-tl from-blue-600 to-blue-800 opacity-5 rounded-tl-full"></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom decorative accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NumberBoxSlideLayout
|
||||
|
|
@ -1,52 +1,60 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
|
||||
import { ImageSchema, IconSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'process-slide'
|
||||
export const layoutName = 'Process Slide'
|
||||
export const layoutDescription = 'A slide with a title, subtitle, and process steps'
|
||||
export const layoutDescription = 'A professional slide featuring step-by-step processes with icons, titles, and descriptions.'
|
||||
|
||||
const processSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Our Process').meta({
|
||||
description: "Title of the slide",
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(3).max(150).optional().meta({
|
||||
subtitle: z.string().min(10).max(200).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
processSteps: z.array(z.object({
|
||||
step: z.number().min(1).max(10).meta({
|
||||
description: "Step number",
|
||||
steps: z.array(z.object({
|
||||
icon: IconSchema.default({
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'Default step icon'
|
||||
}).meta({
|
||||
description: "Icon for the step",
|
||||
}),
|
||||
title: z.string().min(3).max(100).meta({
|
||||
description: "Step title",
|
||||
title: z.string().min(2).max(50).meta({
|
||||
description: "Title for the step",
|
||||
}),
|
||||
description: z.string().min(10).max(200).meta({
|
||||
description: "Step description",
|
||||
description: z.string().min(10).max(150).meta({
|
||||
description: "Description of the step",
|
||||
})
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
step: 1,
|
||||
title: 'Discovery',
|
||||
description: 'Understanding requirements and gathering initial insights'
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'Plan and strategy icon'
|
||||
},
|
||||
title: 'Plan & Strategy',
|
||||
description: 'Define objectives, analyze requirements, and create a comprehensive roadmap'
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: 'Planning',
|
||||
description: 'Strategic planning and roadmap development'
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
|
||||
prompt: 'Execute and build icon'
|
||||
},
|
||||
title: 'Execute & Build',
|
||||
description: 'Implement solutions with precision using cutting-edge technology and best practices'
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: 'Implementation',
|
||||
description: 'Executing the plan with precision and quality'
|
||||
},
|
||||
{
|
||||
step: 4,
|
||||
title: 'Delivery',
|
||||
description: 'Final delivery and ongoing support'
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
|
||||
prompt: 'Launch and optimize icon'
|
||||
},
|
||||
title: 'Launch & Optimize',
|
||||
description: 'Deploy the solution and continuously improve based on performance metrics'
|
||||
}
|
||||
]).describe('Process steps (2-6 items)'),
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
]).meta({
|
||||
description: "List of process steps (2-6 items)",
|
||||
}),
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
|
@ -57,140 +65,85 @@ export type ProcessSlideData = z.infer<typeof processSlideSchema>
|
|||
|
||||
interface ProcessSlideLayoutProps {
|
||||
data?: Partial<ProcessSlideData>
|
||||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const ProcessSlideLayout: React.FC<ProcessSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
green: 'from-emerald-600 to-emerald-800',
|
||||
purple: 'from-violet-600 to-violet-800',
|
||||
orange: 'from-orange-600 to-orange-800',
|
||||
red: 'from-red-600 to-red-800'
|
||||
}
|
||||
|
||||
const stepColors = {
|
||||
blue: 'bg-blue-600 text-white border-blue-600',
|
||||
green: 'bg-emerald-600 text-white border-emerald-600',
|
||||
purple: 'bg-violet-600 text-white border-violet-600',
|
||||
orange: 'bg-orange-600 text-white border-orange-600',
|
||||
red: 'bg-red-600 text-white border-red-600'
|
||||
}
|
||||
|
||||
const accentSolids = {
|
||||
blue: 'bg-blue-600',
|
||||
green: 'bg-emerald-600',
|
||||
purple: 'bg-violet-600',
|
||||
orange: 'bg-orange-600',
|
||||
red: 'bg-red-600'
|
||||
}
|
||||
const ProcessSlideLayout: React.FC<ProcessSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200"
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData?.backgroundImage})`,
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.url})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
{/* Enhanced geometric background decoration */}
|
||||
<div className="absolute inset-0 opacity-[0.03]">
|
||||
<div className={`absolute top-0 right-0 w-96 h-96 ${accentSolids[accentColor]} rounded-full transform translate-x-32 -translate-y-32 blur-3xl`} />
|
||||
<div className={`absolute bottom-0 left-0 w-64 h-64 ${accentSolids[accentColor]} rounded-full transform -translate-x-16 translate-y-16 blur-2xl`} />
|
||||
|
||||
|
||||
{/* Header section */}
|
||||
<div className="text-center px-12 py-8 print:px-8 print:py-6 relative z-10">
|
||||
<h1 className="text-4xl font-bold text-blue-600 mb-4 leading-tight print:text-3xl">
|
||||
{slideData?.title || 'Our Process'}
|
||||
</h1>
|
||||
{slideData?.subtitle && (
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto leading-relaxed print:text-base">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 w-24 h-1 bg-gradient-to-r from-blue-600 to-blue-800 mx-auto rounded-full"></div>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex flex-col h-full px-8 py-8">
|
||||
{/* Professional Header */}
|
||||
<header className="mb-6">
|
||||
<h1 className={`text-4xl md:text-5xl font-bold mb-3 tracking-tight leading-tight break-words ${slideData?.backgroundImage
|
||||
? 'text-white drop-shadow-lg'
|
||||
: 'text-slate-900'
|
||||
}`}>
|
||||
<span className={`bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent`}>
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{slideData?.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData?.backgroundImage
|
||||
? 'text-slate-200 drop-shadow-md'
|
||||
: 'text-slate-600'
|
||||
}`}>
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="relative mt-4">
|
||||
<div className={`w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Enhanced Process Steps */}
|
||||
<main className="flex-1 flex items-center justify-center">
|
||||
<div className="w-full max-w-6xl">
|
||||
<div className="flex items-center justify-between">
|
||||
{slideData?.processSteps?.map((step, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{/* Process Step */}
|
||||
<div className="flex flex-col items-center text-center group" style={{ width: `${100 / (slideData?.processSteps?.length || 0)}%` }}>
|
||||
{/* Step Number Circle */}
|
||||
<div className={`w-16 h-16 rounded-full ${stepColors[accentColor]} flex items-center justify-center text-2xl font-bold mb-4 shadow-2xl border-4 group-hover:scale-110 transition-all duration-300 relative`}>
|
||||
<span className="relative z-10">{step.step}</span>
|
||||
<div className={`absolute inset-0 rounded-full ${accentSolids[accentColor]} blur-md opacity-50`} />
|
||||
{/* Process steps section */}
|
||||
<div className="flex-1 px-12 pb-8 print:px-8 print:pb-6 relative z-10">
|
||||
<div className="flex justify-center items-center h-full">
|
||||
<div className="flex flex-wrap justify-center items-center gap-8 max-w-6xl">
|
||||
{slideData?.steps?.map((step, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{/* Process Step */}
|
||||
<div className="flex flex-col items-center text-center max-w-xs group">
|
||||
{/* Step Number and Icon */}
|
||||
<div className="relative mb-4">
|
||||
<div className="w-20 h-20 bg-gradient-to-br from-blue-600 to-blue-800 rounded-full flex items-center justify-center shadow-2xl group-hover:scale-110 transition-transform duration-300 print:w-16 print:h-16">
|
||||
<span className="text-white font-bold text-lg print:text-base">
|
||||
{index + 1}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Step Content Card */}
|
||||
<div className="bg-white/95 backdrop-blur-lg rounded-2xl p-6 shadow-2xl border border-white/50 w-full max-w-xs relative overflow-hidden group-hover:transform group-hover:scale-105 transition-all duration-300">
|
||||
{/* Card accent */}
|
||||
<div className={`absolute top-0 left-0 right-0 h-1 bg-gradient-to-r ${accentColors[accentColor]}`} />
|
||||
|
||||
{/* Step Title */}
|
||||
<h3 className="text-xl font-bold text-slate-900 mb-3 break-words">
|
||||
{step.title}
|
||||
</h3>
|
||||
|
||||
{/* Step Description */}
|
||||
<p className="text-sm text-slate-600 leading-relaxed break-words font-medium">
|
||||
{step.description}
|
||||
</p>
|
||||
|
||||
{/* Background decoration */}
|
||||
<div className={`absolute bottom-0 right-0 w-12 h-12 bg-gradient-to-tl ${accentColors[accentColor]} opacity-5 rounded-tl-full`} />
|
||||
<div className="absolute -bottom-2 -right-2 w-8 h-8 bg-white rounded-full flex items-center justify-center shadow-lg print:w-6 print:h-6">
|
||||
<img
|
||||
src={step.icon?.url || ''}
|
||||
alt={step.icon?.prompt || step.title}
|
||||
className="w-6 h-6 object-cover rounded-full print:w-4 print:h-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Arrow Between Steps */}
|
||||
{index < (slideData?.processSteps?.length || 0) - 1 && (
|
||||
<div className="flex items-center justify-center mx-4">
|
||||
<div className={`w-8 h-1 bg-gradient-to-r ${accentColors[accentColor]} relative`}>
|
||||
<div className={`absolute -right-2 -top-1 w-0 h-0 border-l-4 border-t-2 border-b-2 ${accentSolids[accentColor]} border-t-transparent border-b-transparent`}
|
||||
style={{
|
||||
borderLeftColor: accentColor === 'blue' ? '#2563eb' :
|
||||
accentColor === 'green' ? '#059669' :
|
||||
accentColor === 'purple' ? '#7c3aed' :
|
||||
accentColor === 'orange' ? '#ea580c' : '#dc2626'
|
||||
}} />
|
||||
</div>
|
||||
{/* Step Title */}
|
||||
<h3 className="text-xl font-bold text-blue-600 mb-3 leading-tight print:text-lg">
|
||||
{step.title}
|
||||
</h3>
|
||||
|
||||
{/* Step Description */}
|
||||
<p className="text-gray-700 leading-relaxed text-sm print:text-xs">
|
||||
{step.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Arrow between steps */}
|
||||
{index < (slideData?.steps?.length || 0) - 1 && (
|
||||
<div className="hidden lg:flex items-center">
|
||||
<div className="w-12 h-0.5 bg-gradient-to-r from-blue-600 to-blue-800 relative print:w-8">
|
||||
<div className="absolute right-0 top-1/2 transform -translate-y-1/2 w-0 h-0 border-l-4 border-l-blue-600 border-t-2 border-b-2 border-t-transparent border-b-transparent"></div>
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enhanced decorative accent */}
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r ${accentColors[accentColor]} shadow-lg`}>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent" />
|
||||
<div className={`absolute inset-0 bg-gradient-to-r ${accentColors[accentColor]} blur-sm opacity-50`} />
|
||||
</div>
|
||||
|
||||
{/* Professional corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl ${accentColors[accentColor]} opacity-5 rounded-bl-full`} />
|
||||
{/* Bottom decorative accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
import { ImageSchema } from './defaultSchemes';
|
||||
|
||||
|
||||
export const layoutId = 'quote-slide'
|
||||
|
|
@ -29,7 +29,7 @@ const quoteSlideSchema = z.object({
|
|||
authorImage: z.string().optional().meta({
|
||||
description: "URL to author photo",
|
||||
}),
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
|
@ -40,26 +40,9 @@ export type QuoteSlideData = z.infer<typeof quoteSlideSchema>
|
|||
|
||||
interface QuoteSlideLayoutProps {
|
||||
data?: Partial<QuoteSlideData>
|
||||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
green: 'from-emerald-600 to-emerald-800',
|
||||
purple: 'from-violet-600 to-violet-800',
|
||||
orange: 'from-orange-600 to-orange-800',
|
||||
red: 'from-red-600 to-red-800'
|
||||
}
|
||||
|
||||
const accentSolids = {
|
||||
blue: 'bg-blue-600',
|
||||
green: 'bg-emerald-600',
|
||||
purple: 'bg-violet-600',
|
||||
orange: 'bg-orange-600',
|
||||
red: 'bg-red-600'
|
||||
}
|
||||
const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -70,117 +53,89 @@ const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data: slideData, ac
|
|||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
{/* Enhanced geometric background decoration */}
|
||||
<div className="absolute inset-0 opacity-[0.03]">
|
||||
<div className={`absolute top-0 right-0 w-96 h-96 ${accentSolids[accentColor]} rounded-full transform translate-x-32 -translate-y-32 blur-3xl`} />
|
||||
<div className={`absolute bottom-0 left-0 w-64 h-64 ${accentSolids[accentColor]} rounded-full transform -translate-x-16 translate-y-16 blur-2xl`} />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex flex-col h-full px-8 py-8">
|
||||
{/* Professional Header */}
|
||||
<header className="mb-6">
|
||||
<h1 className={`text-4xl md:text-5xl font-bold mb-3 tracking-tight leading-tight break-words ${slideData?.backgroundImage
|
||||
? 'text-white drop-shadow-lg'
|
||||
: 'text-slate-900'
|
||||
}`}>
|
||||
<span className={`bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent`}>
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{slideData?.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData?.backgroundImage
|
||||
? 'text-slate-200 drop-shadow-md'
|
||||
: 'text-slate-600'
|
||||
}`}>
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
)}
|
||||
{/* Content */}
|
||||
<div className="relative z-10 h-full flex flex-col justify-center items-center px-16 py-12">
|
||||
{/* Title */}
|
||||
<h1 className="text-5xl font-black mb-16 leading-tight tracking-tight text-center max-w-4xl">
|
||||
<span className="text-gray-900">{slideData?.title?.split(' ').slice(0, -1).join(' ')}</span>{' '}
|
||||
<span className={`bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent`}>
|
||||
{slideData?.title?.split(' ').slice(-1)[0]}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<div className="relative mt-4">
|
||||
<div className={`w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full blur-sm opacity-50`} />
|
||||
{/* Quote */}
|
||||
<div className="max-w-4xl mx-auto text-center mb-16">
|
||||
{/* Decorative line */}
|
||||
<div className="relative flex justify-center mb-12">
|
||||
<div className={`w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Enhanced Quote Content */}
|
||||
<main className="flex-1 flex items-center justify-center">
|
||||
<div className="bg-white/95 backdrop-blur-lg rounded-3xl p-10 shadow-2xl border border-white/50 max-w-5xl text-center relative overflow-hidden">
|
||||
{/* Enhanced Background Quote Decoration */}
|
||||
<div className={`absolute top-4 left-6 text-8xl font-black opacity-10 bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent pointer-events-none select-none`}>
|
||||
"
|
||||
</div>
|
||||
<div className={`absolute bottom-4 right-6 text-8xl font-black opacity-10 bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent pointer-events-none select-none rotate-180`}>
|
||||
"
|
||||
{/* Quote Text */}
|
||||
<blockquote className={`text-2xl md:text-3xl leading-relaxed mb-8 italic break-words relative z-10 font-light text-slate-700`}>
|
||||
"{slideData?.quote}"
|
||||
</blockquote>
|
||||
|
||||
{/* Author Attribution */}
|
||||
<div className="flex items-center justify-center space-x-4 relative z-10">
|
||||
{/* Author Avatar */}
|
||||
<div className="flex-shrink-0">
|
||||
{slideData?.authorImage ? (
|
||||
<img
|
||||
src={slideData?.authorImage}
|
||||
alt={slideData?.author}
|
||||
className="w-16 h-16 rounded-full object-cover shadow-xl border-4 border-white"
|
||||
/>
|
||||
) : (
|
||||
<div className={`w-16 h-16 rounded-full bg-blue-600 flex items-center justify-center text-white font-bold text-xl shadow-xl border-4 border-white`}>
|
||||
{slideData?.author?.split(' ').map(n => n[0]).join('')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Quote Text */}
|
||||
<blockquote className={`text-2xl md:text-3xl leading-relaxed mb-8 italic break-words relative z-10 font-light ${slideData?.backgroundImage
|
||||
? 'text-slate-700'
|
||||
: 'text-slate-700'
|
||||
}`}>
|
||||
"{slideData?.quote}"
|
||||
</blockquote>
|
||||
{/* Author Details */}
|
||||
<div className="text-left">
|
||||
<p className="text-xl font-bold text-slate-900 break-words">
|
||||
{slideData?.author}
|
||||
</p>
|
||||
|
||||
{/* Professional Author Attribution */}
|
||||
<div className="flex items-center justify-center space-x-4 relative z-10">
|
||||
{/* Author Avatar */}
|
||||
<div className="flex-shrink-0">
|
||||
{slideData?.authorImage ? (
|
||||
<img
|
||||
src={slideData?.authorImage}
|
||||
alt={slideData?.author}
|
||||
className="w-16 h-16 rounded-full object-cover shadow-xl border-4 border-white"
|
||||
/>
|
||||
) : (
|
||||
<div className={`w-16 h-16 rounded-full ${accentSolids[accentColor]} flex items-center justify-center text-white font-bold text-xl shadow-xl border-4 border-white`}>
|
||||
{slideData?.author?.split(' ').map(n => n[0]).join('')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Author Details */}
|
||||
<div className="text-left">
|
||||
<p className="text-xl font-bold text-slate-900 break-words">
|
||||
{slideData?.author}
|
||||
{slideData?.authorTitle && (
|
||||
<p className={`text-base font-semibold bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent break-words`}>
|
||||
{slideData?.authorTitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{slideData?.authorTitle && (
|
||||
<p className={`text-base font-semibold bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent break-words`}>
|
||||
{slideData?.authorTitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{slideData?.company && (
|
||||
<p className="text-sm text-slate-600 font-medium break-words">
|
||||
{slideData?.company}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{slideData?.company && (
|
||||
<p className="text-sm text-slate-600 font-medium break-words">
|
||||
{slideData?.company}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Enhanced Quote Accent Line */}
|
||||
<div className="flex justify-center mt-6">
|
||||
<div className="relative">
|
||||
<div className={`w-24 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-24 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Background decoration */}
|
||||
<div className={`absolute bottom-0 right-0 w-20 h-20 bg-gradient-to-tl ${accentColors[accentColor]} opacity-5 rounded-tl-full`} />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Quote Accent Line */}
|
||||
<div className="flex justify-center mt-6">
|
||||
<div className="relative">
|
||||
<div className={`w-24 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-24 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Background decoration */}
|
||||
<div className={`absolute bottom-0 right-0 w-20 h-20 bg-gradient-to-tl from-blue-600 to-blue-800 opacity-5 rounded-tl-full`} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enhanced decorative accent */}
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r ${accentColors[accentColor]} shadow-lg`}>
|
||||
{/* Decorative accent */}
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r from-blue-600 to-blue-800 shadow-lg`}>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent" />
|
||||
<div className={`absolute inset-0 bg-gradient-to-r ${accentColors[accentColor]} blur-sm opacity-50`} />
|
||||
<div className={`absolute inset-0 bg-gradient-to-r from-blue-600 to-blue-800 blur-sm opacity-50`} />
|
||||
</div>
|
||||
|
||||
{/* Professional corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl ${accentColors[accentColor]} opacity-5 rounded-bl-full`} />
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full`} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
import { ImageSchema } from './defaultSchemes';
|
||||
|
||||
|
||||
export const layoutId = 'statistics-slide'
|
||||
|
|
@ -53,7 +53,7 @@ const statisticsSlideSchema = z.object({
|
|||
context: 'Customer service'
|
||||
}
|
||||
]).describe('List of statistics (2-6 items)'),
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
|
@ -64,41 +64,12 @@ export type StatisticsSlideData = z.infer<typeof statisticsSlideSchema>
|
|||
|
||||
interface StatisticsSlideLayoutProps {
|
||||
data?: Partial<StatisticsSlideData>
|
||||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const StatisticsSlideLayout: React.FC<StatisticsSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
const StatisticsSlideLayout: React.FC<StatisticsSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
const statsCount = slideData?.statistics?.length || 0
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
green: 'from-emerald-600 to-emerald-800',
|
||||
purple: 'from-violet-600 to-violet-800',
|
||||
orange: 'from-orange-600 to-orange-800',
|
||||
red: 'from-red-600 to-red-800'
|
||||
}
|
||||
|
||||
const accentSolids = {
|
||||
blue: 'bg-blue-600',
|
||||
green: 'bg-emerald-600',
|
||||
purple: 'bg-violet-600',
|
||||
orange: 'bg-orange-600',
|
||||
red: 'bg-red-600'
|
||||
}
|
||||
|
||||
const trendColors = {
|
||||
up: {
|
||||
blue: 'text-emerald-600 bg-emerald-50 border-emerald-200',
|
||||
green: 'text-emerald-600 bg-emerald-50 border-emerald-200',
|
||||
purple: 'text-emerald-600 bg-emerald-50 border-emerald-200',
|
||||
orange: 'text-emerald-600 bg-emerald-50 border-emerald-200',
|
||||
red: 'text-emerald-600 bg-emerald-50 border-emerald-200'
|
||||
},
|
||||
down: 'text-red-600 bg-red-50 border-red-200',
|
||||
neutral: 'text-slate-600 bg-slate-50 border-slate-200'
|
||||
}
|
||||
|
||||
const getTrendIcon = (trend?: string) => {
|
||||
switch (trend) {
|
||||
case 'up':
|
||||
|
|
@ -121,17 +92,18 @@ const StatisticsSlideLayout: React.FC<StatisticsSlideLayoutProps> = ({ data: sli
|
|||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200"
|
||||
className="relative w-full aspect-[16/9] bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData?.backgroundImage})`,
|
||||
backgroundImage: `url("${slideData.backgroundImage.url}")`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat'
|
||||
} : {}}
|
||||
>
|
||||
{/* Enhanced geometric background decoration */}
|
||||
<div className="absolute inset-0 opacity-[0.03]">
|
||||
<div className={`absolute top-0 right-0 w-96 h-96 ${accentSolids[accentColor]} rounded-full transform translate-x-32 -translate-y-32 blur-3xl`} />
|
||||
<div className={`absolute bottom-0 left-0 w-64 h-64 ${accentSolids[accentColor]} rounded-full transform -translate-x-16 translate-y-16 blur-2xl`} />
|
||||
<div className="absolute top-0 right-0 w-96 h-96 bg-blue-600 rounded-full transform translate-x-32 -translate-y-32 blur-3xl" />
|
||||
<div className="absolute bottom-0 left-0 w-64 h-64 bg-blue-600 rounded-full transform -translate-x-16 translate-y-16 blur-2xl" />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex flex-col h-full px-8 py-8">
|
||||
|
|
@ -141,7 +113,7 @@ const StatisticsSlideLayout: React.FC<StatisticsSlideLayoutProps> = ({ data: sli
|
|||
? 'text-white drop-shadow-lg'
|
||||
: 'text-slate-900'
|
||||
}`}>
|
||||
<span className={`bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent`}>
|
||||
<span className="bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent">
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
|
|
@ -156,8 +128,8 @@ const StatisticsSlideLayout: React.FC<StatisticsSlideLayoutProps> = ({ data: sli
|
|||
)}
|
||||
|
||||
<div className="relative mt-4">
|
||||
<div className={`w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full blur-sm opacity-50`} />
|
||||
<div className="w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full shadow-lg" />
|
||||
<div className="absolute inset-0 w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full blur-sm opacity-50" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
|
@ -167,38 +139,39 @@ const StatisticsSlideLayout: React.FC<StatisticsSlideLayoutProps> = ({ data: sli
|
|||
{slideData?.statistics?.map((stat, index) => (
|
||||
<div key={index} className="bg-white/95 backdrop-blur-lg rounded-2xl p-6 shadow-2xl border border-white/50 text-center relative overflow-hidden group hover:transform hover:scale-105 transition-all duration-300">
|
||||
{/* Card accent */}
|
||||
<div className={`absolute top-0 left-0 right-0 h-1 bg-gradient-to-r ${accentColors[accentColor]}`} />
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800" />
|
||||
|
||||
{/* Statistic Value */}
|
||||
<div className={`text-4xl md:text-5xl font-black mb-2 bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent`}>
|
||||
<div className="text-4xl md:text-5xl font-black mb-2 bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent">
|
||||
{stat?.value}
|
||||
</div>
|
||||
|
||||
{/* Statistic Label */}
|
||||
<h3 className="text-lg md:text-xl font-bold text-slate-900 mb-2 break-words">
|
||||
<h3 className="text-lg font-bold text-slate-900 mb-3 break-words leading-tight">
|
||||
{stat?.label}
|
||||
</h3>
|
||||
|
||||
{/* Trend Indicator */}
|
||||
{stat?.trend && (
|
||||
<div className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-semibold border ${stat?.trend === 'up' ? trendColors.up[accentColor] :
|
||||
stat?.trend === 'down' ? trendColors.down :
|
||||
trendColors.neutral
|
||||
} mb-2`}>
|
||||
<span className="mr-1">{getTrendIcon(stat.trend)}</span>
|
||||
{stat.trend.toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Context */}
|
||||
{stat.context && (
|
||||
<p className="text-sm text-slate-600 break-words font-medium">
|
||||
{stat.context}
|
||||
{/* Description */}
|
||||
{stat?.context && (
|
||||
<p className="text-sm text-slate-600 leading-relaxed break-words mb-4">
|
||||
{stat?.context}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Trend Indicator */}
|
||||
{stat?.trend && (
|
||||
<div className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-semibold border ${stat?.trend === 'up' ? 'text-emerald-600 bg-emerald-50 border-emerald-200' :
|
||||
stat?.trend === 'down' ? 'text-red-600 bg-red-50 border-red-200' :
|
||||
'text-slate-600 bg-slate-50 border-slate-200'
|
||||
}`}>
|
||||
<span className="mr-1">{getTrendIcon(stat?.trend)}</span>
|
||||
{stat?.trend === 'up' ? 'Trending Up' :
|
||||
stat?.trend === 'down' ? 'Trending Down' : 'Stable'}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Background decoration */}
|
||||
<div className={`absolute bottom-0 right-0 w-16 h-16 bg-gradient-to-tl ${accentColors[accentColor]} opacity-5 rounded-tl-full`} />
|
||||
<div className="absolute bottom-0 right-0 w-16 h-16 bg-gradient-to-tl from-blue-600 to-blue-800 opacity-5 rounded-tl-full" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -206,13 +179,13 @@ const StatisticsSlideLayout: React.FC<StatisticsSlideLayoutProps> = ({ data: sli
|
|||
</div>
|
||||
|
||||
{/* Enhanced decorative accent */}
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r ${accentColors[accentColor]} shadow-lg`}>
|
||||
<div className="absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r from-blue-600 to-blue-800 shadow-lg">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent" />
|
||||
<div className={`absolute inset-0 bg-gradient-to-r ${accentColors[accentColor]} blur-sm opacity-50`} />
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-600 to-blue-800 blur-sm opacity-50" />
|
||||
</div>
|
||||
|
||||
{/* Professional corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl ${accentColors[accentColor]} opacity-5 rounded-bl-full`} />
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
import { ImageSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'team-slide'
|
||||
export const layoutName = 'Team Slide'
|
||||
|
|
@ -26,10 +26,10 @@ const teamSlideSchema = z.object({
|
|||
bio: z.string().min(10).max(300).optional().meta({
|
||||
description: "Brief biography or description",
|
||||
}),
|
||||
email: z.string().email().optional().meta({
|
||||
email: z.email().optional().meta({
|
||||
description: "Contact email",
|
||||
}),
|
||||
linkedin: z.string().url().optional().meta({
|
||||
linkedin: z.string().optional().meta({
|
||||
description: "LinkedIn profile URL",
|
||||
})
|
||||
})).min(1).max(6).default([
|
||||
|
|
@ -58,7 +58,7 @@ const teamSlideSchema = z.object({
|
|||
linkedin: 'https://linkedin.com/in/emmarodriguez'
|
||||
}
|
||||
]).describe('Team members (1-6 people)'),
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
}),
|
||||
})
|
||||
|
|
@ -69,50 +69,33 @@ export type TeamSlideData = z.infer<typeof teamSlideSchema>
|
|||
|
||||
interface TeamSlideLayoutProps {
|
||||
data?: Partial<TeamSlideData>
|
||||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
green: 'from-emerald-600 to-emerald-800',
|
||||
purple: 'from-violet-600 to-violet-800',
|
||||
orange: 'from-orange-600 to-orange-800',
|
||||
red: 'from-red-600 to-red-800'
|
||||
}
|
||||
|
||||
const accentSolids = {
|
||||
blue: 'bg-blue-600',
|
||||
green: 'bg-emerald-600',
|
||||
purple: 'bg-violet-600',
|
||||
orange: 'bg-orange-600',
|
||||
red: 'bg-red-600'
|
||||
}
|
||||
|
||||
const iconColors = {
|
||||
blue: 'text-blue-600 hover:text-blue-700',
|
||||
green: 'text-emerald-600 hover:text-emerald-700',
|
||||
purple: 'text-violet-600 hover:text-violet-700',
|
||||
orange: 'text-orange-600 hover:text-orange-700',
|
||||
red: 'text-red-600 hover:text-red-700'
|
||||
}
|
||||
const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200"
|
||||
className="relative w-full aspect-[16/9] bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData?.backgroundImage})`,
|
||||
backgroundImage: `url("${slideData.backgroundImage.url}")`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat'
|
||||
} : {}}
|
||||
>
|
||||
{/* Enhanced geometric background decoration */}
|
||||
<div className="absolute inset-0 opacity-[0.03]">
|
||||
<div className={`absolute top-0 right-0 w-96 h-96 ${accentSolids[accentColor]} rounded-full transform translate-x-32 -translate-y-32 blur-3xl`} />
|
||||
<div className={`absolute bottom-0 left-0 w-64 h-64 ${accentSolids[accentColor]} rounded-full transform -translate-x-16 translate-y-16 blur-2xl`} />
|
||||
<div className="absolute top-0 right-0 w-96 h-96 bg-blue-600 rounded-full transform translate-x-32 -translate-y-32 blur-3xl" />
|
||||
<div className="absolute bottom-0 left-0 w-64 h-64 bg-blue-600 rounded-full transform -translate-x-16 translate-y-16 blur-2xl" />
|
||||
</div>
|
||||
|
||||
{/* Grid overlay for professional look */}
|
||||
<div className="absolute inset-0 opacity-[0.02]" style={{
|
||||
backgroundImage: `linear-gradient(0deg, rgba(0,0,0,0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0,0,0,0.1) 1px, transparent 1px)`,
|
||||
backgroundSize: '60px 60px'
|
||||
}} />
|
||||
|
||||
<div className="relative z-10 flex flex-col h-full px-8 py-8">
|
||||
{/* Professional Header */}
|
||||
<header className="mb-6">
|
||||
|
|
@ -120,7 +103,7 @@ const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data: slideData, acce
|
|||
? 'text-white drop-shadow-lg'
|
||||
: 'text-slate-900'
|
||||
}`}>
|
||||
<span className={`bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent`}>
|
||||
<span className="bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent">
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
|
|
@ -135,103 +118,108 @@ const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data: slideData, acce
|
|||
)}
|
||||
|
||||
<div className="relative mt-4">
|
||||
<div className={`w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full blur-sm opacity-50`} />
|
||||
<div className="w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full shadow-lg" />
|
||||
<div className="absolute inset-0 w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full blur-sm opacity-50" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Enhanced Team Grid */}
|
||||
<main className="flex-1 flex items-center justify-center">
|
||||
<div className={`grid gap-6 w-full max-w-6xl ${slideData?.teamMembers?.length && slideData?.teamMembers?.length <= 2 ? 'grid-cols-2' :
|
||||
slideData?.teamMembers?.length && slideData?.teamMembers?.length <= 3 ? 'grid-cols-3' :
|
||||
slideData?.teamMembers?.length && slideData?.teamMembers?.length <= 4 ? 'grid-cols-2 lg:grid-cols-4' :
|
||||
'grid-cols-2 lg:grid-cols-3'
|
||||
}`}>
|
||||
{slideData?.teamMembers?.map((member, index) => (
|
||||
<div key={index} className="bg-white/95 backdrop-blur-lg rounded-2xl p-6 shadow-2xl border border-white/50 text-center relative overflow-hidden group hover:transform hover:scale-105 transition-all duration-300">
|
||||
{/* Card accent */}
|
||||
<div className={`absolute top-0 left-0 right-0 h-1 bg-gradient-to-r ${accentColors[accentColor]}`} />
|
||||
<div className="bg-white/95 backdrop-blur-lg rounded-2xl p-8 shadow-2xl border border-white/50 w-full max-w-6xl relative overflow-hidden">
|
||||
{/* Team grid with responsive layout */}
|
||||
<div className={`grid gap-6 relative z-10 ${slideData?.teamMembers?.length === 1 ? 'grid-cols-1 max-w-md mx-auto' :
|
||||
slideData?.teamMembers?.length === 2 ? 'grid-cols-1 md:grid-cols-2 max-w-4xl mx-auto' :
|
||||
slideData?.teamMembers?.length === 3 ? 'grid-cols-1 md:grid-cols-3' :
|
||||
slideData?.teamMembers?.length === 4 ? 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4' :
|
||||
slideData?.teamMembers?.length === 5 ? 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5' :
|
||||
'grid-cols-1 md:grid-cols-2 lg:grid-cols-3'
|
||||
}`}>
|
||||
{slideData?.teamMembers?.map((member, index) => (
|
||||
<div key={index} className="bg-white/95 backdrop-blur-lg rounded-2xl p-6 shadow-2xl border border-white/50 text-center relative overflow-hidden group hover:transform hover:scale-105 transition-all duration-300">
|
||||
{/* Card accent */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800" />
|
||||
|
||||
{/* Professional Avatar */}
|
||||
<div className="relative mb-4 inline-block">
|
||||
<div className="w-20 h-20 mx-auto relative">
|
||||
{member.image ? (
|
||||
<img
|
||||
src={member.image}
|
||||
alt={member.name}
|
||||
className="w-full h-full object-cover rounded-full shadow-xl border-4 border-white group-hover:shadow-2xl transition-shadow duration-300"
|
||||
/>
|
||||
) : (
|
||||
<div className={`w-full h-full rounded-full ${accentSolids[accentColor]} flex items-center justify-center text-white font-bold text-xl shadow-xl border-4 border-white`}>
|
||||
{member.name.split(' ').map(n => n[0]).join('')}
|
||||
</div>
|
||||
)}
|
||||
<div className={`absolute inset-0 rounded-full bg-gradient-to-r ${accentColors[accentColor]} opacity-20 group-hover:opacity-30 transition-opacity duration-300`} />
|
||||
{/* Professional Avatar */}
|
||||
<div className="relative mb-4 mx-auto w-24 h-24 group-hover:scale-110 transition-transform duration-300">
|
||||
<div className="w-full h-full rounded-full overflow-hidden shadow-2xl border-4 border-white relative">
|
||||
{member.image ? (
|
||||
<img
|
||||
src={member.image}
|
||||
alt={member.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full rounded-full bg-blue-600 flex items-center justify-center text-white font-bold text-xl shadow-xl border-4 border-white">
|
||||
{member.name.split(' ').map(n => n[0]).join('')}
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute inset-0 rounded-full bg-gradient-to-r from-blue-600 to-blue-800 opacity-20 group-hover:opacity-30 transition-opacity duration-300" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Member Info */}
|
||||
<div className="mb-4">
|
||||
<h3 className="text-xl font-bold text-slate-900 mb-1 break-words">
|
||||
{member.name}
|
||||
</h3>
|
||||
<p className={`text-sm font-semibold mb-3 bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent`}>
|
||||
{member.title}
|
||||
</p>
|
||||
|
||||
{member.bio && (
|
||||
<p className="text-xs text-slate-600 leading-relaxed break-words font-medium">
|
||||
{member.bio}
|
||||
{/* Member Details */}
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg font-bold text-slate-900 break-words leading-tight">
|
||||
{member.name}
|
||||
</h3>
|
||||
<p className="text-sm font-semibold mb-3 bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent">
|
||||
{member.title}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{member.bio && (
|
||||
<p className="text-xs text-slate-600 leading-relaxed break-words">
|
||||
{member.bio}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Contact Icons */}
|
||||
<div className="flex justify-center space-x-3 pt-3">
|
||||
{member.email && (
|
||||
<a
|
||||
href={`mailto:${member.email}`}
|
||||
className="p-2 rounded-full bg-slate-100 hover:bg-slate-200 text-blue-600 transition-all duration-200 hover:transform hover:scale-110"
|
||||
title="Email"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
|
||||
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
|
||||
</svg>
|
||||
</a>
|
||||
)}
|
||||
|
||||
{member.linkedin && (
|
||||
<a
|
||||
href={member.linkedin}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-2 rounded-full bg-slate-100 hover:bg-slate-200 text-blue-600 transition-all duration-200 hover:transform hover:scale-110"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<svg className="w-4 h-4" 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>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Background decoration */}
|
||||
<div className="absolute bottom-0 right-0 w-12 h-12 bg-gradient-to-tl from-blue-600 to-blue-800 opacity-5 rounded-tl-full" />
|
||||
</div>
|
||||
|
||||
{/* Professional Contact Links */}
|
||||
<div className="flex justify-center space-x-3">
|
||||
{member.email && (
|
||||
<a
|
||||
href={`mailto:${member.email}`}
|
||||
className={`p-2 rounded-full bg-slate-100 hover:bg-slate-200 ${iconColors[accentColor]} transition-all duration-200 hover:transform hover:scale-110`}
|
||||
title="Email"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
|
||||
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
|
||||
</svg>
|
||||
</a>
|
||||
)}
|
||||
|
||||
{member.linkedin && (
|
||||
<a
|
||||
href={member.linkedin}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`p-2 rounded-full bg-slate-100 hover:bg-slate-200 ${iconColors[accentColor]} transition-all duration-200 hover:transform hover:scale-110`}
|
||||
title="LinkedIn"
|
||||
>
|
||||
<svg className="w-4 h-4" 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>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Background decoration */}
|
||||
<div className={`absolute bottom-0 right-0 w-12 h-12 bg-gradient-to-tl ${accentColors[accentColor]} opacity-5 rounded-tl-full`} />
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{/* Enhanced decorative accent */}
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r ${accentColors[accentColor]} shadow-lg`}>
|
||||
<div className="absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r from-blue-600 to-blue-800 shadow-lg">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent" />
|
||||
<div className={`absolute inset-0 bg-gradient-to-r ${accentColors[accentColor]} blur-sm opacity-50`} />
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-600 to-blue-800 blur-sm opacity-50" />
|
||||
</div>
|
||||
|
||||
{/* Professional corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl ${accentColors[accentColor]} opacity-5 rounded-bl-full`} />
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,61 +1,75 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
|
||||
import { ImageSchema, IconSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'timeline-slide'
|
||||
export const layoutName = 'Timeline Slide'
|
||||
export const layoutDescription = 'A slide with a title, subtitle, and timeline items'
|
||||
export const layoutDescription = 'A professional slide featuring a chronological timeline with dates, events, and descriptions.'
|
||||
|
||||
const timelineSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Project Timeline').meta({
|
||||
description: "Title of the slide",
|
||||
title: z.string().min(3).max(100).default('Our Journey').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(3).max(150).optional().meta({
|
||||
subtitle: z.string().min(10).max(200).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
timelineItems: z.array(z.object({
|
||||
date: z.string().min(2).max(50).meta({
|
||||
events: z.array(z.object({
|
||||
date: z.string().min(2).max(20).meta({
|
||||
description: "Date or time period",
|
||||
}),
|
||||
title: z.string().min(3).max(100).meta({
|
||||
description: "Event or milestone title",
|
||||
title: z.string().min(2).max(50).meta({
|
||||
description: "Event title",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: z.string().min(10).max(150).meta({
|
||||
description: "Event description",
|
||||
}),
|
||||
status: z.enum(['completed', 'current', 'upcoming']).default('upcoming').meta({
|
||||
description: "Timeline item status",
|
||||
icon: IconSchema.default({
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'Default event icon'
|
||||
}).meta({
|
||||
description: "Icon for the event",
|
||||
})
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
date: 'Q1 2024',
|
||||
title: 'Project Initiation',
|
||||
description: 'Project planning, team assembly, and initial requirements gathering',
|
||||
status: 'completed'
|
||||
date: '2020',
|
||||
title: 'Foundation',
|
||||
description: 'Company founded with a vision to transform digital experiences',
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'Foundation icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
date: 'Q2 2024',
|
||||
title: 'Development Phase',
|
||||
description: 'Core development work, prototype creation, and testing implementation',
|
||||
status: 'current'
|
||||
date: '2021',
|
||||
title: 'First Success',
|
||||
description: 'Launched first product and gained initial market traction',
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
|
||||
prompt: 'First success icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
date: 'Q3 2024',
|
||||
title: 'Testing & QA',
|
||||
description: 'Comprehensive testing, quality assurance, and user acceptance testing',
|
||||
status: 'upcoming'
|
||||
date: '2022',
|
||||
title: 'Expansion',
|
||||
description: 'Expanded team and entered new markets with innovative solutions',
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
|
||||
prompt: 'Expansion icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
date: 'Q4 2024',
|
||||
title: 'Launch & Deployment',
|
||||
description: 'Final deployment, go-live activities, and post-launch monitoring',
|
||||
status: 'upcoming'
|
||||
date: '2023',
|
||||
title: 'Innovation',
|
||||
description: 'Introduced breakthrough technology and achieved industry recognition',
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'Innovation icon'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "Timeline events (2-6 items)",
|
||||
description: "List of timeline events (2-6 items)",
|
||||
}),
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
|
@ -66,179 +80,93 @@ export type TimelineSlideData = z.infer<typeof timelineSlideSchema>
|
|||
|
||||
interface TimelineSlideLayoutProps {
|
||||
data?: Partial<TimelineSlideData>
|
||||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const TimelineSlideLayout: React.FC<TimelineSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
green: 'from-emerald-600 to-emerald-800',
|
||||
purple: 'from-violet-600 to-violet-800',
|
||||
orange: 'from-orange-600 to-orange-800',
|
||||
red: 'from-red-600 to-red-800'
|
||||
}
|
||||
|
||||
const accentSolids = {
|
||||
blue: 'bg-blue-600',
|
||||
green: 'bg-emerald-600',
|
||||
purple: 'bg-violet-600',
|
||||
orange: 'bg-orange-600',
|
||||
red: 'bg-red-600'
|
||||
}
|
||||
|
||||
const statusColors = {
|
||||
completed: {
|
||||
blue: 'bg-emerald-600 border-emerald-600 shadow-emerald-200',
|
||||
green: 'bg-emerald-600 border-emerald-600 shadow-emerald-200',
|
||||
purple: 'bg-emerald-600 border-emerald-600 shadow-emerald-200',
|
||||
orange: 'bg-emerald-600 border-emerald-600 shadow-emerald-200',
|
||||
red: 'bg-emerald-600 border-emerald-600 shadow-emerald-200'
|
||||
},
|
||||
current: {
|
||||
blue: 'bg-blue-600 border-blue-600 ring-4 ring-blue-200 shadow-blue-300',
|
||||
green: 'bg-emerald-600 border-emerald-600 ring-4 ring-emerald-200 shadow-emerald-300',
|
||||
purple: 'bg-violet-600 border-violet-600 ring-4 ring-violet-200 shadow-violet-300',
|
||||
orange: 'bg-orange-600 border-orange-600 ring-4 ring-orange-200 shadow-orange-300',
|
||||
red: 'bg-red-600 border-red-600 ring-4 ring-red-200 shadow-red-300'
|
||||
},
|
||||
upcoming: {
|
||||
blue: 'bg-slate-300 border-slate-400 shadow-slate-200',
|
||||
green: 'bg-slate-300 border-slate-400 shadow-slate-200',
|
||||
purple: 'bg-slate-300 border-slate-400 shadow-slate-200',
|
||||
orange: 'bg-slate-300 border-slate-400 shadow-slate-200',
|
||||
red: 'bg-slate-300 border-slate-400 shadow-slate-200'
|
||||
}
|
||||
}
|
||||
|
||||
const lineColors = {
|
||||
blue: 'bg-blue-200',
|
||||
green: 'bg-emerald-200',
|
||||
purple: 'bg-violet-200',
|
||||
orange: 'bg-orange-200',
|
||||
red: 'bg-red-200'
|
||||
}
|
||||
const TimelineSlideLayout: React.FC<TimelineSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200"
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData?.backgroundImage})`,
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.url})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
{/* Enhanced geometric background decoration */}
|
||||
<div className="absolute inset-0 opacity-[0.03]">
|
||||
<div className={`absolute top-0 right-0 w-96 h-96 ${accentSolids[accentColor]} rounded-full transform translate-x-32 -translate-y-32 blur-3xl`} />
|
||||
<div className={`absolute bottom-0 left-0 w-64 h-64 ${accentSolids[accentColor]} rounded-full transform -translate-x-16 translate-y-16 blur-2xl`} />
|
||||
{/* Background Effects */}
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<div className="absolute top-0 right-0 w-96 h-96 bg-blue-600 rounded-full transform translate-x-32 -translate-y-32 blur-3xl opacity-10" />
|
||||
<div className="absolute bottom-0 left-0 w-64 h-64 bg-blue-600 rounded-full transform -translate-x-16 translate-y-16 blur-2xl opacity-10" />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex flex-col h-full px-8 py-8">
|
||||
{/* Professional Header */}
|
||||
<header className="mb-6">
|
||||
<h1 className={`text-4xl md:text-5xl font-bold mb-3 tracking-tight leading-tight break-words ${slideData?.backgroundImage
|
||||
? 'text-white drop-shadow-lg'
|
||||
: 'text-slate-900'
|
||||
}`}>
|
||||
<span className={`bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent`}>
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
{/* Header section */}
|
||||
<div className="text-center px-12 py-6 print:px-8 print:py-4 relative z-10">
|
||||
<h1 className="text-4xl font-bold text-blue-600 mb-4 leading-tight print:text-3xl">
|
||||
{slideData?.title || 'Our Journey'}
|
||||
</h1>
|
||||
{slideData?.subtitle && (
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto leading-relaxed print:text-base">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 w-24 h-1 bg-gradient-to-r from-blue-600 to-blue-800 mx-auto rounded-full"></div>
|
||||
</div>
|
||||
|
||||
{slideData?.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData?.backgroundImage
|
||||
? 'text-slate-200 drop-shadow-md'
|
||||
: 'text-slate-600'
|
||||
}`}>
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
)}
|
||||
{/* Timeline section */}
|
||||
<div className="flex-1 px-12 pb-8 print:px-8 print:pb-6 relative z-10">
|
||||
<div className="relative max-w-6xl mx-auto">
|
||||
{/* Timeline line */}
|
||||
<div className="absolute left-1/2 transform -translate-x-1/2 w-1 h-full bg-gradient-to-b from-blue-600 to-blue-800 rounded-full"></div>
|
||||
|
||||
<div className="relative mt-4">
|
||||
<div className={`w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Enhanced Timeline */}
|
||||
<main className="flex-1 flex items-center justify-center">
|
||||
<div className="w-full max-w-6xl">
|
||||
<div className="relative flex justify-between items-start">
|
||||
{/* Timeline line */}
|
||||
<div className={`absolute top-8 left-0 right-0 h-1 ${lineColors[accentColor]} rounded-full`} />
|
||||
<div className={`absolute top-8 left-0 right-0 h-1 bg-gradient-to-r ${accentColors[accentColor]} rounded-full opacity-20`} />
|
||||
|
||||
{slideData?.timelineItems?.map((item, index) => (
|
||||
<div key={index} className="relative flex flex-col items-center" style={{ width: `${100 / (slideData?.timelineItems?.length || 0)}%` }}>
|
||||
{/* Timeline node */}
|
||||
<div className={`w-6 h-6 rounded-full border-4 shadow-lg ${statusColors[item.status][accentColor]} relative z-10 mb-4 transition-all duration-300 hover:scale-110`}>
|
||||
{item.status === 'completed' && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
{item.status === 'current' && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="w-2 h-2 bg-white rounded-full animate-pulse" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Timeline content card */}
|
||||
<div className="bg-white/95 backdrop-blur-lg rounded-2xl p-6 shadow-2xl border border-white/50 text-center min-h-[180px] flex flex-col justify-between relative overflow-hidden group hover:transform hover:scale-105 transition-all duration-300">
|
||||
{/* Card accent */}
|
||||
<div className={`absolute top-0 left-0 right-0 h-1 bg-gradient-to-r ${accentColors[accentColor]}`} />
|
||||
|
||||
<div>
|
||||
{/* Date */}
|
||||
<div className={`text-sm font-bold mb-2 px-3 py-1 rounded-full bg-gradient-to-r ${accentColors[accentColor]} text-white inline-block`}>
|
||||
{item.date}
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="text-lg font-bold text-slate-900 mb-3 break-words">
|
||||
{item.title}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-sm text-slate-600 leading-relaxed break-words font-medium">
|
||||
{item.description}
|
||||
</p>
|
||||
{/* Timeline events */}
|
||||
<div className="space-y-8 print:space-y-6">
|
||||
{slideData?.events?.map((event, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`flex items-center ${index % 2 === 0 ? 'justify-start' : 'justify-end'} relative`}
|
||||
>
|
||||
{/* Event content */}
|
||||
<div className={`w-5/12 ${index % 2 === 0 ? 'text-right pr-8' : 'text-left pl-8'} print:w-2/5`}>
|
||||
<div className="bg-white/95 backdrop-blur-sm rounded-2xl p-6 shadow-xl border border-white/50 group hover:transform hover:scale-105 transition-all duration-300 print:shadow-md print:p-4">
|
||||
{/* Date */}
|
||||
<div className="text-blue-600 font-bold text-lg mb-2 print:text-base">
|
||||
{event.date}
|
||||
</div>
|
||||
|
||||
{/* Status indicator */}
|
||||
<div className="mt-4">
|
||||
<span className={`inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold ${item.status === 'completed' ? 'bg-emerald-100 text-emerald-800' :
|
||||
item.status === 'current' ? `${accentColors[accentColor]} bg-opacity-10 text-${accentColor}-800` :
|
||||
'bg-slate-100 text-slate-600'
|
||||
}`}>
|
||||
{item.status === 'completed' && '✓ Completed'}
|
||||
{item.status === 'current' && '● In Progress'}
|
||||
{item.status === 'upcoming' && '○ Upcoming'}
|
||||
</span>
|
||||
</div>
|
||||
{/* Title with icon */}
|
||||
<h3 className="text-xl font-bold text-gray-800 mb-3 leading-tight flex items-center gap-2 print:text-lg">
|
||||
<div className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center overflow-hidden flex-shrink-0 print:w-6 print:h-6">
|
||||
<img
|
||||
src={event.icon?.url || ''}
|
||||
alt={event.icon?.prompt || event.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
{event.title}
|
||||
</h3>
|
||||
|
||||
{/* Background decoration */}
|
||||
<div className={`absolute bottom-0 right-0 w-12 h-12 bg-gradient-to-tl ${accentColors[accentColor]} opacity-5 rounded-tl-full`} />
|
||||
{/* Description */}
|
||||
<p className="text-gray-700 leading-relaxed text-sm print:text-xs">
|
||||
{event.description}
|
||||
</p>
|
||||
|
||||
{/* Accent */}
|
||||
<div className={`absolute top-0 ${index % 2 === 0 ? 'left-0' : 'right-0'} ${index % 2 === 0 ? 'right-0' : 'left-0'} h-1 bg-gradient-to-r from-blue-600 to-blue-800`} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Timeline dot */}
|
||||
<div className="absolute left-1/2 transform -translate-x-1/2 w-6 h-6 bg-gradient-to-br from-blue-600 to-blue-800 rounded-full border-4 border-white shadow-xl z-10 print:w-4 print:h-4 print:border-2">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-600 to-blue-800 rounded-full animate-pulse opacity-50"></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enhanced decorative accent */}
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r ${accentColors[accentColor]} shadow-lg`}>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent" />
|
||||
<div className={`absolute inset-0 bg-gradient-to-r ${accentColors[accentColor]} blur-sm opacity-50`} />
|
||||
</div>
|
||||
|
||||
{/* Professional corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl ${accentColors[accentColor]} opacity-5 rounded-bl-full`} />
|
||||
{/* Bottom decorative accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
65
servers/nextjs/components/layouts/Type1SlideLayout.tsx
Normal file
65
servers/nextjs/components/layouts/Type1SlideLayout.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'type1-slide'
|
||||
export const layoutName = 'Type1 Slide'
|
||||
export const layoutDescription = 'A clean two-column layout with title and description on the left and a featured image on the right.'
|
||||
|
||||
const type1SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Sample Title').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(500).default('Your description content goes here. This layout provides a clean and professional way to present content with supporting imagery.').meta({
|
||||
description: "Main description text",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'A beautiful road in the mountains'
|
||||
}).meta({
|
||||
description: "Main slide image",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type1SlideSchema
|
||||
|
||||
export type Type1SlideData = z.infer<typeof type1SlideSchema>
|
||||
|
||||
interface Type1SlideLayoutProps {
|
||||
data?: Partial<Type1SlideData>
|
||||
}
|
||||
|
||||
const Type1SlideLayout: React.FC<Type1SlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="slide-container w-full rounded-sm max-w-[1280px] shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] max-h-[720px] flex items-center aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3 sm:gap-8 md:gap-12 lg:gap-16 w-full">
|
||||
<div className="flex flex-col w-full items-start justify-center space-y-1 md:space-y-2 lg:space-y-6">
|
||||
{/* Title */}
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Sample Title'}
|
||||
</h1>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-base sm:text-lg lg:text-xl text-gray-700 leading-relaxed">
|
||||
{slideData?.description || 'Your description content goes here. This layout provides a clean and professional way to present content with supporting imagery.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Image */}
|
||||
<div className="w-full h-full min-h-[200px] lg:min-h-[300px]">
|
||||
<img
|
||||
src={slideData?.image?.url || ''}
|
||||
alt={slideData?.image?.prompt || slideData?.title || ''}
|
||||
className="w-full h-full object-cover rounded-lg shadow-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type1SlideLayout
|
||||
124
servers/nextjs/components/layouts/Type2NumberedSlideLayout.tsx
Normal file
124
servers/nextjs/components/layouts/Type2NumberedSlideLayout.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type2-numbered-slide'
|
||||
export const layoutName = 'Type2 Numbered Slide'
|
||||
export const layoutDescription = 'A content layout with title and numbered content items with large numerals and shadow boxes.'
|
||||
|
||||
const type2NumberedSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Main Title').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(4).default([
|
||||
{
|
||||
heading: 'First Point',
|
||||
description: 'Description for the first key point that explains important details'
|
||||
},
|
||||
{
|
||||
heading: 'Second Point',
|
||||
description: 'Description for the second key point with relevant information'
|
||||
},
|
||||
{
|
||||
heading: 'Third Point',
|
||||
description: 'Description for the third key point highlighting crucial aspects'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of content items (2-4 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type2NumberedSlideSchema
|
||||
|
||||
export type Type2NumberedSlideData = z.infer<typeof type2NumberedSlideSchema>
|
||||
|
||||
interface Type2NumberedSlideLayoutProps {
|
||||
data?: Partial<Type2NumberedSlideData>
|
||||
}
|
||||
|
||||
const Type2NumberedSlideLayout: React.FC<Type2NumberedSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const isGridLayout = items.length >= 4
|
||||
const numberTranslations: string[] = ['01', '02', '03', '04', '05', '06']
|
||||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 relative gap-4 lg:gap-8 mt-4 lg:mt-12">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full relative shadow-lg rounded-lg p-3 lg:p-6"
|
||||
>
|
||||
<div className="flex gap-3">
|
||||
<div className="text-[32px] leading-[40px] px-1 font-bold mb-4 text-blue-600">
|
||||
{numberTranslations[index] || `0${index + 1}`}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderHorizontalContent = () => {
|
||||
return (
|
||||
<div className="flex flex-col lg:flex-row mt-4 lg:mt-12 w-full relative gap-4 lg:gap-8">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full relative shadow-lg rounded-lg p-3 lg:p-6"
|
||||
>
|
||||
<div className="text-[32px] leading-[40px] font-semibold lg:mb-4 text-blue-600">
|
||||
{numberTranslations[index] || `0${index + 1}`}
|
||||
</div>
|
||||
<div className="space-y-2 lg:space-y-4">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="slide-container 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-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Main Title'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{isGridLayout ? renderGridContent() : renderHorizontalContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type2NumberedSlideLayout
|
||||
111
servers/nextjs/components/layouts/Type2SlideLayout.tsx
Normal file
111
servers/nextjs/components/layouts/Type2SlideLayout.tsx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type2-slide'
|
||||
export const layoutName = 'Type2 Slide'
|
||||
export const layoutDescription = 'A flexible content layout with title and multiple content items in default presentation style.'
|
||||
|
||||
const type2SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Main Title').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(4).default([
|
||||
{
|
||||
heading: 'First Point',
|
||||
description: 'Description for the first key point that explains important details'
|
||||
},
|
||||
{
|
||||
heading: 'Second Point',
|
||||
description: 'Description for the second key point with relevant information'
|
||||
},
|
||||
{
|
||||
heading: 'Third Point',
|
||||
description: 'Description for the third key point highlighting crucial aspects'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of content items (2-4 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type2SlideSchema
|
||||
|
||||
export type Type2SlideData = z.infer<typeof type2SlideSchema>
|
||||
|
||||
interface Type2SlideLayoutProps {
|
||||
data?: Partial<Type2SlideData>
|
||||
}
|
||||
|
||||
const Type2SlideLayout: React.FC<Type2SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const isGridLayout = items.length >= 4
|
||||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 relative gap-6 md:gap-12 mt-4 lg:mt-12">
|
||||
{items.map((item, index) => (
|
||||
<div key={index} className="w-full relative p-3 lg:p-6 rounded-md"
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderHorizontalContent = () => {
|
||||
return (
|
||||
<div className="flex flex-col lg:flex-row mt-4 lg:mt-12 w-full relative gap-12">
|
||||
{items.map((item, index) => (
|
||||
<div key={index} className="w-full relative p-3 lg:p-6 rounded-md"
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
>
|
||||
<div className="space-y-2 lg:space-y-4">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="slide-container 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-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Main Title'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{isGridLayout ? renderGridContent() : renderHorizontalContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type2SlideLayout
|
||||
102
servers/nextjs/components/layouts/Type2TimelineSlideLayout.tsx
Normal file
102
servers/nextjs/components/layouts/Type2TimelineSlideLayout.tsx
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type2-timeline-slide'
|
||||
export const layoutName = 'Type2 Timeline Slide'
|
||||
export const layoutDescription = 'A timeline layout with title and content items arranged horizontally with numbered circles and connecting line.'
|
||||
|
||||
const type2TimelineSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Main Title').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(4).default([
|
||||
{
|
||||
heading: 'First Point',
|
||||
description: 'Description for the first key point that explains important details'
|
||||
},
|
||||
{
|
||||
heading: 'Second Point',
|
||||
description: 'Description for the second key point with relevant information'
|
||||
},
|
||||
{
|
||||
heading: 'Third Point',
|
||||
description: 'Description for the third key point highlighting crucial aspects'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of content items (2-4 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type2TimelineSlideSchema
|
||||
|
||||
export type Type2TimelineSlideData = z.infer<typeof type2TimelineSlideSchema>
|
||||
|
||||
interface Type2TimelineSlideLayoutProps {
|
||||
data?: Partial<Type2TimelineSlideData>
|
||||
}
|
||||
|
||||
const Type2TimelineSlideLayout: React.FC<Type2TimelineSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const numberTranslations: string[] = ['01', '02', '03', '04', '05', '06']
|
||||
|
||||
const renderTimelineContent = () => {
|
||||
return (
|
||||
<div className="w-full flex flex-col relative mt-4 lg:mt-16">
|
||||
{/* Timeline Header with Numbers and Line */}
|
||||
<div className="relative flex justify-between w-[85%] mx-auto items-center mb-8 px-8">
|
||||
{/* Horizontal Line */}
|
||||
<div className="absolute top-1/2 w-[87%] left-1/2 -translate-x-1/2 h-[2px] bg-blue-600" />
|
||||
|
||||
{/* Timeline Numbers */}
|
||||
{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>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Timeline Content */}
|
||||
<div className="flex justify-between gap-8">
|
||||
{items.map((item, index) => (
|
||||
<div key={index} className="flex-1 text-center relative">
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="slide-container 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-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Main Title'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{renderTimelineContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type2TimelineSlideLayout
|
||||
119
servers/nextjs/components/layouts/Type3SlideLayout.tsx
Normal file
119
servers/nextjs/components/layouts/Type3SlideLayout.tsx
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'type3-slide'
|
||||
export const layoutName = 'Type3 Slide'
|
||||
export const layoutDescription = 'A centered title with a grid of image cards, each containing a heading and description.'
|
||||
|
||||
const type3SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Featured Content').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
}),
|
||||
image: ImageSchema.meta({
|
||||
description: "Item image",
|
||||
})
|
||||
})).min(2).max(4).default([
|
||||
{
|
||||
heading: 'First Feature',
|
||||
description: 'Description for the first featured item with detailed information',
|
||||
image: {
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'A beautiful road in the mountains'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Second Feature',
|
||||
description: 'Description for the second featured item with relevant details',
|
||||
image: {
|
||||
url: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
|
||||
prompt: 'Modern office workspace'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Third Feature',
|
||||
description: 'Description for the third featured item with important points',
|
||||
image: {
|
||||
url: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
|
||||
prompt: 'Laptop with code on screen'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "List of featured items (2-4 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type3SlideSchema
|
||||
|
||||
export type Type3SlideData = z.infer<typeof type3SlideSchema>
|
||||
|
||||
interface Type3SlideLayoutProps {
|
||||
data?: Partial<Type3SlideData>
|
||||
}
|
||||
|
||||
const Type3SlideLayout: React.FC<Type3SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
|
||||
const getGridCols = (length: number) => {
|
||||
switch (length) {
|
||||
case 1: return 'lg:grid-cols-1';
|
||||
case 2: return 'lg:grid-cols-2';
|
||||
case 3: return 'lg:grid-cols-3';
|
||||
case 4: return 'lg:grid-cols-4';
|
||||
default: return 'lg:grid-cols-1';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="slide-container shadow-lg rounded-sm w-full max-w-[1280px] px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] font-inter flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="text-center mb-4 lg:mb-16 w-full">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Featured Content'}
|
||||
</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
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="flex flex-col w-full rounded-lg overflow-hidden relative"
|
||||
>
|
||||
{/* Image */}
|
||||
<div className="max-md:h-[140px] max-lg:h-[180px] h-48 w-full">
|
||||
<img
|
||||
src={item.image?.url || ''}
|
||||
alt={item.image?.prompt || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="space-y-2 p-3 lg:p-6">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type3SlideLayout
|
||||
71
servers/nextjs/components/layouts/Type4SlideLayout.tsx
Normal file
71
servers/nextjs/components/layouts/Type4SlideLayout.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type4-slide'
|
||||
export const layoutName = 'Type4 Slide'
|
||||
export const layoutDescription = 'A chart-focused layout with title, chart visualization, and description text.'
|
||||
|
||||
const type4SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Chart Analysis').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(500).default('This chart shows important data trends and insights that help understand the current situation and make informed decisions.').meta({
|
||||
description: "Description text for the chart",
|
||||
}),
|
||||
chartData: z.any().optional().meta({
|
||||
description: "Chart data object",
|
||||
}),
|
||||
isFullSizeChart: z.boolean().default(false).meta({
|
||||
description: "Whether to display chart in full size mode",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type4SlideSchema
|
||||
|
||||
export type Type4SlideData = z.infer<typeof type4SlideSchema>
|
||||
|
||||
interface Type4SlideLayoutProps {
|
||||
data?: Partial<Type4SlideData>
|
||||
}
|
||||
|
||||
const Type4SlideLayout: React.FC<Type4SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const isFullSizeGraph = slideData?.isFullSizeChart || false
|
||||
|
||||
// Simple placeholder chart component
|
||||
const ChartPlaceholder = () => (
|
||||
<div className="w-full h-64 lg:h-80 bg-gradient-to-br from-blue-50 to-blue-100 rounded-lg border-2 border-blue-200 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="text-blue-600 text-4xl mb-4">📊</div>
|
||||
<p className="text-blue-700 font-semibold">Chart Component</p>
|
||||
<p className="text-blue-600 text-sm mt-1">Data visualization will appear here</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
className="slide-container font-inter 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>
|
||||
|
||||
<div className={`flex w-full items-center ${isFullSizeGraph
|
||||
? "flex-col mt-4 lg:mt-10 gap-2 sm:gap-4 md:gap-6 lg:gap-10"
|
||||
: "mt-4 lg:mt-16 gap-4 sm:gap-8 md:gap-12 lg:gap-16"
|
||||
}`}>
|
||||
<div className="w-full">
|
||||
<ChartPlaceholder />
|
||||
</div>
|
||||
<div className="w-full text-center">
|
||||
<p className={`text-base sm:text-lg lg:text-xl text-gray-700 leading-relaxed ${isFullSizeGraph ? 'text-center' : ''}`}>
|
||||
{slideData?.description || 'This chart shows important data trends and insights that help understand the current situation and make informed decisions.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type4SlideLayout
|
||||
102
servers/nextjs/components/layouts/Type5SlideLayout.tsx
Normal file
102
servers/nextjs/components/layouts/Type5SlideLayout.tsx
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type5-slide'
|
||||
export const layoutName = 'Type5 Slide'
|
||||
export const layoutDescription = 'A two-column layout with title and description on the left, and numbered items with large numerals on the right.'
|
||||
|
||||
const type5SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Key Points').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(500).default('Here is the main description that provides context and introduction to the numbered points on the right side.').meta({
|
||||
description: "Main description text",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(3).default([
|
||||
{
|
||||
heading: 'First Key Point',
|
||||
description: 'Detailed explanation of the first important point that supports the main topic'
|
||||
},
|
||||
{
|
||||
heading: 'Second Key Point',
|
||||
description: 'Detailed explanation of the second important point with relevant information'
|
||||
},
|
||||
{
|
||||
heading: 'Third Key Point',
|
||||
description: 'Detailed explanation of the third important point that concludes the discussion'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of numbered items (2-3 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type5SlideSchema
|
||||
|
||||
export type Type5SlideData = z.infer<typeof type5SlideSchema>
|
||||
|
||||
interface Type5SlideLayoutProps {
|
||||
data?: Partial<Type5SlideData>
|
||||
}
|
||||
|
||||
const Type5SlideLayout: React.FC<Type5SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const numberTranslations: string[] = ['01', '02', '03', '04', '05', '06']
|
||||
|
||||
return (
|
||||
<div
|
||||
className="slide-container rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="flex flex-col lg:flex-row gap-4 sm:gap-18 md:gap-16 items-center w-full">
|
||||
{/* Left section - Title and Description */}
|
||||
<div className="lg:w-1/2 lg:space-y-8">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Key Points'}
|
||||
</h1>
|
||||
|
||||
<p className="text-base sm:text-lg lg:text-xl text-gray-700 leading-relaxed">
|
||||
{slideData?.description || 'Here is the main description that provides context and introduction to the numbered points on the right side.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right section - Numbered items */}
|
||||
<div className="lg:w-1/2 relative">
|
||||
<div className="space-y-3 lg:space-y-6">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex gap-6">
|
||||
<div className="text-[26px] lg:text-[32px] leading-[40px] px-1 font-bold mb-4 text-blue-600">
|
||||
{numberTranslations[index] || `0${index + 1}`}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type5SlideLayout
|
||||
150
servers/nextjs/components/layouts/Type6SlideLayout.tsx
Normal file
150
servers/nextjs/components/layouts/Type6SlideLayout.tsx
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type6-slide'
|
||||
export const layoutName = 'Type6 Slide'
|
||||
export const layoutDescription = 'A centered title with a flexible grid of icon-based content items, adapting layout based on item count.'
|
||||
|
||||
const type6SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Our Services').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
}),
|
||||
icon: z.string().default('⭐').meta({
|
||||
description: "Icon emoji for the item",
|
||||
})
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
heading: 'Professional Service',
|
||||
description: 'High-quality professional services tailored to your specific needs and requirements',
|
||||
icon: '🎯'
|
||||
},
|
||||
{
|
||||
heading: 'Expert Consultation',
|
||||
description: 'Expert advice and consultation from experienced professionals in the field',
|
||||
icon: '💡'
|
||||
},
|
||||
{
|
||||
heading: 'Quality Assurance',
|
||||
description: 'Comprehensive quality assurance processes to ensure excellent results',
|
||||
icon: '✅'
|
||||
},
|
||||
{
|
||||
heading: 'Customer Support',
|
||||
description: 'Dedicated customer support available to assist you throughout the process',
|
||||
icon: '🤝'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of service items (2-6 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type6SlideSchema
|
||||
|
||||
export type Type6SlideData = z.infer<typeof type6SlideSchema>
|
||||
|
||||
interface Type6SlideLayoutProps {
|
||||
data?: Partial<Type6SlideData>
|
||||
}
|
||||
|
||||
const Type6SlideLayout: React.FC<Type6SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const isGridLayout = items.length >= 4
|
||||
|
||||
const getGridCols = (length: number) => {
|
||||
switch (length) {
|
||||
case 1: return 'lg:grid-cols-1';
|
||||
case 2: return 'lg:grid-cols-2';
|
||||
case 3: return 'lg:grid-cols-3';
|
||||
case 4: return 'lg:grid-cols-4';
|
||||
case 5: return 'lg:grid-cols-5';
|
||||
case 6: return 'lg:grid-cols-6';
|
||||
default: return 'lg:grid-cols-1';
|
||||
}
|
||||
}
|
||||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<div className={`grid grid-cols-1 ${items.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
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex items-start gap-2 mg:gap-4">
|
||||
<div className="flex-shrink-0 lg:w-16">
|
||||
<div className="w-12 h-12 lg:w-16 lg:h-16 bg-blue-600 rounded-lg flex items-center justify-center text-white text-xl lg:text-2xl">
|
||||
{item.icon}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight mb-2">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="text-center mb-4">
|
||||
<div className="w-16 h-16 lg:w-20 lg:h-20 bg-blue-600 rounded-lg flex items-center justify-center text-white text-2xl lg:text-3xl mx-auto mb-4">
|
||||
{item.icon}
|
||||
</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>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed text-center">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="slide-container 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-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Our Services'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{isGridLayout ? renderGridContent() : renderHorizontalContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type6SlideLayout
|
||||
173
servers/nextjs/components/layouts/Type7SlideLayout.tsx
Normal file
173
servers/nextjs/components/layouts/Type7SlideLayout.tsx
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'type7-slide'
|
||||
export const layoutName = 'Type7 Slide'
|
||||
export const layoutDescription = 'A centered title with a flexible grid of icon-based content items, adapting layout based on item count.'
|
||||
|
||||
const type7SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Our Services').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
}),
|
||||
icon: IconSchema.default({
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'Default icon'
|
||||
}).meta({
|
||||
description: "Icon for the item",
|
||||
})
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
heading: 'Professional Service',
|
||||
description: 'High-quality professional services tailored to your specific needs and requirements',
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'Professional service icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Expert Consultation',
|
||||
description: 'Expert advice and consultation from experienced professionals in the field',
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
|
||||
prompt: 'Expert consultation icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Quality Assurance',
|
||||
description: 'Comprehensive quality assurance processes to ensure excellent results',
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
|
||||
prompt: 'Quality assurance icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Customer Support',
|
||||
description: 'Dedicated customer support available to assist you throughout the process',
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'Customer support icon'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "List of service items (2-6 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type7SlideSchema
|
||||
|
||||
export type Type7SlideData = z.infer<typeof type7SlideSchema>
|
||||
|
||||
interface Type7SlideLayoutProps {
|
||||
data?: Partial<Type7SlideData>
|
||||
}
|
||||
|
||||
const Type7SlideLayout: React.FC<Type7SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const isGridLayout = items.length >= 4
|
||||
|
||||
const getGridCols = (length: number) => {
|
||||
switch (length) {
|
||||
case 1: return 'lg:grid-cols-1';
|
||||
case 2: return 'lg:grid-cols-2';
|
||||
case 3: return 'lg:grid-cols-3';
|
||||
case 4: return 'lg:grid-cols-4';
|
||||
case 5: return 'lg:grid-cols-5';
|
||||
case 6: return 'lg:grid-cols-6';
|
||||
default: return 'lg:grid-cols-1';
|
||||
}
|
||||
}
|
||||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<div className={`grid grid-cols-1 ${items.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
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex items-start gap-2 mg:gap-4">
|
||||
<div className="flex-shrink-0 lg:w-16">
|
||||
<div className="w-12 h-12 lg:w-16 lg:h-16 bg-blue-600 rounded-lg flex items-center justify-center overflow-hidden">
|
||||
<img
|
||||
src={item.icon?.url || ''}
|
||||
alt={item.icon?.prompt || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight mb-2">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="text-center mb-4">
|
||||
<div className="w-16 h-16 lg:w-20 lg:h-20 bg-blue-600 rounded-lg flex items-center justify-center mx-auto mb-4 overflow-hidden">
|
||||
<img
|
||||
src={item.icon?.url || ''}
|
||||
alt={item.icon?.prompt || 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>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed text-center">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="slide-container 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-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Our Services'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{isGridLayout ? renderGridContent() : renderHorizontalContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type7SlideLayout
|
||||
167
servers/nextjs/components/layouts/Type8SlideLayout.tsx
Normal file
167
servers/nextjs/components/layouts/Type8SlideLayout.tsx
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'type8-slide'
|
||||
export const layoutName = 'Type8 Slide'
|
||||
export const layoutDescription = 'A two-column layout with title and description on the left, and icon-based items on the right.'
|
||||
|
||||
const type8SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Key Features').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(500).default('Here is the main description that provides context and introduces the key features outlined on the right side.').meta({
|
||||
description: "Main description text",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
}),
|
||||
icon: IconSchema.default({
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'Default icon'
|
||||
}).meta({
|
||||
description: "Icon for the item",
|
||||
})
|
||||
})).min(2).max(3).default([
|
||||
{
|
||||
heading: 'Advanced Features',
|
||||
description: 'Cutting-edge functionality designed to enhance productivity and user experience',
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
prompt: 'Advanced features icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Reliable Performance',
|
||||
description: 'Consistent and dependable performance across all platforms and devices',
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
|
||||
prompt: 'Reliable performance icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Secure Environment',
|
||||
description: 'Enterprise-grade security measures to protect your data and privacy',
|
||||
icon: {
|
||||
url: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
|
||||
prompt: 'Secure environment icon'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "List of featured items (2-3 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type8SlideSchema
|
||||
|
||||
export type Type8SlideData = z.infer<typeof type8SlideSchema>
|
||||
|
||||
interface Type8SlideLayoutProps {
|
||||
data?: Partial<Type8SlideData>
|
||||
}
|
||||
|
||||
const Type8SlideLayout: React.FC<Type8SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
|
||||
const renderItems = () => {
|
||||
if (items.length === 2) {
|
||||
// Vertical stacked layout for 2 items
|
||||
return (
|
||||
<div className="space-y-4 lg:space-y-8">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="text-center mb-4">
|
||||
<div className="w-16 h-16 lg:w-20 lg:h-20 bg-blue-600 rounded-lg flex items-center justify-center mx-auto mb-4 overflow-hidden">
|
||||
<img
|
||||
src={item.icon?.url || ''}
|
||||
alt={item.icon?.prompt || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1 lg:space-y-3">
|
||||
<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">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
// Horizontal layout with side icons for 3+ items
|
||||
return (
|
||||
<div className="space-y-4 lg:space-y-8">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-[32px] md:w-[64px] h-[32px] md:h-[64px]">
|
||||
<div className="w-full h-full bg-blue-600 rounded-lg flex items-center justify-center overflow-hidden">
|
||||
<img
|
||||
src={item.icon?.url || ''}
|
||||
alt={item.icon?.prompt || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:space-y-3">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="slide-container shadow-lg w-full max-w-[1280px] rounded-sm font-inter px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-8 lg:gap-16 items-center w-full">
|
||||
{/* Left section - Title and Description */}
|
||||
<div className="space-y-2 lg:space-y-6">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Key Features'}
|
||||
</h1>
|
||||
|
||||
<p className="text-base sm:text-lg lg:text-xl text-gray-700 leading-relaxed">
|
||||
{slideData?.description || 'Here is the main description that provides context and introduces the key features outlined on the right side.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right section - Items */}
|
||||
<div className="relative">
|
||||
{renderItems()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type8SlideLayout
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import * as z from "zod";
|
||||
|
||||
export const imageSchema = z.object({
|
||||
export const ImageSchema = z.object({
|
||||
url: z.url().meta({
|
||||
description: "URL to image",
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export interface PresentationData {
|
|||
thumbnail: string | null;
|
||||
language: string;
|
||||
} | null;
|
||||
slides: Slide[];
|
||||
slides: any;
|
||||
}
|
||||
|
||||
interface PresentationGenerationState {
|
||||
|
|
@ -143,7 +143,7 @@ const presentationGenerationSlice = createSlice({
|
|||
|
||||
// Update indices for all slides to ensure they remain sequential
|
||||
state.presentationData.slides = state.presentationData.slides.map(
|
||||
(slide, idx) => ({
|
||||
(slide: any, idx: number) => ({
|
||||
...slide,
|
||||
index: idx,
|
||||
})
|
||||
|
|
@ -154,7 +154,7 @@ const presentationGenerationSlice = createSlice({
|
|||
if (state.presentationData) {
|
||||
state.presentationData.slides.splice(action.payload, 1);
|
||||
state.presentationData.slides = state.presentationData.slides.map(
|
||||
(slide, idx) => ({
|
||||
(slide: any, idx: number) => ({
|
||||
...slide,
|
||||
index: idx,
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue