Merge branch 'feat/custom_layouts' into feat/custom_schema_and_layout

This commit is contained in:
shiva raj badu 2025-07-15 18:45:32 +05:45
commit e4e6a8a308
23 changed files with 3021 additions and 916 deletions

View file

@ -0,0 +1,29 @@
import { NextResponse } from 'next/server'
import { promises as fs } from 'fs'
import path from 'path'
export async function GET() {
try {
// Get the path to the layouts directory
const layoutsDirectory = path.join(process.cwd(), 'components', 'layouts')
// Read all files in the layouts directory
const files = await fs.readdir(layoutsDirectory)
// Filter for .tsx files and exclude any non-layout files
const layoutFiles = files.filter(file =>
file.endsWith('.tsx') &&
!file.startsWith('.') &&
!file.includes('.test.') &&
!file.includes('.spec.')
)
return NextResponse.json(layoutFiles)
} catch (error) {
console.error('Error reading layouts directory:', error)
return NextResponse.json(
{ error: 'Failed to read layouts directory' },
{ status: 500 }
)
}
}

View file

@ -79,6 +79,9 @@ body {
strong{
@apply font-black ;
}
::selection {
background-color: hsl(var(--chart-1));
color: white;

View file

@ -0,0 +1,137 @@
# Layout Preview Studio
A modular, responsive layout preview system for viewing and testing presentation layout components with realistic sample data.
## ✨ Features
- **Dynamic Layout Discovery**: Automatically discovers and loads layout components
- **Interactive Navigation**: Easy navigation with quick select grid
- **Beautiful Presentation Display**: Mock browser frame with professional styling
- **Detailed Information Panel**: Layout metadata and sample data inspection
- **Responsive Design**: Mobile-friendly with collapsible sidebar
- **Professional Loading States**: Skeleton loaders and error handling
- **Type Safety**: Full TypeScript support with shared types
## 🏗️ Architecture
The system is built with a modular architecture for maintainability and reusability:
```
layout-preview/
├── components/ # Modular UI components
│ ├── LayoutNavigation.tsx # Navigation controls & layout selector
│ ├── LayoutDisplay.tsx # Main layout preview area
│ ├── LayoutInfoPanel.tsx # Information and data structure display
│ └── LoadingStates.tsx # Loading, error, and empty states
├── hooks/ # Custom React hooks
│ └── useLayoutLoader.ts # Layout loading logic and state management
├── utils/ # Utility functions
│ └── sampleDataGenerator.ts # Realistic sample data generation
├── types/ # Shared TypeScript types
│ └── index.ts # Common interfaces and types
├── page.tsx # Main page component
└── README.md # This file
```
## 🧩 Components
### LayoutNavigation
- Previous/Next navigation buttons
- Layout counter and current layout info
- Quick select grid for fast switching
- Responsive design with mobile optimization
### LayoutDisplay
- Mock browser frame presentation
- Layout rendering with sample data
- Professional shadow and styling effects
- Empty state with helpful messaging
### LayoutInfoPanel
- Layout metadata display
- Collapsible sample data viewer
- Copy to clipboard functionality
- Position tracking in collection
### LoadingStates
- Loading spinner with animated dots
- Error state with retry functionality
- Empty state with helpful instructions
- Skeleton loading animations
## 🎯 Custom Hooks
### useLayoutLoader
Handles all layout loading logic:
- Fetches layout files from API
- Imports and validates components
- Generates realistic sample data
- Provides retry functionality
- Manages loading and error states
## 🛠️ Utilities
### sampleDataGenerator
Intelligent sample data generation:
- Context-aware field value generation
- Support for images, emails, phones, URLs
- Realistic business content
- Zod schema parsing and validation
- Array and object handling
## 📱 Responsive Design
The layout preview system is fully responsive:
- **Desktop**: Sidebar navigation with main preview area
- **Tablet**: Collapsible navigation panels
- **Mobile**: Stacked layout with touch-friendly controls
## 🎨 Styling
Built with:
- **Tailwind CSS**: Utility-first styling
- **shadcn/ui**: Consistent component library
- **Gradient Backgrounds**: Modern visual appeal
- **Glass Morphism**: Backdrop blur effects
- **Smooth Animations**: Hover and transition effects
## 🔧 Usage
The system automatically discovers layout components that export:
```typescript
// Layout component
export default function MyLayout({ data }: { data: any }) {
return <div>{/* Your layout */}</div>
}
// Zod schema for type safety and sample data generation
export const Schema = z.object({
title: z.string(),
description: z.string(),
// ... other fields
})
```
## 🚀 Getting Started
1. **Add Layout Components**: Place your layout files in the appropriate directory
2. **Export Requirements**: Ensure each layout exports both a default component and Schema
3. **Navigate**: Use the navigation controls or quick select grid
4. **Inspect**: View layout information and sample data structure
5. **Test**: See how your layouts render with realistic data
## 🎯 Benefits
- **Modular Architecture**: Easy to maintain and extend
- **Type Safety**: Full TypeScript support prevents runtime errors
- **Beautiful UI**: Professional design that's pleasant to use
- **Developer Experience**: Quick feedback loop for layout development
- **Responsive**: Works on all device sizes
- **Accessible**: Keyboard navigation and screen reader support
## 📈 Performance
- **Lazy Loading**: Components are imported only when needed
- **Optimized Rendering**: Efficient re-renders with React best practices
- **Minimal Bundle**: Modular structure enables tree shaking
- **Caching**: Sample data generation is memoized

View file

@ -0,0 +1,185 @@
'use client'
import React from 'react'
import { Card, CardContent } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Loader2, AlertCircle, RefreshCw, FileX, Layers } from 'lucide-react'
interface LoadingStatesProps {
type: 'loading' | 'error' | 'empty'
message?: string
onRetry?: () => void
}
const LoadingStates: React.FC<LoadingStatesProps> = ({
type,
message,
onRetry
}) => {
if (type === 'loading') {
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-indigo-50 flex items-center justify-center">
<Card className="p-8 text-center shadow-xl border-0 bg-white/80 backdrop-blur-sm">
<CardContent className="space-y-6">
<div className="relative">
<div className="w-16 h-16 mx-auto mb-4 relative">
<Loader2 className="w-16 h-16 text-blue-500 animate-spin" />
<div className="absolute inset-0 w-16 h-16 border-4 border-blue-100 rounded-full animate-pulse"></div>
</div>
</div>
<div className="space-y-2">
<h3 className="text-xl font-semibold text-gray-900">
Loading Layouts
</h3>
<p className="text-gray-600">
{message || 'Discovering and loading layout components...'}
</p>
</div>
{/* Loading animation dots */}
<div className="flex justify-center space-x-1">
<div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style={{ animationDelay: '0ms' }}></div>
<div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style={{ animationDelay: '150ms' }}></div>
<div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style={{ animationDelay: '300ms' }}></div>
</div>
</CardContent>
</Card>
</div>
)
}
if (type === 'error') {
return (
<div className="min-h-screen bg-gradient-to-br from-red-50 via-white to-orange-50 flex items-center justify-center">
<Card className="p-8 text-center shadow-xl border-0 bg-white/80 backdrop-blur-sm max-w-md">
<CardContent className="space-y-6">
<div className="w-16 h-16 mx-auto p-4 bg-red-100 rounded-full">
<AlertCircle className="w-8 h-8 text-red-500" />
</div>
<div className="space-y-2">
<h3 className="text-xl font-semibold text-gray-900">
Something went wrong
</h3>
<p className="text-gray-600 text-sm leading-relaxed">
{message || 'Failed to load layouts. Please check your layout files and try again.'}
</p>
</div>
{onRetry && (
<Button
onClick={onRetry}
className="mt-4 bg-red-500 hover:bg-red-600 text-white"
>
<RefreshCw className="w-4 h-4 mr-2" />
Try Again
</Button>
)}
</CardContent>
</Card>
</div>
)
}
if (type === 'empty') {
return (
<div className="min-h-screen bg-gradient-to-br from-gray-50 via-white to-slate-50 flex items-center justify-center">
<Card className="p-8 text-center shadow-xl border-0 bg-white/80 backdrop-blur-sm max-w-md">
<CardContent className="space-y-6">
<div className="w-16 h-16 mx-auto p-4 bg-gray-100 rounded-full">
<FileX className="w-8 h-8 text-gray-400" />
</div>
<div className="space-y-2">
<h3 className="text-xl font-semibold text-gray-700">
No Layouts Found
</h3>
<p className="text-gray-500 text-sm leading-relaxed">
No valid layout files were discovered. Make sure your layout components export both a default component and a Schema.
</p>
</div>
<div className="bg-gray-50 p-4 rounded-lg text-left text-xs text-gray-600">
<p className="font-medium mb-2">Expected structure:</p>
<code className="block">
export default MyLayout<br />
export const Schema = z.object(...)
</code>
</div>
{onRetry && (
<Button
onClick={onRetry}
variant="outline"
className="mt-4"
>
<RefreshCw className="w-4 h-4 mr-2" />
Refresh
</Button>
)}
</CardContent>
</Card>
</div>
)
}
return null
}
// Component for layout grid skeleton while loading
export const LayoutGridSkeleton: React.FC = () => {
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-indigo-50">
{/* Header Skeleton */}
<div className="bg-white shadow-sm border-b">
<div className="max-w-7xl mx-auto px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-gray-200 rounded-lg animate-pulse"></div>
<div className="w-32 h-6 bg-gray-200 rounded animate-pulse"></div>
</div>
<div className="w-16 h-6 bg-gray-200 rounded animate-pulse"></div>
</div>
</div>
</div>
{/* Main Content Skeleton */}
<div className="max-w-7xl mx-auto p-6">
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
{/* Sidebar Skeleton */}
<div className="lg:col-span-1 space-y-4">
<Card className="p-4">
<div className="space-y-3">
<div className="w-24 h-4 bg-gray-200 rounded animate-pulse"></div>
<div className="space-y-2">
<div className="w-full h-8 bg-gray-200 rounded animate-pulse"></div>
<div className="w-full h-8 bg-gray-200 rounded animate-pulse"></div>
</div>
<div className="grid grid-cols-3 gap-2">
{[...Array(6)].map((_, i) => (
<div key={i} className="w-full h-12 bg-gray-200 rounded animate-pulse"></div>
))}
</div>
</div>
</Card>
</div>
{/* Main Display Skeleton */}
<div className="lg:col-span-3">
<Card className="p-6">
<div className="space-y-4">
<div className="w-full h-96 bg-gray-200 rounded-lg animate-pulse"></div>
<div className="space-y-2">
<div className="w-48 h-4 bg-gray-200 rounded animate-pulse"></div>
<div className="w-32 h-3 bg-gray-200 rounded animate-pulse"></div>
</div>
</div>
</Card>
</div>
</div>
</div>
</div>
)
}
export default LoadingStates

