mulmocast 2.2.5 → 2.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  export { generateSlideHTML } from "./render.js";
2
+ export type { ResolvedBranding } from "./render.js";
2
3
  export { renderSlideContent } from "./layouts/index.js";
3
4
  export { renderContentBlock, renderContentBlocks } from "./blocks.js";
4
- export { mulmoSlideMediaSchema, slideLayoutSchema, slideThemeSchema, contentBlockSchema, imageRefBlockSchema, chartBlockSchema, mermaidBlockSchema, accentColorKeySchema, } from "./schema.js";
5
- export type { MulmoSlideMedia, SlideLayout, SlideTheme, SlideThemeColors, SlideThemeFonts, ContentBlock, ImageRefBlock, ChartBlock, MermaidBlock, AccentColorKey, TitleSlide, ColumnsSlide, ComparisonSlide, GridSlide, BigQuoteSlide, StatsSlide, TimelineSlide, SplitSlide, MatrixSlide, TableSlide, FunnelSlide, Card, CalloutBar, SlideStyle, } from "./schema.js";
5
+ export { mulmoSlideMediaSchema, slideLayoutSchema, slideThemeSchema, contentBlockSchema, imageRefBlockSchema, chartBlockSchema, mermaidBlockSchema, accentColorKeySchema, slideBrandingLogoSchema, slideBrandingSchema, } from "./schema.js";
6
+ export type { MulmoSlideMedia, SlideLayout, SlideTheme, SlideThemeColors, SlideThemeFonts, ContentBlock, ImageRefBlock, ChartBlock, MermaidBlock, AccentColorKey, TitleSlide, ColumnsSlide, ComparisonSlide, GridSlide, BigQuoteSlide, StatsSlide, TimelineSlide, SplitSlide, MatrixSlide, TableSlide, FunnelSlide, Card, CalloutBar, SlideStyle, SlideBrandingLogo, SlideBranding, } from "./schema.js";
@@ -4,4 +4,4 @@ export { generateSlideHTML } from "./render.js";
4
4
  export { renderSlideContent } from "./layouts/index.js";
5
5
  export { renderContentBlock, renderContentBlocks } from "./blocks.js";
6
6
  // Schemas
7
- export { mulmoSlideMediaSchema, slideLayoutSchema, slideThemeSchema, contentBlockSchema, imageRefBlockSchema, chartBlockSchema, mermaidBlockSchema, accentColorKeySchema, } from "./schema.js";
7
+ export { mulmoSlideMediaSchema, slideLayoutSchema, slideThemeSchema, contentBlockSchema, imageRefBlockSchema, chartBlockSchema, mermaidBlockSchema, accentColorKeySchema, slideBrandingLogoSchema, slideBrandingSchema, } from "./schema.js";
@@ -1,3 +1,17 @@
1
1
  import type { SlideTheme, SlideLayout } from "./schema.js";
2
+ /** Pre-resolved branding data (all sources converted to data URLs) */
3
+ export type ResolvedBranding = {
4
+ logo?: {
5
+ dataUrl: string;
6
+ position: string;
7
+ width: number;
8
+ };
9
+ backgroundImage?: {
10
+ dataUrl: string;
11
+ size: string;
12
+ opacity: number;
13
+ bgOpacity?: number;
14
+ };
15
+ };
2
16
  /** Generate a complete HTML document for a single slide */
3
- export declare const generateSlideHTML: (theme: SlideTheme, slide: SlideLayout, reference?: string) => string;
17
+ export declare const generateSlideHTML: (theme: SlideTheme, slide: SlideLayout, reference?: string, branding?: ResolvedBranding) => string;
@@ -21,18 +21,56 @@ const buildCdnScripts = (theme, slide) => {
21
21
  }
22
22
  return scripts.join("\n");
23
23
  };
