@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/blocks.js CHANGED
@@ -131,14 +131,24 @@ const renderSubBullets = (item) => {
131
131
  .join("\n");
132
132
  return `\n${subs}`;
133
133
  };
134
+ /** Map a per-item status icon variant to its glyph and color class segment. */
135
+ const STATUS_ICON_GLYPHS = {
136
+ ok: { glyph: "\u2713", color: "success" }, // \u2713
137
+ no: { glyph: "\u2715", color: "danger" }, // \u2715
138
+ warn: { glyph: "\u26a0", color: "warning" }, // \u26a0
139
+ };
134
140
  const renderBullets = (block) => {
135
141
  const tag = block.ordered ? "ol" : "ul";
136
142
  const items = block.items
137
143
  .map((item, i) => {
138
- const marker = block.ordered ? `${i + 1}.` : escapeHtml(block.icon || "\u2022");
144
+ // Per-item status icon overrides the block-level marker / numbered prefix.
145
+ const statusIcon = typeof item === "object" && item.icon ? STATUS_ICON_GLYPHS[item.icon] : undefined;
146
+ const markerHtml = statusIcon
147
+ ? `<span class="text-${c(statusIcon.color)} font-extrabold shrink-0">${statusIcon.glyph}</span>`
148
+ : `<span class="text-d-dim shrink-0">${block.ordered ? `${i + 1}.` : escapeHtml(block.icon || "\u2022")}</span>`;
139
149
  const text = bulletItemText(item);
140
150
  const subHtml = renderSubBullets(item);
141
- return ` <li class="flex flex-col gap-1"><div class="flex gap-2"><span class="text-d-dim shrink-0">${marker}</span><span>${renderInlineMarkup(text)}</span></div>${subHtml}</li>`;
151
+ return ` <li class="flex flex-col gap-1"><div class="flex gap-2">${markerHtml}<span>${renderInlineMarkup(text)}</span></div>${subHtml}</li>`;
142
152
  })
143
153
  .join("\n");
144
154
  return `<${tag} class="space-y-2 text-[15px] text-d-muted font-body">\n${items}\n</${tag}>`;
@@ -1,8 +1,11 @@
1
- import { renderInlineMarkup, accentBar, resolveAccent } from "../utils.js";
1
+ import { renderInlineMarkup, accentBar, resolveAccent, renderEyebrow } from "../utils.js";
2
2
  export const layoutBigQuote = (data) => {
3
3
  const accent = resolveAccent(data.accentColor);
4
+ const eyebrowHtml = renderEyebrow(data.eyebrow, accent);
4
5
  const parts = [];
5
6
  parts.push(`<div class="flex flex-col items-center justify-center h-full px-20">`);
7
+ if (eyebrowHtml)
8
+ parts.push(` <div class="mb-6">${eyebrowHtml}</div>`);
6
9
  parts.push(` ${accentBar(accent, "w-24 mb-8")}`);
7
10
  parts.push(` <blockquote class="text-[32px] text-d-text font-title italic text-center leading-relaxed">`);
8
11
  parts.push(` &ldquo;${renderInlineMarkup(data.quote)}&rdquo;`);
@@ -1,4 +1,4 @@
1
- import { renderInlineMarkup, c, cardWrap, numBadge, iconSquare, slideHeader, renderOptionalCallout, resolveAccent } from "../utils.js";
1
+ import { renderInlineMarkup, c, cardWrap, numBadge, iconSquare, slideHeader, renderOptionalCallout, resolveAccent, renderNumLabel } from "../utils.js";
2
2
  import { renderCardContentBlocks } from "../blocks.js";
3
3
  const buildColumnCard = (col) => {
4
4
  const accent = resolveAccent(col.accentColor);
@@ -19,7 +19,8 @@ const buildColumnCard = (col) => {
19
19
  if (col.label) {
20
20
  inner.push(`<p class="text-sm font-bold text-${c(accent)} font-body">${renderInlineMarkup(col.label)}</p>`);
21
21
  }
22
- inner.push(`<h3 class="text-2xl font-title font-bold text-d-text mt-1">${renderInlineMarkup(col.title)}</h3>`);
22
+ const numPrefix = renderNumLabel(col.numLabel, accent);
23
+ inner.push(`<h3 class="text-2xl font-title font-bold text-d-text mt-1">${numPrefix}${renderInlineMarkup(col.title)}</h3>`);
23
24
  }
24
25
  if (col.content) {
25
26
  const centerCls = col.icon ? "text-center" : "";
@@ -10,6 +10,7 @@ import { layoutMatrix } from "./matrix.js";
10
10
  import { layoutTable } from "./table.js";
11
11
  import { layoutFunnel } from "./funnel.js";
12
12
  import { layoutWaterfall } from "./waterfall.js";
13
+ import { layoutManifesto } from "./manifesto.js";
13
14
  import { escapeHtml } from "../utils.js";
14
15
  /** Render the inner content of a slide (without the wrapper div) */
15
16
  export const renderSlideContent = (slide) => {
@@ -38,6 +39,8 @@ export const renderSlideContent = (slide) => {
38
39
  return layoutFunnel(slide);
39
40
  case "waterfall":
40
41
  return layoutWaterfall(slide);
42
+ case "manifesto":
43
+ return layoutManifesto(slide);
41
44
  default: {
42
45
  const _exhaustive = slide;
43
46
  return `<p class="text-white p-8">Unknown layout: ${escapeHtml(String(_exhaustive.layout))}</p>`;
@@ -0,0 +1,2 @@
1
+ import type { ManifestoSlide } from "../schema.js";
2
+ export declare const layoutManifesto: (data: ManifestoSlide) => string;
@@ -0,0 +1,39 @@
1
+ import { renderInlineMarkup, c, slideHeader, renderOptionalCallout, resolveItemColor } from "../utils.js";
2
+ const buildManifestoCard = (line, slideAccent) => {
3
+ const color = resolveItemColor(line.accentColor, slideAccent);
4
+ const parts = [];
5
+ parts.push(`<div class="relative bg-d-card rounded-lg shadow-md overflow-hidden flex flex-col">`);
6
+ parts.push(` <div class="absolute left-0 top-0 bottom-0 w-1 bg-${c(color)}"></div>`);
7
+ parts.push(` <div class="px-5 py-4 pl-6 flex-1">`);
8
+ parts.push(` <h3 class="text-lg font-bold text-d-text font-body leading-snug">${renderInlineMarkup(line.title)}</h3>`);
9
+ if (line.description) {
10
+ parts.push(` <p class="text-sm text-d-muted font-body mt-1 leading-relaxed">${renderInlineMarkup(line.description)}</p>`);
11
+ }
12
+ parts.push(` </div>`);
13
+ parts.push(`</div>`);
14
+ return parts.join("\n");
15
+ };
16
+ const DEFAULT_COLUMNS = 2;
17
+ const MAX_COLUMNS = 4;
18
+ const gridColsClass = (columns) => {
19
+ // Tailwind requires literal class names — map explicitly.
20
+ const cls = {
21
+ 1: "grid-cols-1",
22
+ 2: "grid-cols-2",
23
+ 3: "grid-cols-3",
24
+ 4: "grid-cols-4",
25
+ };
26
+ return cls[columns] || cls[DEFAULT_COLUMNS];
27
+ };
28
+ export const layoutManifesto = (data) => {
29
+ const cols = Math.min(Math.max(data.columns ?? DEFAULT_COLUMNS, 1), MAX_COLUMNS);
30
+ const items = data.items || [];
31
+ const parts = [slideHeader(data)];
32
+ parts.push(`<div class="grid ${gridColsClass(cols)} gap-4 px-12 mt-5 flex-1 min-h-0 content-start">`);
33
+ items.forEach((line) => {
34
+ parts.push(buildManifestoCard(line, data.accentColor));
35
+ });
36
+ parts.push(`</div>`);
37
+ parts.push(renderOptionalCallout(data.callout));
38
+ return parts.join("\n");
39
+ };
@@ -9,6 +9,9 @@ export const layoutStats = (data) => {
9
9
  const color = resolveItemColor(stat.color, data.accentColor);
10
10
  parts.push(`<div class="flex-1 bg-d-card rounded-lg shadow-lg p-10 text-center">`);
11
11
  parts.push(` <div class="h-[3px] bg-${c(color)} rounded-full w-12 mx-auto mb-6"></div>`);
12
+ if (stat.numLabel) {
13
+ parts.push(` <p class="font-accent font-extrabold text-${c(color)} text-sm tracking-wider mb-2">${renderInlineMarkup(stat.numLabel)}</p>`);
14
+ }
12
15
  parts.push(` <p class="text-[52px] font-bold text-${c(color)} font-body leading-none">${renderInlineMarkup(stat.value)}</p>`);
13
16
  parts.push(` <p class="text-lg text-d-muted font-body mt-4">${renderInlineMarkup(stat.label)}</p>`);
14
17
  if (stat.change) {
@@ -7,11 +7,14 @@ export const layoutTimeline = (data) => {
7
7
  parts.push(`<div class="flex items-start mt-10 relative">`);
8
8
  parts.push(`<div class="absolute left-4 right-4 top-[52px] h-[2px] bg-d-alt"></div>`);
9
9
  items.forEach((item) => {
10
- const color = resolveItemColor(item.color, data.accentColor);
10
+ const baseColor = resolveItemColor(item.color, data.accentColor);
11
+ // 'hot' items are emphasized — keep the configured color when set, else fall back to warning (amber) for visibility.
12
+ const color = item.hot ? item.color || "warning" : baseColor;
11
13
  const dotBorder = item.done ? `bg-${c(color)}` : `bg-d-alt`;
12
14
  const dotInner = item.done ? "bg-d-text" : `bg-${c(color)}`;
15
+ const hotRing = item.hot ? ` ring-2 ring-${c(color)} ring-offset-2 ring-offset-d-bg` : "";
13
16
  parts.push(`<div class="flex-1 flex flex-col items-center text-center relative z-10">`);
14
- parts.push(` <div class="w-10 h-10 rounded-full ${dotBorder} flex items-center justify-center shadow-lg">`);
17
+ parts.push(` <div class="w-10 h-10 rounded-full ${dotBorder} flex items-center justify-center shadow-lg${hotRing}">`);
15
18
  parts.push(` <div class="w-4 h-4 rounded-full ${dotInner}"></div>`);
16
19
  parts.push(` </div>`);
17
20
  parts.push(` <p class="text-sm font-bold text-${c(color)} font-body mt-4">${renderInlineMarkup(item.date)}</p>`);
@@ -1,16 +1,21 @@
1
- import { renderInlineMarkup, accentBar } from "../utils.js";
1
+ import { renderInlineMarkup, accentBar, renderEyebrow, renderChipRow, resolveAccent } from "../utils.js";
2
2
  export const layoutTitle = (data) => {
3
+ const accent = resolveAccent(data.accentColor);
4
+ const eyebrowHtml = renderEyebrow(data.eyebrow, accent);
5
+ const chipsHtml = renderChipRow(data.chips);
3
6
  return [
4
7
  accentBar("primary"),
5
8
  `<div class="absolute -top-20 -right-8 w-[360px] h-[360px] rounded-full bg-d-primary opacity-10"></div>`,
6
9
  `<div class="absolute -bottom-12 -left-16 w-[280px] h-[280px] rounded-full bg-d-accent opacity-10"></div>`,
7
10
  `<div class="flex flex-col justify-center h-full px-16 relative z-10">`,
11
+ eyebrowHtml ? ` <div class="mb-4">${eyebrowHtml}</div>` : "",
8
12
  ` <h1 class="text-[60px] leading-tight font-title font-bold text-d-text">${renderInlineMarkup(data.title)}</h1>`,
9
13
  data.subtitle ? ` <p class="text-2xl text-d-muted mt-6 font-body">${renderInlineMarkup(data.subtitle)}</p>` : "",
10
14
  data.author ? ` <p class="text-lg text-d-dim mt-10 font-body">${renderInlineMarkup(data.author)}</p>` : "",
11
15
  data.note
12
16
  ? ` <div class="bg-d-card px-4 py-2 mt-6 inline-block rounded"><p class="text-sm text-d-dim font-body">${renderInlineMarkup(data.note)}</p></div>`
13
17
  : "",
18
+ chipsHtml,
14
19
  `</div>`,
15
20
  accentBar("accent", "absolute bottom-0 left-0 right-0"),
16
21
  ]