View file

@ -0,0 +1,136 @@
'use client'
import { useState, useEffect } from 'react'
import { LayoutInfo } from '../types'
import { toast } from '@/hooks/use-toast'
interface UseLayoutLoaderReturn {
layouts: LayoutInfo[]
loading: boolean
error: string | null
retry: () => void
}
export const useLayoutLoader = (): UseLayoutLoaderReturn => {
const [layouts, setLayouts] = useState<LayoutInfo[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const loadAllLayouts = async () => {
try {
setLoading(true)
setError(null)
const response = await fetch('/api/layouts')
if (!response.ok) {
toast({
title: 'Error loading layouts',
description: response.statusText,
})
return
}
const layoutFiles: string[] = await response.json()
const loadedLayouts: LayoutInfo[] = []
for (const fileName of layoutFiles) {
try {
const layoutName = fileName.replace('.tsx', '').replace('.ts', '')
const module = await import(`@/components/layouts/${layoutName}`)
if (!module.default) {
toast({
title: `${layoutName} has no default export`,
description: 'Please ensure the layout file exports a default component',
})
console.warn(`${layoutName} has no default export`)
continue
}
if (!module.Schema) {
toast({
title: `${layoutName} is missing required Schema export`,
description: 'Please ensure the layout file exports a Schema',
})
console.error(`${layoutName} is missing required Schema export`)
continue
}
// Use empty object to let schema apply its default values
// User will need to provide actual data when using the layouts
const sampleData = module.Schema.parse({})
loadedLayouts.push({
name: layoutName,
component: module.default,
schema: module.Schema,
sampleData,
fileName
})
} catch (importError) {
console.error(`Failed to import ${fileName}:`, importError)
// Try alternative import path
try {
const layoutName = fileName.replace('.tsx', '').replace('.ts', '')
const module = await import(`@/components/layouts/${layoutName}`)
if (module.default && module.Schema) {
// Use empty object to let schema apply its default values
const sampleData = module.Schema.parse({})
loadedLayouts.push({
name: layoutName,
component: module.default,
schema: module.Schema,
sampleData,
fileName
})
} else {
console.error(`${layoutName} is missing required exports (default component or Schema)`)
}
} catch (altError) {
console.error(`Alternative import also failed for ${fileName}:`, altError)
}
}
}
if (loadedLayouts.length === 0) {
toast({
title: 'No valid layouts found',
description: 'Make sure your layout files export both a default component and a Schema.',
})
setError('No valid layouts found. Make sure your layout files export both a default component and a Schema.')
} else {
setLayouts(loadedLayouts)
setError(null)
}
} catch (error) {
console.error('Error loading layouts:', error)
setError(error instanceof Error ? error.message : 'Failed to load layouts')
} finally {
setLoading(false)
}
}
const retry = () => {
loadAllLayouts()
}
useEffect(() => {
loadAllLayouts()
}, [])
return {
layouts,
loading,
error,
retry
}
}

View file

@ -0,0 +1,86 @@
'use client'
import React from 'react'
import { useLayoutLoader } from './hooks/useLayoutLoader'
import LoadingStates from './components/LoadingStates'
import { Card } from '@/components/ui/card'
/**
* Layout Preview Page
*
* Simple vertical display of all layout components with their sample data.
*/
const LayoutPreview = () => {
const { layouts, loading, error, retry } = useLayoutLoader()
// Handle loading state
if (loading) {
return <LoadingStates type="loading" />
}
// Handle error state
if (error) {
return <LoadingStates type="error" message={error} onRetry={retry} />
}
// Handle empty state
if (layouts.length === 0) {
return <LoadingStates type="empty" onRetry={retry} />
}
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-indigo-50">
{/* Header */}
<header className="bg-white z-20 backdrop-blur-sm border-b border-white/20 shadow-sm sticky top-0 ">
<div className="max-w-4xl mx-auto px-6 py-4">
<div className="text-center">
<h1 className="text-2xl font-bold text-gray-900">Layout Preview</h1>
<p className="text-sm text-gray-600 mt-1">
{layouts.length} layout{layouts.length !== 1 ? 's' : ''} found
</p>
</div>
</div>
</header>
{/* Layouts List */}
<main className="max-w-7xl mx-auto space-y-8">
{layouts.map((layout, index) => {
const { component: LayoutComponent, sampleData, name, fileName } = layout
return (
<Card key={index} className="overflow-hidden py-0 shadow-lg border-0 bg-white">
{/* Layout Header */}
<div className="bg-gray-50 px-6 py-4 border-b border-gray-200">
<div className="flex items-center justify-between">
<div>
<h2 className="text-lg font-semibold text-gray-900">{name}</h2>
<p className="text-sm text-gray-600 font-mono">{fileName}</p>
</div>
<div className="px-3 py-1 bg-blue-100 text-blue-700 rounded-md text-sm font-medium">
#{index + 1}
</div>
</div>
</div>
{/* Layout Content */}
<LayoutComponent data={sampleData} />
</Card>
)
})}
</main>
{/* Footer */}
<footer className="mt-16 bg-white/50 backdrop-blur-sm border-t border-white/20">
<div className="max-w-4xl mx-auto px-6 py-8">
<div className="text-center">
<p className="text-sm text-gray-600">
Layout Preview {layouts.length} component{layouts.length !== 1 ? 's' : ''} rendered
</p>
</div>
</div>
</footer>
</div>
)
}
export default LayoutPreview

