@deckspec/cli 0.1.1 → 0.1.3

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.
Files changed (50) hide show
  1. package/dist/cli.js +13 -2
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/dev.d.ts +3 -1
  4. package/dist/commands/dev.d.ts.map +1 -1
  5. package/dist/commands/dev.js +4 -2
  6. package/dist/commands/dev.js.map +1 -1
  7. package/dist/commands/init.d.ts.map +1 -1
  8. package/dist/commands/init.js +22 -17
  9. package/dist/commands/init.js.map +1 -1
  10. package/dist/commands/render.d.ts.map +1 -1
  11. package/dist/commands/render.js +13 -4
  12. package/dist/commands/render.js.map +1 -1
  13. package/package.json +6 -5
  14. package/src/cli.ts +12 -2
  15. package/src/commands/dev.ts +3 -2
  16. package/src/commands/init.ts +22 -17
  17. package/src/commands/render.ts +13 -4
  18. package/templates/noir-display/components/card/index.tsx +31 -0
  19. package/templates/noir-display/components/index.ts +2 -0
  20. package/templates/noir-display/components/slide-header/index.tsx +47 -0
  21. package/templates/noir-display/design.md +289 -0
  22. package/templates/noir-display/globals.css +263 -0
  23. package/templates/noir-display/package.json +39 -0
  24. package/templates/noir-display/patterns/_lib/chart-colors.ts +12 -0
  25. package/templates/noir-display/patterns/_lib/icon.tsx +50 -0
  26. package/templates/noir-display/patterns/big-number/index.tsx +87 -0
  27. package/templates/noir-display/patterns/body-message/index.tsx +295 -0
  28. package/templates/noir-display/patterns/bullet-list/index.tsx +132 -0
  29. package/templates/noir-display/patterns/challenge-cards/index.tsx +112 -0
  30. package/templates/noir-display/patterns/chart-bar/index.tsx +107 -0
  31. package/templates/noir-display/patterns/comparison-columns/index.tsx +115 -0
  32. package/templates/noir-display/patterns/feature-metrics/index.tsx +94 -0
  33. package/templates/noir-display/patterns/flow-diagram/index.tsx +151 -0
  34. package/templates/noir-display/patterns/hero-statement/index.tsx +72 -0
  35. package/templates/noir-display/patterns/icon-grid/index.tsx +126 -0
  36. package/templates/noir-display/patterns/index.ts +17 -0
  37. package/templates/noir-display/patterns/phased-roadmap/index.tsx +179 -0
  38. package/templates/noir-display/patterns/photo-split/index.tsx +110 -0
  39. package/templates/noir-display/patterns/pricing-tiers/index.tsx +127 -0
  40. package/templates/noir-display/patterns/showcase-grid/index.tsx +99 -0
  41. package/templates/noir-display/patterns/thank-you/index.tsx +86 -0
  42. package/templates/noir-display/patterns/three-pillars/index.tsx +112 -0
  43. package/templates/noir-display/patterns/title-center/index.tsx +46 -0
  44. package/templates/noir-display/tokens.json +30 -0
  45. package/templates/noir-display/tsconfig.json +13 -0
  46. package/templates/skills/deckspec-add-pattern/SKILL.md +78 -0
  47. package/templates/skills/deckspec-make-slides/SKILL.md +70 -0
  48. package/templates/skills/deckspec-promote-pattern/SKILL.md +133 -0
  49. package/templates/skills/deckspec-screenshot/SKILL.md +41 -0
  50. package/templates/skills/deckspec-to-pptx/SKILL.md +36 -0