24
+ /** Map branding logo position to Tailwind CSS classes */
25
+ const logoPositionClasses = {
26
+ "top-left": "top-5 left-6",
27
+ "top-right": "top-5 right-6",
28
+ "bottom-left": "bottom-5 left-6",
29
+ "bottom-right": "bottom-5 right-6",
30
+ };
31
+ /**
32
+ * Render branding background layers.
33
+ * - Without bgOpacity: image overlaid on slide bg at given opacity
34
+ * - With bgOpacity: image at full opacity, then slide bg color as semi-transparent overlay
35
+ */
36
+ const renderBrandingBackground = (branding, bgHex) => {
37
+ if (!branding.backgroundImage)
38
+ return "";
39
+ const { dataUrl, size, opacity, bgOpacity } = branding.backgroundImage;
40
+ const bgSize = size === "fill" ? "100% 100%" : size;
41
+ if (bgOpacity !== undefined) {
42
+ const parts = [];
43
+ parts.push(`<div class="absolute inset-0 z-0" style="background-image:url('${dataUrl}');background-size:${bgSize};background-position:center;background-repeat:no-repeat;opacity:${opacity}"></div>`);
44
+ parts.push(`<div class="absolute inset-0 z-0" style="background-color:#${bgHex};opacity:${bgOpacity}"></div>`);
45
+ return parts.join("\n");
46
+ }
47
+ return `<div class="absolute inset-0 z-0" style="background-image:url('${dataUrl}');background-size:${bgSize};background-position:center;background-repeat:no-repeat;opacity:${opacity}"></div>`;
48
+ };
49
+ /** Render branding logo element */
50
+ const renderBrandingLogo = (branding) => {
51
+ if (!branding.logo)
52
+ return "";
53
+ const { dataUrl, position, width } = branding.logo;
54
+ const posClasses = logoPositionClasses[position] ?? logoPositionClasses["top-right"];
55
+ return `<img class="absolute ${posClasses} z-10" src="${dataUrl}" width="${width}" alt="" style="pointer-events:none">`;
56
+ };
24
57
  /** Generate a complete HTML document for a single slide */
25
- export const generateSlideHTML = (theme, slide, reference) => {
58
+ export const generateSlideHTML = (theme, slide, reference, branding) => {
26
59
  const content = renderSlideContent(slide);
27
60
  const twConfig = buildTailwindConfig(theme);
28
61
  const cdnScripts = buildCdnScripts(theme, slide);
29
62
  const slideStyle = slide.style;
30
- const bgCls = slideStyle?.bgColor ? "" : "bg-d-bg";
31
- const inlineStyle = slideStyle?.bgColor ? ` style="background-color:#${sanitizeHex(slideStyle.bgColor)}"` : "";
63
+ const hasBgOpacity = branding?.backgroundImage?.bgOpacity !== undefined;
64
+ const bgCls = hasBgOpacity || slideStyle?.bgColor ? "" : "bg-d-bg";
65
+ const bgColorStyle = slideStyle?.bgColor ? ` style="background-color:#${sanitizeHex(slideStyle.bgColor)}"` : "";
66
+ const inlineStyle = hasBgOpacity ? "" : bgColorStyle;
32
67
  const footer = slideStyle?.footer ? `<p class="absolute bottom-2 right-4 text-xs text-d-dim font-body">${escapeHtml(slideStyle.footer)}</p>` : "";
33
68
  const referenceHtml = reference
34
69
  ? `<div class="mt-auto px-4 pb-2"><p class="text-sm text-d-muted font-body opacity-80">${escapeHtml(reference)}</p></div>`
35
70
  : "";
71
+ const bgHex = sanitizeHex(slideStyle?.bgColor ?? theme.colors.bg);
72
+ const brandingBg = branding ? renderBrandingBackground(branding, bgHex) : "";
73
+ const brandingLogo = branding ? renderBrandingLogo(branding) : "";
36
74
  return `<!DOCTYPE html>
37
75
  <html lang="en" class="h-full">
38
76
  <head>
@@ -47,10 +85,14 @@ ${cdnScripts}
47
85
  </head>
48
86
  <body class="h-full">
49
87
  <div class="relative overflow-hidden ${bgCls} w-full h-full flex flex-col"${inlineStyle}>
88
+ ${brandingBg}
89
+ <div class="relative z-[1] flex flex-col flex-1">
50
90
  ${content}
51
91
  ${referenceHtml}
52
92
  ${footer}
53
93
  </div>
94
+ ${brandingLogo}
95
+ </div>
54
96
  </body>
55
97
  </html>`;
56
98
  };
@@ -4351,6 +4351,66 @@ export declare const funnelSlideSchema: z.ZodObject<{
4351
4351
  }, z.core.$strip>>;
4352
4352
  layout: z.ZodLiteral<"funnel">;
4353
4353
  }, z.core.$strip>;