View file

@ -0,0 +1,28 @@
/**
* Shared types for the Layout Preview system
*/
export interface LayoutInfo {
name: string
component: React.ComponentType<any>
schema: any
sampleData: any
fileName: string
}
export interface LoadingState {
loading: boolean
error: string | null
}
export interface NavigationState {
currentLayout: number
totalLayouts: number
}
export type LoadingStateType = 'loading' | 'error' | 'empty'
export interface ComponentProps {
className?: string
children?: React.ReactNode
}

View file

@ -0,0 +1,305 @@
/**
* Sample Data Generator Utility
*
* Generates realistic sample data from Zod schemas for layout previews.
* Provides context-aware data generation based on field names and types.
*/
export const generateSampleDataFromSchema = (schema: any, layoutName: string): any => {
if (!schema) return {}
try {
// Generate realistic sample data for all fields first
const generatedData = generateRealisticData(schema._def?.shape || schema.shape, layoutName)
// Merge generated data with defaults, giving priority to defaults
return generatedData
} catch (error) {
console.error(`Error generating sample data for ${layoutName}:`, error)
return {}
}
}
const generateRealisticData = (shape: any, layoutName: string): any => {
if (!shape) return {}
const data: any = {}
for (const [key, fieldSchema] of Object.entries(shape as any)) {
const field = fieldSchema as any
// Generate data for all fields (both required and optional)
// We'll let the defaults override this later if they exist
data[key] = generateFieldValue(key, field, layoutName)
}
return data
}
// const arrayMock = (length:number,element:any) => {
// return Array.from({length},()=>generateFieldValue(fieldName, element, layoutName))
// }
// const mockObject = (shapes:any) => {
// let obj:any = {}
// for(const [key,shape] of Object.entries(shapes)){
// const defaultValue = shape.def.defaultValue
// obj[key] = defaultValue ? defaultValue : generateFieldValue(key, shape, layoutName)
// }
// return obj
// }
// const generateMockValue = (fileType:string,format?:string)=>{
// switch(fileType){
// case 'number':
// return Math.floor(Math.random() * 100) + 1
// case 'string':
// return generateStringValue(fieldName, fieldSchema, layoutName)
// case 'boolean':
// return Math.random() > 0.5
// case 'object':
// return mockObject(fieldSchema.def.shape)
// case 'array':
// return arrayMock(fieldSchema.def.length,fieldSchema.def.element)
// }
// }
const generateFieldValue = (fieldName: string, fieldSchema: any, layoutName: string): any => {
console.log('BADU',fieldSchema,fieldName,layoutName)
const defaultValue = fieldSchema.def.defaultValue;
if(defaultValue){
console.log('DEFAULT VALUE',defaultValue)
return defaultValue;
}
if(fieldSchema.def.type ==='optional'){
return generateFieldValue(fieldName, fieldSchema.def.innerType, layoutName)
}
// Get the actual field type - handle optional fields properly
let actualFieldSchema = fieldSchema
let fieldType = fieldSchema._def?.typeName
// If this is an optional field (ZodOptional), get the inner type
if (fieldType === 'ZodOptional') {
actualFieldSchema = fieldSchema._def?.innerType
fieldType = actualFieldSchema?._def?.typeName
}
// For preview purposes, always generate data for optional fields
// (users want to see how the layout looks with content)
// Handle different field types
switch (fieldType) {
case 'ZodString':
return generateStringValue(fieldName, actualFieldSchema, layoutName)
case 'ZodArray':
return generateArrayValue(fieldName, actualFieldSchema, layoutName)
case 'ZodObject':
return generateObjectValue(fieldName, actualFieldSchema, layoutName)
case 'ZodEnum':
const options = actualFieldSchema._def?.values || []
return options[Math.floor(Math.random() * options.length)]
case 'ZodBoolean':
return Math.random() > 0.5
case 'ZodNumber':
return Math.floor(Math.random() * 100) + 1
default:
return generateStringValue(fieldName, actualFieldSchema, layoutName)
}
}
const generateStringValue = (fieldName: string, fieldSchema: any, layoutName: string): string => {
const lowerField = fieldName.toLowerCase()
// Handle URLs (images, logos, backgrounds, etc.)
if (lowerField.includes('url') || lowerField.includes('image') || lowerField.includes('logo')) {
if (lowerField.includes('logo')) {
return 'https://images.unsplash.com/photo-1611224923853-80b023f02d71?w=200&h=200&fit=crop'
}
if (lowerField.includes('background')) {
const backgrounds = [
'https://images.unsplash.com/photo-1557804506-669a67965ba0?w=1920&h=1080&fit=crop',
'https://images.unsplash.com/photo-1451187580459-43490279c0fa?w=1920&h=1080&fit=crop',
'https://images.unsplash.com/photo-1519389950473-47ba0277781c?w=1920&h=1080&fit=crop'
]
return backgrounds[Math.floor(Math.random() * backgrounds.length)]
}
// Regular images
const images = [
'https://images.unsplash.com/photo-1551434678-e076c223a692?w=800&h=600&fit=crop',
'https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=800&h=600&fit=crop',
'https://images.unsplash.com/photo-1504384308090-c894fdcc538d?w=800&h=600&fit=crop',
'https://images.unsplash.com/photo-1519389950473-47ba0277781c?w=800&h=600&fit=crop'
]
return images[Math.floor(Math.random() * images.length)]
}
// Handle email
if (lowerField.includes('email')) {
const domains = ['example.com', 'company.com', 'business.org']
const names = ['contact', 'info', 'hello', 'support']
return `${names[Math.floor(Math.random() * names.length)]}@${domains[Math.floor(Math.random() * domains.length)]}`
}
// Handle phone
if (lowerField.includes('phone')) {
return `+1 (555) ${Math.floor(Math.random() * 900) + 100}-${Math.floor(Math.random() * 9000) + 1000}`
}
// Handle website
if (lowerField.includes('website')) {
const sites = ['https://example.com', 'https://company.com', 'https://business.org']
return sites[Math.floor(Math.random() * sites.length)]
}
// Handle LinkedIn
if (lowerField.includes('linkedin')) {
return 'https://linkedin.com/company/example'
}
// Handle specific field names
if (lowerField.includes('title')) {
const titles = [
'Welcome to Our Presentation',
'Key Business Insights',
'Product Overview',
'Market Analysis',
'Future Vision',
'Strategic Goals'
]
return titles[Math.floor(Math.random() * titles.length)]
}
if (lowerField.includes('subtitle')) {
const subtitles = [
'Driving innovation through technology',
'Transforming the way we work',
'Building solutions for tomorrow',
'Excellence in every detail',
'Your success is our mission'
]
return subtitles[Math.floor(Math.random() * subtitles.length)]
}
if (lowerField.includes('author') || lowerField.includes('name')) {
const names = ['Alex Johnson', 'Sarah Chen', 'Michael Rodriguez', 'Emily Davis', 'David Kim']
return names[Math.floor(Math.random() * names.length)]
}
if (lowerField.includes('organization') || lowerField.includes('company')) {
const orgs = ['Tech Innovations Inc.', 'Future Solutions Ltd.', 'Global Dynamics Corp.', 'NextGen Enterprises']
return orgs[Math.floor(Math.random() * orgs.length)]
}
if (lowerField.includes('date')) {
return new Date().toLocaleDateString()
}
if (lowerField.includes('content')) {
const contents = [
'Our innovative approach combines cutting-edge technology with proven methodologies to deliver exceptional results. We focus on scalability, reliability, and user experience.',
'Through strategic partnerships and continuous innovation, we\'ve established ourselves as leaders in the industry. Our solutions are designed to meet evolving market demands.',
'With over a decade of experience, our team brings deep expertise and fresh perspectives to every project. We\'re committed to exceeding expectations and driving growth.'
]
return contents[Math.floor(Math.random() * contents.length)]
}
if (lowerField.includes('caption')) {
const captions = [
'Innovative solutions driving business transformation',
'Real-time analytics and insights at your fingertips',
'Seamless integration with existing workflows',
'Empowering teams to achieve more'
]
return captions[Math.floor(Math.random() * captions.length)]
}
if (lowerField.includes('action') || lowerField.includes('cta')) {
const actions = [
'Get Started Today!',
'Schedule a Demo',
'Contact Our Team',
'Learn More',
'Try It Free'
]
return actions[Math.floor(Math.random() * actions.length)]
}
// Default text based on field length constraints
const minLength = fieldSchema._def?.checks?.find((c: any) => c.kind === 'min')?.value || 10
const maxLength = fieldSchema._def?.checks?.find((c: any) => c.kind === 'max')?.value || 100
if (maxLength <= 50) {
return 'Sample short text content'
} else if (maxLength <= 150) {
return 'This is sample medium-length text content for preview purposes'
} else {
return 'This is sample long-form text content that demonstrates how the layout will look with realistic data. It provides a good representation of the final presentation slide.'
}
}
const generateArrayValue = (fieldName: string, fieldSchema: any, layoutName: string): any[] => {
const itemSchema = fieldSchema._def?.type
const minItems = fieldSchema._def?.minLength?.value || 2
const maxItems = Math.min(fieldSchema._def?.maxLength?.value || 5, 6)
const itemCount = Math.floor(Math.random() * (maxItems - minItems + 1)) + minItems
const lowerField = fieldName.toLowerCase()
if (lowerField.includes('bullet') || lowerField.includes('point')) {
const bulletPoints = [
'Increased efficiency and productivity',
'Cost-effective solutions',
'Enhanced user experience',
'Scalable architecture',
'Real-time analytics',
'24/7 customer support',
'Seamless integration capabilities',
'Advanced security features'
]
return bulletPoints.slice(0, itemCount)
}
if (lowerField.includes('takeaway') || lowerField.includes('key')) {
const takeaways = [
'Strategic advantage through innovation',
'Proven ROI within 6 months',
'Comprehensive support included',
'Future-ready technology stack',
'Industry-leading performance'
]
return takeaways.slice(0, itemCount)
}
// Generate generic array items
const items = []
for (let i = 0; i < itemCount; i++) {
if (itemSchema) {
items.push(generateFieldValue(`${fieldName}Item`, itemSchema, layoutName))
} else {
items.push(`Sample item ${i + 1}`)
}
}
return items
}
const generateObjectValue = (fieldName: string, fieldSchema: any, layoutName: string): any => {
const shape = fieldSchema._def?.shape
if (!shape) return {}
const obj: any = {}
for (const [key, subSchema] of Object.entries(shape)) {
obj[key] = generateFieldValue(key, subSchema, layoutName)
}
return obj
}

