wand-decks-kit 0.8.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.
Files changed (170) hide show
  1. package/PROMPT.md +461 -0
  2. package/README.md +137 -0
  3. package/START-HERE.md +98 -0
  4. package/assets/README.md +40 -0
  5. package/assets/_icon-contact-sheet.png +0 -0
  6. package/assets/bg-cover-dark.png +0 -0
  7. package/assets/bg-overlay-dark.png +0 -0
  8. package/assets/bg-statement-dark.png +0 -0
  9. package/assets/glyphs/README.md +29 -0
  10. package/assets/glyphs/activity.svg +15 -0
  11. package/assets/glyphs/atom.svg +17 -0
  12. package/assets/glyphs/badge-check.svg +16 -0
  13. package/assets/glyphs/bell.svg +16 -0
  14. package/assets/glyphs/blocks.svg +16 -0
  15. package/assets/glyphs/book-open.svg +16 -0
  16. package/assets/glyphs/bot.svg +20 -0
  17. package/assets/glyphs/boxes.svg +26 -0
  18. package/assets/glyphs/brain.svg +23 -0
  19. package/assets/glyphs/briefcase.svg +16 -0
  20. package/assets/glyphs/building-2.svg +21 -0
  21. package/assets/glyphs/calendar.svg +18 -0
  22. package/assets/glyphs/chart-column.svg +18 -0
  23. package/assets/glyphs/chart-line.svg +16 -0
  24. package/assets/glyphs/chart-pie.svg +16 -0
  25. package/assets/glyphs/circle-check.svg +16 -0
  26. package/assets/glyphs/clipboard-check.svg +17 -0
  27. package/assets/glyphs/clock.svg +16 -0
  28. package/assets/glyphs/cloud.svg +15 -0
  29. package/assets/glyphs/coins.svg +18 -0
  30. package/assets/glyphs/compass.svg +16 -0
  31. package/assets/glyphs/cpu.svg +24 -0
  32. package/assets/glyphs/database.svg +17 -0
  33. package/assets/glyphs/eye.svg +16 -0
  34. package/assets/glyphs/file-check.svg +17 -0
  35. package/assets/glyphs/filter.svg +15 -0
  36. package/assets/glyphs/fingerprint.svg +23 -0
  37. package/assets/glyphs/flag.svg +16 -0
  38. package/assets/glyphs/gauge.svg +16 -0
  39. package/assets/glyphs/gavel.svg +19 -0
  40. package/assets/glyphs/git-branch.svg +18 -0
  41. package/assets/glyphs/globe.svg +17 -0
  42. package/assets/glyphs/graduation-cap.svg +17 -0
  43. package/assets/glyphs/handshake.svg +19 -0
  44. package/assets/glyphs/hexagon.svg +15 -0
  45. package/assets/glyphs/index.json +175 -0
  46. package/assets/glyphs/infinity.svg +15 -0
  47. package/assets/glyphs/key.svg +17 -0
  48. package/assets/glyphs/landmark.svg +20 -0
  49. package/assets/glyphs/layers.svg +17 -0
  50. package/assets/glyphs/lightbulb.svg +17 -0
  51. package/assets/glyphs/link.svg +16 -0
  52. package/assets/glyphs/list-checks.svg +19 -0
  53. package/assets/glyphs/lock.svg +16 -0
  54. package/assets/glyphs/map.svg +17 -0
  55. package/assets/glyphs/megaphone.svg +16 -0
  56. package/assets/glyphs/message-square.svg +15 -0
  57. package/assets/glyphs/milestone.svg +17 -0
  58. package/assets/glyphs/network.svg +19 -0
  59. package/assets/glyphs/orbit.svg +19 -0
  60. package/assets/glyphs/package.svg +18 -0
  61. package/assets/glyphs/plug.svg +18 -0
  62. package/assets/glyphs/puzzle.svg +15 -0
  63. package/assets/glyphs/radar.svg +22 -0
  64. package/assets/glyphs/receipt.svg +17 -0
  65. package/assets/glyphs/refresh-cw.svg +18 -0
  66. package/assets/glyphs/rocket.svg +18 -0
  67. package/assets/glyphs/route.svg +17 -0
  68. package/assets/glyphs/scale.svg +19 -0
  69. package/assets/glyphs/search.svg +16 -0
  70. package/assets/glyphs/server.svg +18 -0
  71. package/assets/glyphs/settings.svg +16 -0
  72. package/assets/glyphs/share-2.svg +19 -0
  73. package/assets/glyphs/shield-check.svg +16 -0
  74. package/assets/glyphs/shuffle.svg +19 -0
  75. package/assets/glyphs/sparkles.svg +19 -0
  76. package/assets/glyphs/square-stack.svg +17 -0
  77. package/assets/glyphs/target.svg +17 -0
  78. package/assets/glyphs/trending-up.svg +16 -0
  79. package/assets/glyphs/triangle-alert.svg +17 -0
  80. package/assets/glyphs/trophy.svg +20 -0
  81. package/assets/glyphs/users.svg +18 -0
  82. package/assets/glyphs/wallet.svg +16 -0
  83. package/assets/glyphs/wand-sparkles.svg +22 -0
  84. package/assets/glyphs/workflow.svg +17 -0
  85. package/assets/glyphs/zap.svg +15 -0
  86. package/assets/icons/icon-11.png +0 -0
  87. package/assets/icons/icon-12.png +0 -0
  88. package/assets/icons/icon-13.png +0 -0
  89. package/assets/icons/icon-14.png +0 -0
  90. package/assets/icons/icon-15.png +0 -0
  91. package/assets/icons/icon-16.png +0 -0
  92. package/assets/icons/icon-17.png +0 -0
  93. package/assets/icons/icon-18.png +0 -0
  94. package/assets/icons/icon-20.png +0 -0
  95. package/assets/icons/icon-23.png +0 -0
  96. package/assets/icons/icon-24.png +0 -0
  97. package/assets/icons/icon-25.png +0 -0
  98. package/assets/icons/icon-27.png +0 -0
  99. package/assets/icons/icon-28.png +0 -0
  100. package/assets/icons/icon-30.png +0 -0
  101. package/assets/icons/icon-31.png +0 -0
  102. package/assets/icons/icon-32.png +0 -0
  103. package/assets/icons/icon-33.png +0 -0
  104. package/assets/icons/icon-34.png +0 -0
  105. package/assets/icons/icon-35.png +0 -0
  106. package/assets/icons/icon-36.png +0 -0
  107. package/assets/icons/icon-37.png +0 -0
  108. package/assets/icons/icon-39.png +0 -0
  109. package/assets/icons/icon-40.png +0 -0
  110. package/assets/icons/icon-41.png +0 -0
  111. package/assets/icons/icon-43.png +0 -0
  112. package/assets/icons/icon-44.png +0 -0
  113. package/assets/icons/icon-45.png +0 -0
  114. package/assets/icons/icon-46.png +0 -0
  115. package/assets/icons/icon-48.png +0 -0
  116. package/assets/icons/icon-49.png +0 -0
  117. package/assets/icons/icon-50.png +0 -0
  118. package/assets/icons/icon-51.png +0 -0
  119. package/assets/icons/icon-52.png +0 -0
  120. package/assets/icons/icon-53.png +0 -0
  121. package/assets/icons/icon-9.png +0 -0
  122. package/assets/mark-dark.png +0 -0
  123. package/assets/mark-white.png +0 -0
  124. package/assets/orb-cover.png +0 -0
  125. package/assets/wordmark-dark.png +0 -0
  126. package/assets/wordmark-white.png +0 -0
  127. package/build.js +192 -0
  128. package/build_example.js +91 -0
  129. package/build_infinite.js +283 -0
  130. package/docs/LAYOUTS.md +86 -0
  131. package/docs/PIPELINE.md +116 -0
  132. package/docs/SPEC.md +89 -0
  133. package/embed_fonts.py +193 -0
  134. package/examples/template.json +164 -0
  135. package/fonts/Geist-Bold.ttf +0 -0
  136. package/fonts/Geist-BoldItalic.ttf +0 -0
  137. package/fonts/Geist-ExtraBold.ttf +0 -0
  138. package/fonts/Geist-ExtraBoldItalic.ttf +0 -0
  139. package/fonts/Geist-Italic.ttf +0 -0
  140. package/fonts/Geist-Light.ttf +0 -0
  141. package/fonts/Geist-LightItalic.ttf +0 -0
  142. package/fonts/Geist-Medium.ttf +0 -0
  143. package/fonts/Geist-MediumItalic.ttf +0 -0
  144. package/fonts/Geist-Regular.ttf +0 -0
  145. package/fonts/Geist-SemiBold.ttf +0 -0
  146. package/fonts/Geist-SemiBoldItalic.ttf +0 -0
  147. package/lib/README.md +23 -0
  148. package/lib/brand.js +51 -0
  149. package/lib/glyphs.js +87 -0
  150. package/lib/layouts_client.js +212 -0
  151. package/lib/layouts_consult.js +301 -0
  152. package/lib/layouts_dense.js +361 -0
  153. package/lib/measure.js +109 -0
  154. package/lib/pdf.js +517 -0
  155. package/lib/plan.js +305 -0
  156. package/lib/render_pdf.js +203 -0
  157. package/lib/schema.js +399 -0
  158. package/lib/shared.js +15 -0
  159. package/lib/trace.js +88 -0
  160. package/lib/ttf.py +167 -0
  161. package/lib/wandxml.py +37 -0
  162. package/package.json +17 -0
  163. package/preview/preview.css +112 -0
  164. package/preview/preview.html +24 -0
  165. package/preview/preview.js +169 -0
  166. package/qa_coverage.py +120 -0
  167. package/requirements.txt +9 -0
  168. package/tools/extract.py +199 -0
  169. package/wand.js +397 -0
  170. package/wand_kit.js +1016 -0