4354
+ export declare const slideBrandingLogoSchema: z.ZodObject<{
4355
+ source: z.ZodDiscriminatedUnion<[z.ZodObject<{
4356
+ kind: z.ZodLiteral<"url">;
4357
+ url: z.ZodURL;
4358
+ }, z.core.$strict>, z.ZodObject<{
4359
+ kind: z.ZodLiteral<"base64">;
4360
+ data: z.ZodString;
4361
+ }, z.core.$strict>, z.ZodObject<{
4362
+ kind: z.ZodLiteral<"path">;
4363
+ path: z.ZodString;
4364
+ }, z.core.$strict>], "kind">;
4365
+ position: z.ZodDefault<z.ZodEnum<{
4366
+ "top-left": "top-left";
4367
+ "top-right": "top-right";
4368
+ "bottom-left": "bottom-left";
4369
+ "bottom-right": "bottom-right";
4370
+ }>>;
4371
+ width: z.ZodDefault<z.ZodNumber>;
4372
+ }, z.core.$strict>;
4373
+ export declare const slideBrandingSchema: z.ZodObject<{
4374
+ logo: z.ZodOptional<z.ZodObject<{
4375
+ source: z.ZodDiscriminatedUnion<[z.ZodObject<{
4376
+ kind: z.ZodLiteral<"url">;
4377
+ url: z.ZodURL;
4378
+ }, z.core.$strict>, z.ZodObject<{
4379
+ kind: z.ZodLiteral<"base64">;
4380
+ data: z.ZodString;
4381
+ }, z.core.$strict>, z.ZodObject<{
4382
+ kind: z.ZodLiteral<"path">;
4383
+ path: z.ZodString;
4384
+ }, z.core.$strict>], "kind">;
4385
+ position: z.ZodDefault<z.ZodEnum<{
4386
+ "top-left": "top-left";
4387
+ "top-right": "top-right";
4388
+ "bottom-left": "bottom-left";
4389
+ "bottom-right": "bottom-right";
4390
+ }>>;
4391
+ width: z.ZodDefault<z.ZodNumber>;
4392
+ }, z.core.$strict>>;
4393
+ backgroundImage: z.ZodOptional<z.ZodObject<{
4394
+ source: z.ZodDiscriminatedUnion<[z.ZodObject<{
4395
+ kind: z.ZodLiteral<"url">;
4396
+ url: z.ZodURL;
4397
+ }, z.core.$strict>, z.ZodObject<{
4398
+ kind: z.ZodLiteral<"base64">;
4399
+ data: z.ZodString;
4400
+ }, z.core.$strict>, z.ZodObject<{
4401
+ kind: z.ZodLiteral<"path">;
4402
+ path: z.ZodString;
4403
+ }, z.core.$strict>], "kind">;
4404
+ size: z.ZodOptional<z.ZodEnum<{
4405
+ contain: "contain";
4406
+ cover: "cover";
4407
+ fill: "fill";
4408
+ auto: "auto";
4409
+ }>>;
4410
+ opacity: z.ZodOptional<z.ZodNumber>;
4411
+ bgOpacity: z.ZodOptional<z.ZodNumber>;
4412
+ }, z.core.$strip>>;
4413
+ }, z.core.$strict>;
4354
4414
  export declare const slideLayoutSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
4355
4415
  title: z.ZodString;
4356
4416
  subtitle: z.ZodOptional<z.ZodString>;
@@ -8985,6 +9045,47 @@ export declare const mulmoSlideMediaSchema: z.ZodObject<{
8985
9045
  layout: z.ZodLiteral<"funnel">;
8986
9046
  }, z.core.$strip>], "layout">;
8987
9047
  reference: z.ZodOptional<z.ZodString>;