View file

@ -2,7 +2,6 @@ import { Tooltip } from '@radix-ui/react-tooltip'
import { TooltipProvider } from '@radix-ui/react-tooltip'
import React from 'react'
import { TooltipContent, TooltipTrigger, } from './ui/tooltip'
import { Button } from './ui/button'
const ToolTip = ({ children, content }: { children: React.ReactNode, content: string }) => {
return (

View file

@ -0,0 +1,139 @@
import React from 'react'
import { zodToJsonSchema } from "zod-to-json-schema";
import * as z from "zod";
const bulletPointSlideSchema = z.object({
title: z.string().min(3).max(100).default('Key Points').describe('Title of the slide'),
subtitle: z.string().min(3).max(150).optional().describe('Optional subtitle or description'),
icon: z.string().optional().describe('Icon to display in the slide'),
bulletPoints: z.array(z.string().min(5).max(200)).min(2).max(8).default([
'First key point that highlights important information',
'Second bullet point with valuable insights',
'Third point demonstrating clear benefits',
'Fourth item showcasing key features'
]).describe('List of bullet points (2-8 items)'),
backgroundImage: z.string().optional().describe('URL to background image for the slide')
})
console.log(zodToJsonSchema(bulletPointSlideSchema))
export const Schema = bulletPointSlideSchema
export type BulletPointSlideData = z.infer<typeof bulletPointSlideSchema>
interface BulletPointSlideLayoutProps {
data?: Partial<BulletPointSlideData>
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
}
const BulletPointSlideLayout: React.FC<BulletPointSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
const slideData = bulletPointSlideSchema.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 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 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 <= 4 ? '6' : 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>
{/* 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 BulletPointSlideLayout

View file

@ -0,0 +1,228 @@
import React from 'react'
import * as z from "zod";
const conclusionSlideSchema = z.object({
title: z.string().min(3).max(100).default('Conclusion').describe('Title of the slide'),
subtitle: z.string().min(3).max(150).optional().describe('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'
]).describe('Key takeaways or summary points (2-6 items)'),
callToAction: z.string().min(5).max(150).optional().describe('Optional call to action or next steps'),
contactInfo: z.object({
email: z.string().email().optional().describe('Contact email'),
phone: z.string().min(5).max(50).optional().describe('Contact phone number'),
website: z.string().url().optional().describe('Website URL')
}).optional().describe('Optional contact information'),
backgroundImage: z.string().optional().describe('URL to 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, accentColor = 'blue' }) => {
const slideData = conclusionSlideSchema.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 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

View file

@ -0,0 +1,122 @@
import React from 'react'
import * as z from "zod";
const contentSlideSchema = z.object({
title: z.string().min(3).max(100).default('Slide Title').describe('Title of the slide'),
subtitle: z.string().min(3).max(150).optional().describe('Optional subtitle or description'),
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.').describe('Main content text'),
backgroundImage: z.string().optional().describe('URL to background image for the slide')
})
export const Schema = contentSlideSchema
export type ContentSlideData = z.infer<typeof contentSlideSchema>
interface ContentSlideLayoutProps {
data?: Partial<ContentSlideData>
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
}
const ContentSlideLayout: React.FC<ContentSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
const slideData = contentSlideSchema.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>
{/* Grid overlay for professional look */}
<div className="absolute inset-0 opacity-[0.015]" 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: '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}
</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>
{/* 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'
}`}>
{slideData.content.split('\n').map((paragraph, index) => (
paragraph.trim() && (
<p key={index} className="mb-5 last:mb-0 font-medium leading-relaxed">
{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 ContentSlideLayout

View file

@ -0,0 +1,132 @@
import React from 'react'
import * as z from "zod";
const firstSlideSchema = z.object({
title: z.string().min(3).max(100).default('Welcome to Our Presentation').describe('Main title of the presentation'),
subtitle: z.string().min(10).max(200).default('Subtitle for the slide').optional().describe('Optional subtitle or tagline'),
author: z.string().min(2).max(100).default('John Doe').optional().describe('Author or presenter name'),
date: z.string().optional().describe('Presentation date'),
company: z.string().min(2).max(100).default('Company Name').optional().describe('Company or organization name'),
backgroundImage: z.string().optional().describe('URL to background image for the slide')
})
export const Schema = firstSlideSchema
export type FirstSlideData = z.infer<typeof firstSlideSchema>
interface FirstSlideLayoutProps {
data?: Partial<FirstSlideData>
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
}
const FirstSlideLayout: React.FC<FirstSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
const slideData = firstSlideSchema.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 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">
{/* 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}
</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>
)}
{/* 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>
{/* 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 && (
<p className={`text-lg font-semibold break-words text-slate-800`}>
{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>
)}
</div>
</div>
</main>
</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`} />
</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>
)
}
export default FirstSlideLayout

View file

@ -0,0 +1,139 @@
import React from 'react'
import * as z from "zod";
const imageSlideSchema = z.object({
title: z.string().min(3).max(100).default('Image Showcase').describe('Title of the slide'),
subtitle: z.string().min(3).max(150).default('Subtitle for the slide').optional().describe('Optional subtitle or description'),
image: z.string().default('https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=800&h=600&fit=crop').describe('Main image URL'),
imageCaption: z.string().min(5).max(200).default('Image caption').optional().describe('Optional image caption or description'),
content: z.string().min(10).max(600).optional().describe('Optional supporting content text'),
backgroundImage: z.string().optional().describe('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

View file

@ -0,0 +1,181 @@
import React from 'react'
import * as z from "zod";
const processSlideSchema = z.object({
title: z.string().min(3).max(100).default('Our Process').describe('Title of the slide'),
subtitle: z.string().min(3).max(150).optional().describe('Optional subtitle or description'),
processSteps: z.array(z.object({
step: z.number().min(1).max(10).describe('Step number'),
title: z.string().min(3).max(100).describe('Step title'),
description: z.string().min(10).max(200).describe('Step description')
})).min(2).max(6).default([
{
step: 1,
title: 'Discovery',
description: 'Understanding requirements and gathering initial insights'
},
{
step: 2,
title: 'Planning',
description: 'Strategic planning and roadmap development'
},
{
step: 3,
title: 'Implementation',
description: 'Executing the plan with precision and quality'
},
{
step: 4,
title: 'Delivery',
description: 'Final delivery and ongoing support'
}
]).describe('Process steps (2-6 items)'),
backgroundImage: z.string().optional().describe('URL to background image for the slide')
})
export const Schema = processSlideSchema
export type ProcessSlideData = z.infer<typeof processSlideSchema>
interface ProcessSlideLayoutProps {
data?: Partial<ProcessSlideData>
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
}
const ProcessSlideLayout: React.FC<ProcessSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
const slideData = processSlideSchema.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 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'
}
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 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}%` }}>
{/* 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`} />
</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>
</div>
{/* Arrow Between Steps */}
{index < slideData.processSteps.length - 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>
</div>
)}
</React.Fragment>
))}
</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 ProcessSlideLayout