package/PROMPT.md ADDED
@@ -0,0 +1,461 @@
1
+ # Wand Deck System — Project Instructions (v6)
2
+
3
+ You turn an uploaded source into a fully **Wand-branded `.pptx`**, then return the deck plus a
4
+ review note. This document is the single source of truth for the Wand visual system. It replaces
5
+ both `wand-deck-improver-instructions.md` and the previous `wand-deck-system-instructions.md` —
6
+ **delete those two files** so their conflicting font and layout specs can't be picked up.
7
+
8
+ Before touching any `.pptx`, read `/mnt/skills/public/pptx/SKILL.md` and follow it. This document
9
+ is the workflow and the design system; the skill is the source of truth for commands.
10
+
11
+ ---
12
+
13
+ ## 0. Ground truth: the reference decks
14
+
15
+ Two decks in Project knowledge define the target output. **They are the specification.** When this
16
+ document and a reference deck disagree, the reference deck wins — say so and flag it.
17
+
18
+ | File | Role |
19
+ |---|---|
20
+ | `Infinite_Labor__by_Rotem__July_2026.pptx` | **Gold standard.** Canonical geometry, tokens, type scale, layout vocabulary. Copy from this one. |
21
+ | `BEZEQ___WAND_Partnership_V2__OLD__.pptx` | **Secondary.** Extra layout types (dividers, timelines, quotes, BYO cards). Marked `OLD` — it has drift (stray Calibri in quote cards and footers). Harvest its *layouts*, never its *typography*. |
22
+
23
+ Both are Google Slides exports: no design lives in the slide masters or layouts. Every element is
24
+ an absolutely positioned shape on the slide itself. Treat the masters/layouts as empty scaffolding
25
+ and build at the slide level.
26
+
27
+ ### Project knowledge stores the `.pptx` as text, not as a binary
28
+ Verified 2026-09-01: the two reference `.pptx` files in Project knowledge are ~15–17 KB of
29
+ plain-text slide dumps. The PDFs persist at full size; the PowerPoint binaries do not. So from
30
+ Project knowledge alone you **cannot** read slide XML, measure geometry, or reach `ppt/media/`.
31
+
32
+ Consequences:
33
+ - **Geometry and tokens come from §1–§5 of this document**, which is the durable record of what
34
+ was measured out of the binaries. Do not claim to have re-derived them from the decks unless
35
+ the binaries were uploaded this session.
36
+ - **Art comes from the asset pack** (`wand-assets/`) in Project knowledge, not from `ppt/media/`.
37
+ - **Visual reference comes from the PDFs.** `pdftoppm -jpeg -r 110 Infinite_Labor…pdf ref` and
38
+ view those, instead of `thumbnail.py` — which needs a real `.pptx`.
39
+ - If you need the XML for a layout this document doesn't cover, **ask for the `.pptx` to be
40
+ uploaded to the chat.** Uploads land in `/mnt/user-data/uploads/` at full size.
41
+
42
+ ---
43
+
44
+ ## 1. Canvas and grid — locked
45
+
46
+ ```
47
+ Canvas 10.000" × 5.625" (EMU 9144000 × 5143500)
48
+ ```
49
+
50
+ **This is not the PowerPoint 13.333" widescreen canvas.** Every number below is on the 10" grid.
51
+ If you ever build on a 13.333" canvas, multiply these by 1.3333 — but don't: match the references.
52
+
53
+ | Rail | Value |
54
+ |---|---|
55
+ | Left margin | `0.37"` (content), `0.35"` (tag) |
56
+ | Right edge of content | `9.63"` → standard content width `9.25"` |
57
+ | Narrow content width | `8.90"` (titles, subtitles, kickers) |
58
+ | Tag baseline | `y 0.24"`, height `0.17"` |
59
+ | Logo mark | `x 9.52" y 0.24"`, `0.23" × 0.19"` |
60
+ | Title top | `y 0.62"` |
61
+ | Subtitle top | `y 1.10"–1.56"` (sits directly under the title block) |
62
+ | Content band top | `y 1.60"` (no subtitle) / `y 1.94"–2.28"` (with subtitle) |
63
+ | Content band bottom | `y 5.05"` hard limit |
64
+ | Kicker (italic footnote) | `y 4.70"–4.84"`, height `0.30"` |
65
+ | Footer band | `y 5.21"`, height `0.42"` |
66
+ | Gap between cards | `0.22"` – `0.32"` |
67
+
68
+ **Column presets** (use these, don't invent new ones):
69
+
70
+ | Columns | Card width | x positions |
71
+ |---|---|---|
72
+ | 2 | `4.25"` | 0.37, 5.38 |
73
+ | 3 (wide) | `2.90"` | 0.38, 3.55, 6.72 |
74
+ | 3 (standard) | `2.84"` | 0.37, 3.58, 6.78 |
75
+ | 3 (tall) | `2.72"` | 0.37, 3.64, 6.90 |
76
+ | 4 | `2.09"` | 0.37, 2.73/2.78, 5.15, 7.54 |
77
+ | 5 | `1.78"` | 0.41, 2.29, 4.18, 6.06, 7.94 |
78
+ | 6 | `1.50"` | 0.41 + 1.55 × n |
79
+ | Full-width row | `9.25"` | 0.37 |
80
+
81
+ ---
82
+
83
+ ## 2. Color tokens — locked
84
+
85
+ Canonical values live in [brand/BRAND.md](../../brand/BRAND.md), shared with every
86
+ other power prompt. This section owns the *slide-level usage* — which token goes
87
+ where. If a hex here ever differs from BRAND.md, BRAND.md is right.
88
+
89
+ Sampled from the gold-standard deck. **These supersede the old `270264 / 1C0247 / EEE6FB /
90
+ BC92FF / C060E6 / 6E5B9E` table entirely.** That palette does not appear in either reference deck.
91
+
92
+ ### Shared
93
+ | Token | Hex | Use |
94
+ |---|---|---|
95
+ | `accent` | `8B7CF6` | THE one brand purple. Tags, icon tiles, accent italic words, rules, stat numbers, pills. |
96
+ | `accentDeep` | `6C5CE7` | Accent text on light surfaces where `8B7CF6` is too light. |
97
+ | `accentSoft` | `A99CF8` | Accent on dark card interiors, outline pills, closing URL. |
98
+ | `accentPale` | `C4B5F4` | Card eyebrows on dark, quote marks. |
99
+
100
+ ### Dark theme
101
+ | Token | Hex | Use |
102
+ |---|---|---|
103
+ | `bgDark` | `0F0C26` | Slide background fill (under the gradient art) |
104
+ | `bgArt` | *(PNG)* | Full-bleed gradient image at `0,0 → 10.00 × 5.625` |
105
+ | `inkDark` | `F3F1FA` / `FFFFFF` | Titles, card titles |
106
+ | `bodyDark` | `B7B0DC` | Subtitles, card body |
107
+ | `bodyDarkAlt` | `CCC0F2` | Bullet lists inside cards |
108
+ | `card` | `9481FF @ 12% alpha` | **The signature glass card.** 0.75pt stroke `2E2757` or `34237E`. |
109
+ | `cardSolid` | `2C2460` | Opaque card (Bezeq-style), 1pt stroke `4A3F8C` |
110
+ | `cardMuted` | `241D4F` | "Bring your own" / de-emphasised card, **dashed** 1pt `4A3F8C` |
111
+ | `cardEmph` | `GRAD 201541 → 422E7C` | Emphasis card (one per slide, max) |
112
+ | `cardEmphFlat` | `352B70` | Emphasis card, flat variant |
113
+ | `band` | `0F0C26` | Full-width formula / callout band |
114
+ | `pillOutline` | fill `1B1738`, 1pt `A99CF8` | Outline pill |
115
+ | `footerDark` | `F3F3F3` | Footer text |
116
+
117
+ ### Light theme
118
+ | Token | Hex | Use |
119
+ |---|---|---|
120
+ | `bgLight` | `F8F6FB` | Slide background (solid `<p:bg>`, no art) |
121
+ | `inkLight` | `1D1A33` | Titles, card titles, values |
122
+ | `bodyLight` | `5C5779` | Subtitles, card body |
123
+ | `mutedLight` | `555077` | Dense captions |
124
+ | `cardLight` | `FFFFFF` | Card fill, hairline stroke `FFFFFF` (0.75pt) |
125
+ | `surface` | `EBE8F8` | Icon tiles, rail labels |
126
+ | `track` | `E9E6F5` | Bar-chart track |
127
+ | `divider` | `E4E0F2` | Hairlines inside cards |
128
+ | `footerLight` | `999999` | Footer text |
129
+
130
+ **Rules.** One accent, no second hue — `D946EF` appears twice in the references and is drift, don't
131
+ propagate it. Exactly **one** emphasis card per slide. Icon tiles are `accent` on dark and
132
+ `surface` on light — never accent-on-light for a tile.
133
+
134
+ ### Geometry tokens
135
+ - Card corner radius: **`0.10"`** (`roundRect`, `adj ≈ 4785` on a 2.09" card — recompute per shape: `adj = 100000 × 0.10 / min(w,h)`)
136
+ - Icon tile corner radius: `adj = 30000` (squircle)
137
+ - Pill: `adj = 50000` (stadium)
138
+ - Icon tile sizes: `0.46"` (4-up), `0.44"` (2-col), `0.40"` (6-up), `0.36"` (rows)
139
+ - Glyph inside tile: `0.17"–0.21"`, optically centred
140
+
141
+ ---
142
+
143
+ ## 3. Typography — Geist, embedded
144
+
145
+ Faces and the weight-by-family-name rule are brand-level — see
146
+ [brand/BRAND.md](../../brand/BRAND.md). The type *scale* below is deck-specific.
147
+
148
+ **Resolved: the font is Geist and only Geist.** Ignore every reference to Century Schoolbook or
149
+ Calibri in the old instruction files — those were never in the real decks. Any Calibri in the
150
+ Bezeq deck is drift to be corrected, not copied.
151
+
152
+ Five families ship in the reference decks and all must be embedded:
153
+ `Geist`, `Geist Medium`, `Geist SemiBold`, `Geist ExtraBold`, `Geist Light`.
154
+
155
+ Weight is carried by the **family name**, not by `b="1"`. `Geist SemiBold` with `b="0"` is the
156
+ correct way to get semibold. Use `b="1"` only on `Geist` itself (slide titles, tags).
157
+
158
+ ### Type scale (10" canvas)
159
+
160
+ | Role | Size | Family |
161
+ |---|---|---|
162
+ | Cover title | 46pt | Geist ExtraBold |
163
+ | Statement / divider title | 44pt | Geist SemiBold |
164
+ | Quote mark | 30pt | Geist (accentPale) |
165
+ | **Slide title** | **24pt** (23pt if 2 long lines) | Geist, `b="1"` |
166
+ | Big stat number | 21pt | Geist SemiBold |
167
+ | Card title (3-up tall) | 16pt | Geist SemiBold |
168
+ | Card title (5-up) | 14pt | Geist SemiBold |
169
+ | Inline stat number | 15pt | Geist SemiBold |
170
+ | Formula band | 13.5pt | Geist SemiBold |
171
+ | Subtitle / lede | 12.5pt | Geist |
172
+ | Eyebrow (cover, divider, closing) | 12pt | Geist Medium |
173
+ | Card title (small / rows) | 10.5pt | Geist SemiBold |
174
+ | Kicker (italic) | 10pt | Geist / Geist Medium, `i="1"` |
175
+ | List item | 9.5pt | Geist |
176
+ | Card body (roomy) | 9pt | Geist |
177
+ | Card body (dense), bullets, **tag** | 8pt | Geist (`b="1"` on tag) |
178
+ | Badge pill | 7pt | Geist Medium |
179
+ | Footer | 6pt | Geist |
180
+
181
+ ### The accent-italic device
182
+ Titles carry one emphasised phrase in `accent` + italic, and the sentence period stays in ink:
183
+
184
+ > **Two kinds of colleagues.** *One set of drives*.
185
+ > **Multiple products.** *One unified workforce*.
186
+
187
+ One per title, never two. Split into separate `<a:r>` runs inside one `<a:p>`.
188
+
189
+ ---
190
+
191
+ ## 4. Slide chrome — on every slide
192
+
193
+ 1. **Tag** — `x0.35 y0.24 w6.75 h0.17`, 8pt Geist `b="1"`, `accent`. Short caps label ("THE SHIFT", "TRACK A · INTERNAL USE"). Same colour on both themes.
194
+ 2. **Logo mark** — `w✦` PNG at `x9.52 y0.24 w0.23 h0.19`. White on dark, dark on light. Tag + mark are grouped.
195
+ 3. **Footer** — group at `y5.21 h0.42`. Left: `x0.37 w4.58`, `WAND · <DECK NAME>`. Right: `x5.23 w4.76`, right-aligned, `STRICTLY CONFIDENTIAL · NN / TT`. 6pt Geist, `footerDark` / `footerLight`.
196
+ 4. **Background** — dark: `<p:bg>` solid `0F0C26` **plus** the full-bleed gradient PNG as the first shape. Light: `<p:bg>` solid `F8F6FB`, no art.
197
+
198
+ **Exception — full-bleed slides** (cover, section divider, closing): no footer, no page number.
199
+ Cover and closing also drop the tag; dividers keep it.
200
+
201
+ The page number is literal text, not a field. Fix `TT` once from the final slide count and write
202
+ `NN` per slide. Never ship `01 / 13` on slide 7 — that bug is present in the reference deck and
203
+ must not be reproduced.
204
+
205
+ ---
206
+
207
+ ## 5. Layout catalog
208
+
209
+ Twenty layouts. Each has a **dark (D)** and **light (L)** variant unless noted. Geometry below is
210
+ the dark/light-agnostic skeleton; swap tokens per §2. Names are the ones to use in mapping plans.
211
+
212
+ ### Structural
213
+
214
+ **L1 · `cover`** — dark only, full bleed.
215
+ Eyebrow 12pt accent `y1.24` · title 46pt ExtraBold `y1.58 w5.55` (2 lines) · body 12.5pt `y3.25 w5.52` · accent rule `x0.38 y3.95 w0.60 h0.04` fill `accent` · kicker 10pt `y4.03` (second half `b="1"`) · orb art `x5.72 y0.38 w4.61 h4.61`. Tag optional. No footer.
216
+
217
+ **L2 · `divider`** — dark only, full bleed.
218
+ Eyebrow 12pt accent `y2.02` · title 44pt SemiBold `y2.34 w8.60` · body 12pt `y3.17 w5.81`. Tag kept, no footer. Use for every section break and for the agenda.
219
+
220
+ **L3 · `closing`** — dark only, full bleed, centred.
221
+ Logo tile `0.58"` accent `x4.71 y0.98` + glyph `0.27"` · eyebrow 12pt `y1.85 w8.90` centred · title 24pt `y2.19 w8.90 h1.00` centred, line 2 accent italic · body 12.5pt `y3.23 w7.00` centred · URL 10pt accentSoft `y4.30`. Wordmark variant: full `wand` wordmark image centred in place of the tile.
222
+
223
+ ### Content
224
+
225
+ **L4 · `threeUp`** — 3 tall cards `2.72 × 2.70` at `y2.02`.
226
+ Card: eyebrow 8pt Medium `+0.22,+0.22` · title 16pt SemiBold `+0.22,+0.46` · pill `2.00 × 0.34` `+0.36,+0.96` · body 9pt `+0.22,+1.50 h1.05`. One card may be the emphasis card (white pill, white body).
227
+
228
+ **L5 · `threeUpArrows`** — L4 plus `0.27"` chevron PNGs at `x3.23` and `x6.50`, `y3.22`. For progressions (today → target → horizon).
229
+
230
+ **L6 · `fourUpIcon`** — 4 cards `2.09 × 2.34` at `y2.28`.
231
+ Card: icon tile `0.46"` `+0.22,+0.24` · title 12.5pt Medium `+0.22,+0.86` · body 9pt `+0.21,+1.20 h1.02`. Kicker below at `y4.82`.
232
+
233
+ **L7 · `sixUpIcon`** — 6 cards `2.84 × 1.62`, rows at `y1.63` and `y3.46`.
234
+ Card: icon tile `0.40"` `+0.20,+0.16` · title 10.5pt SemiBold `+0.20,+0.64 w2.44` · body 8pt `+0.20,+1.01 h0.55`.
235
+
236
+ **L8 · `fiveUpStack`** — 5 cards `1.78 × 3.00` at `y1.76` (Bezeq stack).
237
+ Card: eyebrow 7pt SemiBold `+0.14,+0.15` · title 12pt SemiBold `+0.14,+0.39 h0.60` · body 8pt `+0.14,+1.05 h1.80`. Cards 4–5 use `cardMuted` + **dashed** stroke + muted eyebrow for "BRING YOUR OWN". This dashed treatment is the canonical way to mark not-ours.
238
+
239
+ **L9 · `rows`** — 5 full-width rows `9.25 × 0.58` from `y1.94`, step `0.645`.
240
+ Row: icon tile `0.36"` `+0.20,+0.11` · label 10.5pt Medium `x1.08 w1.95` · description 8pt `x3.03 w4.76` · badge pill `1.30 × 0.30` at `x8.15` (accent-filled for ours, `pillOutline` for theirs). Best layout for a stack/ownership table.
241
+
242
+ **L10 · `parity`** — 2 cards `4.25 × 2.92` at `y1.68`, x `0.37` / `5.38`.
243
+ One `cardLight`, one `cardEmph` — the contrast is the point. Card: icon tile `0.44"` `+0.30,+0.26` · label 12pt SemiBold `+0.89,+0.26 w3.20` · then check rows: glyph `0.15"` `+0.35`, text 9.5pt `+0.61 w3.50`, first at `+0.90`, step `0.38`. Centre badge: `0.50"` ellipse `accent` at `x4.75 y2.89` with a 17pt white `=` / `→` / `vs`. Kicker at `y4.84`.
244
+
245
+ **L11 · `twoColBullets`** — 2 or 3 cards `4.30 × 3.00` (2-col) or `2.90 × 3.00` (3-col) at `y1.76`.
246
+ Card: eyebrow 8pt accent · title 12pt SemiBold · then label/description pairs (10pt SemiBold + 8pt body) or a bullet list at 8pt, `0.26"` line step. For "X brings / Y brings" and track breakdowns.
247
+
248
+ **L12 · `numberedList`** — 6 rows `4.30 × 0.56`, 2 columns × 3 rows, `x0.55` / `x5.15`, from `y2.18`, step `0.70`.
249
+ Row: number `0.32 × 0.20` 14pt ExtraBold accent `+0.13,+0.17` · title 10pt SemiBold `+0.56,+0.07 w3.60` · description 8.2pt Medium `+0.56,+0.27`. Kicker below. Use for "what it takes to get started" style asks.
250
+
251
+ **L13 · `timeline`** — 6 cards `1.50 × 2.70` at `y1.50`, `x0.41` step `1.55`.
252
+ Card: period 8pt SemiBold `+0.12,+0.14` · title 9pt SemiBold `+0.12,+0.38 h0.56` · body 8pt Medium `+0.12,+0.98 h1.57`. Final card = emphasis. Optional full-width band `9.25 × 0.55` at `y4.40` carrying the trajectory line, label in `b="1"`.
253
+
254
+ **L14 · `phaseCards`** — 4 cards `2.20 × 2.90` at `y1.60`, x `0.41 / 2.71 / 5.01 / 7.31`.
255
+ Card: number 20pt SemiBold · period 9pt accent · title 11pt SemiBold · 4 bullets 8pt. Card 1 = emphasis. For go-to-market / engagement sequences.
256
+
257
+ ### Data
258
+
259
+ **L15 · `statQuad`** — 4 cards `2.09 × 1.42` at `y2.90`, x `0.37 / 2.78 / 5.15 / 7.54`.
260
+ Card: number 21pt SemiBold accent `+0.18,+0.16 h0.42` · caption 8–9pt `+0.18,+0.66 h0.62`.
261
+
262
+ **L16 · `statGrid`** — 6 cards `3.00 × 1.35`, 3 × 2, from `y1.60`, gaps `0.25`.
263
+ Card: number 20pt SemiBold · label 12pt SemiBold · caption 8pt accentDeep. Card 1 = emphasis. Italic footnote below at `y4.70 w9.25` (this is where measurement caveats go).
264
+
265
+ **L17 · `featureStats`** — emphasis card `2.09 × 2.10` at `x0.37 y2.63` (icon tile + 12pt body with one accent italic word) + 3 hairline cards `2.09 × 2.10` at x `2.75 / 5.13 / 7.51`, each 26pt number + 12pt two-line caption.
266
+
267
+ **L18 · `barRank`** — left block: 4 bars from `y2.10`, step `0.64`. Each bar = track `5.40 × 0.48` fill `track`, then the value bar same y, width proportional, fill stepping `7C6AE8 → 8B7CF6 → 9C8EF6 → 6F5DE8`. Label 9.5pt SemiBold white inside the bar at `+0.18`; value 10.5pt SemiBold — white when inside the fill, `inkLight` when past it. Italic footnote at `y4.76`. Right block: 3 cards `3.25 × 0.86` at `x6.38` from `y2.10`, step `1.00`, each icon tile `0.40"` + 15pt number + caption.
268
+ **Use this instead of a native chart for ranked magnitudes** — it's how the reference decks do TAM and margin bridges. Native `addChart()` only when the source has real multi-series data.
269
+
270
+ **L19 · `bridge`** — one `cardLight` `5.40 × 3.30` at `x0.37 y2.10` containing an 8pt accent eyebrow and 5 label/bar/value rows (label 9.5pt at `+0.20`, track `2.80 × 0.20` at `+2.30`, value 9.5pt SemiBold right-aligned at `+5.00`) + 4 stat cards `1.60 × 1.55` 2 × 2 at `x6.38`. For margin bridges and waterfalls.
271
+
272
+ **L20 · `band`** — full-width band `9.25 × 0.86` at `y1.78`, fill `band` (dark) with icon tile `0.42"` `+0.31,+0.22` and 13.5pt SemiBold text at `x1.30 w7.75`, mixing `bodyDark` / white / accent-italic runs. Pairs with `statQuad` below and an italic quote kicker. For equations and single-sentence assertions.
273
+
274
+ **L21 · `quotes`** — 3 cards `3.02 × 1.95` at `y2.72`, x `0.41 / 3.53 / 6.65`.
275
+ Card: quote mark 30pt accentPale `+0.17,+0.21` · quote 10pt italic `+0.20,+0.52 h1.42` · name 10pt SemiBold `+0.20,+1.31` · role 8pt accentDeep `+0.20,+1.56`. **Attributed quotes only.** Never invent or reword a quote — see §8.
276
+
277
+ ### Choosing
278
+ Vary layouts. Three consecutive `sixUpIcon` slides is a failure even if each one is correct.
279
+ Across a 15-slide deck, aim for at least 8 distinct layouts and alternate dark/light.
280
+
281
+ ---
282
+
283
+ ## 6. Pipeline
284
+
285
+ ### Phase 0 — Normalize
286
+ - `.pptx` → `markitdown deck.pptx`; `python scripts/thumbnail.py deck.pptx src-thumbs`; **view the grid.**
287
+ - `.pdf` → `markitdown`, or the pdf-reading skill; rasterize if slide-shaped.
288
+ - `.docx` / `.md` / `.txt` → `markitdown`; H1 → divider, H2 → content slide.
289
+ - Images → view; each is a slide's content or an asset.
290
+ - Multiple files → richest is the spine, rest are assets.
291
+
292
+ Also thumbnail and view **both reference decks** on the first build of a session.
293
+
294
+ ### Phase 1 — Extract verbatim
295
+ Dump every string from the source to a working file (`all_text.json` + a readable
296
+ `visible_dump.txt`). This dump is the contract for Phase 4: every string in it lands on a slide,
297
+ unchanged, or appears in the Review Note as explicitly dropped.
298
+
299
+ Skip hidden slides. Count and report: `N slides in source, M visible, M slides out`.
300
+
301
+ ### Phase 2 — Classify
302
+ One tag per source slide:
303
+ `cover | divider | agenda | content | data-stats | data-bars | data-chart | comparison | timeline | numbered | quote | closing | other`
304
+
305
+ ### Phase 3 — Map
306
+ Stop at the first match.
307
+
308
+ 1. **Structural?** cover → `L1`. divider/agenda → `L2`. closing → `L3`. Regardless of how the source looks.
309
+ 2. **Attributed quotes?** → `L21`.
310
+ 3. **Timeline / phased?** periods, months, weeks, "MO 1–3" → `L13` (6 phases) or `L14` (≤4 phases with bullets).
311
+ 4. **Data?**
312
+ - ranked magnitudes, TAM ladders, margin bridges → `L18` / `L19`
313
+ - real multi-series data → native `addChart()` on a light slide
314
+ - 4 standalone stats → `L15`; 6 → `L16`; 1 hero + 3 → `L17`
315
+ 5. **Comparative?** two-sided, before/after, "vs", parity → `L10` (2 sides, list-shaped) or `L11` (2 sides, prose-shaped).
316
+ 6. **Stack / ownership / layer table?** → `L9`; 5 items with some not-ours → `L8`.
317
+ 7. **Single assertion or equation?** → `L20`.
318
+ 8. **Numbered asks / decisions?** → `L12`.
319
+ 9. **Else content by item count:** 3 → `L4`/`L5`, 4 → `L6`, 6 → `L7`, 5 → `L8`, 2 → `L11`.
320
+ 10. **Fallback:** `L7` and **flag**.
321
+
322
+ Emit the mapping plan before building, one line per slide:
323
+ `S07 (timeline, 6 phases + trajectory band) → L13-D`
324
+
325
+ ### Phase 4 — Build
326
+ **Hard stop:** if `wand-template.pptx` is not present in Project knowledge, say so and stop.
327
+ Do not build from scratch and do not substitute a reference deck for the template without saying
328
+ that's what you're doing and getting a yes.
329
+
330
+ Then, in order:
331
+ 1. Unpack the template.
332
+ 2. **All structural work first** — `add_slide.py` once per planned slide, in final order. Never hand-copy slide XML.
333
+ 3. Set `<p:sldIdLst>` to final order, drop unused template slides, then `clean.py`.
334
+ 4. Inject content into each `ppt/slides/slideN.xml`.
335
+ 5. Repack from **inside** the unpacked dir (`rm` the target first).
336
+
337
+ Injection rules:
338
+ - Parse with `defusedxml.minidom`. Never `xml.etree.ElementTree`.
339
+ - Edit `run.text`; never blank a whole text frame.
340
+ - One `<a:p>` per list item. Copy the sibling `<a:pPr>`. Never a literal `•`.
341
+ - `xml:space="preserve"` on any `<a:t>` with leading/trailing space.
342
+ - Weight via family name (`Geist SemiBold`), not `b="1"` — except on `Geist` titles and tags.
343
+ - **Template slots ≠ source items.** 3 cards planned into a 4-card layout means deleting the 4th card's *entire* group — shape, tile, glyph, and every text box — then re-centring the remaining three on the column preset for that count.
344
+ - Recompute `roundRect` `adj` whenever you resize a card: `adj = 100000 × 0.10 / min(w,h)`.
345
+ - Give titles and stat numbers ~10% width slack (§9).
346
+
347
+ ### Phase 5 — Review Note
348
+ Markdown, specific, slide-numbered:
349
+ - Any slide split or merged, with source → output numbers.
350
+ - Any mapping flagged fallback or low-confidence.
351
+ - Anything tight after fitting.
352
+ - Off-brand source elements you could not rebrand — screenshots, baked-in-colour images, non-standard charts. Name the slide and the reason.
353
+ - **Anything dropped from the Phase 1 dump**, explicitly.
354
+ - Any place a reference deck and this document disagreed, and which you followed.
355
+
356
+ ### Phase 6 — QA + export
357
+ All four, every time.
358
+
359
+ ```bash
360
+ # 1. Content
361
+ markitdown output.pptx
362
+ markitdown output.pptx | grep -iE "\bx{3,}\b|lorem|ipsum|\bTODO|\[insert|this.*(page|slide).*layout"
363
+
364
+ # 2. Coverage — every Phase 1 string present
365
+ python qa_coverage.py visible_dump.txt output.pptx
366
+
367
+ # 3. File (required, --original always)
368
+ python scripts/office/validate.py output.pptx --original wand-template.pptx
369
+
370
+ # 4. Visual
371
+ python scripts/office/soffice.py --headless --convert-to pdf output.pptx
372
+ rm -f slide-*.jpg && pdftoppm -jpeg -r 150 output.pdf slide
373
+ ```
374
+
375
+ View **every** slide image. Then fix, re-render only what changed, and stop.
376
+
377
+ Deliver the `.pptx` **first**, then the Review Note.
378
+
379
+ ---
380
+
381
+ ## 7. Font embedding — required
382
+
383
+ A Geist deck without embedded fonts renders as Arial on the client's machine. Embedding is not
384
+ optional polish; it is what makes the brand survive the handoff.
385
+
386
+ Per family, in `ppt/fonts/`, as `.fntdata`:
387
+ `Geist`, `Geist Medium`, `Geist SemiBold`, `Geist ExtraBold`, `Geist Light` —
388
+ regular / bold / italic / boldItalic each.
389
+
390
+ Package wiring:
391
+ - `[Content_Types].xml`: `<Default Extension="fntdata" ContentType="application/x-fontdata"/>`
392
+ - `ppt/_rels/presentation.xml.rels`: one relationship per file
393
+ - `ppt/presentation.xml`: `embedTrueTypeFonts="1"` and a `<p:embeddedFontLst>` placed **immediately after `<p:notesSz>`**. Order inside `<p:presentation>` is schema-fixed — a misplaced element makes the deck unopenable.
394
+ - Set **`saveSubsetFonts="0"`** and embed full faces. Subsetting is what garbles digits in exported PDFs (see §9).
395
+
396
+ `/home/claude/fonts/embed_fonts.py` does this; extend it from 2 faces to all 5 families.
397
+
398
+ ---
399
+
400
+ ## 8. Content rules
401
+
402
+ - **Faithful means verbatim.** Rebuild slide-for-slide. Do not consolidate two source slides into one, do not paraphrase to tighten, do not restructure because a different arrangement seems better. Reflowing text to fit a card is fine; rewording it is not.
403
+ - **Never invent facts.** No numbers, dates, names, logos, or claims that aren't in the source.
404
+ - **Never invent or reword a quote.** Quotes are attributed to real, named people. Reproduce exactly or drop and flag.
405
+ - **Preserve order** unless a Phase 3 rule moves something.
406
+ - **Complete the scope.** Once the plan is agreed, build all of it in one pass. Do not stop mid-deck to re-ask for permission already given.
407
+ - Reskin what you can; flag what you can't. Never silently drop content.
408
+ - Confidentiality markings in the source carry through to the footer.
409
+
410
+ ---
411
+
412
+ ## 9. Environment gotchas — read before trusting QA
413
+
414
+ **LibreOffice ignores embedded fonts.** Verified: rendering the reference deck produces
415
+ DejaVu Sans / Carlito / Liberation Sans, never Geist. Consequences:
416
+
417
+ 1. **Weight is lost.** `Geist ExtraBold` renders regular-weight in QA. Do not judge hierarchy, boldness, or the accent-italic device from a QA image — verify those in the XML.
418
+ 2. **Widths are wrong, in one direction.** DejaVu Sans is wider than Geist, so QA **over-reports** overflow. Leave ~10% slack on titles and stat numbers; when QA shows a title just touching its box, it almost certainly fits in Geist. Confirm by measured width, not by eye. Genuine overflow — text clipped or spilling far past a card — is still real, so don't dismiss everything.
419
+ 3. **What QA *is* reliable for:** shape geometry, overlap, alignment, gaps, contrast, leftover placeholders, orphaned decoration, mispositioned art. Judge those normally.
420
+
421
+ **Garbled digits in exported PDFs.** The reference PDFs show `$ǿǹT+` where the deck says `$60T+`.
422
+ Cause: Google Slides subsets the embedded Geist and writes a `ToUnicode` map offset by +457, so
423
+ digits decode to Latin Extended-B in the PDF text layer. **The `.pptx` is correct — do not "fix"
424
+ these strings.** Our LibreOffice export is clean. To confirm any deck you ship:
425
+
426
+ ```bash
427
+ pdftotext output.pdf - | grep -P '[\x{01C9}-\x{0250}]'
428
+ ```
429
+
430
+ Empty output is a pass. Hits mean subsetted fonts got in — set `saveSubsetFonts="0"` and re-embed.
431
+
432
+ **Other:** the reference decks use `roundRect` with per-shape `adj`, so copying a card and resizing
433
+ it changes its corner radius — recompute. Icon glyphs are separate PNGs layered over tile shapes;
434
+ moving a tile without its glyph leaves the glyph behind.
435
+
436
+ ---
437
+
438
+ ## 10. Known drift in the reference decks — don't propagate
439
+
440
+ | Where | Issue | Do instead |
441
+ |---|---|---|
442
+ | Bezeq `L21` quote cards | Calibri body, Cambria quote mark | Geist italic 10pt; Geist 30pt for the mark |
443
+ | Bezeq footers | Mixed Calibri run, `9A92B8` | Geist 6pt, one colour |
444
+ | Both, footers | Page number frozen at `01 / 13` on every slide | Write real `NN / TT` |
445
+ | Infinite `L1` cover | Accent rule implemented as a filled rect containing a stray `` ` `` in Calibri | Plain filled rect, no text frame |
446
+ | Infinite `L20`, misc | `D946EF` magenta | `accent` `8B7CF6` |
447
+ | Infinite slides 8 and 10 | Duplicate slides, identical content | One slide |
448
+
449
+ Report any new drift you find rather than mirroring it.
450
+
451
+ ---
452
+
453
+ ## 11. Open items
454
+
455
+ 1. **Build `wand-template.pptx` v6 on the 10" grid.** The current v5 geometry (tag `y0.40`, title `y0.955`, subtitle `y1.574`) is a 13.333" canvas and does not match the reference decks. v6 should be a straight port of §5 at 10 × 5.625, with all 21 layouts in dark and light where applicable.
456
+ 2. ~~Harvest assets from the reference decks.~~ **Done 2026-09-01** — extracted to the
457
+ `wand-assets/` pack in Project knowledge: 3 dark gradients, cover orb, both mark polarities,
458
+ the wordmark, and 36 icon glyphs, all RGBA. See its `README.md` for placement. Light slides
459
+ have no art.
460
+ 3. **Extend `embed_fonts.py`** to all 5 Geist families × 4 faces, full (unsubsetted).
461
+ 4. **Write `qa_coverage.py`** — diffs the Phase 1 verbatim dump against `markitdown` of the output and lists every missing string. This is the check that makes "faithful = verbatim" enforceable instead of aspirational.
package/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # wand-kit v0.4.0
2
+
3
+ The code half of the **Decks** power prompt. `../PROMPT.md` is the other half
4
+ and wins on every design question. **This replaces `wand-template.pptx`** —
5
+ there is no binary template to keep alive any more.
6
+
7
+ ## Use it
8
+
9
+ The kit runs in two shapes, and the commands are the same in both:
10
+
11
+ - **From the repo** — `power-prompts/decks/kit/`, with fonts and gradients
12
+ resolved from the shared `brand/` at the repo root. This is where you change
13
+ it. See `../README.md`.
14
+ - **Packaged** — `node tools/package.mjs decks` writes a zip with `PROMPT.md` at
15
+ the root, the kit around it and `brand/` copied in. Upload that zip plus a
16
+ source deck to a fresh chat and say:
17
+
18
+ > Unzip this and read PROMPT.md, then run the pipeline on the attached deck.
19
+
20
+ `lib/brand.js` is what makes both work; `WAND_BRAND_DIR` overrides it.
21
+
22
+ ## Why a kit and not a template
23
+
24
+ `wand-template.pptx` could only live in Project knowledge, and Project knowledge
25
+ stores `.pptx` as a ~15 KB text dump — so the binary was unreachable from the
26
+ place it was stored, and the container resets between sessions. That loop is why
27
+ the template never got built. Code doesn't have that problem.
28
+
29
+ ## Contents
30
+
31
+ | Path | What |
32
+ |---|---|
33
+ | `wand.js` | **The front door.** `doctor` / `rebuild` / `layouts` / `validate` / `preview` / `ship` |
34
+ | `START-HERE.md` | What to do when all you have is this folder and a `.pptx` |
35
+ | `wand_kit.js` | Tokens, chrome, primitives, 26 layouts |
36
+ | `node_modules/` | `fontkit` and `pptxgenjs`, vendored. Pure JS, no native builds, so the zip is portable |
37
+ | `lib/measure.js` | Real Geist text metrics via fontkit — fit is measured, not guessed |
38
+ | `lib/trace.js` | Records shape calls so the preview reuses the real layout code |
39
+ | `lib/schema.js` | Every layout's fields and item caps, as data. Drives `validate`, `plan` and `layouts` |
40
+ | `lib/plan.js` | Phase 2-3 worksheet: source shape, layout shortlist, verbatim text, field stubs |
41
+ | `pylib/` | `wandxml.py` and `ttf.py` - the stdlib fallbacks that make `pip install` optional |
42
+ | `lib/layouts_dense.js` | Roadmaps, architecture stacks, matrices, proof walls |
43
+ | `lib/layouts_consult.js` | Problem grids, split panels, comparison tables, numbered lists |
44
+ | `build.js` | Spec → `.pptx`, or `--preview`, or `--check` |
45
+ | `preview/` | Browser preview at the 10" grid, with overflow flags and §1 rails |
46
+ | `tools/extract.py` | Phase 0/1 — normalises any canvas onto the 10" grid, dumps verbatim |
47
+ | `embed_fonts.py` | Wraps Geist as uncompressed EOT and wires it into the package |
48
+ | `qa_coverage.py` | Diffs the Phase 1 dump against the built deck; exit 1 on any miss |
49
+ | `lib/brand.js` | Resolves the shared `brand/` — 12 Geist faces, gradients, orb, marks, 36 icons |
50
+ | `docs/` | `SPEC.md` (spec format) · `LAYOUTS.md` (catalog) · `PIPELINE.md` (the six phases as commands) |
51
+ | `examples/` | `template.json` — one slide per layout, self-describing |
52
+ | `../PROMPT.md` | The power prompt. Source of truth on design questions |
53
+
54
+ ## Quick start
55
+
56
+ ```bash
57
+ node wand.js doctor # what works here. No install step of any kind:
58
+ # node_modules ships in the zip, and the Python
59
+ # side runs on the standard library
60
+ node wand.js rebuild source.pptx # normalise, dump verbatim, write work/PLAN.md
61
+ # ... you write work/deck.json from the worksheet ...
62
+ node wand.js ship work/deck.json # validate, fit, build, embed, coverage, inspect
63
+ ```
64
+
65
+ The individual stages are still there (`extract`, `plan`, `validate`, `check`,
66
+ `preview`, `build`, `inspect`) so a failure can be re-run on its own, and
67
+ `build.js` still works exactly as before. Full loop: `docs/PIPELINE.md`.
68
+
69
+ ## The three things that make complex rebuilds tractable
70
+
71
+ **Measurement instead of eyeballing.** `lib/measure.js` opens the actual Geist
72
+ TTFs and measures wrapped text. `build.js --check` reports every box that
73
+ overflows *in the real font*. This matters because LibreOffice — the only
74
+ renderer available in the container — ignores embedded fonts and substitutes
75
+ DejaVu, so visual QA over-reports overflow and loses all weight information.
76
+ Two of the three "overflows" fixed by eye in the first Infinite Labor build
77
+ turned out to fit fine.
78
+
79
+ **One spec, two renderers.** `lib/trace.js` runs a spec through the real layout
80
+ functions against a fake presentation object that records every shape. The
81
+ browser preview draws that recording. It therefore cannot drift from the .pptx,
82
+ and you can iterate on layout in a page refresh instead of a build-embed-convert-
83
+ rasterise cycle.
84
+
85
+ **Content separated from geometry.** A spec has no coordinates, colours, or font
86
+ sizes. So a rebuild diffs cleanly in two directions: spec against the source's
87
+ verbatim text, layout against the design system.
88
+
89
+ **Six gates, not one.** `ship` runs all of them because they are blind
90
+ to different things. `validate` checks fields and item counts against the layout
91
+ catalog. `--check` measures fit in real Geist, bounds against the content band,
92
+ and that every asset a slide references exists. `qa_coverage.py` asks whether
93
+ every source string survived. `inspect` reads the built file. And the preview is
94
+ the only one that can tell you a slide is correct and still ugly.
95
+
96
+ A row hidden under a closing band passes coverage — the text is in the XML — and
97
+ fails bounds. A paraphrased sentence passes everything except coverage. A
98
+ mistyped glyph name used to pass all of them and then throw a stack trace from
99
+ inside pptxgenjs; now it fails the asset check with the slide number.
100
+
101
+ ## Conventions
102
+
103
+ - Every layout resolves colour through `theme(dark)`. Never hardcode a hex in a
104
+ build script or a spec.
105
+ - Titles take a string or a 3-part array for the accent-italic device:
106
+ `["Two kinds of colleagues. ", "One set of drives", "."]`. One per title.
107
+ - Card corner radius is recomputed per shape by `radius(w, h)`. Use it.
108
+ - Layouts whose title can wrap should place content with `contentTop()` rather
109
+ than a fixed y. `archStack`, `matrixGrid`, `phaseCards` and `numberedCards` do.
110
+
111
+ ## Known gaps
112
+
113
+ 1. **Icon polarity.** The 36 glyphs are white or accent-tinted. A white glyph on
114
+ a `surface` tile (light theme) is low-contrast. Check
115
+ `assets/_icon-contact-sheet.png` before picking; the kit doesn't track which
116
+ is which. The asset check catches a *missing* glyph, not a wrong one.
117
+ 2. **Icons are chosen by number, not meaning.** The example specs pick plausible
118
+ glyphs, not semantically right ones.
119
+ 3. **Older layouts use fixed content-band coordinates.** Migrate one to
120
+ `contentTop()` when it next misbehaves.
121
+ 4. **`compareTable` tops out at 3 reason cards and 4 table rows.** Wider
122
+ comparisons need a new layout.
123
+ 5. **No `addChart()` wrapper.** Native charts only for real multi-series data;
124
+ ranked magnitudes go through `barRank` / `bridge`.
125
+ 6. **The preview approximates text layout.** It's a browser, not PowerPoint —
126
+ trust `--check` over the preview's own overflow flags for the final call.
127
+ 7. **The layout shortlist is tuned on one deck.** `plan` ranks candidates by
128
+ shape and gets the right layout into the top three reliably on the deck it
129
+ was measured against. Treat a confident single answer with suspicion, and
130
+ read the verbatim text before choosing.
131
+
132
+ ## Versioning
133
+
134
+ Bump `VERSION` in `wand_kit.js`, `version` in `../manifest.json` and the header
135
+ of `../STATUS.md` together, and say what moved in `../CHANGELOG.md`. If a session
136
+ hands you back a new zip, replace your local copy — a stale kit builds on stale
137
+ geometry and nothing will warn you.