9048
+ branding: z.ZodOptional<z.ZodNullable<z.ZodObject<{
9049
+ logo: z.ZodOptional<z.ZodObject<{
9050
+ source: z.ZodDiscriminatedUnion<[z.ZodObject<{
9051
+ kind: z.ZodLiteral<"url">;
9052
+ url: z.ZodURL;
9053
+ }, z.core.$strict>, z.ZodObject<{
9054
+ kind: z.ZodLiteral<"base64">;
9055
+ data: z.ZodString;
9056
+ }, z.core.$strict>, z.ZodObject<{
9057
+ kind: z.ZodLiteral<"path">;
9058
+ path: z.ZodString;
9059
+ }, z.core.$strict>], "kind">;
9060
+ position: z.ZodDefault<z.ZodEnum<{
9061
+ "top-left": "top-left";
9062
+ "top-right": "top-right";
9063
+ "bottom-left": "bottom-left";
9064
+ "bottom-right": "bottom-right";
9065
+ }>>;
9066
+ width: z.ZodDefault<z.ZodNumber>;
9067
+ }, z.core.$strict>>;
9068
+ backgroundImage: z.ZodOptional<z.ZodObject<{
9069
+ source: z.ZodDiscriminatedUnion<[z.ZodObject<{
9070
+ kind: z.ZodLiteral<"url">;
9071
+ url: z.ZodURL;
9072
+ }, z.core.$strict>, z.ZodObject<{
9073
+ kind: z.ZodLiteral<"base64">;
9074
+ data: z.ZodString;
9075
+ }, z.core.$strict>, z.ZodObject<{
9076
+ kind: z.ZodLiteral<"path">;
9077
+ path: z.ZodString;
9078
+ }, z.core.$strict>], "kind">;
9079
+ size: z.ZodOptional<z.ZodEnum<{
9080
+ contain: "contain";
9081
+ cover: "cover";
9082
+ fill: "fill";
9083
+ auto: "auto";
9084
+ }>>;
9085
+ opacity: z.ZodOptional<z.ZodNumber>;
9086
+ bgOpacity: z.ZodOptional<z.ZodNumber>;
9087
+ }, z.core.$strip>>;
9088
+ }, z.core.$strict>>>;
8988
9089
  }, z.core.$strict>;
8989
9090
  export type AccentColorKey = z.infer<typeof accentColorKeySchema>;
8990
9091
  export type SlideThemeColors = z.infer<typeof slideThemeColorsSchema>;
@@ -9027,4 +9128,6 @@ export type TableCellValue = z.infer<typeof tableCellValueSchema>;
9027
9128
  export type TableSlide = z.infer<typeof tableSlideSchema>;
9028
9129
  export type FunnelStage = z.infer<typeof funnelStageSchema>;
9029
9130
  export type FunnelSlide = z.infer<typeof funnelSlideSchema>;
9131
+ export type SlideBrandingLogo = z.infer<typeof slideBrandingLogoSchema>;
9132
+ export type SlideBranding = z.infer<typeof slideBrandingSchema>;
9030
9133
  export type MulmoSlideMedia = z.infer<typeof mulmoSlideMediaSchema>;
@@ -358,6 +358,37 @@ export const funnelSlideSchema = z.object({
358
358
  callout: calloutBarSchema.optional(),
359
359
  });
360
360
  // ═══════════════════════════════════════════════════════════
361
+ // Branding — logo & background image overlay
362
+ // ═══════════════════════════════════════════════════════════
363
+ /**
364
+ * Media source for branding assets (self-contained definition to avoid
365
+ * circular dependency with src/types/schema.ts).
366
+ */
367
+ const slideMediaSourceSchema = z.discriminatedUnion("kind", [
368
+ z.object({ kind: z.literal("url"), url: z.url() }).strict(),
369
+ z.object({ kind: z.literal("base64"), data: z.string().min(1) }).strict(),
370
+ z.object({ kind: z.literal("path"), path: z.string().min(1) }).strict(),
371
+ ]);
372
+ const slideBackgroundImageSourceSchema = z.object({
373
+ source: slideMediaSourceSchema,
374
+ size: z.enum(["cover", "contain", "fill", "auto"]).optional(),
375
+ opacity: z.number().min(0).max(1).optional(),
376
+ bgOpacity: z.number().min(0).max(1).optional(),
377
+ });
378
+ export const slideBrandingLogoSchema = z
379
+ .object({
380
+ source: slideMediaSourceSchema,
381
+ position: z.enum(["top-left", "top-right", "bottom-left", "bottom-right"]).default("top-right"),
382
+ width: z.number().positive().default(120),
383
+ })
384
+ .strict();
385
+ export const slideBrandingSchema = z
386
+ .object({
387
+ logo: slideBrandingLogoSchema.optional(),
388
+ backgroundImage: slideBackgroundImageSourceSchema.optional(),
389
+ })
390
+ .strict();
391
+ // ═══════════════════════════════════════════════════════════
361
392
  // Slide Union & Media Schema
362
393
  // ═══════════════════════════════════════════════════════════
363
394
  export const slideLayoutSchema = z.discriminatedUnion("layout", [
@@ -380,5 +411,6 @@ export const mulmoSlideMediaSchema = z
380
411
  theme: slideThemeSchema.optional(),
381
412
  slide: slideLayoutSchema,
382
413
  reference: z.string().optional(),
414
+ branding: slideBrandingSchema.nullable().optional(),
383
415
  })
384
416
  .strict();