View file

@ -0,0 +1,167 @@
import React from 'react'
import * as z from "zod";
const quoteSlideSchema = z.object({
title: z.string().min(3).max(100).default('Testimonials').describe('Title of the slide'),
subtitle: z.string().min(3).max(150).optional().describe('Optional subtitle or description'),
quote: z.string().min(10).max(500).default('This solution has transformed our business operations and exceeded all expectations.').describe('The main quote or testimonial'),
author: z.string().min(2).max(100).default('John Smith').describe('Quote author name'),
authorTitle: z.string().min(2).max(100).optional().describe('Author job title or position'),
company: z.string().min(2).max(100).optional().describe('Author company or organization'),
authorImage: z.string().optional().describe('URL to author photo'),
backgroundImage: z.string().optional().describe('URL to background image for the slide')
})
export const Schema = quoteSlideSchema
export type QuoteSlideData = z.infer<typeof quoteSlideSchema>
interface QuoteSlideLayoutProps {
data?: Partial<QuoteSlideData>
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
}
const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
const slideData = quoteSlideSchema.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 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`}>
"
</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>
{/* 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}
</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>
</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>
</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 QuoteSlideLayout

View file

@ -0,0 +1,200 @@
import React from 'react'
import * as z from "zod";
const statisticsSlideSchema = z.object({
title: z.string().min(3).max(100).default('Key Statistics').describe('Title of the slide'),
subtitle: z.string().min(3).max(150).optional().describe('Optional subtitle or description'),
statistics: z.array(z.object({
value: z.string().min(1).max(20).describe('Statistical value (e.g., "250%", "$1.2M", "99.9%")'),
label: z.string().min(3).max(100).describe('Description of the statistic'),
trend: z.enum(['up', 'down', 'neutral']).optional().describe('Trend direction indicator'),
context: z.string().min(5).max(200).optional().describe('Additional context or time period')
})).min(2).max(6).default([
{
value: '250%',
label: 'Revenue Growth',
trend: 'up',
context: 'Year over year increase'
},
{
value: '50M+',
label: 'Active Users',
trend: 'up',
context: 'Global user base'
},
{
value: '99.9%',
label: 'Uptime',
trend: 'neutral',
context: 'Service reliability'
},
{
value: '24/7',
label: 'Support',
trend: 'neutral',
context: 'Customer service'
}
]).describe('List of statistics (2-6 items)'),
backgroundImage: z.string().optional().describe('URL to background image for the slide')
})
export const Schema = statisticsSlideSchema
export type StatisticsSlideData = z.infer<typeof statisticsSlideSchema>
interface StatisticsSlideLayoutProps {
data?: Partial<StatisticsSlideData>
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 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':
return '↗'
case 'down':
return '↘'
case 'neutral':
return '→'
default:
return ''
}
}
const getGridCols = () => {
if (statsCount <= 2) return 'grid-cols-2'
if (statsCount <= 3) return 'grid-cols-3'
if (statsCount <= 4) return 'grid-cols-2 lg:grid-cols-4'
return 'grid-cols-2 lg:grid-cols-3'
}
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 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) => (
<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}
</div>
{/* Statistic Label */}
<h3 className="text-lg md:text-xl font-bold text-slate-900 mb-2 break-words">
{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}
</p>
)}
{/* 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>
</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 StatisticsSlideLayout

View file

@ -0,0 +1,217 @@
import React from 'react'
import * as z from "zod";
const teamSlideSchema = z.object({
title: z.string().min(3).max(100).default('Meet Our Team').describe('Title of the slide'),
subtitle: z.string().min(3).max(150).optional().describe('Optional subtitle or team description'),
teamMembers: z.array(z.object({
name: z.string().min(2).max(100).describe('Team member name'),
title: z.string().min(2).max(100).describe('Job title or role'),
image: z.string().optional().describe('URL to team member photo'),
bio: z.string().min(10).max(300).optional().describe('Brief biography or description'),
email: z.string().email().optional().describe('Contact email'),
linkedin: z.string().url().optional().describe('LinkedIn profile URL')
})).min(1).max(6).default([
{
name: 'Sarah Johnson',
title: 'Chief Executive Officer',
image: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=300&h=300&fit=crop&crop=face',
bio: 'Strategic leader with 15+ years experience driving innovation and growth in technology companies.',
email: 'sarah@company.com',
linkedin: 'https://linkedin.com/in/sarahjohnson'
},
{
name: 'Michael Chen',
title: 'Chief Technology Officer',
image: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=300&h=300&fit=crop&crop=face',
bio: 'Technology visionary specializing in scalable architecture and emerging technologies.',
email: 'michael@company.com',
linkedin: 'https://linkedin.com/in/michaelchen'
},
{
name: 'Emma Rodriguez',
title: 'Head of Design',
image: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=300&h=300&fit=crop&crop=face',
bio: 'Creative director passionate about user-centered design and innovative digital experiences.',
email: 'emma@company.com',
linkedin: 'https://linkedin.com/in/emmarodriguez'
}
]).describe('Team members (1-6 people)'),
backgroundImage: z.string().optional().describe('URL to background image for the slide')
})
export const Schema = teamSlideSchema
export type TeamSlideData = z.infer<typeof teamSlideSchema>
interface TeamSlideLayoutProps {
data?: Partial<TeamSlideData>
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
}
const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
const slideData = teamSlideSchema.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'
}
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'
}
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 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' :
'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]}`} />
{/* 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`} />
</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}
</p>
)}
</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>
</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 TeamSlideLayout

View file

@ -0,0 +1,225 @@
import React from 'react'
import * as z from "zod";
const timelineSlideSchema = z.object({
title: z.string().min(3).max(100).default('Project Timeline').describe('Title of the slide'),
subtitle: z.string().min(3).max(150).optional().describe('Optional subtitle or description'),
timelineItems: z.array(z.object({
date: z.string().min(2).max(50).describe('Date or time period'),
title: z.string().min(3).max(100).describe('Event or milestone title'),
description: z.string().min(10).max(300).describe('Event description'),
status: z.enum(['completed', 'current', 'upcoming']).default('upcoming').describe('Timeline item status')
})).min(2).max(6).default([
{
date: 'Q1 2024',
title: 'Project Initiation',
description: 'Project planning, team assembly, and initial requirements gathering',
status: 'completed'
},
{
date: 'Q2 2024',
title: 'Development Phase',
description: 'Core development work, prototype creation, and testing implementation',
status: 'current'
},
{
date: 'Q3 2024',
title: 'Testing & QA',
description: 'Comprehensive testing, quality assurance, and user acceptance testing',
status: 'upcoming'
},
{
date: 'Q4 2024',
title: 'Launch & Deployment',
description: 'Final deployment, go-live activities, and post-launch monitoring',
status: 'upcoming'
}
]).describe('Timeline events (2-6 items)'),
backgroundImage: z.string().optional().describe('URL to background image for the slide')
})
export const Schema = timelineSlideSchema
export type TimelineSlideData = z.infer<typeof timelineSlideSchema>
interface TimelineSlideLayoutProps {
data?: Partial<TimelineSlideData>
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red'
}
const TimelineSlideLayout: React.FC<TimelineSlideLayoutProps> = ({ data, accentColor = 'blue' }) => {
const slideData = timelineSlideSchema.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'
}
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'
}
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 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}%` }}>
{/* 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>
</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>
{/* 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>
</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 TimelineSlideLayout

View file

@ -0,0 +1,162 @@
import React from 'react'
import * as z from "zod";
const twoColumnSlideSchema = z.object({
title: z.string().min(3).max(100).default('Two Column Layout').describe('Title of the slide'),
subtitle: z.string().min(3).max(150).optional().describe('Optional subtitle or description'),
leftColumn: z.object({
title: z.string().min(3).max(100).default('Left Column').describe('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.').describe('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').describe('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.').describe('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().describe('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

View file

@ -0,0 +1,31 @@
"use client"
import { useTheme } from "next-themes"
import { Toaster as Sonner } from "sonner"
type ToasterProps = React.ComponentProps<typeof Sonner>
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
toastOptions={{
classNames: {
toast:
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-muted-foreground",
actionButton:
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
cancelButton:
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
},
}}
{...props}
/>
)
}
export { Toaster }

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
{
"name": "pfm",
"name": "presenton",
"version": "0.1.0",
"private": true,
"type": "module",
@ -46,15 +46,18 @@
"lucide-react": "^0.447.0",
"marked": "^15.0.11",
"next": "^14.2.14",
"puppeteer": "24.8.2",
"next-themes": "^0.4.6",
"react": "^18",
"react-dom": "^18",
"react-redux": "^9.1.2",
"recharts": "^2.15.0",
"sonner": "^2.0.6",
"tailwind-merge": "^2.5.3",
"tailwind-scrollbar-hide": "^2.0.0",
"tailwindcss-animate": "^1.0.7",
"tiptap-markdown": "^0.8.10"
"tiptap-markdown": "^0.8.10",
"zod": "^3.25.76",
"zod-to-json-schema": "^3.24.6"
},
"devDependencies": {
"@types/animejs": "^3.1.12",