wand-decks-kit 0.8.0 → 0.8.2
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/SKILL.md +147 -0
- package/build.js +5 -1
- package/lib/schema.js +15 -3
- package/package.json +1 -1
- package/wand_kit.js +17 -12
package/SKILL.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wand-decks
|
|
3
|
+
description: Rebrand, merge, edit or author a PowerPoint deck on the Wand design system. Use when a .pptx is attached for restyling, or a deck is wanted from a brief. Outputs a client-ready .pptx and QA gates.
|
|
4
|
+
compatibility: Requires Node.js 18+ and code execution enabled in claude.ai (Settings > Features). No network access needed - dependencies are bundled. Python 3 is used to read a source .pptx and to embed fonts; without it the kit can still author and check a deck, but the output will render in Arial on machines without Geist.
|
|
5
|
+
license: Proprietary - Wand AI
|
|
6
|
+
metadata:
|
|
7
|
+
version: "0.7.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Wand decks
|
|
12
|
+
|
|
13
|
+
## First: find the kit and stand in it
|
|
14
|
+
|
|
15
|
+
This file ships inside the kit, so if you are reading it you are close. The kit
|
|
16
|
+
arrives one of three ways — work out which, then `cd` into the kit's folder and
|
|
17
|
+
run everything from there:
|
|
18
|
+
|
|
19
|
+
1. **Installed from npm** (the usual way): it is at `node_modules/wand-decks-kit/`.
|
|
20
|
+
If it is not there yet: `npm install wand-decks-kit@latest --no-audit --no-fund`.
|
|
21
|
+
2. **Attached to the chat as a zip**: unzip it and enter the folder it makes.
|
|
22
|
+
3. **You are already inside it**: `wand.js` is next to this file.
|
|
23
|
+
|
|
24
|
+
Then confirm the ground you are standing on:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
node wand.js doctor
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If none of the three work — npm unreachable, nothing attached — **stop and say
|
|
31
|
+
so, and ask the user to attach the kit zip.** Never improvise a deck, and never
|
|
32
|
+
report checks you did not run; the gates are the only reason to trust the
|
|
33
|
+
output.
|
|
34
|
+
|
|
35
|
+
Every path below is relative to the kit folder.
|
|
36
|
+
|
|
37
|
+
A deck engine: 28 layouts, real font metrics, embedded Geist, and six gates that
|
|
38
|
+
each catch something the others cannot.
|
|
39
|
+
|
|
40
|
+
**Read `PROMPT.md` before making any design decision.** It is the source of
|
|
41
|
+
truth on canvas, colour, type, layout choice and content rules. This file is
|
|
42
|
+
only the operating procedure.
|
|
43
|
+
|
|
44
|
+
## Available scripts
|
|
45
|
+
|
|
46
|
+
- **`wand.js`** — the front door - doctor, rebuild, layouts, glyphs, validate, check, preview, ship, spec, inspect
|
|
47
|
+
- **`build.js`** — spec -> .pptx, or --check, or --preview
|
|
48
|
+
- **`tools/extract.py`** — read a source deck: verbatim dump, geometry, analysis
|
|
49
|
+
- **`embed_fonts.py`** — embed Geist unsubsetted, and carry the spec inside the deck
|
|
50
|
+
- **`qa_coverage.py`** — diff the verbatim dump against the built deck
|
|
51
|
+
|
|
52
|
+
## Setup
|
|
53
|
+
|
|
54
|
+
None beyond the doctor check above. Dependencies come with the package —
|
|
55
|
+
npm resolves them on install, and the zip carries them vendored.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
node wand.js doctor
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
If `node_modules` is somehow missing, `npm install` in this directory.
|
|
63
|
+
|
|
64
|
+
## The four jobs
|
|
65
|
+
|
|
66
|
+
### Rebrand a deck they attached
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
node wand.js rebuild <their-deck>.pptx # phases 0-3
|
|
70
|
+
# ...you write work/deck.json...
|
|
71
|
+
node wand.js ship work/deck.json # phases 4-6, prints the gate table
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`rebuild` normalises the source onto the 10in grid, dumps every verbatim string
|
|
75
|
+
and writes `work/PLAN.md` — per slide: the source's shape, a layout shortlist
|
|
76
|
+
with reasons, the verbatim text grouped by frame, and a stub with the required
|
|
77
|
+
fields. Choose the layout yourself; the shortlist is ranked by shape and cannot
|
|
78
|
+
see meaning.
|
|
79
|
+
|
|
80
|
+
### Author from a brief
|
|
81
|
+
|
|
82
|
+
Skip `rebuild`. Write the spec against `node wand.js layouts` and
|
|
83
|
+
`examples/template.json`, then `ship`. Coverage is skipped — there is no source
|
|
84
|
+
to cover — and it says so.
|
|
85
|
+
|
|
86
|
+
### Merge several decks
|
|
87
|
+
|
|
88
|
+
`node wand.js rebuild` each one into its own work directory, then write a single
|
|
89
|
+
spec. Ask which deck leads if they have not said. Flag duplicates rather than
|
|
90
|
+
silently dropping them, and list every merge in the review note.
|
|
91
|
+
|
|
92
|
+
### Edit a deck this kit built
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
node wand.js spec <their-deck>.pptx work/deck.json
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Decks built here carry their own spec inside the file. Change only what was
|
|
99
|
+
asked, then `ship`. If the deck carries no spec it was not built here — treat it
|
|
100
|
+
as a rebrand.
|
|
101
|
+
|
|
102
|
+
## Work in fast loops, ship once
|
|
103
|
+
|
|
104
|
+
`node wand.js validate` and `node wand.js check` run in seconds; `ship` runs the
|
|
105
|
+
whole pipeline. Iterate spec edits against the first two, and run `ship` when
|
|
106
|
+
they are clean — never as your discovery tool.
|
|
107
|
+
|
|
108
|
+
When one error reveals a pattern — a field on the wrong layout, a missing
|
|
109
|
+
optional — **sweep the entire spec for that pattern before re-running anything**.
|
|
110
|
+
Paying a full cycle per instance is the slow way to learn the same fact
|
|
111
|
+
repeatedly.
|
|
112
|
+
|
|
113
|
+
## Rules that decide whether the output is usable
|
|
114
|
+
|
|
115
|
+
1. **Faithful means verbatim.** No consolidating slides, no paraphrasing to
|
|
116
|
+
tighten, no restructuring. Reflowing to fit is fine; rewording is not.
|
|
117
|
+
Paraphrase passes the schema check and passes the eye — only coverage catches
|
|
118
|
+
it.
|
|
119
|
+
2. **A spec has no coordinates, colours or font sizes.** If a slide needs
|
|
120
|
+
geometry the layouts do not have, add a layout. Never leak positioning into
|
|
121
|
+
content.
|
|
122
|
+
3. **Vary the layouts.** Three of the same in a row is a failure even if each is
|
|
123
|
+
correct. Across 15 slides aim for at least eight distinct ones, alternating
|
|
124
|
+
dark and light. `validate` warns when you do not.
|
|
125
|
+
4. **Icons are named.** `node wand.js glyphs` lists all 75. Pick by meaning.
|
|
126
|
+
They recolour to the theme, so there is no polarity to get wrong.
|
|
127
|
+
5. **Never invent content.** If the source is ambiguous, ask.
|
|
128
|
+
|
|
129
|
+
## Useful commands
|
|
130
|
+
|
|
131
|
+
| | |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `node wand.js layouts [name]` | the catalog, and any layout's fields |
|
|
134
|
+
| `node wand.js glyphs [search]` | the named icon set |
|
|
135
|
+
| `node wand.js validate <spec>` | fields, types, item caps — run before building |
|
|
136
|
+
| `node wand.js check <spec>` | fit in real Geist, bounds, assets |
|
|
137
|
+
| `node wand.js preview <spec>` | writes `preview/trace.js` for the browser preview |
|
|
138
|
+
| `node wand.js inspect <deck>` | parts, relationships, embedded fonts |
|
|
139
|
+
|
|
140
|
+
## Deliver
|
|
141
|
+
|
|
142
|
+
The `.pptx` first, then a review note: every split or merge, every fallback
|
|
143
|
+
mapping, anything tight after fitting, and **everything dropped from the
|
|
144
|
+
verbatim dump**. A rebuild that drops a string without saying so is not done.
|
|
145
|
+
|
|
146
|
+
Tell them the gate table honestly. The visual gate is the one no script passes —
|
|
147
|
+
say plainly that nobody has looked at the slides yet if nobody has.
|
package/build.js
CHANGED
|
@@ -129,7 +129,11 @@ function check(spec) {
|
|
|
129
129
|
(bad ? ` — ${bad} overflow${bad > 1 ? "s" : ""} above` : " — clean"));
|
|
130
130
|
console.log(`bounds check: ${oob ? oob + " shape(s) outside the content band — listed above" : "clean"}`);
|
|
131
131
|
console.log(`asset check : ${missing ? missing + " missing — listed above" : `${seen.size} resolved`}`);
|
|
132
|
-
|
|
132
|
+
// A slide whose layout threw has no shapes, so nothing above can fail for
|
|
133
|
+
// it — a crash used to print an ERROR line and exit 0, and the spec author
|
|
134
|
+
// paid a full ship cycle to find out.
|
|
135
|
+
if (t.errors.length) console.log(`layout check: ${t.errors.length} slide(s) threw — listed above`);
|
|
136
|
+
return bad + oob + missing + t.errors.length;
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
/** The same trace, as a PDF. Fonts embedded, no LibreOffice, no conversion. */
|
package/lib/schema.js
CHANGED
|
@@ -32,7 +32,7 @@ const COMMON = {
|
|
|
32
32
|
const L = {
|
|
33
33
|
/* ------------------------------------------------------------ structural */
|
|
34
34
|
cover: {
|
|
35
|
-
use: "Slide 1.
|
|
35
|
+
use: "Slide 1. Always dark, full bleed, no footer. The lede goes in `body` — there is no subtitleText here",
|
|
36
36
|
bleed: true,
|
|
37
37
|
fields: {
|
|
38
38
|
eyebrow: str(), body: str(), kickerPlain: str(), kickerBold: str(),
|
|
@@ -40,12 +40,12 @@ const L = {
|
|
|
40
40
|
},
|
|
41
41
|
},
|
|
42
42
|
divider: {
|
|
43
|
-
use: "Section breaks
|
|
43
|
+
use: "Section breaks. Always dark, keeps the tag, drops the footer. Prose goes in `body`",
|
|
44
44
|
bleed: true,
|
|
45
45
|
fields: { eyebrow: str(), titleText: s("title", req()), body: str() },
|
|
46
46
|
},
|
|
47
47
|
closing: {
|
|
48
|
-
use: "Last slide.
|
|
48
|
+
use: "Last slide. Always dark, centred, full bleed",
|
|
49
49
|
bleed: true,
|
|
50
50
|
fields: { eyebrow: str(), line1: str(req()), line2: str(), body: str(), url: str() },
|
|
51
51
|
},
|
|
@@ -357,6 +357,18 @@ function validate(spec) {
|
|
|
357
357
|
}
|
|
358
358
|
if (sl.layout === "TODO") return;
|
|
359
359
|
checkFields({ ...COMMON, ...def.fields }, sl, path, errs, warns);
|
|
360
|
+
// COMMON exempts fields every layout takes — but the full-bleed three do
|
|
361
|
+
// not take all of them, and a lede typed as subtitleText on a cover was
|
|
362
|
+
// silently dropped: valid name, wrong layout, no message, one ship cycle
|
|
363
|
+
// wasted finding out. Say it at validate time instead.
|
|
364
|
+
if (def.bleed) {
|
|
365
|
+
if (sl.subtitleText != null && !def.fields.subtitleText) {
|
|
366
|
+
errs.push(`${path}: ${sl.layout} does not render subtitleText — its lede goes in \`body\``);
|
|
367
|
+
}
|
|
368
|
+
if (sl.dark === false) {
|
|
369
|
+
warns.push(`${path}: ${sl.layout} is always dark — \`dark: false\` is ignored`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
360
372
|
seen.push(sl.layout);
|
|
361
373
|
});
|
|
362
374
|
|
package/package.json
CHANGED
package/wand_kit.js
CHANGED
|
@@ -273,7 +273,7 @@ function cover(pres, { eyebrow, titleLines, body, kickerPlain, kickerBold, tag }
|
|
|
273
273
|
s.addImage({ path: `${ASSETS}/bg-cover-dark.png`, x: 0, y: 0, w: 10, h: 5.625 });
|
|
274
274
|
s.addImage({ path: `${ASSETS}/orb-cover.png`, x: 5.72, y: 0.38, w: 4.61, h: 4.61 });
|
|
275
275
|
if (tag) chrome(s, { tag, dark: true });
|
|
276
|
-
s.addText(eyebrow.toUpperCase(), {
|
|
276
|
+
if (eyebrow) s.addText(eyebrow.toUpperCase(), {
|
|
277
277
|
x: 0.38, y: 1.24, w: 6.0, h: 0.4, margin: 0, isTextBox: true,
|
|
278
278
|
fontFace: F.med, fontSize: 12, color: T.accentSoft, valign: "middle",
|
|
279
279
|
});
|
|
@@ -290,10 +290,14 @@ function cover(pres, { eyebrow, titleLines, body, kickerPlain, kickerBold, tag }
|
|
|
290
290
|
fontFace: F.reg, fontSize: 12.5, color: T.inkDark, valign: "top", lineSpacingMultiple: 1.12,
|
|
291
291
|
});
|
|
292
292
|
s.addShape("rect", { x: 0.38, y: 3.95, w: 0.60, h: 0.04, fill: { color: T.accent }, line: { width: 0 } });
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
293
|
+
// Concatenating an absent kicker used to paint the word "undefined" on the
|
|
294
|
+
// cover — visible only in a render, past every machine gate.
|
|
295
|
+
const kickerRuns = [
|
|
296
|
+
kickerPlain && { text: kickerPlain + " ", options: { fontFace: F.reg, fontSize: 10, color: T.inkDark } },
|
|
297
|
+
kickerBold && { text: kickerBold, options: { fontFace: F.reg, fontSize: 10, bold: true, color: T.white } },
|
|
298
|
+
].filter(Boolean);
|
|
299
|
+
if (kickerRuns.length) s.addText(kickerRuns,
|
|
300
|
+
{ x: 0.39, y: 4.03, w: 6.0, h: 0.4, margin: 0, isTextBox: true, valign: "middle" });
|
|
297
301
|
return s;
|
|
298
302
|
}
|
|
299
303
|
|
|
@@ -303,7 +307,7 @@ function divider(pres, { tag, eyebrow, titleText, body }) {
|
|
|
303
307
|
s.background = { color: T.bgDark };
|
|
304
308
|
s.addImage({ path: `${ASSETS}/bg-statement-dark.png`, x: 0, y: 0, w: 10, h: 5.625 });
|
|
305
309
|
chrome(s, { tag, dark: true });
|
|
306
|
-
s.addText(eyebrow.toUpperCase(), {
|
|
310
|
+
if (eyebrow) s.addText(eyebrow.toUpperCase(), {
|
|
307
311
|
x: 0.38, y: 1.77, w: 8.9, h: 0.24, margin: 0, isTextBox: true,
|
|
308
312
|
fontFace: F.med, fontSize: 12, color: T.accentSoft, valign: "middle",
|
|
309
313
|
});
|
|
@@ -329,14 +333,14 @@ function closing(pres, { eyebrow, line1, line2, body, url }) {
|
|
|
329
333
|
s.background = { color: T.bgDark };
|
|
330
334
|
s.addImage({ path: `${ASSETS}/bg-statement-dark.png`, x: 0, y: 0, w: 10, h: 5.625 });
|
|
331
335
|
s.addImage({ path: `${ASSETS}/wordmark-white.png`, x: 4.60, y: 0.98, w: 0.80, h: 0.22 });
|
|
332
|
-
s.addText(eyebrow.toUpperCase(), {
|
|
336
|
+
if (eyebrow) s.addText(eyebrow.toUpperCase(), {
|
|
333
337
|
x: 0.55, y: 1.85, w: 8.9, h: 0.3, margin: 0, isTextBox: true,
|
|
334
338
|
fontFace: F.med, fontSize: 12, color: T.accentSoft, align: "center", valign: "middle",
|
|
335
339
|
});
|
|
336
340
|
s.addText([
|
|
337
|
-
{ text: line1 + "\n", options: { fontFace: F.reg, fontSize: 24, bold: true, color: T.white } },
|
|
338
|
-
{ text: line2,
|
|
339
|
-
], { x: 0.55, y: 2.19, w: 8.9, h: 1.0, margin: 0, isTextBox: true, align: "center", valign: "top", lineSpacingMultiple: 1.15 });
|
|
341
|
+
line1 && { text: line1 + (line2 ? "\n" : ""), options: { fontFace: F.reg, fontSize: 24, bold: true, color: T.white } },
|
|
342
|
+
line2 && { text: line2, options: { fontFace: F.reg, fontSize: 24, bold: true, italic: true, color: T.accentSoft } },
|
|
343
|
+
].filter(Boolean), { x: 0.55, y: 2.19, w: 8.9, h: 1.0, margin: 0, isTextBox: true, align: "center", valign: "top", lineSpacingMultiple: 1.15 });
|
|
340
344
|
if (body) s.addText(body, {
|
|
341
345
|
x: 1.5, y: 3.23, w: 7.0, h: 0.7, margin: 0, isTextBox: true,
|
|
342
346
|
fontFace: F.reg, fontSize: 12.5, color: T.bodyDark, align: "center", valign: "top", lineSpacingMultiple: 1.25,
|
|
@@ -363,7 +367,7 @@ function threeUp(pres, { tag, titleText, subtitleText, items, dark = true, arrow
|
|
|
363
367
|
items.slice(0, 3).forEach((it, i) => {
|
|
364
368
|
const cx = x[i], cy = 2.02;
|
|
365
369
|
card(s, { x: cx, y: cy, w, h: 2.70, dark, variant: it.emph ? "emph" : "glass" });
|
|
366
|
-
s.addText(it.eyebrow.toUpperCase(), {
|
|
370
|
+
if (it.eyebrow) s.addText(it.eyebrow.toUpperCase(), {
|
|
367
371
|
x: cx + 0.22, y: cy + 0.22, w: w - 0.42, h: 0.20, margin: 0, isTextBox: true,
|
|
368
372
|
fontFace: F.med, fontSize: 8, color: th.eyebrow, valign: "middle",
|
|
369
373
|
});
|
|
@@ -917,6 +921,7 @@ function productMap(pres, { tag, titleText, subtitleText, bandRow, cards, split,
|
|
|
917
921
|
if (subtitleText) subtitle(s, subtitleText, { dark, y: 1.16, h: 0.28 });
|
|
918
922
|
const th = theme(dark);
|
|
919
923
|
const rail = (label, y, h) => {
|
|
924
|
+
if (!label) return;
|
|
920
925
|
s.addShape("roundRect", {
|
|
921
926
|
x: 0.37, y, w: 1.0, h, rectRadius: radius(1.0, h),
|
|
922
927
|
fill: { color: dark ? T.cardMuted : T.surface }, line: { width: 0 },
|
|
@@ -994,7 +999,7 @@ function productMap(pres, { tag, titleText, subtitleText, bandRow, cards, split,
|
|
|
994
999
|
|
|
995
1000
|
|
|
996
1001
|
module.exports = {
|
|
997
|
-
VERSION: "0.8.
|
|
1002
|
+
VERSION: "0.8.2",
|
|
998
1003
|
T, F, COLS, ASSETS, radius, theme, autoSize, measure: M,
|
|
999
1004
|
background, chrome, footer, title, subtitle, kicker, contentTop,
|
|
1000
1005
|
card, iconTile, pill,
|