@mulmocast/deck 0.2.0 → 0.4.0

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.
package/lib/schema.js CHANGED
@@ -50,12 +50,16 @@ export const textBlockSchema = z.object({
50
50
  });
51
51
  /** Sub-bullet item: plain string or object with text */
52
52
  const subBulletItemSchema = z.union([z.string(), z.object({ text: z.string() })]);
53
- /** Bullet item: plain string or object with text and optional sub-items (2 levels max) */
53
+ /** Status-icon variants for bullets (renders / / in the accent color of the variant). */
54
+ export const bulletIconSchema = z.enum(["ok", "no", "warn"]);
55
+ /** Bullet item: plain string, or object with text + optional sub-items + optional status icon. */
54
56
  export const bulletItemSchema = z.union([
55
57
  z.string(),
56
58
  z.object({
57
59
  text: z.string(),
58
60
  items: z.array(subBulletItemSchema).optional(),
61
+ /** Optional status icon shown in place of the default bullet marker ("ok" → ✓, "no" → ✕, "warn" → ⚠). */
62
+ icon: bulletIconSchema.optional(),
59
63
  }),
60
64
  ]);
61
65
  export const bulletsBlockSchema = z.object({
@@ -170,6 +174,8 @@ export const cardSchema = z.object({
170
174
  footer: z.string().optional(),
171
175
  label: z.string().optional(),
172
176
  num: z.number().optional(),
177
+ /** Optional accent-colored typographic prefix before the card title (e.g. "01", "Ⅱ", "★"). */
178
+ numLabel: z.string().optional(),
173
179
  icon: z.string().optional(),
174
180
  });
175
181
  // ═══════════════════════════════════════════════════════════
@@ -183,10 +189,18 @@ export const slideStyleSchema = z.object({
183
189
  bgOpacity: z.number().optional(),
184
190
  footer: z.string().optional(),
185
191
  });
192
+ /** Optional kicker/category label rendered at the top of a slide (small uppercase pill). */
193
+ export const eyebrowSchema = z.object({
194
+ label: z.string(),
195
+ /** Color token (e.g. "primary", "amber", "success"). Falls back to slide.accentColor / theme primary when absent. */
196
+ color: accentColorKeySchema.optional(),
197
+ });
186
198
  /** Common slide properties shared across all layouts */
187
199
  const slideBaseFields = {
188
200
  accentColor: accentColorKeySchema.optional(),
189
201
  style: slideStyleSchema.optional(),
202
+ /** Optional eyebrow (small uppercase pill) shown at the top of the slide above the header/title. */
203
+ eyebrow: eyebrowSchema.optional(),
190
204
  };
191
205
  // ═══════════════════════════════════════════════════════════
192
206
  // Layouts
@@ -199,6 +213,8 @@ export const titleSlideSchema = z.object({
199
213
  subtitle: z.string().optional(),
200
214
  author: z.string().optional(),
201
215
  note: z.string().optional(),
216
+ /** Optional row of small pill badges shown below the subtitle / note (e.g. ["🚀 deploy or die", "⚡ weekly output"]). */
217
+ chips: z.array(z.string()).optional(),
202
218
  });
203
219
  // ─── columns ───
204
220
  export const columnsSlideSchema = z.object({
@@ -261,6 +277,8 @@ export const statItemSchema = z.object({
261
277
  label: z.string(),
262
278
  color: accentColorKeySchema.optional(),
263
279
  change: z.string().optional(),
280
+ /** Optional accent-colored typographic prefix shown above the value (e.g. "01"). */
281
+ numLabel: z.string().optional(),
264
282
  });
265
283
  export const statsSlideSchema = z.object({
266
284
  layout: z.literal("stats"),
@@ -278,6 +296,8 @@ export const timelineItemSchema = z.object({
278
296
  description: z.string().optional(),
279
297
  color: accentColorKeySchema.optional(),
280
298
  done: z.boolean().optional(),
299
+ /** Optional emphasis flag — when true the step is rendered with a stronger accent border and tinted background. */
300
+ hot: z.boolean().optional(),
281
301
  });
282
302
  export const timelineSlideSchema = z.object({
283
303
  layout: z.literal("timeline"),
@@ -382,6 +402,29 @@ export const funnelSlideSchema = z.object({
382
402
  stages: z.array(funnelStageSchema),
383
403
  callout: calloutBarSchema.optional(),
384
404
  });
405
+ // ─── manifesto ───
406
+ /** A single line in a manifesto / creed grid. */
407
+ export const manifestoLineSchema = z.object({
408
+ title: z.string(),
409
+ description: z.string().optional(),
410
+ /** Accent color for the line's left-border highlight. Falls back to slide.accentColor / "primary". */
411
+ accentColor: accentColorKeySchema.optional(),
412
+ });
413
+ /**
414
+ * Grid of short bordered cards, each with a colored left accent — useful for manifestos,
415
+ * principles, "what we believe" lists, etc.
416
+ */
417
+ export const manifestoSlideSchema = z.object({
418
+ layout: z.literal("manifesto"),
419
+ ...slideBaseFields,
420
+ title: z.string(),
421
+ stepLabel: z.string().optional(),
422
+ subtitle: z.string().optional(),
423
+ items: z.array(manifestoLineSchema),
424
+ /** Number of grid columns (default: 2). */
425
+ columns: z.number().int().min(1).max(4).optional(),
426
+ callout: calloutBarSchema.optional(),
427
+ });
385
428
  // ═══════════════════════════════════════════════════════════
386
429
  // Branding — logo & background image overlay
387
430
  // ═══════════════════════════════════════════════════════════
@@ -429,6 +472,7 @@ export const slideLayoutSchema = z.discriminatedUnion("layout", [
429
472
  tableSlideSchema,
430
473
  funnelSlideSchema,
431
474
  waterfallSlideSchema,
475
+ manifestoSlideSchema,
432
476
  ]);
433
477
  /** Media schema registered in mulmoImageAssetSchema */
434
478
  export const mulmoSlideMediaSchema = z
package/lib/utils.d.ts CHANGED
@@ -47,19 +47,39 @@ export declare const renderCalloutBar: (obj: {
47
47
  align?: string;
48
48
  leftBar?: boolean;
49
49
  }) => string;
50
+ /**
51
+ * Render an eyebrow pill (small uppercase letter-spaced category label).
52
+ * Renders nothing when `eyebrow` is undefined, so callers can pass through unconditionally.
53
+ */
54
+ export declare const renderEyebrow: (eyebrow: {
55
+ label: string;
56
+ color?: string;
57
+ } | undefined, defaultColor?: string) => string;
58
+ /** Render a chip-row (small bordered pill badges, e.g. tags below a title). Empty / undefined input renders nothing. */
59
+ export declare const renderChipRow: (chips: string[] | undefined) => string;
60
+ /** Render an accent-colored typographic prefix (e.g. "01") to be placed before a card / stat title. */
61
+ export declare const renderNumLabel: (label: string | undefined, colorKey?: string) => string;
50
62
  /** Render header text elements (stepLabel + title + subtitle) without wrapping div */
51
63
  export declare const renderHeaderText: (data: {
52
64
  accentColor?: string;
53
65
  stepLabel?: string;
54
66
  title: string;
55
67
  subtitle?: string;
68
+ eyebrow?: {
69
+ label: string;
70
+ color?: string;
71
+ };
56
72
  }) => string;
57
- /** Render the common slide header (accent bar + title + subtitle) */
73
+ /** Render the common slide header (accent bar + title + subtitle, plus optional eyebrow pill) */
58
74
  export declare const slideHeader: (data: {
59
75
  accentColor?: string;
60
76
  stepLabel?: string;
61
77
  title: string;
62
78
  subtitle?: string;
79
+ eyebrow?: {
80
+ label: string;
81
+ color?: string;
82
+ };
63
83
  }) => string;
64
84
  /** Render accent bar + vertically-centered wrapper with header text (used by stats, timeline) */
65
85
  export declare const centeredSlideHeader: (data: {
@@ -67,6 +87,10 @@ export declare const centeredSlideHeader: (data: {
67
87
  stepLabel?: string;
68
88
  title: string;
69
89
  subtitle?: string;
90
+ eyebrow?: {
91
+ label: string;
92
+ color?: string;
93
+ };
70
94
  }) => string;
71
95
  /** Generate a unique ID with the given prefix (e.g. "chart-0", "mermaid-1") */
72
96
  export declare const generateSlideId: (prefix: string) => string;
package/lib/utils.js CHANGED
@@ -155,10 +155,39 @@ export const renderCalloutBar = (obj) => {
155
155
  <div class="px-4 py-3 text-sm font-body flex-1">${inner}</div>
156
156
  </div>`;
157
157
  };
158
+ /**
159
+ * Render an eyebrow pill (small uppercase letter-spaced category label).
160
+ * Renders nothing when `eyebrow` is undefined, so callers can pass through unconditionally.
161
+ */
162
+ export const renderEyebrow = (eyebrow, defaultColor) => {
163
+ if (!eyebrow)
164
+ return "";
165
+ const color = c(eyebrow.color ?? defaultColor ?? "primary");
166
+ return `<span class="inline-flex items-center gap-2 font-accent font-extrabold uppercase tracking-[0.16em] text-[12px] px-3 py-1 rounded-full border border-d-textDim/30 bg-${color}/10 text-${color}">${renderInlineMarkup(eyebrow.label)}</span>`;
167
+ };
168
+ /** Render a chip-row (small bordered pill badges, e.g. tags below a title). Empty / undefined input renders nothing. */
169
+ export const renderChipRow = (chips) => {
170
+ if (!chips || chips.length === 0)
171
+ return "";
172
+ const items = chips
173
+ .map((label) => `<span class="text-sm px-3 py-1.5 rounded-full border border-d-textDim/30 bg-d-card/40 text-d-text">${renderInlineMarkup(label)}</span>`)
174
+ .join("");
175
+ return `<div class="flex gap-2 flex-wrap mt-4">${items}</div>`;
176
+ };
177
+ /** Render an accent-colored typographic prefix (e.g. "01") to be placed before a card / stat title. */
178
+ export const renderNumLabel = (label, colorKey) => {
179
+ if (!label)
180
+ return "";
181
+ const color = c(colorKey ?? "primary");
182
+ return `<span class="font-accent font-extrabold text-${color} mr-2">${renderInlineMarkup(label)}</span>`;
183
+ };
158
184
  /** Render header text elements (stepLabel + title + subtitle) without wrapping div */
159
185
  export const renderHeaderText = (data) => {
160
186
  const accent = resolveAccent(data.accentColor);
161
187
  const lines = [];
188
+ const eyebrowHtml = renderEyebrow(data.eyebrow, accent);
189
+ if (eyebrowHtml)
190
+ lines.push(`<div class="mb-2">${eyebrowHtml}</div>`);
162
191
  if (data.stepLabel) {
163
192
  lines.push(`<p class="text-sm font-bold text-${c(accent)} font-body">${renderInlineMarkup(data.stepLabel)}</p>`);
164
193
  }
@@ -168,7 +197,7 @@ export const renderHeaderText = (data) => {
168
197
  }
169
198
  return lines.join("\n");
170
199
  };
171
- /** Render the common slide header (accent bar + title + subtitle) */
200
+ /** Render the common slide header (accent bar + title + subtitle, plus optional eyebrow pill) */
172
201
  export const slideHeader = (data) => {
173
202
  const accent = resolveAccent(data.accentColor);
174
203
  return [accentBar(accent), `<div class="px-12 pt-5 shrink-0">`, renderHeaderText(data), `</div>`].join("\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mulmocast/deck",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "MulmoCast deck DSL: JSON-described semantic slide layouts (stats, comparison, timeline, ...) rendered to Tailwind-based HTML",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",