| 1 | import * as z from "zod"; |
| 2 | |
| 3 | const hexColorSchema = z |
| 4 | .string() |
| 5 | .regex(/^#[0-9a-f]{6}$/i, "Expected a 6-digit hex color"); |
| 6 | |
| 7 | const presentationThemeModeSchema = z.enum(["light", "dark"]); |
| 8 | |
| 9 | const presentationThemeColorsSchema = z.object({ |
| 10 | primary: hexColorSchema.describe( |
| 11 | "Main brand/action color used for emphasis and prominent visual accents.", |
| 12 | ), |
| 13 | accent: hexColorSchema.describe( |
| 14 | "Secondary accent color that complements the primary color.", |
| 15 | ), |
| 16 | background: hexColorSchema.describe("Main slide/page background color."), |
| 17 | text: hexColorSchema.describe( |
| 18 | "Body text color with strong contrast against background and cardBackground.", |
| 19 | ), |
| 20 | heading: hexColorSchema.describe( |
| 21 | "Heading text color with strong contrast against background and cardBackground.", |
| 22 | ), |
| 23 | smartLayout: hexColorSchema.describe( |
| 24 | "Fill color for smart layout SVGs and visual structures such as pyramids, pie charts, staircase blocks, cycles, timelines, and other diagram elements. It usually sits close to the primary color or a deliberate variant of it, not a neutral card surface color.", |
| 25 | ), |
| 26 | cardBackground: hexColorSchema.describe( |
| 27 | "Surface color for cards or text containers where text is rendered. This is separate from smartLayout and should preserve text readability.", |
| 28 | ), |
| 29 | }); |
| 30 | |
| 31 | const presentationThemeFontsSchema = z.object({ |
| 32 | heading: z |
| 33 | .string() |
| 34 | .min(1) |
| 35 | .describe( |
| 36 | "A real, well-known font family name, such as Inter, Manrope, Poppins, IBM Plex Sans, Playfair Display, Merriweather, Sora, or Space Grotesk. Do not invent font names.", |
| 37 | ), |
| 38 | body: z |
| 39 | .string() |
| 40 | .min(1) |
| 41 | .describe( |
| 42 | "A real, well-known font family name, such as Inter, Manrope, Source Sans Pro, Work Sans, IBM Plex Sans, Lato, Open Sans, or DM Sans. Do not invent font names.", |
| 43 | ), |
| 44 | headingWeight: z.number().int().min(100).max(900).optional(), |
| 45 | bodyWeight: z.number().int().min(100).max(900).optional(), |
| 46 | headingUrl: z.string().url().optional(), |
| 47 | bodyUrl: z.string().url().optional(), |
| 48 | }); |
| 49 | |
| 50 | const presentationThemeBorderRadiusSchema = z.object({ |
| 51 | card: z.string().min(1), |
| 52 | slide: z.string().min(1), |
| 53 | button: z.string().min(1), |
| 54 | }); |
| 55 | |
| 56 | const presentationThemeTransitionsSchema = z.object({ |
| 57 | default: z.string().min(1), |
| 58 | }); |
| 59 | |
| 60 | const presentationThemeShadowsSchema = z.object({ |
| 61 | card: z.string(), |
| 62 | button: z.string(), |
| 63 | slide: z.string(), |
| 64 | }); |
| 65 | |
| 66 | const presentationThemeMaskSchema = z |
| 67 | .object({ |
| 68 | clipPath: z.string().optional(), |
| 69 | maskImage: z.string().optional(), |
| 70 | maskSize: z.string().optional(), |
| 71 | maskPosition: z.string().optional(), |
| 72 | maskRepeat: z.string().optional(), |
| 73 | }) |
| 74 | .optional(); |
| 75 | |
| 76 | const presentationThemeBackgroundSchema = z |
| 77 | .object({ |
| 78 | type: z.enum(["solid", "linear", "radial", "image"]).optional(), |
| 79 | override: z.string().optional(), |
| 80 | gradient: z |
| 81 | .object({ |
| 82 | type: z.enum(["linear", "radial"]), |
| 83 | angle: z.number().optional(), |
| 84 | shape: z.enum(["circle", "ellipse"]).optional(), |
| 85 | at: z |
| 86 | .object({ |
| 87 | x: z.number(), |
| 88 | y: z.number(), |
| 89 | }) |
| 90 | .optional(), |
| 91 | stops: z |
| 92 | .array( |
| 93 | z.object({ |
| 94 | id: z.string().min(1), |
| 95 | color: hexColorSchema, |
| 96 | position: z.number().min(0).max(100), |
| 97 | }), |
| 98 | ) |
| 99 | .optional(), |
| 100 | }) |
| 101 | .optional(), |
| 102 | imageUrl: z.string().url().optional(), |
| 103 | }) |
| 104 | .optional(); |
| 105 | |
| 106 | export const presentationAiThemePropertiesSchema = z.object({ |
| 107 | name: z.string().min(1).max(80).optional(), |
| 108 | description: z.string().min(1).max(240).optional(), |
| 109 | colors: presentationThemeColorsSchema.partial().optional(), |
| 110 | fonts: presentationThemeFontsSchema.partial().optional(), |
| 111 | background: presentationThemeBackgroundSchema, |
| 112 | }); |
| 113 | |
| 114 | export const presentationThemePropertiesSchema = z.object({ |
| 115 | name: z.string().min(1).max(80), |
| 116 | description: z.string().max(240), |
| 117 | mode: presentationThemeModeSchema, |
| 118 | colors: presentationThemeColorsSchema, |
| 119 | fonts: presentationThemeFontsSchema, |
| 120 | borderRadius: presentationThemeBorderRadiusSchema, |
| 121 | transitions: presentationThemeTransitionsSchema, |
| 122 | shadows: presentationThemeShadowsSchema, |
| 123 | mask: presentationThemeMaskSchema, |
| 124 | background: presentationThemeBackgroundSchema, |
| 125 | }); |
| 126 | |
| 127 | export const presentationThemeStyleDataSchema = |
| 128 | presentationThemePropertiesSchema |
| 129 | .omit({ |
| 130 | name: true, |
| 131 | description: true, |
| 132 | }) |
| 133 | .extend({ |
| 134 | name: z.string().min(1).max(80).optional(), |
| 135 | description: z.string().max(240).optional(), |
| 136 | }); |
| 137 | |
| 138 | export type PresentationAiThemeProperties = z.infer< |
| 139 | typeof presentationAiThemePropertiesSchema |
| 140 | >; |
| 141 |