@@ -0,0 +1,87 @@
1
+ import { z } from "zod";
2
+
3
+ export const schema = z.object({
4
+ label: z.string().min(1).max(30).optional().describe("Accent eyebrow"),
5
+ value: z.string().min(1).max(20).describe("Massive display number"),
6
+ unit: z.string().max(10).optional().describe("Unit after number"),
7
+ headline: z.string().min(1).max(60).describe("Headline"),
8
+ description: z.string().max(200).optional().describe("Body text"),
9
+ });
10
+
11
+ type Props = z.infer<typeof schema>;
12
+
13
+ export default function BigNumber({ label, value, unit, headline, description }: Props) {
14
+ return (
15
+ <div className="slide" style={{ display: "flex", alignItems: "center", justifyContent: "center", background: "#ffffff" }}>
16
+ <div className="stack-center" style={{ gap: 20, maxWidth: 800, marginBottom: 32 }}>
17
+ {label && (
18
+ <span
19
+ style={{
20
+ fontSize: 17,
21
+ fontWeight: 600,
22
+ letterSpacing: "-0.022em",
23
+ color: "var(--color-primary)",
24
+ }}
25
+ >
26
+ {label}
27
+ </span>
28
+ )}
29
+
30
+ <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
31
+ <span
32
+ style={{
33
+ fontSize: 120,
34
+ fontWeight: 600,
35
+ fontFamily: "var(--font-heading)",
36
+ lineHeight: 1,
37
+ letterSpacing: "-0.025em",
38
+ color: "var(--color-foreground)",
39
+ }}
40
+ >
41
+ {value}
42
+ </span>
43
+ {unit && (
44
+ <span
45
+ style={{
46
+ fontSize: 40,
47
+ fontWeight: 600,
48
+ fontFamily: "var(--font-heading)",
49
+ letterSpacing: "-0.009em",
50
+ color: "var(--color-muted-foreground)",
51
+ }}
52
+ >
53
+ {unit}
54
+ </span>
55
+ )}
56
+ </div>
57
+
58
+ <h3
59
+ style={{
60
+ fontSize: 28,
61
+ fontWeight: 600,
62
+ lineHeight: 1.14,
63
+ letterSpacing: "0.007em",
64
+ }}
65
+ >
66
+ {headline}
67
+ </h3>
68
+
69
+ {description && (
70
+ <p
71
+ style={{
72
+ fontSize: 17,
73
+ fontWeight: 400,
74
+ lineHeight: 1.47,
75
+ letterSpacing: "-0.022em",
76
+ color: "var(--color-muted-foreground)",
77
+ maxWidth: 600,
78
+ textAlign: "center",
79
+ }}
80
+ >
81
+ {description}
82
+ </p>
83
+ )}
84
+ </div>
85
+ </div>
86
+ );
87
+ }
@@ -0,0 +1,295 @@
1
+ import { z } from "zod";
2
+ import { SlideHeader } from "../../components/index.js";
3
+
4
+ const nodeSchema = z.object({
5
+ id: z.string().min(1).max(3).describe("Node ID (A, B, C)"),
6
+ title: z.string().min(1).max(30).describe("Node title"),
7
+ });
8
+
9
+ const explanationSchema = z.object({
10
+ id: z.string().min(1).max(3).describe("Explanation ID matching node"),
11
+ title: z.string().min(1).max(30).describe("Explanation title"),
12
+ items: z.array(z.string()).min(1).max(5).describe("Detail points"),
13
+ });
14
+
15
+ export const schema = z.object({
16
+ label: z.string().min(1).max(30).optional().describe("Accent eyebrow"),
17
+ heading: z.string().min(1).max(60).describe("Section heading"),
18
+ center: z.string().min(1).max(40).describe("Central concept"),
19
+ nodes: z.array(nodeSchema).min(2).max(5).describe("Surrounding nodes"),
20
+ explanations: z.array(explanationSchema).min(2).max(5).describe("Explanations for each node"),
21
+ footer: z.string().min(1).max(100).optional().describe("Footer message"),
22
+ });
23
+
24
+ type Props = z.infer<typeof schema>;
25
+
26
+ export default function BodyMessage({
27
+ label,
28
+ heading,
29
+ center,
30
+ nodes,
31
+ explanations,
32
+ footer,
33
+ }: Props) {
34
+ // Position nodes around the center concept in a radial layout
35
+ const nodePositions = getNodePositions(nodes.length);
36
+
37
+ return (
38
+ <div
39
+ className="slide"
40
+ style={{
41
+ display: "flex",
42
+ flexDirection: "column",
43
+ background: "var(--color-card-background)",
44
+ padding: "40px 60px",
45
+ gap: 20,
46
+ }}
47
+ >
48
+ {/* Header */}
49
+ <SlideHeader label={label} heading={heading} />
50
+
51
+ {/* Main content: diagram + explanations */}
52
+ <div style={{ display: "flex", gap: 32, flex: 1, minHeight: 0 }}>
53
+ {/* Left: diagram (60%) */}
54
+ <div
55
+ style={{
56
+ width: "58%",
57
+ position: "relative",
58
+ display: "flex",
59
+ alignItems: "center",
60
+ justifyContent: "center",
61
+ }}
62
+ >
63
+ {/* Center concept card */}
64
+ <div
65
+ style={{
66
+ position: "absolute",
67
+ left: "50%",
68
+ top: "50%",
69
+ transform: "translate(-50%, -50%)",
70
+ backgroundColor: "var(--color-card-background)",
71
+ border: "2px solid var(--color-primary)",
72
+ borderRadius: 16,
73
+ padding: "20px 28px",
74
+ textAlign: "center",
75
+ zIndex: 2,
76
+ boxShadow: "4px 4px 12px rgba(0,0,0,0.06)",
77
+ }}
78
+ >
79
+ <span
80
+ style={{
81
+ fontSize: 19,
82
+ fontWeight: 600,
83
+ fontFamily: "var(--font-heading)",
84
+ letterSpacing: "-0.022em",
85
+ color: "var(--color-foreground)",
86
+ lineHeight: 1.3,
87
+ }}
88
+ >
89
+ {center}
90
+ </span>
91
+ </div>
92
+
93
+ {/* Surrounding node cards */}
94
+ {nodes.map((node, i) => {
95
+ const pos = nodePositions[i];
96
+ return (
97
+ <div
98
+ key={i}
99
+ style={{
100
+ position: "absolute",
101
+ left: `${pos.x}%`,
102
+ top: `${pos.y}%`,
103
+ transform: "translate(-50%, -50%)",
104
+ backgroundColor: "var(--color-muted)",
105
+ borderRadius: 12,
106
+ padding: "12px 18px",
107
+ textAlign: "center",
108
+ zIndex: 1,
109
+ minWidth: 100,
110
+ }}
111
+ >
112
+ <span
113
+ style={{
114
+ fontSize: 12,
115
+ fontWeight: 600,
116
+ fontFamily: "var(--font-heading)",
117
+ color: "var(--color-primary)",
118
+ display: "block",
119
+ marginBottom: 2,
120
+ }}
121
+ >
122
+ {node.id}
123
+ </span>
124
+ <span
125
+ style={{
126
+ fontSize: 14,
127
+ fontWeight: 600,
128
+ fontFamily: "var(--font-heading)",
129
+ letterSpacing: "-0.016em",
130
+ color: "var(--color-foreground)",
131
+ lineHeight: 1.3,
132
+ }}
133
+ >
134
+ {node.title}
135
+ </span>
136
+ </div>
137
+ );
138
+ })}
139
+
140
+ {/* Connecting lines (SVG) */}
141
+ <svg
142
+ style={{
143
+ position: "absolute",
144
+ left: 0,
145
+ top: 0,
146
+ width: "100%",
147
+ height: "100%",
148
+ zIndex: 0,
149
+ overflow: "visible",
150
+ }}
151
+ viewBox="0 0 100 100"
152
+ preserveAspectRatio="none"
153
+ >
154
+ {nodes.map((_, i) => {
155
+ const pos = nodePositions[i];
156
+ return (
157
+ <line
158
+ key={i}
159
+ x1="50"
160
+ y1="50"
161
+ x2={pos.x}
162
+ y2={pos.y}
163
+ stroke="var(--color-border)"
164
+ strokeWidth="0.4"
165
+ strokeDasharray="1,1"
166
+ />
167
+ );
168
+ })}
169
+ </svg>
170
+ </div>
171
+
172
+ {/* Right: explanations (40%) */}
173
+ <div
174
+ style={{
175
+ width: "42%",
176
+ display: "flex",
177
+ flexDirection: "column",
178
+ justifyContent: "center",
179
+ gap: 16,
180
+ }}
181
+ >
182
+ {explanations.map((exp, i) => (
183
+ <div
184
+ key={i}
185
+ style={{
186
+ display: "flex",
187
+ flexDirection: "column",
188
+ gap: 6,
189
+ }}
190
+ >
191
+ <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
192
+ <span
193
+ style={{
194
+ fontSize: 13,
195
+ fontWeight: 600,
196
+ fontFamily: "var(--font-heading)",
197
+ color: "var(--color-primary)",
198
+ backgroundColor: "var(--color-background)",
199
+ borderRadius: 6,
200
+ padding: "2px 8px",
201
+ lineHeight: 1.4,
202
+ }}
203
+ >
204
+ {exp.id}
205
+ </span>
206
+ <span
207
+ style={{
208
+ fontSize: 17,
209
+ fontWeight: 600,
210
+ fontFamily: "var(--font-heading)",
211
+ letterSpacing: "-0.022em",
212
+ color: "var(--color-foreground)",
213
+ lineHeight: 1.3,
214
+ }}
215
+ >
216
+ {exp.title}
217
+ </span>
218
+ </div>
219
+ <ul
220
+ style={{
221
+ listStyle: "none",
222
+ margin: 0,
223
+ padding: 0,
224
+ display: "flex",
225
+ flexDirection: "column",
226
+ gap: 4,
227
+ paddingLeft: 4,
228
+ }}
229
+ >
230
+ {exp.items.map((item, j) => (
231
+ <li
232
+ key={j}
233
+ style={{
234
+ fontSize: 14,
235
+ fontWeight: 400,
236
+ fontFamily: "var(--font-body)",
237
+ letterSpacing: "-0.016em",
238
+ lineHeight: 1.43,
239
+ color: "var(--color-muted-foreground)",
240
+ paddingLeft: 14,
241
+ position: "relative",
242
+ }}
243
+ >
244
+ <span
245
+ style={{
246
+ position: "absolute",
247
+ left: 0,
248
+ top: 0,
249
+ color: "var(--color-muted-foreground)",
250
+ }}
251
+ >
252
+ &#x2022;
253
+ </span>
254
+ {item}
255
+ </li>
256
+ ))}
257
+ </ul>
258
+ </div>
259
+ ))}
260
+ </div>
261
+ </div>
262
+
263
+ {/* Footer */}
264
+ {footer && (
265
+ <div style={{ textAlign: "center" }}>
266
+ <span
267
+ style={{
268
+ fontSize: 14,
269
+ fontWeight: 400,
270
+ fontFamily: "var(--font-body)",
271
+ letterSpacing: "-0.016em",
272
+ color: "var(--color-muted-foreground)",
273
+ lineHeight: 1.43,
274
+ }}
275
+ >
276
+ {footer}
277
+ </span>
278
+ </div>
279
+ )}
280
+ </div>
281
+ );
282
+ }
283
+
284
+ /** Compute radial positions (%) for nodes around center (50,50) */
285
+ function getNodePositions(count: number): Array<{ x: number; y: number }> {
286
+ const radius = 36;
287
+ const startAngle = -Math.PI / 2; // Start from top
288
+ return Array.from({ length: count }, (_, i) => {
289
+ const angle = startAngle + (2 * Math.PI * i) / count;
290
+ return {
291
+ x: 50 + radius * Math.cos(angle),
292
+ y: 50 + radius * Math.sin(angle),
293
+ };
294
+ });
295
+ }
@@ -0,0 +1,132 @@
1
+ import { z } from "zod";
2
+
3
+ const itemSchema = z.object({
4
+ title: z.string().min(1).describe("Item title"),
5
+ description: z.string().optional().describe("Item description"),
6
+ });
7
+
8
+ export const schema = z.object({
9
+ label: z.string().min(1).max(30).optional().describe("Accent eyebrow"),
10
+ heading: z.string().min(1).max(80).describe("Section heading"),
11
+ items: z.array(itemSchema).min(2).max(6).describe("List items"),
12
+ });
13
+
14
+ type Props = z.infer<typeof schema>;
15
+
16
+ export default function BulletList({ label, heading, items }: Props) {
17
+ return (
18
+ <div
19
+ className="slide"
20
+ style={{
21
+ display: "flex",
22
+ background: "#ffffff",
23
+ overflow: "hidden",
24
+ }}
25
+ >
26
+ {/* Left: heading area */}
27
+ <div
28
+ style={{
29
+ width: "40%",
30
+ padding: "60px 48px 60px 80px",
31
+ display: "flex",
32
+ flexDirection: "column",
33
+ justifyContent: "center",
34
+ gap: 8,
35
+ }}
36
+ >
37
+ {label && (
38
+ <span
39
+ style={{
40
+ fontSize: 17,
41
+ fontWeight: 600,
42
+ letterSpacing: "-0.022em",
43
+ color: "var(--color-primary)",
44
+ }}
45
+ >
46
+ {label}
47
+ </span>
48
+ )}
49
+ <h2
50
+ style={{
51
+ fontSize: 40,
52
+ fontWeight: 600,
53
+ lineHeight: 1.1,
54
+ letterSpacing: "-0.009em",
55
+ }}
56
+ >
57
+ {heading}
58
+ </h2>
59
+ </div>
60
+
61
+ {/* Right: list items */}
62
+ <div
63
+ style={{
64
+ flex: 1,
65
+ padding: "60px 80px 60px 0",
66
+ display: "flex",
67
+ flexDirection: "column",
68
+ justifyContent: "center",
69
+ }}
70
+ >
71
+ {items.map((item, i) => (
72
+ <div
73
+ key={i}
74
+ style={{
75
+ display: "flex",
76
+ alignItems: "baseline",
77
+ gap: 16,
78
+ padding: "14px 0",
79
+ borderBottom:
80
+ i < items.length - 1
81
+ ? "1px solid rgba(0,0,0,0.08)"
82
+ : "none",
83
+ }}
84
+ >
85
+ <span
86
+ style={{
87
+ fontSize: 14,
88
+ fontWeight: 600,
89
+ fontFamily: "var(--font-heading)",
90
+ color: "var(--color-muted-foreground)",
91
+ letterSpacing: "-0.016em",
92
+ minWidth: 24,
93
+ flexShrink: 0,
94
+ }}
95
+ >
96
+ {String(i + 1).padStart(2, "0")}
97
+ </span>
98
+
99
+ <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
100
+ <span
101
+ style={{
102
+ fontSize: 19,
103
+ fontWeight: 600,
104
+ fontFamily: "var(--font-heading)",
105
+ letterSpacing: "0.012em",
106
+ color: "var(--color-foreground)",
107
+ lineHeight: 1.37,
108
+ }}
109
+ >
110
+ {item.title}
111
+ </span>
112
+ {item.description && (
113
+ <span
114
+ style={{
115
+ fontSize: 15,
116
+ fontWeight: 400,
117
+ fontFamily: "var(--font-body)",
118
+ letterSpacing: "-0.022em",
119
+ color: "var(--color-muted-foreground)",
120
+ lineHeight: 1.47,
121
+ }}
122
+ >
123
+ {item.description}
124
+ </span>
125
+ )}
126
+ </div>
127
+ </div>
128
+ ))}
129
+ </div>
130
+ </div>
131
+ );
132
+ }
@@ -0,0 +1,112 @@
1
+ import { z } from "zod";
2
+ import { Icon } from "../_lib/icon.js";
3
+ import { SlideHeader } from "../../components/index.js";
4
+
5
+ const challengeSchema = z.object({
6
+ title: z.string().min(1).max(30).describe("Challenge card title"),
7
+ icon: z.string().min(1).describe("Lucide icon name"),
8
+ items: z.array(z.string()).min(2).max(5).describe("Bullet points"),
9
+ });
10
+
11
+ export const schema = z.object({
12
+ label: z.string().min(1).max(30).optional().describe("Accent eyebrow"),
13
+ heading: z.string().min(1).max(60).describe("Section heading"),
14
+ challenges: z.array(challengeSchema).min(2).max(3).describe("Challenge cards (2-3)"),
15
+ });
16
+
17
+ type Props = z.infer<typeof schema>;
18
+
19
+ export default function ChallengeCards({ label, heading, challenges }: Props) {
20
+ return (
21
+ <div
22
+ className="slide-stack"
23
+ style={{ gap: 32, justifyContent: "center" }}
24
+ >
25
+ {/* Header */}
26
+ <SlideHeader label={label} heading={heading} />
27
+
28
+ {/* Cards */}
29
+ <div
30
+ style={{
31
+ display: "grid",
32
+ gridTemplateColumns: `repeat(${challenges.length}, 1fr)`,
33
+ gap: 20,
34
+ }}
35
+ >
36
+ {challenges.map((c, i) => (
37
+ <div
38
+ key={i}
39
+ style={{
40
+ backgroundColor: "var(--color-card-background)",
41
+ borderRadius: 18,
42
+ padding: 0,
43
+ boxShadow: "4px 4px 12px rgba(0,0,0,0.06)",
44
+ display: "flex",
45
+ flexDirection: "column",
46
+ overflow: "hidden",
47
+ }}
48
+ >
49
+ {/* Content */}
50
+ <div style={{ padding: "24px 24px 28px", display: "flex", flexDirection: "column", gap: 16 }}>
51
+ {/* Title */}
52
+ <span
53
+ style={{
54
+ fontSize: 21,
55
+ fontWeight: 600,
56
+ fontFamily: "var(--font-heading)",
57
+ letterSpacing: "-0.022em",
58
+ color: "var(--color-foreground)",
59
+ lineHeight: 1.24,
60
+ }}
61
+ >
62
+ {c.title}
63
+ </span>
64
+ <div style={{ color: "var(--color-muted-foreground)" }}>
65
+ <Icon name={c.icon} size={48} />
66
+ </div>
67
+
68
+ <ul
69
+ style={{
70
+ listStyle: "none",
71
+ margin: 0,
72
+ padding: 0,
73
+ display: "flex",
74
+ flexDirection: "column",
75
+ gap: 8,
76
+ }}
77
+ >
78
+ {c.items.map((item, j) => (
79
+ <li
80
+ key={j}
81
+ style={{
82
+ fontSize: 14,
83
+ fontWeight: 400,
84
+ fontFamily: "var(--font-body)",
85
+ letterSpacing: "-0.016em",
86
+ lineHeight: 1.43,
87
+ color: "var(--color-foreground)",
88
+ paddingLeft: 14,
89
+ position: "relative",
90
+ }}
91
+ >
92
+ <span
93
+ style={{
94
+ position: "absolute",
95
+ left: 0,
96
+ top: 0,
97
+ color: "var(--color-muted-foreground)",
98
+ }}
99
+ >
100
+ &#x2022;
101
+ </span>
102
+ {item}
103
+ </li>
104
+ ))}
105
+ </ul>
106
+ </div>
107
+ </div>
108
+ ))}
109
+ </div>
110
+ </div>
111
+ );
112
+ }