add: swift template
This commit is contained in:
parent
d3209e4b9f
commit
a1b7dfd51b
10 changed files with 1696 additions and 0 deletions
|
|
@ -0,0 +1,242 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
|
||||
import { IconSchema, ImageSchema } from '@/presentation-templates/defaultSchemes';
|
||||
import { RemoteSvgIcon } from "@/app/hooks/useRemoteSvgIcon";
|
||||
|
||||
const layoutId = "bullet-with-icons-title-description"
|
||||
const layoutName = "Bullet With Icons Title Description"
|
||||
const layoutDescription = "Bullet with icons with title and description and title and description for whole"
|
||||
|
||||
const ItemSchema = z
|
||||
.object({
|
||||
icon: IconSchema,
|
||||
title: z.string().min(3).max(40).default("Lorem ipsum dolor"),
|
||||
description: z
|
||||
.string()
|
||||
.min(0)
|
||||
.max(160)
|
||||
.default(
|
||||
"Short supporting description that fits under the icon title."
|
||||
),
|
||||
})
|
||||
.default({
|
||||
icon: {
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/graduation-cap-bold.svg",
|
||||
__icon_query__: "feature icon",
|
||||
},
|
||||
title: "Lorem ipsum dolor",
|
||||
description: "Short supporting description that fits under the icon title.",
|
||||
})
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
title: z
|
||||
.string()
|
||||
.min(3)
|
||||
.max(60)
|
||||
.default("Our Infographic"),
|
||||
sideHeading: z.string().min(0).max(60).default("Lorem ipsum dolor sit amet,"),
|
||||
sideParagraph: z
|
||||
.string()
|
||||
.min(0)
|
||||
.max(300)
|
||||
.default(
|
||||
"Concise paragraph describing context. Keep it short and readable across one or two lines."
|
||||
),
|
||||
items: z
|
||||
.array(ItemSchema)
|
||||
.min(3)
|
||||
.max(4)
|
||||
.default([
|
||||
{
|
||||
icon: {
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/graduation-cap-bold.svg",
|
||||
__icon_query__: "feature icon",
|
||||
},
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the first icon explaining the point.",
|
||||
},
|
||||
{
|
||||
icon: {
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/user-bold.svg",
|
||||
__icon_query__: "feature icon",
|
||||
},
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the second icon explaining the point.",
|
||||
},
|
||||
{
|
||||
icon: {
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/calendar-blank-bold.svg",
|
||||
__icon_query__: "feature icon",
|
||||
},
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the third icon explaining the point.",
|
||||
},
|
||||
{
|
||||
icon: {
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/x-bold.svg",
|
||||
__icon_query__: "feature icon",
|
||||
},
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the fourth icon explaining the point.",
|
||||
},
|
||||
]),
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
})
|
||||
.default({
|
||||
title: "Our Infographic",
|
||||
sideHeading: "Lorem ipsum dolor sit amet,",
|
||||
sideParagraph:
|
||||
"Concise paragraph describing context. Keep it short and readable across one or two lines.",
|
||||
items: [
|
||||
{
|
||||
icon: { __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/graduation-cap-bold.svg", __icon_query__: "feature icon" },
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the first icon explaining the point.",
|
||||
},
|
||||
{
|
||||
icon: { __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/user-bold.svg", __icon_query__: "feature icon" },
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the second icon explaining the point.",
|
||||
},
|
||||
{
|
||||
icon: { __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/calendar-blank-bold.svg", __icon_query__: "feature icon" },
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the third icon explaining the point.",
|
||||
},
|
||||
{
|
||||
icon: { __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/x-bold.svg", __icon_query__: "feature icon" },
|
||||
title: "Lorem ipsum dolor",
|
||||
description:
|
||||
"Concise supporting text under the fourth icon explaining the point.",
|
||||
},
|
||||
],
|
||||
website: "www.yourwebsite.com",
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const InfographicFourIcons: React.FC<SlideLayoutProps> = ({ data }) => {
|
||||
const slideData = data || {}
|
||||
const items = slideData.items || []
|
||||
|
||||
const renderTitle = (title?: string) => {
|
||||
if (!title) return null
|
||||
const parts = title.split("\n")
|
||||
return (
|
||||
<>
|
||||
{parts.map((p, i) => (
|
||||
<div key={i}>{p}</div>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className=" w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
{(slideData as any )?.__companyName__ && <span className="text-[16px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{(slideData as any)?.__companyName__}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Title + right paragraph */}
|
||||
<div className="px-12 pt-2">
|
||||
<div className="grid grid-cols-[1.2fr_1fr] gap-10 items-start">
|
||||
<div className="text-[56px] leading-[1.05] font-semibold" style={{ color: "var(--text-heading-color, #111827)" }}>
|
||||
{renderTitle(slideData.title)}
|
||||
</div>
|
||||
<div>
|
||||
{slideData.sideHeading && (
|
||||
<div className="text-[16px] mb-2 font-semibold" style={{ color: "var(--text-heading-color, #111827)" }}>
|
||||
{slideData.sideHeading}
|
||||
</div>
|
||||
)}
|
||||
{slideData.sideParagraph && (
|
||||
<div className="text-[14px] leading-[1.6]" style={{ color: "var(--text-body-color, #6B7280)" }}>
|
||||
{slideData.sideParagraph}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Icons row */}
|
||||
<div className="px-12 pt-12">
|
||||
<div className="grid grid-flow-col auto-cols-[260px] gap-8 justify-center">
|
||||
{items.slice(0, 4).map((item, idx) => (
|
||||
<div key={idx} className="flex flex-col items-center text-center">
|
||||
<div className="relative">
|
||||
<div
|
||||
className="w-40 h-40 rounded-full flex items-center justify-center shadow"
|
||||
style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}
|
||||
>
|
||||
{/* Icon */}
|
||||
<RemoteSvgIcon
|
||||
url={item.icon.__icon_url__}
|
||||
strokeColor={"currentColor"}
|
||||
className="w-14 h-14"
|
||||
color="var(--text-heading-color, #111827)"
|
||||
title={item.icon.__icon_query__}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 text-[16px] font-semibold" style={{ color: "var(--text-heading-color, #111827)" }}>
|
||||
{item.title}
|
||||
</div>
|
||||
<div className="mt-2 text-[13px] leading-[1.6] max-w-[260px]" style={{ color: "var(--text-body-color, #6B7280)" }}>
|
||||
{item.description}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer (standardized like IntroSlideLayout) */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default InfographicFourIcons
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
import { RemoteSvgIcon } from "@/app/hooks/useRemoteSvgIcon";
|
||||
|
||||
const layoutId = "icon-bullet-list-description-slide"
|
||||
const layoutName = "Icon Bullet List Description"
|
||||
const layoutDescription = "Bullet list with title, description, and icon"
|
||||
|
||||
const IconSchema = z
|
||||
.object({
|
||||
__icon_url__: z
|
||||
.string()
|
||||
.default(
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/file-text-bold.svg"
|
||||
),
|
||||
__icon_query__: z.string().min(0).max(80).default("feature icon"),
|
||||
})
|
||||
.default({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/file-text-bold.svg",
|
||||
__icon_query__: "feature icon",
|
||||
})
|
||||
|
||||
const FeatureSchema = z
|
||||
.object({
|
||||
title: z.string().min(4).max(28).default("Customizable Workflows"),
|
||||
body: z
|
||||
.string()
|
||||
.min(20)
|
||||
.max(140)
|
||||
.default("Lorem ipsum dolor sit amet, dolor sit amet."),
|
||||
icon: IconSchema,
|
||||
})
|
||||
.default({
|
||||
title: "Customizable Workflows",
|
||||
body: "Lorem ipsum dolor sit amet, dolor sit amet.",
|
||||
icon: IconSchema.parse({}),
|
||||
})
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
|
||||
title: z
|
||||
.string()
|
||||
.min(8)
|
||||
.max(48)
|
||||
.default("Key Product Features"),
|
||||
description: z
|
||||
.string()
|
||||
.min(30)
|
||||
.max(200)
|
||||
.default(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor."
|
||||
),
|
||||
features: z
|
||||
.array(FeatureSchema)
|
||||
.min(3)
|
||||
.max(4)
|
||||
.default([
|
||||
FeatureSchema.parse({}),
|
||||
{
|
||||
title: "Multi-Device Access",
|
||||
body: "Lorem ipsum dolor sit amet.",
|
||||
icon: IconSchema.parse({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/devices-bold.svg",
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "Scalable Architecture",
|
||||
body: "Lorem ipsum dolor sit amet.",
|
||||
icon: IconSchema.parse({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/chart-line-up-bold.svg",
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "Detailed Reports",
|
||||
body: "Lorem ipsum dolor sit amet.",
|
||||
icon: IconSchema.parse({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/checks-bold.svg",
|
||||
}),
|
||||
},
|
||||
]),
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
})
|
||||
.default({
|
||||
|
||||
title: "Key Product Features",
|
||||
description:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor.",
|
||||
features: [
|
||||
FeatureSchema.parse({}),
|
||||
{
|
||||
title: "Multi-Device Access",
|
||||
body: "Lorem ipsum dolor sit amet.",
|
||||
icon: IconSchema.parse({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/devices-bold.svg",
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "Scalable Architecture",
|
||||
body: "Lorem ipsum dolor sit amet.",
|
||||
icon: IconSchema.parse({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/chart-line-up-bold.svg",
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "Detailed Reports",
|
||||
body: "Lorem ipsum dolor sit amet.",
|
||||
icon: IconSchema.parse({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/checks-bold.svg",
|
||||
}),
|
||||
},
|
||||
],
|
||||
website: "www.yourwebsite.com",
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const FeatureCards: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const features = slideData?.features || []
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className=" w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
{(slideData as any)?.__companyName__ && <span className="text-[16px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{(slideData as any)?.__companyName__}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Decorative right image area removed to keep imagery-driven design */}
|
||||
|
||||
<div className="px-12 pt-3">
|
||||
<h1 className="text-[48px] leading-[1.1] font-semibold" style={{ color: "var(--text-heading-color, #111827)" }}>{slideData?.title}</h1>
|
||||
<p className="mt-3 text-[16px] max-w-[760px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.description}</p>
|
||||
</div>
|
||||
|
||||
{/* Cyan band */}
|
||||
<div className="absolute left-0 right-0 bottom-20 h-[160px]" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}></div>
|
||||
|
||||
{/* Feature cards */}
|
||||
<div className="relative px-12 mt-8">
|
||||
<div className="grid grid-flow-col auto-cols-[260px] gap-6 justify-center">
|
||||
{features.slice(0,4).map((f, i) => (
|
||||
<div key={i} className="rounded-[22px] shadow-[0_16px_40px_rgba(0,0,0,0.08)] overflow-hidden" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}>
|
||||
<div className="px-6 py-5">
|
||||
<div className="w-10 h-10 rounded-sm flex items-center justify-center" style={{ backgroundColor: 'var(--secondary-accent-color, #FFFFFF)' }}>
|
||||
<RemoteSvgIcon
|
||||
url={f.icon.__icon_url__}
|
||||
strokeColor={"currentColor"}
|
||||
className="w-6 h-6"
|
||||
color="var(--text-heading-color, #111827)"
|
||||
title={f.icon.__icon_query__}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 text-[18px] font-semibold whitespace-pre-line" style={{ color: 'var(--text-heading-color, #111827)' }}>{f.title}</div>
|
||||
<p className="mt-3 text-[14px] leading-[1.7]" style={{ color: 'var(--text-body-color, #6B7280)' }}>{f.body}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer (standardized like IntroSlideLayout) */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default FeatureCards
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
|
||||
const layoutId = "image-list-description-slide"
|
||||
const layoutName = "Image List Description"
|
||||
const layoutDescription = "List of Images with subtitle and description with one description for the entire page"
|
||||
|
||||
const ImageSchema = z
|
||||
.object({
|
||||
__image_url__: z
|
||||
.string()
|
||||
.url()
|
||||
.default(
|
||||
"https://images.unsplash.com/photo-1522199710521-72d69614c702?w=1200&q=80&auto=format&fit=crop"
|
||||
),
|
||||
__image_prompt__: z
|
||||
.string()
|
||||
.min(0)
|
||||
.max(120)
|
||||
.default("abstract gradient background"),
|
||||
})
|
||||
.default({
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1522199710521-72d69614c702?w=1200&q=80&auto=format&fit=crop",
|
||||
__image_prompt__: "abstract gradient background",
|
||||
})
|
||||
|
||||
const ItemSchema = z
|
||||
.object({
|
||||
title: z.string().min(2).max(40).default("Sample Title"),
|
||||
description: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(140)
|
||||
.default("Short description for the image or item."),
|
||||
image: ImageSchema,
|
||||
})
|
||||
.default({
|
||||
title: "Sample Title",
|
||||
description: "Short description for the image or item.",
|
||||
image: ImageSchema.parse({}),
|
||||
})
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
titleLine1: z.string().min(3).max(24).default("Meet Our"),
|
||||
titleLine2: z.string().min(3).max(24).default("Team"),
|
||||
description: z
|
||||
.string()
|
||||
.min(20)
|
||||
.max(200)
|
||||
.default(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
),
|
||||
items: z
|
||||
.array(ItemSchema)
|
||||
.min(3)
|
||||
.max(6)
|
||||
.default([
|
||||
ItemSchema.parse({}),
|
||||
ItemSchema.parse({ title: "Another Item", description: "Concise supporting text.", image: ImageSchema.parse({}) }),
|
||||
ItemSchema.parse({ title: "Third Item", description: "Concise supporting text.", image: ImageSchema.parse({}) }),
|
||||
]),
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
})
|
||||
.default({
|
||||
|
||||
titleLine1: "Meet Our",
|
||||
titleLine2: "Team",
|
||||
description:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
items: [
|
||||
ItemSchema.parse({}),
|
||||
ItemSchema.parse({ title: "Another Item", description: "Concise supporting text.", image: ImageSchema.parse({}) }),
|
||||
ItemSchema.parse({ title: "Third Item", description: "Concise supporting text.", image: ImageSchema.parse({}) }),
|
||||
],
|
||||
website: "www.yourwebsite.com",
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const TeamMembers: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className=" w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header: diamond + business name */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="w-3 h-3 rotate-45"
|
||||
style={{ backgroundColor: "var(--text-heading-color, #111827)" }}
|
||||
></div>
|
||||
{ (slideData as any)?.__companyName__ && <span
|
||||
className="text-[16px]"
|
||||
style={{ color: "var(--text-body-color, #6B7280)" }}
|
||||
>
|
||||
{(slideData as any)?.__companyName__}
|
||||
</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-12 pt-6 grid grid-cols-[36%_64%] gap-8 items-start">
|
||||
{/* Left text stack */}
|
||||
<div>
|
||||
<div
|
||||
className="text-[56px] leading-[1.05] font-semibold"
|
||||
style={{ color: "var(--text-heading-color, #111827)" }}
|
||||
>
|
||||
{slideData?.titleLine1}
|
||||
<br />
|
||||
{slideData?.titleLine2}
|
||||
</div>
|
||||
<p
|
||||
className="mt-8 text-[16px] leading-[1.8] max-w-[300px]"
|
||||
style={{ color: "var(--text-body-color, #6B7280)" }}
|
||||
>
|
||||
{slideData?.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right generic image cards */}
|
||||
<div className="grid grid-cols-3 gap-10">
|
||||
{items.slice(0, 3).map((it, i) => (
|
||||
<div key={i} className="flex flex-col items-stretch">
|
||||
{/* Photo block uses provided image */}
|
||||
<div className="h-[180px] rounded-t-md overflow-hidden">
|
||||
<img src={it.image.__image_url__} alt={it.image.__image_prompt__} className="w-full h-full object-cover" />
|
||||
</div>
|
||||
{/* Cyan details panel */}
|
||||
<div className="relative -mt-[1px] rounded-b-[28px] px-6 pt-6 pb-7" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}>
|
||||
<div className="text-[20px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>{it.title}</div>
|
||||
<p className="mt-3 text-[14px] leading-[1.7]" style={{ color: 'var(--text-body-color, #6B7280)' }}>{it.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer line with website and end diamond */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "#E5E7EB" }}></div>
|
||||
</div>
|
||||
|
||||
{/* Big bottom-right diamond */}
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default TeamMembers
|
||||
|
||||
|
||||
177
servers/nextjs/presentation-templates/swift/IntroSlideLayout.tsx
Normal file
177
servers/nextjs/presentation-templates/swift/IntroSlideLayout.tsx
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
|
||||
const layoutId = "IntroSlideLayout"
|
||||
const layoutName = "Intro Slide Layout"
|
||||
const layoutDescription = "Intro slide with header, title, subtitle, body, image. If used for last slide, then intro card should be disabled."
|
||||
|
||||
const ImageSchema = z
|
||||
.object({
|
||||
__image_url__: z.string().url().default("https://images.unsplash.com/photo-1522199710521-72d69614c702?w=1200&q=80&auto=format&fit=crop"),
|
||||
__image_prompt__: z.string().min(0).max(120).default("abstract gradient background"),
|
||||
})
|
||||
.default({
|
||||
__image_url__:
|
||||
"https://images.unsplash.com/photo-1522199710521-72d69614c702?w=1200&q=80&auto=format&fit=crop",
|
||||
__image_prompt__: "abstract gradient background",
|
||||
})
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
|
||||
title: z
|
||||
.string()
|
||||
.min(12)
|
||||
.max(68)
|
||||
.default("Pitch Deck")
|
||||
.meta({ description: "Main slide title" }),
|
||||
|
||||
subtitlePrefix: z.string().min(3).max(40).default("Presentation"),
|
||||
subtitleAccent: z.string().min(3).max(40).default("Template"),
|
||||
|
||||
paragraph: z
|
||||
.string()
|
||||
.min(40)
|
||||
.max(200)
|
||||
.default(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
),
|
||||
|
||||
website: z
|
||||
.string()
|
||||
.min(6)
|
||||
.max(60)
|
||||
.default("www.yourwebsite.com"),
|
||||
|
||||
introCard: z
|
||||
.object({
|
||||
enabled: z.boolean().default(false),
|
||||
name: z.string().min(3).max(40).default("John Doe"),
|
||||
date: z.string().min(4).max(40).default("Jan 1, 2025"),
|
||||
})
|
||||
.default({ enabled: true, name: "John Doe", date: "Jan 1, 2025" }),
|
||||
|
||||
media: z
|
||||
.object({
|
||||
type: z.literal("image").default("image"),
|
||||
image: ImageSchema,
|
||||
})
|
||||
.default({ type: "image", image: ImageSchema.parse({}) }),
|
||||
})
|
||||
.default({
|
||||
title: "Pitch Deck",
|
||||
subtitlePrefix: "Presentation",
|
||||
subtitleAccent: "Template",
|
||||
paragraph:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
website: "www.yourwebsite.com",
|
||||
introCard: { enabled: true, name: "John Doe", date: "Jan 1, 2025" },
|
||||
media: { type: "image", image: ImageSchema.parse({}) },
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const IntroSlideLayout: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className=" w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header: diamond + business name */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="w-3 h-3 rotate-45"
|
||||
style={{ backgroundColor: "var(--text-heading-color, #111827)" }}
|
||||
></div>
|
||||
{ (slideData as any)?.__companyName__ && <span
|
||||
className="text-[16px] "
|
||||
style={{ color: "var(--text-body-color, #6B7280)" }}
|
||||
>
|
||||
{(slideData as any)?.__companyName__}
|
||||
</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right panel image (replaces dark gradient box) */}
|
||||
<div className="absolute top-0 right-0 h-[520px] w-[36%] overflow-hidden">
|
||||
<img
|
||||
src={slideData?.media?.image?.__image_url__}
|
||||
alt={slideData?.media?.image?.__image_prompt__}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Vertical diamond decorations */}
|
||||
<div className="absolute top-8 right-[38%] flex flex-col items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-[58%_42%] gap-10 px-12 pt-4 pb-10 items-start">
|
||||
<div className="pt-4">
|
||||
<h1
|
||||
className="text-[64px] leading-[1.05] tracking-tight font-semibold"
|
||||
style={{ color: "var(--text-heading-color, #111827)" }}
|
||||
>
|
||||
{slideData?.title}
|
||||
</h1>
|
||||
<p className="mt-5 text-[16px] leading-[1.6] max-w-[620px] " style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.paragraph}</p>
|
||||
|
||||
{slideData?.introCard?.enabled && (
|
||||
<div
|
||||
className="mt-6 inline-flex items-center gap-4 rounded-sm border px-4 py-3 "
|
||||
style={{
|
||||
borderColor: 'var(--primary-accent-color, #BFF4FF)',
|
||||
backgroundColor: 'var(--primary-accent-color, #BFF4FF)'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="w-2 h-6"
|
||||
style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}
|
||||
></div>
|
||||
<div className="flex items-baseline gap-3">
|
||||
<span className="text-[16px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{slideData?.introCard?.name}
|
||||
</span>
|
||||
<span className="text-[14px]" style={{ color: 'var(--text-body-color, #6B7280)' }}>
|
||||
{slideData?.introCard?.date}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative h-[420px]"></div>
|
||||
</div>
|
||||
|
||||
{/* Footer line with website and end diamond */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px] " style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-body-color, #E5E7EB)" }}></div>
|
||||
</div>
|
||||
|
||||
{/* Big bottom-right diamond */}
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default IntroSlideLayout
|
||||
|
||||
|
||||
178
servers/nextjs/presentation-templates/swift/MetricsNumbers.tsx
Normal file
178
servers/nextjs/presentation-templates/swift/MetricsNumbers.tsx
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
|
||||
const layoutId = "MetricsNumbers"
|
||||
const layoutName = "Metrics Numbers"
|
||||
const layoutDescription = "Swift: Our Impact in Numbers with three stacked metric cards"
|
||||
|
||||
const MetricSchema = z
|
||||
.object({
|
||||
value: z.string().min(1).max(8).default("10K+"),
|
||||
line1: z.string().min(2).max(22).default("Total"),
|
||||
line2: z.string().min(0).max(22).default("Users"),
|
||||
description: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(140)
|
||||
.default("active users across multiple industries"),
|
||||
})
|
||||
.default({
|
||||
value: "10K+",
|
||||
line1: "Total",
|
||||
line2: "Users",
|
||||
description: "active users across multiple industries",
|
||||
})
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
title: z
|
||||
.string()
|
||||
.min(8)
|
||||
.max(60)
|
||||
.default("Our Impact in Numbers"),
|
||||
leftTitle: z
|
||||
.string()
|
||||
.min(6)
|
||||
.max(40)
|
||||
.default("Proven Results\nThrough Data"),
|
||||
leftBody: z
|
||||
.string()
|
||||
.min(30)
|
||||
.max(220)
|
||||
.default(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
),
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
metrics: z
|
||||
.array(MetricSchema)
|
||||
.min(1)
|
||||
.max(4)
|
||||
.default([
|
||||
MetricSchema.parse({
|
||||
value: "10K+",
|
||||
line1: "Total",
|
||||
line2: "Users",
|
||||
description: "active users across multiple industries",
|
||||
}),
|
||||
MetricSchema.parse({
|
||||
value: "150%",
|
||||
line1: "Revenue",
|
||||
line2: "Growth",
|
||||
description: "year-over-year revenue growth",
|
||||
}),
|
||||
MetricSchema.parse({
|
||||
value: "95%",
|
||||
line1: "Customer",
|
||||
line2: "Satisfaction",
|
||||
description: "retention rate with an average rating of 4.8/5",
|
||||
}),
|
||||
]),
|
||||
})
|
||||
.default({
|
||||
title: "Our Impact in Numbers",
|
||||
leftTitle: "Proven Results\nThrough Data",
|
||||
leftBody: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
website: "www.yourwebsite.com",
|
||||
metrics: [
|
||||
MetricSchema.parse({
|
||||
value: "10K+",
|
||||
line1: "Total",
|
||||
line2: "Users",
|
||||
description: "active users across multiple industries",
|
||||
}),
|
||||
MetricSchema.parse({
|
||||
value: "150%",
|
||||
line1: "Revenue",
|
||||
line2: "Growth",
|
||||
description: "year-over-year revenue growth",
|
||||
}),
|
||||
MetricSchema.parse({
|
||||
value: "95%",
|
||||
line1: "Customer",
|
||||
line2: "Satisfaction",
|
||||
description: "retention rate with an average rating of 4.8/5",
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const MetricsNumbers: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const metrics = slideData?.metrics || []
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className=" w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
{ (slideData as any)?.__companyName__ && <span className="text-[16px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{(slideData as any)?.__companyName__}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Separator line like the reference */}
|
||||
<div className="absolute top-0 left-1/2 w-[1px] h-full" style={{ backgroundColor: "rgba(0,0,0,0.1)" }}></div>
|
||||
|
||||
<div className="px-12 pt-3 grid grid-cols-[42%_58%] gap-8 items-start">
|
||||
{/* Left content */}
|
||||
<div>
|
||||
<h1 className="text-[48px] leading-[1.1] font-semibold max-w-[420px]" style={{ color: "var(--text-heading-color, #111827)" }}>{slideData?.title}</h1>
|
||||
<div className="mt-8 inline-flex items-center gap-3">
|
||||
<div className="w-5 h-5 rounded-full" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
<div>
|
||||
<div className="text-[20px] font-semibold whitespace-pre-line" style={{ color: "var(--text-heading-color, #111827)" }}>{slideData?.leftTitle}</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-5 text-[16px] leading-[1.8] max-w-[360px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.leftBody}</p>
|
||||
</div>
|
||||
|
||||
{/* Right stacked metric cards */}
|
||||
<div className="relative">
|
||||
{/* decorative circle on the right */}
|
||||
<div className="absolute top-6 -right-24 w-[220px] h-[220px] rounded-full border" style={{ borderColor: "rgba(0,0,0,0.2)" }}></div>
|
||||
|
||||
<div className="flex flex-col gap-6">
|
||||
{metrics.slice(0,3).map((m, i) => (
|
||||
<div key={i} className="rounded-[18px] px-6 py-5 grid grid-cols-[38%_62%] items-start shadow-[0_16px_40px_rgba(0,0,0,0.08)]" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}>
|
||||
<div className="text-[40px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>{m.value}</div>
|
||||
<div>
|
||||
<div className="text-[16px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>{m.line1}</div>
|
||||
{m.line2 && <div className="-mt-1 text-[16px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>{m.line2}</div>}
|
||||
<p className="mt-3 text-[12px] leading-[1.6]" style={{ color: 'var(--text-body-color, #6B7280)' }}>{m.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer (standardized like IntroSlideLayout) */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default MetricsNumbers
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
|
||||
const layoutId = "simple-bullet-points-layout"
|
||||
const layoutName = "Simple Bullet Points"
|
||||
const layoutDescription = "Bullet Points with title and description"
|
||||
|
||||
const PointSchema = z
|
||||
.object({
|
||||
title: z.string().min(6).max(60).default("Your Title Here"),
|
||||
body: z
|
||||
.string()
|
||||
.min(30)
|
||||
.max(220)
|
||||
.default(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa."
|
||||
),
|
||||
})
|
||||
.default({ title: "Your Title Here", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa." })
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
title: z.string().min(4).max(36).default("Our Commitment"),
|
||||
statement: z
|
||||
.string()
|
||||
.min(20)
|
||||
.max(260)
|
||||
.default(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
),
|
||||
points: z
|
||||
.array(PointSchema)
|
||||
.min(1)
|
||||
.max(4)
|
||||
.default([PointSchema.parse({}), PointSchema.parse({}), PointSchema.parse({}), PointSchema.parse({ title: "Your Title Here", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa." })]),
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
})
|
||||
.default({
|
||||
title: "Our Commitment to Innovation",
|
||||
statement: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
points: [
|
||||
{ title: "Your Title Here", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa." },
|
||||
{ title: "Your Title Here", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa." },
|
||||
{ title: "Your Title Here", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa." },
|
||||
{ title: "Your Title Here", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa." },
|
||||
|
||||
],
|
||||
website: "www.yourwebsite.com",
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const CommitmentTwoPoints: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const points = slideData?.points || []
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className=" w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
{ (slideData as any)?.__companyName__ && <span className="text-[16px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{(slideData as any)?.__companyName__}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Subtle background motif */}
|
||||
<div
|
||||
className="pointer-events-none absolute left-1/2 -translate-x-1/2 bottom-[140px] w-[900px] h-[260px]"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(closest-side, rgba(17,24,39,0.06), transparent 70%)",
|
||||
filter: "blur(0.2px)",
|
||||
}}
|
||||
></div>
|
||||
|
||||
{/* Content grid */}
|
||||
<div className="px-12 pt-4 grid grid-cols-[48%_52%] gap-10 items-start">
|
||||
{/* Left heading and statement */}
|
||||
<div>
|
||||
<div className="text-[56px] leading-[1.05] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>
|
||||
{slideData?.title}
|
||||
</div>
|
||||
|
||||
<p className="mt-10 text-[16px] leading-[1.8] max-w-[520px]" style={{ color: 'var(--text-body-color, #6B7280)' }}>
|
||||
{slideData?.statement}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right numbered points (up to 4) */}
|
||||
<div className="flex flex-col gap-6">
|
||||
{points.slice(0, 4).map((p, i) => (
|
||||
<div key={i}>
|
||||
<div className="text-[24px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>{p.title}</div>
|
||||
<p className="mt-3 text-[16px] leading-[1.8]" style={{ color: 'var(--text-body-color, #6B7280)' }}>{p.body}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer (align with other Swift layouts) */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default CommitmentTwoPoints
|
||||
|
||||
|
||||
144
servers/nextjs/presentation-templates/swift/TableOfContents.tsx
Normal file
144
servers/nextjs/presentation-templates/swift/TableOfContents.tsx
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
|
||||
const layoutId = "SwiftTableOfContents"
|
||||
const layoutName = "Table Of Contents"
|
||||
const layoutDescription = "Swift: Table of contents with up to 10 items (title + description)"
|
||||
|
||||
const ToCItemSchema = z
|
||||
.object({
|
||||
title: z.string().min(3).max(40).default("Introduction"),
|
||||
description: z
|
||||
.string()
|
||||
.min(0)
|
||||
.max(60)
|
||||
.default("A brief overview of the section."),
|
||||
})
|
||||
.default({ title: "Introduction", description: "A brief overview of the section." })
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
title: z
|
||||
.string()
|
||||
.min(3)
|
||||
.max(60)
|
||||
.default("Table of Contents"),
|
||||
items: z
|
||||
.array(ToCItemSchema)
|
||||
.min(1)
|
||||
.max(10)
|
||||
.default([
|
||||
{ title: "Introduction", description: "A brief description of our company and goals." },
|
||||
{ title: "Our Team", description: "Leadership and core contributors." },
|
||||
{ title: "Timeline", description: "High-level execution plan and milestones." },
|
||||
{ title: "Recommendations", description: "Key suggestions based on initial requirements." },
|
||||
{ title: "Solution", description: "What we propose and why it works." },
|
||||
{ title: "Market", description: "Audience, segments, and opportunity size." },
|
||||
{ title: "Business Model", description: "How we create and capture value." },
|
||||
{ title: "Conclusion", description: "Closing notes and next steps." },
|
||||
{ title: "Business Model", description: "How we create and capture value." },
|
||||
{ title: "Conclusion", description: "Closing notes and next steps." },
|
||||
]),
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
})
|
||||
.default({
|
||||
title: "Table of Contents",
|
||||
items: [
|
||||
{ title: "Introduction", description: "A brief description of our company and goals." },
|
||||
{ title: "Our Team", description: "Leadership and core contributors." },
|
||||
{ title: "Timeline", description: "High-level execution plan and milestones." },
|
||||
{ title: "Recommendations", description: "Key suggestions based on initial requirements." },
|
||||
{ title: "Solution", description: "What we propose and why it works." },
|
||||
{ title: "Market", description: "Audience, segments, and opportunity size." },
|
||||
{ title: "Business Model", description: "How we create and capture value." },
|
||||
{ title: "Conclusion", description: "Closing notes and next steps." },
|
||||
{ title: "Business Model", description: "How we create and capture value." },
|
||||
{ title: "Conclusion", description: "Closing notes and next steps." },
|
||||
|
||||
],
|
||||
website: "www.yourwebsite.com",
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const TableOfContents: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className=" w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
{ (slideData as any)?.__companyName__ && <span className="text-[16px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{(slideData as any)?.__companyName__}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-12 pt-3">
|
||||
<h1 className="text-[48px] leading-[1.1] font-semibold" style={{ color: "var(--text-heading-color, #111827)" }}>{slideData?.title}</h1>
|
||||
</div>
|
||||
|
||||
{/* List */}
|
||||
<div className="px-12 pt-8">
|
||||
<div className="grid grid-cols-2 gap-x-12 gap-y-6 max-w-[1180px]">
|
||||
{items.slice(0, 10).map((item, idx) => (
|
||||
<div key={idx} className="relative">
|
||||
<div className="flex items-start gap-6">
|
||||
<div className="flex-none">
|
||||
<div
|
||||
className="leading-none font-semibold"
|
||||
style={{
|
||||
fontSize: 48,
|
||||
color: "var(--text-heading-color, #BFF4FF)",
|
||||
}}
|
||||
>
|
||||
{String(idx + 1).padStart(2, "0")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 pt-1">
|
||||
<div className="text-[22px] leading-[1.2] font-semibold" style={{ color: "var(--text-heading-color, #111827)" }}>
|
||||
{item.title}
|
||||
</div>
|
||||
{item.description && (
|
||||
<div className="mt-2 text-[14px] leading-[1.6]" style={{ color: "var(--text-body-color, #6B7280)" }}>
|
||||
{item.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 h-px" style={{ backgroundColor: "rgba(59,130,246,0.15)" }}></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer (standardized like IntroSlideLayout) */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default TableOfContents
|
||||
|
||||
|
||||
279
servers/nextjs/presentation-templates/swift/TableorChart.tsx
Normal file
279
servers/nextjs/presentation-templates/swift/TableorChart.tsx
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
Legend,
|
||||
LineChart,
|
||||
Line,
|
||||
PieChart,
|
||||
Pie,
|
||||
Cell,
|
||||
} from "recharts"
|
||||
|
||||
const layoutId = "tableorChart"
|
||||
const layoutName = "Table Or Chart"
|
||||
const layoutDescription = "Swift: Generic data table with option to render a chart (bar, horizontalBar, line, pie)"
|
||||
|
||||
const ChartDatumSchema = z.object({
|
||||
label: z.string().min(1).max(12).default("A"),
|
||||
value: z.number().min(0).max(1000000).default(60),
|
||||
})
|
||||
|
||||
const TableRowSchema = z.object({
|
||||
cells: z
|
||||
.array(z.string().min(0).max(200))
|
||||
.min(2)
|
||||
.max(10)
|
||||
.default(["Row 1", "Value", "Value"])
|
||||
.meta({ description: "Row cells; count should match columns length" }),
|
||||
})
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
title: z.string().min(6).max(60).default("Data Table or Chart"),
|
||||
description: z
|
||||
.string()
|
||||
.min(20)
|
||||
.max(220)
|
||||
.default(
|
||||
"Present structured information in a flexible table or visualize it with a chart."
|
||||
),
|
||||
|
||||
mode: z.enum(["table", "chart"]).default("table"),
|
||||
|
||||
// Table configuration (generic)
|
||||
columns: z
|
||||
.array(z.string().min(1).max(40))
|
||||
.min(2)
|
||||
.max(10)
|
||||
.default(["Column 1", "Column 2", "Column 3"]),
|
||||
rows: z
|
||||
.array(TableRowSchema)
|
||||
.min(1)
|
||||
.max(30)
|
||||
.default([
|
||||
{ cells: ["Row A", "✓", "-"] },
|
||||
{ cells: ["Row B", "Text", "123"] },
|
||||
{ cells: ["Row C", "More text", "456"] },
|
||||
]),
|
||||
|
||||
// Chart configuration (parity with @standard ChartLeftTextRightLayout)
|
||||
chart: z
|
||||
.object({
|
||||
type: z.enum(["bar", "horizontalBar", "line", "pie"]).default("line"),
|
||||
data: z.array(ChartDatumSchema).min(3).max(12).default([
|
||||
{ label: "A", value: 60 },
|
||||
{ label: "B", value: 42 },
|
||||
{ label: "C", value: 75 },
|
||||
{ label: "D", value: 30 },
|
||||
]),
|
||||
primaryColor: z.string().default("var(--text-heading-color, #111827)"),
|
||||
gridColor: z.string().default("var(--primary-accent-color, #BFF4FF)"),
|
||||
pieColors: z
|
||||
.array(z.string())
|
||||
.min(1)
|
||||
.max(10)
|
||||
.default(["var(--text-heading-color, #111827)", "#3b82f6", "#f59e0b", "#10b981", "#ef4444"]),
|
||||
showLabels: z.boolean().default(true),
|
||||
})
|
||||
.default({
|
||||
type: "line",
|
||||
data: [
|
||||
{ label: "A", value: 60 },
|
||||
{ label: "B", value: 42 },
|
||||
{ label: "C", value: 75 },
|
||||
{ label: "D", value: 30 },
|
||||
],
|
||||
primaryColor: "#1B8C2D",
|
||||
gridColor: "#E5E7EB",
|
||||
pieColors: ["#1B8C2D", "#3b82f6", "#f59e0b", "#10b981", "#ef4444"],
|
||||
showLabels: true,
|
||||
}),
|
||||
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
})
|
||||
.default({
|
||||
title: "Data Table or Chart",
|
||||
description:
|
||||
"Present structured information in a flexible table or visualize it with a chart.",
|
||||
mode: "table",
|
||||
columns: ["Column 1", "Column 2", "Column 3"],
|
||||
rows: [
|
||||
{ cells: ["Row A", "✓", "-"] },
|
||||
{ cells: ["Row B", "Text", "123"] },
|
||||
{ cells: ["Row C", "More text", "456"] },
|
||||
],
|
||||
chart: {
|
||||
type: "line",
|
||||
data: [
|
||||
{ label: "A", value: 60 },
|
||||
{ label: "B", value: 42 },
|
||||
{ label: "C", value: 75 },
|
||||
{ label: "D", value: 30 },
|
||||
],
|
||||
primaryColor: "#1B8C2D",
|
||||
gridColor: "#E5E7EB",
|
||||
pieColors: ["#1B8C2D", "#3b82f6", "#f59e0b", "#10b981", "#ef4444"],
|
||||
showLabels: true,
|
||||
},
|
||||
website: "www.yourwebsite.com",
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const TableOrChart: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const mode = slideData?.mode || "table"
|
||||
const columns = slideData?.columns || []
|
||||
const rows = slideData?.rows || []
|
||||
|
||||
const cData = slideData?.chart?.data || []
|
||||
const type = slideData?.chart?.type || "bar"
|
||||
const primaryColor = slideData?.chart?.primaryColor || "var(--text-heading-color, #111827)"
|
||||
const gridColor = slideData?.chart?.gridColor || "var(--primary-accent-color, #BFF4FF)"
|
||||
const pieColors = slideData?.chart?.pieColors || ["var(--text-heading-color, #111827)"]
|
||||
const showLabels = slideData?.chart?.showLabels !== false
|
||||
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className=" w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
{(slideData as any)?.__companyName__ && <span className="text-[16px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{(slideData as any)?.__companyName__}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Title and description */}
|
||||
<div className="px-12 pt-3">
|
||||
<h1 className="text-[48px] leading-[1.1] font-semibold" style={{ color: "var(--text-heading-color, #111827)" }}>{slideData?.title}</h1>
|
||||
<p className="mt-3 text-[16px] max-w-[900px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.description}</p>
|
||||
</div>
|
||||
|
||||
{/* Content area: Table or Chart */}
|
||||
<div className="px-12 pt-6">
|
||||
{mode === "table" ? (
|
||||
<div className="rounded-xl p-5" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}>
|
||||
<div className="overflow-x-auto rounded-lg bg-white ring-1" style={{ borderColor: 'rgba(0,0,0,0.08)' }}>
|
||||
<table className="w-full border-separate border-spacing-0">
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.map((col, idx) => (
|
||||
<th
|
||||
key={idx}
|
||||
className="text-left text-[14px] font-semibold px-4 py-3 border-b first:rounded-tl-md last:rounded-tr-md"
|
||||
style={{
|
||||
color: 'var(--text-heading-color, #111827)',
|
||||
borderColor: 'var(--secondary-accent-color, rgba(0,0,0,0.12))',
|
||||
backgroundColor: 'var(--primary-accent-color, #BFF4FF)'
|
||||
}}
|
||||
>
|
||||
{col}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row, rIdx) => (
|
||||
<tr key={rIdx} className="align-top">
|
||||
{columns.map((_, cIdx) => (
|
||||
<td
|
||||
key={cIdx}
|
||||
className={`text-[14px] px-4 py-3 border-t ${rIdx === rows.length - 1 ? 'first:rounded-bl-md last:rounded-br-md' : ''}`}
|
||||
style={{
|
||||
color: 'var(--text-body-color, #374151)',
|
||||
borderColor: 'rgba(0,0,0,0.08)',
|
||||
backgroundColor: rIdx % 2 === 0 ? 'var(--primary-accent-color, #BFF4FF)' : 'var(--tertiary-accent-color, #E5E7EB)'
|
||||
}}
|
||||
>
|
||||
{row.cells[cIdx] || ''}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full h-[360px] rounded-xl p-4" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
{type === 'bar' ? (
|
||||
<BarChart data={cData} margin={{ top: 10, right: 20, bottom: 10, left: 0 }}>
|
||||
<CartesianGrid stroke={gridColor} strokeDasharray="3 3" />
|
||||
<XAxis dataKey="label" tick={{ fill: 'var(--text-body-color, #6B7280)', fontWeight: 600 }} />
|
||||
<YAxis tick={{ fill: 'var(--text-body-color, #6B7280)', fontWeight: 600 }} />
|
||||
<Tooltip labelStyle={{ color: 'var(--text-body-color, #6B7280)' }} itemStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Legend wrapperStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Bar dataKey="value" fill={primaryColor} radius={[6, 6, 0, 0]} label={showLabels ? { position: 'top', fill: '#111827', fontSize: 12 } : false} />
|
||||
</BarChart>
|
||||
) : type === 'horizontalBar' ? (
|
||||
<BarChart data={cData} layout="vertical" margin={{ top: 10, right: 20, bottom: 10, left: 20 }}>
|
||||
<CartesianGrid stroke={gridColor} strokeDasharray="3 3" />
|
||||
<XAxis type="number" tick={{ fill: 'var(--text-body-color, #6B7280)', fontWeight: 600 }} />
|
||||
<YAxis type="category" dataKey="label" tick={{ fill: 'var(--text-body-color, #6B7280)', fontWeight: 600 }} />
|
||||
<Tooltip labelStyle={{ color: 'var(--text-body-color, #6B7280)' }} itemStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Legend wrapperStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Bar dataKey="value" fill={primaryColor} radius={[0, 6, 6, 0]} label={showLabels ? { position: 'right', fill: '#111827', fontSize: 12 } : false} />
|
||||
</BarChart>
|
||||
) : type === 'line' ? (
|
||||
<LineChart data={cData} margin={{ top: 10, right: 20, bottom: 10, left: 0 }}>
|
||||
<CartesianGrid stroke={gridColor} strokeDasharray="3 3" />
|
||||
<XAxis dataKey="label" tick={{ fill: 'var(--text-body-color, #6B7280)', fontWeight: 600 }} />
|
||||
<YAxis tick={{ fill: 'var(--text-body-color, #6B7280)', fontWeight: 600 }} />
|
||||
<Tooltip labelStyle={{ color: 'var(--text-body-color, #6B7280)' }} itemStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Legend wrapperStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Line type="monotone" dataKey="value" stroke={primaryColor} strokeWidth={3} dot={{ r: 3 }} label={showLabels ? { position: 'top', fill: '#111827', fontSize: 12 } : false} />
|
||||
</LineChart>
|
||||
) : (
|
||||
<PieChart>
|
||||
<Tooltip labelStyle={{ color: 'var(--text-body-color, #6B7280)' }} itemStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Legend wrapperStyle={{ color: 'var(--text-body-color, #6B7280)' }} />
|
||||
<Pie data={cData} dataKey="value" nameKey="label" cx="50%" cy="50%" outerRadius={120} label={showLabels ? { fill: 'var(--text-body-color, #6B7280)' } : false}>
|
||||
{cData.map((_, i) => (
|
||||
<Cell key={i} fill={primaryColor} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
)}
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer (standardized like IntroSlideLayout) */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default TableOrChart
|
||||
|
||||
|
||||
170
servers/nextjs/presentation-templates/swift/Timeline.tsx
Normal file
170
servers/nextjs/presentation-templates/swift/Timeline.tsx
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
import React from "react"
|
||||
import * as z from "zod"
|
||||
|
||||
const layoutId = "Timeline"
|
||||
const layoutName = "Timeline"
|
||||
const layoutDescription = "Timeline of cards with title, subtitle banner"
|
||||
|
||||
const IconSchema = z
|
||||
.object({
|
||||
__icon_url__: z
|
||||
.string()
|
||||
.default(
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/clipboard-text-bold.svg"
|
||||
),
|
||||
__icon_query__: z.string().min(0).max(80).default("timeline icon"),
|
||||
})
|
||||
.default({
|
||||
__icon_url__:
|
||||
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/bold/clipboard-text-bold.svg",
|
||||
__icon_query__: "timeline icon",
|
||||
})
|
||||
|
||||
const ItemSchema = z
|
||||
.object({
|
||||
year: z.string().min(3).max(6).default("2018"),
|
||||
heading: z.string().min(3).max(28).default("Founded in 2020"),
|
||||
body: z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(160)
|
||||
.default("Lorem ipsum dolor"),
|
||||
icon: IconSchema,
|
||||
})
|
||||
.default({ year: "2018", heading: "Founded in 2020", body: "Lorem ipsum dolor", icon: IconSchema.parse({}) })
|
||||
|
||||
const Schema = z
|
||||
.object({
|
||||
title: z
|
||||
.string()
|
||||
.min(8)
|
||||
.max(60)
|
||||
.default("Our Journey at a Glance"),
|
||||
subtitle: z
|
||||
.string()
|
||||
.min(20)
|
||||
.max(200)
|
||||
.default(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa."
|
||||
),
|
||||
items: z
|
||||
.array(ItemSchema)
|
||||
.min(1)
|
||||
.max(4)
|
||||
.default([
|
||||
ItemSchema.parse({ year: "2018", heading: "Founded in 2020", body: "Lorem ipsum dolor" }),
|
||||
ItemSchema.parse({ year: "2019", heading: "First Product in 2021", body: "Lorem ipsum dolor" }),
|
||||
ItemSchema.parse({ year: "2020", heading: "Key Milestone in 2022", body: "Lorem ipsum dolor" }),
|
||||
ItemSchema.parse({ year: "2021", heading: "Global Expansion in 2024", body: "Lorem ipsum dolor" }),
|
||||
]),
|
||||
website: z.string().min(6).max(60).default("www.yourwebsite.com"),
|
||||
})
|
||||
.default({
|
||||
title: "Our Journey at a Glance",
|
||||
subtitle:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.",
|
||||
items: [
|
||||
ItemSchema.parse({ year: "2018", heading: "Founded in 2020", body: "Lorem ipsum dolor" }),
|
||||
ItemSchema.parse({ year: "2019", heading: "First Product in 2021", body: "Lorem ipsum dolor" }),
|
||||
ItemSchema.parse({ year: "2020", heading: "Key Milestone in 2022", body: "Lorem ipsum dolor" }),
|
||||
ItemSchema.parse({ year: "2021", heading: "Global Expansion in 2024", body: "Lorem ipsum dolor" }),
|
||||
],
|
||||
website: "www.yourwebsite.com",
|
||||
})
|
||||
|
||||
type SlideData = z.infer<typeof Schema>
|
||||
|
||||
interface SlideLayoutProps {
|
||||
data?: Partial<SlideData>
|
||||
}
|
||||
|
||||
const Timeline: React.FC<SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
return (
|
||||
<>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className=" w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: "var(--heading-font-family,Albert Sans)",
|
||||
backgroundColor: "var(--card-background-color, #FFFFFF)",
|
||||
}}
|
||||
>
|
||||
{/* Keep white background to match Swift layouts */}
|
||||
|
||||
{/* Header: diamond + business name */}
|
||||
<div className="relative px-12 pt-6 pb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
{(slideData as any)?.__companyName__ && <span className="text-[16px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{(slideData as any)?.__companyName__}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right vertical diamonds */}
|
||||
<div className="absolute top-16 right-6 flex flex-col items-center gap-3">
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
<div className="w-3 h-3 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<div className="relative px-12 pt-3 flex flex-col items-center">
|
||||
<h1 className="text-[48px] leading-[1.1] font-semibold text-center" style={{ color: "var(--text-heading-color, #111827)" }}>{slideData?.title}</h1>
|
||||
{/* Subtitle banner */}
|
||||
<div className="mt-5 max-w-[720px] w-full rounded-md text-center px-6 py-3" style={{
|
||||
borderColor: "rgba(0,0,0,0.25)",
|
||||
color: "var(--text-body-color, #6B7280)",
|
||||
backgroundColor: "var(--primary-accent-color, #BFF4FF)",
|
||||
}}>
|
||||
<span className="text-[16px]">{slideData?.subtitle}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Horizontal timeline */}
|
||||
<div className="relative px-12 pt-10">
|
||||
{/* center line */}
|
||||
<div className="absolute left-12 right-12 top-1/2 h-[4px]" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}></div>
|
||||
|
||||
<div className="relative flex justify-center gap-10">
|
||||
{items.slice(0, 4).map((it, idx) => (
|
||||
<div key={idx} className="relative flex flex-col items-center">
|
||||
{/* year badge - placed close to the card */}
|
||||
<div className="mb-2 px-5 py-2 rounded-md text-white text-[16px] font-semibold" style={{ backgroundColor: 'var(--text-heading-color, #111827)' }}>{it.year}</div>
|
||||
|
||||
{/* connector dot */}
|
||||
<div className="w-5 h-5 rounded-full border-4" style={{ backgroundColor: 'var(--card-background-color, #FFFFFF)', borderColor: 'var(--primary-accent-color, #BFF4FF)' }}></div>
|
||||
|
||||
{/* card container */}
|
||||
<div className="mt-4">
|
||||
<div className="rounded-[16px] bg-white shadow-[0_16px_40px_rgba(0,0,0,0.08)] px-6 pt-6 pb-5 text-center w-[260px]">
|
||||
<div className="mx-auto w-12 h-12 rounded-full flex items-center justify-center mb-4" style={{ backgroundColor: 'var(--primary-accent-color, #BFF4FF)' }}>
|
||||
<img src={it.icon.__icon_url__} alt={it.icon.__icon_query__} className="w-6 h-6 object-contain" />
|
||||
</div>
|
||||
<div className="text-[18px] font-semibold" style={{ color: 'var(--text-heading-color, #111827)' }}>{it.heading}</div>
|
||||
<p className="mt-3 text-[14px]" style={{ color: 'var(--text-body-color, #6B7280)' }}>{it.body}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer (standardized like IntroSlideLayout) */}
|
||||
<div className="absolute bottom-8 left-12 right-12 flex items-center">
|
||||
<span className="text-[14px]" style={{ color: "var(--text-body-color, #6B7280)" }}>{slideData?.website}</span>
|
||||
<div className="ml-6 h-[2px] flex-1" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
<div className="absolute bottom-7 right-6 w-8 h-8 rotate-45" style={{ backgroundColor: "var(--text-heading-color, #111827)" }}></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { Schema, layoutId, layoutName, layoutDescription }
|
||||
export default Timeline
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"description": "Swift layouts for presentations",
|
||||
"ordered": false,
|
||||
"default": false
|
||||
}
|
||||
|
||||
Loading…
Add table
Reference in a new issue