Merge branch 'feat/custom_schema_and_layout' of github.com:presenton/presenton into feat/custom_schema_and_layout
This commit is contained in:
commit
e92603d689
13 changed files with 180 additions and 491 deletions
|
|
@ -115,7 +115,13 @@ const SlideContent = ({
|
|||
// }, [presentationData?.slides, isStreaming]);
|
||||
|
||||
const renderLayout = (slide: any) => {
|
||||
const layoutName = idMapFileNames[slide.layoutId];
|
||||
console.log(slide)
|
||||
console.log(idMapFileNames)
|
||||
const layoutName = idMapFileNames[slide.layout];
|
||||
if (!layoutName) {
|
||||
return <div>Layout not found</div>
|
||||
}
|
||||
console.log(layoutName)
|
||||
const Layout = dynamic(() => import(`@/components/layouts/${layoutName}`)) as React.ComponentType<{ data: any }>;
|
||||
return <Layout data={slide.content} />
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,23 +1,16 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'bullet-point-slide'
|
||||
export const layoutName = 'Bullet Point Slide'
|
||||
export const layoutDescription = 'A slide with a title, subtitle, and a list of bullet points.'
|
||||
|
||||
const imageSchema = z.object({
|
||||
url: z.url().meta({
|
||||
description: "URL to image",
|
||||
}),
|
||||
prompt: z.string().meta({
|
||||
description: "Prompt used to generate the image",
|
||||
}),
|
||||
})
|
||||
|
||||
const bulletPointSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Key Points').meta({
|
||||
description: "Title of the slide",
|
||||
badu: "'badf"
|
||||
|
||||
}),
|
||||
subtitle: z.string().min(3).max(150).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
|
|
@ -47,8 +40,8 @@ interface BulletPointSlideLayoutProps {
|
|||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const BulletPointSlideLayout: React.FC<BulletPointSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
|
||||
const slideData = bulletPointSlideSchema.parse(data || {})
|
||||
const BulletPointSlideLayout: React.FC<BulletPointSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
|
|
@ -77,7 +70,7 @@ const BulletPointSlideLayout: React.FC<BulletPointSlideLayoutProps> = ({ data, a
|
|||
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 ? {
|
||||
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'
|
||||
|
|
@ -92,21 +85,21 @@ const BulletPointSlideLayout: React.FC<BulletPointSlideLayoutProps> = ({ data, a
|
|||
<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
|
||||
<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}
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{slideData.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData.backgroundImage
|
||||
{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}
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
|
@ -122,8 +115,8 @@ const BulletPointSlideLayout: React.FC<BulletPointSlideLayoutProps> = ({ data, a
|
|||
{/* 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 <= 4 ? '6' : slideData.bulletPoints.length <= 6 ? '4' : '3'} relative z-10`}>
|
||||
{slideData.bulletPoints.map((point, index) => (
|
||||
<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">
|
||||
|
|
@ -132,7 +125,7 @@ const BulletPointSlideLayout: React.FC<BulletPointSlideLayoutProps> = ({ data, a
|
|||
</div>
|
||||
|
||||
{/* Enhanced bullet text */}
|
||||
<span className={`text-lg md:text-xl leading-relaxed break-words font-medium ${slideData.backgroundImage
|
||||
<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`}>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'conclusion-slide'
|
||||
export const layoutName = 'Conclusion Slide'
|
||||
|
|
@ -36,8 +37,8 @@ const conclusionSlideSchema = z.object({
|
|||
}).optional().meta({
|
||||
description: "Optional contact information",
|
||||
}),
|
||||
backgroundImage: z.string().optional().meta({
|
||||
description: "URL to background image for the slide",
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -50,8 +51,7 @@ interface ConclusionSlideLayoutProps {
|
|||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const ConclusionSlideLayout: React.FC<ConclusionSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
|
||||
const slideData = conclusionSlideSchema.parse(data || {})
|
||||
const ConclusionSlideLayout: React.FC<ConclusionSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
|
|
@ -80,7 +80,7 @@ const ConclusionSlideLayout: React.FC<ConclusionSlideLayoutProps> = ({ data, acc
|
|||
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 ? {
|
||||
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'
|
||||
|
|
@ -95,21 +95,21 @@ const ConclusionSlideLayout: React.FC<ConclusionSlideLayoutProps> = ({ data, acc
|
|||
<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
|
||||
<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}
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{slideData.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData.backgroundImage
|
||||
{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}
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ const ConclusionSlideLayout: React.FC<ConclusionSlideLayoutProps> = ({ data, acc
|
|||
<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) => (
|
||||
{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">
|
||||
|
|
@ -153,7 +153,7 @@ const ConclusionSlideLayout: React.FC<ConclusionSlideLayoutProps> = ({ data, acc
|
|||
{/* Call to Action & Contact Info - Takes up 1/3 of space */}
|
||||
<div className="space-y-6">
|
||||
{/* Call to Action */}
|
||||
{slideData.callToAction && (
|
||||
{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]}`} />
|
||||
|
|
@ -167,13 +167,13 @@ const ConclusionSlideLayout: React.FC<ConclusionSlideLayoutProps> = ({ data, acc
|
|||
|
||||
<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}
|
||||
{slideData?.callToAction}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Contact Information */}
|
||||
{slideData.contactInfo && Object.values(slideData.contactInfo).some(Boolean) && (
|
||||
{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]}`} />
|
||||
|
|
@ -181,9 +181,9 @@ const ConclusionSlideLayout: React.FC<ConclusionSlideLayoutProps> = ({ data, acc
|
|||
<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 && (
|
||||
{slideData?.contactInfo?.email && (
|
||||
<a
|
||||
href={`mailto:${slideData.contactInfo.email}`}
|
||||
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`}>
|
||||
|
|
@ -192,13 +192,13 @@ const ConclusionSlideLayout: React.FC<ConclusionSlideLayoutProps> = ({ data, acc
|
|||
<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>
|
||||
<span className="text-sm font-medium text-slate-700 break-all">{slideData?.contactInfo?.email}</span>
|
||||
</a>
|
||||
)}
|
||||
|
||||
{slideData.contactInfo.phone && (
|
||||
{slideData?.contactInfo?.phone && (
|
||||
<a
|
||||
href={`tel:${slideData.contactInfo.phone}`}
|
||||
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`}>
|
||||
|
|
@ -210,9 +210,9 @@ const ConclusionSlideLayout: React.FC<ConclusionSlideLayoutProps> = ({ data, acc
|
|||
</a>
|
||||
)}
|
||||
|
||||
{slideData.contactInfo.website && (
|
||||
{slideData?.contactInfo?.website && (
|
||||
<a
|
||||
href={slideData.contactInfo.website}
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
|
||||
|
||||
export const layoutId = 'content-slide'
|
||||
|
|
@ -16,8 +17,8 @@ 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: z.string().optional().meta({
|
||||
description: "URL to background image for the slide",
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -30,8 +31,7 @@ interface ContentSlideLayoutProps {
|
|||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const ContentSlideLayout: React.FC<ContentSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
|
||||
const slideData = contentSlideSchema.parse(data || {})
|
||||
const ContentSlideLayout: React.FC<ContentSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
|
|
@ -52,8 +52,8 @@ const ContentSlideLayout: React.FC<ContentSlideLayoutProps> = ({ data, accentCol
|
|||
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})`,
|
||||
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'
|
||||
} : {}}
|
||||
|
|
@ -74,21 +74,21 @@ const ContentSlideLayout: React.FC<ContentSlideLayoutProps> = ({ data, accentCol
|
|||
<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
|
||||
<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}
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{slideData.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData.backgroundImage
|
||||
{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}
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
|
@ -104,11 +104,11 @@ const ContentSlideLayout: React.FC<ContentSlideLayoutProps> = ({ data, accentCol
|
|||
{/* 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
|
||||
<div className={`text-lg md:text-xl leading-relaxed break-words relative z-10 ${slideData?.backgroundImage
|
||||
? 'text-slate-700'
|
||||
: 'text-slate-700'
|
||||
}`}>
|
||||
{slideData.content.split('\n').map((paragraph, index) => (
|
||||
{slideData?.content?.split('\n').map((paragraph, index) => (
|
||||
paragraph.trim() && (
|
||||
<p key={index} className="mb-5 last:mb-0 font-medium leading-relaxed">
|
||||
{paragraph}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
|
||||
|
||||
export const layoutId = 'first-slide'
|
||||
|
|
@ -13,17 +14,17 @@ const firstSlideSchema = z.object({
|
|||
subtitle: z.string().min(10).max(200).default('Subtitle for the slide').optional().meta({
|
||||
description: "Optional subtitle or tagline",
|
||||
}),
|
||||
author: z.string().min(2).max(100).default('John Doe').optional().meta({
|
||||
author: z.string().max(100).default('John Doe').optional().meta({
|
||||
description: "Author or presenter name",
|
||||
}),
|
||||
date: z.string().optional().meta({
|
||||
description: "Presentation date",
|
||||
}),
|
||||
company: z.string().min(2).max(100).default('Company Name').optional().meta({
|
||||
company: z.string().max(100).default('Company Name').optional().meta({
|
||||
description: "Company or organization name",
|
||||
}),
|
||||
backgroundImage: z.string().optional().meta({
|
||||
description: "URL to background image for the slide",
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -36,8 +37,8 @@ interface FirstSlideLayoutProps {
|
|||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const FirstSlideLayout: React.FC<FirstSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
|
||||
const slideData = firstSlideSchema.parse(data || {})
|
||||
const FirstSlideLayout: React.FC<FirstSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
|
|
@ -58,7 +59,7 @@ const FirstSlideLayout: React.FC<FirstSlideLayoutProps> = ({ data, accentColor =
|
|||
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 ? {
|
||||
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'
|
||||
|
|
@ -82,22 +83,22 @@ const FirstSlideLayout: React.FC<FirstSlideLayoutProps> = ({ data, accentColor =
|
|||
{/* Main Content */}
|
||||
<main className="flex-1 flex flex-col justify-center items-center max-w-5xl">
|
||||
{/* Title */}
|
||||
<h1 className={`text-5xl md:text-6xl font-black mb-6 tracking-tight leading-[0.9] break-words ${slideData.backgroundImage
|
||||
<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}
|
||||
{slideData?.title}
|
||||
</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
|
||||
{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}
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
|
@ -110,21 +111,21 @@ const FirstSlideLayout: React.FC<FirstSlideLayoutProps> = ({ data, accentColor =
|
|||
{/* 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">
|
||||
{slideData.author && (
|
||||
{slideData?.author && (
|
||||
<p className={`text-lg font-semibold break-words text-slate-800`}>
|
||||
{slideData.author}
|
||||
{slideData?.author}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{slideData.company && (
|
||||
{slideData?.company && (
|
||||
<p className={`text-base font-medium break-words ${accentColors[accentColor]} bg-gradient-to-r bg-clip-text text-transparent`}>
|
||||
{slideData.company}
|
||||
{slideData?.company}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{slideData.date && (
|
||||
{slideData?.date && (
|
||||
<p className={`text-sm break-words text-slate-600 font-medium tracking-wide`}>
|
||||
{slideData.date}
|
||||
{slideData?.date}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,156 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
|
||||
export const layoutId = 'image-slide'
|
||||
export const layoutName = 'Image Slide'
|
||||
export const layoutDescription = 'A slide with a title, subtitle, image, and content'
|
||||
|
||||
const imageSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Image Showcase').meta({
|
||||
description: "Title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(3).max(150).default('Subtitle for the slide').optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
image: z.string().default('https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=800&h=600&fit=crop').meta({
|
||||
description: "Main image URL",
|
||||
}),
|
||||
imageCaption: z.string().min(5).max(200).default('Image caption').optional().meta({
|
||||
description: "Optional image caption or description",
|
||||
}),
|
||||
content: z.string().min(10).max(600).optional().meta({
|
||||
description: "Optional supporting content text",
|
||||
}),
|
||||
backgroundImage: z.string().optional().meta({
|
||||
description: "URL to background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = imageSlideSchema
|
||||
|
||||
export type ImageSlideData = z.infer<typeof imageSlideSchema>
|
||||
|
||||
interface ImageSlideLayoutProps {
|
||||
data?: Partial<ImageSlideData>
|
||||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const ImageSlideLayout: React.FC<ImageSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
|
||||
const slideData = imageSlideSchema.parse(data || {})
|
||||
|
||||
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"
|
||||
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 flex items-center min-h-0">
|
||||
<div className=" ">
|
||||
|
||||
|
||||
<div className={`text-lg md:text-xl leading-relaxed break-words font-medium relative z-10 ${slideData.backgroundImage
|
||||
? 'text-slate-700'
|
||||
: 'text-slate-700'
|
||||
}`}>
|
||||
{slideData.content?.split('\n').map((paragraph, index) => (
|
||||
paragraph.trim() && (
|
||||
<p key={index} className="mb-5 last:mb-0">
|
||||
{paragraph}
|
||||
</p>
|
||||
)
|
||||
))}
|
||||
</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>
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="relative flex-1 group overflow-hidden rounded-2xl shadow-2xl">
|
||||
<img
|
||||
src={slideData.image}
|
||||
alt={slideData.imageCaption || slideData.title}
|
||||
className="w-full h-full object-cover transition-transform duration-300 "
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||
</div>
|
||||
{slideData.imageCaption && (
|
||||
<p className={`text-sm mt-4 text-center italic break-words font-medium ${slideData.backgroundImage
|
||||
? 'text-slate-300'
|
||||
: 'text-slate-600'
|
||||
}`}>
|
||||
{slideData.imageCaption}
|
||||
</p>
|
||||
)}
|
||||
</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 ImageSlideLayout
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
|
||||
|
||||
export const layoutId = 'process-slide'
|
||||
|
|
@ -45,8 +46,8 @@ const processSlideSchema = z.object({
|
|||
description: 'Final delivery and ongoing support'
|
||||
}
|
||||
]).describe('Process steps (2-6 items)'),
|
||||
backgroundImage: z.string().optional().meta({
|
||||
description: "URL to background image for the slide",
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -59,8 +60,7 @@ interface ProcessSlideLayoutProps {
|
|||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const ProcessSlideLayout: React.FC<ProcessSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
|
||||
const slideData = processSlideSchema.parse(data || {})
|
||||
const ProcessSlideLayout: React.FC<ProcessSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
|
|
@ -89,8 +89,8 @@ const ProcessSlideLayout: React.FC<ProcessSlideLayoutProps> = ({ data, accentCol
|
|||
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})`,
|
||||
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'
|
||||
} : {}}
|
||||
|
|
@ -104,21 +104,21 @@ const ProcessSlideLayout: React.FC<ProcessSlideLayoutProps> = ({ data, accentCol
|
|||
<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
|
||||
<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}
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{slideData.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData.backgroundImage
|
||||
{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}
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
|
@ -132,10 +132,10 @@ const ProcessSlideLayout: React.FC<ProcessSlideLayoutProps> = ({ data, accentCol
|
|||
<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) => (
|
||||
{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}%` }}>
|
||||
<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>
|
||||
|
|
@ -163,7 +163,7 @@ const ProcessSlideLayout: React.FC<ProcessSlideLayoutProps> = ({ data, accentCol
|
|||
</div>
|
||||
|
||||
{/* Arrow Between Steps */}
|
||||
{index < slideData.processSteps.length - 1 && (
|
||||
{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`}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
|
||||
|
||||
export const layoutId = 'quote-slide'
|
||||
|
|
@ -28,8 +29,8 @@ const quoteSlideSchema = z.object({
|
|||
authorImage: z.string().optional().meta({
|
||||
description: "URL to author photo",
|
||||
}),
|
||||
backgroundImage: z.string().optional().meta({
|
||||
description: "URL to background image for the slide",
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -42,8 +43,7 @@ interface QuoteSlideLayoutProps {
|
|||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
|
||||
const slideData = quoteSlideSchema.parse(data || {})
|
||||
const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
|
|
@ -64,8 +64,8 @@ const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data, accentColor =
|
|||
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})`,
|
||||
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'
|
||||
} : {}}
|
||||
|
|
@ -79,21 +79,21 @@ const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data, accentColor =
|
|||
<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
|
||||
<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}
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{slideData.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData.backgroundImage
|
||||
{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}
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
|
@ -115,26 +115,26 @@ const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data, accentColor =
|
|||
</div>
|
||||
|
||||
{/* Quote Text */}
|
||||
<blockquote className={`text-2xl md:text-3xl leading-relaxed mb-8 italic break-words relative z-10 font-light ${slideData.backgroundImage
|
||||
<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}"
|
||||
"{slideData?.quote}"
|
||||
</blockquote>
|
||||
|
||||
{/* 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 ? (
|
||||
{slideData?.authorImage ? (
|
||||
<img
|
||||
src={slideData.authorImage}
|
||||
alt={slideData.author}
|
||||
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('')}
|
||||
{slideData?.author?.split(' ').map(n => n[0]).join('')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -142,18 +142,18 @@ const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data, accentColor =
|
|||
{/* Author Details */}
|
||||
<div className="text-left">
|
||||
<p className="text-xl font-bold text-slate-900 break-words">
|
||||
{slideData.author}
|
||||
{slideData?.author}
|
||||
</p>
|
||||
|
||||
{slideData.authorTitle && (
|
||||
{slideData?.authorTitle && (
|
||||
<p className={`text-base font-semibold bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent break-words`}>
|
||||
{slideData.authorTitle}
|
||||
{slideData?.authorTitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{slideData.company && (
|
||||
{slideData?.company && (
|
||||
<p className="text-sm text-slate-600 font-medium break-words">
|
||||
{slideData.company}
|
||||
{slideData?.company}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
|
||||
|
||||
export const layoutId = 'statistics-slide'
|
||||
|
|
@ -52,8 +53,8 @@ const statisticsSlideSchema = z.object({
|
|||
context: 'Customer service'
|
||||
}
|
||||
]).describe('List of statistics (2-6 items)'),
|
||||
backgroundImage: z.string().optional().meta({
|
||||
description: "URL to background image for the slide",
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -66,9 +67,9 @@ interface StatisticsSlideLayoutProps {
|
|||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const StatisticsSlideLayout: React.FC<StatisticsSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
|
||||
const slideData = statisticsSlideSchema.parse(data || {})
|
||||
const statsCount = slideData.statistics.length
|
||||
const StatisticsSlideLayout: React.FC<StatisticsSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
|
||||
const statsCount = slideData?.statistics?.length || 0
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
|
|
@ -121,8 +122,8 @@ const StatisticsSlideLayout: React.FC<StatisticsSlideLayoutProps> = ({ data, acc
|
|||
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})`,
|
||||
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'
|
||||
} : {}}
|
||||
|
|
@ -136,21 +137,21 @@ const StatisticsSlideLayout: React.FC<StatisticsSlideLayoutProps> = ({ data, acc
|
|||
<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
|
||||
<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}
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{slideData.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData.backgroundImage
|
||||
{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}
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
|
@ -163,25 +164,25 @@ const StatisticsSlideLayout: React.FC<StatisticsSlideLayoutProps> = ({ data, acc
|
|||
{/* Enhanced Statistics Grid */}
|
||||
<main className="flex-1 flex items-center justify-center">
|
||||
<div className={`grid ${getGridCols()} gap-6 w-full max-w-6xl`}>
|
||||
{slideData.statistics.map((stat, index) => (
|
||||
{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]}`} />
|
||||
|
||||
{/* Statistic Value */}
|
||||
<div className={`text-4xl md:text-5xl font-black mb-2 bg-gradient-to-r ${accentColors[accentColor]} bg-clip-text text-transparent`}>
|
||||
{stat.value}
|
||||
{stat?.value}
|
||||
</div>
|
||||
|
||||
{/* Statistic Label */}
|
||||
<h3 className="text-lg md:text-xl font-bold text-slate-900 mb-2 break-words">
|
||||
{stat.label}
|
||||
{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 :
|
||||
{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>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
|
||||
export const layoutId = 'team-slide'
|
||||
export const layoutName = 'Team Slide'
|
||||
|
|
@ -57,7 +58,9 @@ const teamSlideSchema = z.object({
|
|||
linkedin: 'https://linkedin.com/in/emmarodriguez'
|
||||
}
|
||||
]).describe('Team members (1-6 people)'),
|
||||
backgroundImage: z.string().optional().describe('URL to background image for the slide')
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = teamSlideSchema
|
||||
|
|
@ -69,8 +72,7 @@ interface TeamSlideLayoutProps {
|
|||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
|
||||
const slideData = teamSlideSchema.parse(data || {})
|
||||
const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
|
|
@ -99,8 +101,8 @@ const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data, accentColor = '
|
|||
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})`,
|
||||
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'
|
||||
} : {}}
|
||||
|
|
@ -114,21 +116,21 @@ const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data, accentColor = '
|
|||
<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
|
||||
<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}
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{slideData.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData.backgroundImage
|
||||
{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}
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
|
@ -140,12 +142,12 @@ const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data, accentColor = '
|
|||
|
||||
{/* 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 <= 2 ? 'grid-cols-2' :
|
||||
slideData.teamMembers.length <= 3 ? 'grid-cols-3' :
|
||||
slideData.teamMembers.length <= 4 ? 'grid-cols-2 lg:grid-cols-4' :
|
||||
<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) => (
|
||||
{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]}`} />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { imageSchema } from './defaultSchemes';
|
||||
|
||||
|
||||
export const layoutId = 'timeline-slide'
|
||||
|
|
@ -54,8 +55,8 @@ const timelineSlideSchema = z.object({
|
|||
]).meta({
|
||||
description: "Timeline events (2-6 items)",
|
||||
}),
|
||||
backgroundImage: z.string().optional().meta({
|
||||
description: "URL to background image for the slide",
|
||||
backgroundImage: imageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -68,8 +69,7 @@ interface TimelineSlideLayoutProps {
|
|||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const TimelineSlideLayout: React.FC<TimelineSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
|
||||
const slideData = timelineSlideSchema.parse(data || {})
|
||||
const TimelineSlideLayout: React.FC<TimelineSlideLayoutProps> = ({ data: slideData, accentColor = 'blue' }) => {
|
||||
|
||||
const accentColors = {
|
||||
blue: 'from-blue-600 to-blue-800',
|
||||
|
|
@ -122,8 +122,8 @@ const TimelineSlideLayout: React.FC<TimelineSlideLayoutProps> = ({ data, accentC
|
|||
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})`,
|
||||
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'
|
||||
} : {}}
|
||||
|
|
@ -137,21 +137,21 @@ const TimelineSlideLayout: React.FC<TimelineSlideLayoutProps> = ({ data, accentC
|
|||
<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
|
||||
<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}
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{slideData.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData.backgroundImage
|
||||
{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}
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
|
@ -169,8 +169,8 @@ const TimelineSlideLayout: React.FC<TimelineSlideLayoutProps> = ({ data, accentC
|
|||
<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}%` }}>
|
||||
{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' && (
|
||||
|
|
|
|||
|
|
@ -1,181 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
|
||||
export const layoutId = 'two-column-slide'
|
||||
export const layoutName = 'Two Column Slide'
|
||||
export const layoutDescription = 'A slide with a title, subtitle, and two columns of content'
|
||||
|
||||
const twoColumnSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Two Column Layout').meta({
|
||||
description: "Title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(3).max(150).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
leftColumn: z.object({
|
||||
title: z.string().min(3).max(100).default('Left Column').meta({
|
||||
description: "Left column title",
|
||||
}),
|
||||
content: z.string().min(10).max(800).default('Content for the left column goes here. This can include detailed information, explanations, or supporting details.').meta({
|
||||
description: "Left column content",
|
||||
})
|
||||
}).default({
|
||||
title: 'Left Column',
|
||||
content: 'Content for the left column goes here. This can include detailed information, explanations, or supporting details.'
|
||||
}),
|
||||
rightColumn: z.object({
|
||||
title: z.string().min(3).max(100).default('Right Column').meta({
|
||||
description: "Right column title",
|
||||
}),
|
||||
content: z.string().min(10).max(800).default('Content for the right column goes here. This can include additional information, comparisons, or contrasting details.').meta({
|
||||
description: "Right column content",
|
||||
})
|
||||
}).default({
|
||||
title: 'Right Column',
|
||||
content: 'Content for the right column goes here. This can include additional information, comparisons, or contrasting details.'
|
||||
}),
|
||||
backgroundImage: z.string().optional().meta({
|
||||
description: "URL to background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = twoColumnSlideSchema
|
||||
|
||||
|
||||
export type TwoColumnSlideData = z.infer<typeof twoColumnSlideSchema>
|
||||
|
||||
interface TwoColumnSlideLayoutProps {
|
||||
data?: Partial<TwoColumnSlideData>
|
||||
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
|
||||
}
|
||||
|
||||
const TwoColumnSlideLayout: React.FC<TwoColumnSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
|
||||
|
||||
const slideData = twoColumnSlideSchema.parse(data || {})
|
||||
|
||||
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"
|
||||
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>
|
||||
|
||||
{/* Two Column Content with Enhanced Styling */}
|
||||
<main className="flex-1 grid grid-cols-2 gap-6">
|
||||
{/* Left Column */}
|
||||
<div className="bg-white/95 backdrop-blur-lg rounded-2xl p-8 shadow-2xl border border-white/50 flex flex-col relative overflow-hidden hover:shadow-3xl transition-shadow duration-300">
|
||||
{/* Column accent */}
|
||||
<div className={`absolute top-0 left-0 w-full h-1 bg-gradient-to-r ${accentColors[accentColor]}`} />
|
||||
|
||||
<h2 className={`text-2xl md:text-3xl font-bold mb-5 break-words ${slideData.backgroundImage
|
||||
? 'text-slate-900'
|
||||
: 'text-slate-900'
|
||||
}`}>
|
||||
{slideData.leftColumn.title}
|
||||
</h2>
|
||||
<div className={`text-base md:text-lg leading-relaxed break-words flex-1 ${slideData.backgroundImage
|
||||
? 'text-slate-700'
|
||||
: 'text-slate-700'
|
||||
}`}>
|
||||
{slideData.leftColumn.content.split('\n').map((paragraph, index) => (
|
||||
paragraph.trim() && (
|
||||
<p key={index} className="mb-4 last:mb-0 font-medium">
|
||||
{paragraph}
|
||||
</p>
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column */}
|
||||
<div className="bg-white/95 backdrop-blur-lg rounded-2xl p-8 shadow-2xl border border-white/50 flex flex-col relative overflow-hidden hover:shadow-3xl transition-shadow duration-300">
|
||||
{/* Column accent */}
|
||||
<div className={`absolute top-0 left-0 w-full h-1 bg-gradient-to-r ${accentColors[accentColor]}`} />
|
||||
|
||||
<h2 className={`text-2xl md:text-3xl font-bold mb-5 break-words ${slideData.backgroundImage
|
||||
? 'text-slate-900'
|
||||
: 'text-slate-900'
|
||||
}`}>
|
||||
{slideData.rightColumn.title}
|
||||
</h2>
|
||||
<div className={`text-base md:text-lg leading-relaxed break-words flex-1 ${slideData.backgroundImage
|
||||
? 'text-slate-700'
|
||||
: 'text-slate-700'
|
||||
}`}>
|
||||
{slideData.rightColumn.content.split('\n').map((paragraph, index) => (
|
||||
paragraph.trim() && (
|
||||
<p key={index} className="mb-4 last:mb-0 font-medium">
|
||||
{paragraph}
|
||||
</p>
|
||||
)
|
||||
))}
|
||||
</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 TwoColumnSlideLayout
|
||||
23
servers/nextjs/components/layouts/defaultSchemes.ts
Normal file
23
servers/nextjs/components/layouts/defaultSchemes.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import * as z from "zod";
|
||||
|
||||
export const imageSchema = z.object({
|
||||
url: z.url().meta({
|
||||
description: "URL to image",
|
||||
}),
|
||||
prompt: z.string().meta({
|
||||
description: "Prompt used to generate the image",
|
||||
}),
|
||||
}).meta({
|
||||
imageType: 'image',
|
||||
})
|
||||
|
||||
export const IconSchema = z.object({
|
||||
url: z.string().meta({
|
||||
description: "URL to icon",
|
||||
}),
|
||||
prompt: z.string().meta({
|
||||
description: "Prompt used to generate the icon",
|
||||
}),
|
||||
}).meta({
|
||||
imageType: 'icon',
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue