wand-kit 0.0.0-stage → 0.2.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 (262) hide show
  1. package/.gitkeep +0 -0
  2. package/PROMPT.md +255 -0
  3. package/SKILL.md +117 -0
  4. package/START-HERE.md +16 -0
  5. package/brands/current/assets/bg-cover-dark.png +0 -0
  6. package/brands/current/assets/bg-overlay-dark.png +0 -0
  7. package/brands/current/assets/bg-statement-dark.png +0 -0
  8. package/brands/current/assets/closing-art.png +0 -0
  9. package/brands/current/assets/cover-art.png +0 -0
  10. package/brands/current/assets/divider-art.png +0 -0
  11. package/brands/current/assets/glyphs/README.md +29 -0
  12. package/brands/current/assets/glyphs/activity.svg +15 -0
  13. package/brands/current/assets/glyphs/atom.svg +17 -0
  14. package/brands/current/assets/glyphs/badge-check.svg +16 -0
  15. package/brands/current/assets/glyphs/bell.svg +16 -0
  16. package/brands/current/assets/glyphs/blocks.svg +16 -0
  17. package/brands/current/assets/glyphs/book-open.svg +16 -0
  18. package/brands/current/assets/glyphs/bot.svg +20 -0
  19. package/brands/current/assets/glyphs/boxes.svg +26 -0
  20. package/brands/current/assets/glyphs/brain.svg +23 -0
  21. package/brands/current/assets/glyphs/briefcase.svg +16 -0
  22. package/brands/current/assets/glyphs/building-2.svg +21 -0
  23. package/brands/current/assets/glyphs/calendar.svg +18 -0
  24. package/brands/current/assets/glyphs/chart-column.svg +18 -0
  25. package/brands/current/assets/glyphs/chart-line.svg +16 -0
  26. package/brands/current/assets/glyphs/chart-pie.svg +16 -0
  27. package/brands/current/assets/glyphs/circle-check.svg +16 -0
  28. package/brands/current/assets/glyphs/clipboard-check.svg +17 -0
  29. package/brands/current/assets/glyphs/clock.svg +16 -0
  30. package/brands/current/assets/glyphs/cloud.svg +15 -0
  31. package/brands/current/assets/glyphs/code.svg +15 -0
  32. package/brands/current/assets/glyphs/coins.svg +18 -0
  33. package/brands/current/assets/glyphs/compass.svg +16 -0
  34. package/brands/current/assets/glyphs/cpu.svg +24 -0
  35. package/brands/current/assets/glyphs/database.svg +17 -0
  36. package/brands/current/assets/glyphs/eye.svg +16 -0
  37. package/brands/current/assets/glyphs/file-check.svg +17 -0
  38. package/brands/current/assets/glyphs/filter.svg +15 -0
  39. package/brands/current/assets/glyphs/fingerprint.svg +23 -0
  40. package/brands/current/assets/glyphs/flag.svg +16 -0
  41. package/brands/current/assets/glyphs/gauge.svg +16 -0
  42. package/brands/current/assets/glyphs/gavel.svg +19 -0
  43. package/brands/current/assets/glyphs/git-branch.svg +18 -0
  44. package/brands/current/assets/glyphs/globe.svg +17 -0
  45. package/brands/current/assets/glyphs/graduation-cap.svg +17 -0
  46. package/brands/current/assets/glyphs/handshake.svg +19 -0
  47. package/brands/current/assets/glyphs/hexagon.svg +15 -0
  48. package/brands/current/assets/glyphs/index.json +177 -0
  49. package/brands/current/assets/glyphs/infinity.svg +15 -0
  50. package/brands/current/assets/glyphs/key.svg +17 -0
  51. package/brands/current/assets/glyphs/landmark.svg +20 -0
  52. package/brands/current/assets/glyphs/layers.svg +17 -0
  53. package/brands/current/assets/glyphs/lightbulb.svg +17 -0
  54. package/brands/current/assets/glyphs/link.svg +16 -0
  55. package/brands/current/assets/glyphs/list-checks.svg +19 -0
  56. package/brands/current/assets/glyphs/lock.svg +16 -0
  57. package/brands/current/assets/glyphs/map.svg +17 -0
  58. package/brands/current/assets/glyphs/megaphone.svg +16 -0
  59. package/brands/current/assets/glyphs/message-square.svg +15 -0
  60. package/brands/current/assets/glyphs/milestone.svg +17 -0
  61. package/brands/current/assets/glyphs/network.svg +19 -0
  62. package/brands/current/assets/glyphs/orbit.svg +19 -0
  63. package/brands/current/assets/glyphs/package.svg +18 -0
  64. package/brands/current/assets/glyphs/plug.svg +18 -0
  65. package/brands/current/assets/glyphs/puzzle.svg +15 -0
  66. package/brands/current/assets/glyphs/radar.svg +22 -0
  67. package/brands/current/assets/glyphs/receipt.svg +17 -0
  68. package/brands/current/assets/glyphs/refresh-cw.svg +18 -0
  69. package/brands/current/assets/glyphs/rocket.svg +18 -0
  70. package/brands/current/assets/glyphs/route.svg +17 -0
  71. package/brands/current/assets/glyphs/scale.svg +19 -0
  72. package/brands/current/assets/glyphs/search.svg +16 -0
  73. package/brands/current/assets/glyphs/server.svg +18 -0
  74. package/brands/current/assets/glyphs/settings.svg +16 -0
  75. package/brands/current/assets/glyphs/share-2.svg +19 -0
  76. package/brands/current/assets/glyphs/shield-check.svg +16 -0
  77. package/brands/current/assets/glyphs/shuffle.svg +19 -0
  78. package/brands/current/assets/glyphs/sparkles.svg +19 -0
  79. package/brands/current/assets/glyphs/square-stack.svg +17 -0
  80. package/brands/current/assets/glyphs/target.svg +17 -0
  81. package/brands/current/assets/glyphs/trending-up.svg +16 -0
  82. package/brands/current/assets/glyphs/triangle-alert.svg +17 -0
  83. package/brands/current/assets/glyphs/trophy.svg +20 -0
  84. package/brands/current/assets/glyphs/users.svg +18 -0
  85. package/brands/current/assets/glyphs/wallet.svg +16 -0
  86. package/brands/current/assets/glyphs/wand-sparkles.svg +22 -0
  87. package/brands/current/assets/glyphs/workflow.svg +17 -0
  88. package/brands/current/assets/glyphs/zap.svg +15 -0
  89. package/brands/current/assets/mark-dark.png +0 -0
  90. package/brands/current/assets/mark-white.png +0 -0
  91. package/brands/current/assets/orb-cover.png +0 -0
  92. package/brands/current/assets/orb-glow.png +0 -0
  93. package/brands/current/assets/wordmark-dark.png +0 -0
  94. package/brands/current/assets/wordmark-white.png +0 -0
  95. package/brands/current/fonts/Geist-Bold.ttf +0 -0
  96. package/brands/current/fonts/Geist-BoldItalic.ttf +0 -0
  97. package/brands/current/fonts/Geist-ExtraBold.ttf +0 -0
  98. package/brands/current/fonts/Geist-ExtraBoldItalic.ttf +0 -0
  99. package/brands/current/fonts/Geist-Italic.ttf +0 -0
  100. package/brands/current/fonts/Geist-Light.ttf +0 -0
  101. package/brands/current/fonts/Geist-LightItalic.ttf +0 -0
  102. package/brands/current/fonts/Geist-Medium.ttf +0 -0
  103. package/brands/current/fonts/Geist-MediumItalic.ttf +0 -0
  104. package/brands/current/fonts/Geist-Regular.ttf +0 -0
  105. package/brands/current/fonts/Geist-SemiBold.ttf +0 -0
  106. package/brands/current/fonts/Geist-SemiBoldItalic.ttf +0 -0
  107. package/brands/current/lib/glyphs.js +87 -0
  108. package/brands/current/lib/measure.js +115 -0
  109. package/brands/current/lib/pdf.js +603 -0
  110. package/brands/current/lib/ttf.py +167 -0
  111. package/brands/current/lib/wandxml.py +37 -0
  112. package/brands/current/tokens.json +206 -0
  113. package/brands/next/assets/closing-art.png +0 -0
  114. package/brands/next/assets/cover-art.png +0 -0
  115. package/brands/next/assets/divider-art.png +0 -0
  116. package/brands/next/assets/glyphs/README.md +29 -0
  117. package/brands/next/assets/glyphs/activity.svg +15 -0
  118. package/brands/next/assets/glyphs/atom.svg +17 -0
  119. package/brands/next/assets/glyphs/badge-check.svg +16 -0
  120. package/brands/next/assets/glyphs/bell.svg +16 -0
  121. package/brands/next/assets/glyphs/blocks.svg +16 -0
  122. package/brands/next/assets/glyphs/book-open.svg +16 -0
  123. package/brands/next/assets/glyphs/bot.svg +20 -0
  124. package/brands/next/assets/glyphs/boxes.svg +26 -0
  125. package/brands/next/assets/glyphs/brain.svg +23 -0
  126. package/brands/next/assets/glyphs/briefcase.svg +16 -0
  127. package/brands/next/assets/glyphs/building-2.svg +21 -0
  128. package/brands/next/assets/glyphs/calendar.svg +18 -0
  129. package/brands/next/assets/glyphs/chart-column.svg +18 -0
  130. package/brands/next/assets/glyphs/chart-line.svg +16 -0
  131. package/brands/next/assets/glyphs/chart-pie.svg +16 -0
  132. package/brands/next/assets/glyphs/circle-check.svg +16 -0
  133. package/brands/next/assets/glyphs/clipboard-check.svg +17 -0
  134. package/brands/next/assets/glyphs/clock.svg +16 -0
  135. package/brands/next/assets/glyphs/cloud.svg +15 -0
  136. package/brands/next/assets/glyphs/code.svg +15 -0
  137. package/brands/next/assets/glyphs/coins.svg +18 -0
  138. package/brands/next/assets/glyphs/compass.svg +16 -0
  139. package/brands/next/assets/glyphs/cpu.svg +24 -0
  140. package/brands/next/assets/glyphs/database.svg +17 -0
  141. package/brands/next/assets/glyphs/eye.svg +16 -0
  142. package/brands/next/assets/glyphs/file-check.svg +17 -0
  143. package/brands/next/assets/glyphs/filter.svg +15 -0
  144. package/brands/next/assets/glyphs/fingerprint.svg +23 -0
  145. package/brands/next/assets/glyphs/flag.svg +16 -0
  146. package/brands/next/assets/glyphs/gauge.svg +16 -0
  147. package/brands/next/assets/glyphs/gavel.svg +19 -0
  148. package/brands/next/assets/glyphs/git-branch.svg +18 -0
  149. package/brands/next/assets/glyphs/globe.svg +17 -0
  150. package/brands/next/assets/glyphs/graduation-cap.svg +17 -0
  151. package/brands/next/assets/glyphs/handshake.svg +19 -0
  152. package/brands/next/assets/glyphs/hexagon.svg +15 -0
  153. package/brands/next/assets/glyphs/index.json +177 -0
  154. package/brands/next/assets/glyphs/infinity.svg +15 -0
  155. package/brands/next/assets/glyphs/key.svg +17 -0
  156. package/brands/next/assets/glyphs/landmark.svg +20 -0
  157. package/brands/next/assets/glyphs/layers.svg +17 -0
  158. package/brands/next/assets/glyphs/lightbulb.svg +17 -0
  159. package/brands/next/assets/glyphs/link.svg +16 -0
  160. package/brands/next/assets/glyphs/list-checks.svg +19 -0
  161. package/brands/next/assets/glyphs/lock.svg +16 -0
  162. package/brands/next/assets/glyphs/map.svg +17 -0
  163. package/brands/next/assets/glyphs/megaphone.svg +16 -0
  164. package/brands/next/assets/glyphs/message-square.svg +15 -0
  165. package/brands/next/assets/glyphs/milestone.svg +17 -0
  166. package/brands/next/assets/glyphs/network.svg +19 -0
  167. package/brands/next/assets/glyphs/orbit.svg +19 -0
  168. package/brands/next/assets/glyphs/package.svg +18 -0
  169. package/brands/next/assets/glyphs/plug.svg +18 -0
  170. package/brands/next/assets/glyphs/puzzle.svg +15 -0
  171. package/brands/next/assets/glyphs/radar.svg +22 -0
  172. package/brands/next/assets/glyphs/receipt.svg +17 -0
  173. package/brands/next/assets/glyphs/refresh-cw.svg +18 -0
  174. package/brands/next/assets/glyphs/rocket.svg +18 -0
  175. package/brands/next/assets/glyphs/route.svg +17 -0
  176. package/brands/next/assets/glyphs/scale.svg +19 -0
  177. package/brands/next/assets/glyphs/search.svg +16 -0
  178. package/brands/next/assets/glyphs/server.svg +18 -0
  179. package/brands/next/assets/glyphs/settings.svg +16 -0
  180. package/brands/next/assets/glyphs/share-2.svg +19 -0
  181. package/brands/next/assets/glyphs/shield-check.svg +16 -0
  182. package/brands/next/assets/glyphs/shuffle.svg +19 -0
  183. package/brands/next/assets/glyphs/sparkles.svg +19 -0
  184. package/brands/next/assets/glyphs/square-stack.svg +17 -0
  185. package/brands/next/assets/glyphs/target.svg +17 -0
  186. package/brands/next/assets/glyphs/trending-up.svg +16 -0
  187. package/brands/next/assets/glyphs/triangle-alert.svg +17 -0
  188. package/brands/next/assets/glyphs/trophy.svg +20 -0
  189. package/brands/next/assets/glyphs/users.svg +18 -0
  190. package/brands/next/assets/glyphs/wallet.svg +16 -0
  191. package/brands/next/assets/glyphs/wand-sparkles.svg +22 -0
  192. package/brands/next/assets/glyphs/workflow.svg +17 -0
  193. package/brands/next/assets/glyphs/zap.svg +15 -0
  194. package/brands/next/assets/layers-dark.png +0 -0
  195. package/brands/next/assets/layers-light.png +0 -0
  196. package/brands/next/assets/ornaments/burst-dark.png +0 -0
  197. package/brands/next/assets/ornaments/burst-light.png +0 -0
  198. package/brands/next/assets/ornaments/chain-dark.png +0 -0
  199. package/brands/next/assets/ornaments/chain-light.png +0 -0
  200. package/brands/next/assets/ornaments/coil-dark.png +0 -0
  201. package/brands/next/assets/ornaments/coil-light.png +0 -0
  202. package/brands/next/assets/ornaments/cone-dark.png +0 -0
  203. package/brands/next/assets/ornaments/cone-light.png +0 -0
  204. package/brands/next/assets/ornaments/diamond-dark.png +0 -0
  205. package/brands/next/assets/ornaments/diamond-light.png +0 -0
  206. package/brands/next/assets/ornaments/fan-dark.png +0 -0
  207. package/brands/next/assets/ornaments/fan-light.png +0 -0
  208. package/brands/next/assets/ornaments/globe-dark.png +0 -0
  209. package/brands/next/assets/ornaments/globe-light.png +0 -0
  210. package/brands/next/assets/ornaments/orbit-dark.png +0 -0
  211. package/brands/next/assets/ornaments/orbit-light.png +0 -0
  212. package/brands/next/assets/ornaments/rings-dark.png +0 -0
  213. package/brands/next/assets/ornaments/rings-light.png +0 -0
  214. package/brands/next/assets/ornaments/sphere-dark.png +0 -0
  215. package/brands/next/assets/ornaments/sphere-light.png +0 -0
  216. package/brands/next/assets/ornaments/sun-dark.png +0 -0
  217. package/brands/next/assets/ornaments/sun-light.png +0 -0
  218. package/brands/next/assets/photos/atrium.jpg +0 -0
  219. package/brands/next/assets/photos/cantilever.jpg +0 -0
  220. package/brands/next/assets/photos/corridor.jpg +0 -0
  221. package/brands/next/assets/photos/dome.jpg +0 -0
  222. package/brands/next/assets/photos/facade.jpg +0 -0
  223. package/brands/next/assets/photos/glass.jpg +0 -0
  224. package/brands/next/assets/photos/stairs.jpg +0 -0
  225. package/brands/next/assets/photos/tower.jpg +0 -0
  226. package/brands/next/assets/photos/walk.jpg +0 -0
  227. package/brands/next/assets/spark-gold.png +0 -0
  228. package/brands/next/assets/statement-art.png +0 -0
  229. package/brands/next/assets/wordmark-dark.png +0 -0
  230. package/brands/next/assets/wordmark-light.png +0 -0
  231. package/brands/next/assets/wordmark.svg +7 -0
  232. package/brands/next/fonts/FKRomanStandard-Light.ttf +0 -0
  233. package/brands/next/fonts/OFL-OverusedGrotesk.txt +93 -0
  234. package/brands/next/fonts/OverusedGrotesk-Italic.ttf +0 -0
  235. package/brands/next/fonts/OverusedGrotesk-Medium.ttf +0 -0
  236. package/brands/next/fonts/OverusedGrotesk-MediumItalic.ttf +0 -0
  237. package/brands/next/fonts/OverusedGrotesk-Roman.ttf +0 -0
  238. package/brands/next/fonts/OverusedGrotesk-SemiBold.ttf +0 -0
  239. package/brands/next/fonts/OverusedGrotesk-SemiBoldItalic.ttf +0 -0
  240. package/brands/next/fonts/faces.json +27 -0
  241. package/brands/next/lib/glyphs.js +89 -0
  242. package/brands/next/lib/measure.js +121 -0
  243. package/brands/next/lib/pdf.js +597 -0
  244. package/brands/next/lib/ttf.py +169 -0
  245. package/brands/next/lib/wandxml.py +37 -0
  246. package/brands/next/tokens.json +174 -0
  247. package/examples/gallery/job.mjs +83 -0
  248. package/examples/overview/job.mjs +99 -0
  249. package/lib/brand.js +135 -0
  250. package/lib/components.js +313 -0
  251. package/lib/content.js +66 -0
  252. package/lib/engine.js +280 -0
  253. package/lib/html_export.js +71 -0
  254. package/lib/pptx.js +446 -0
  255. package/lib/surfaces.js +62 -0
  256. package/package.json +17 -4
  257. package/templates/card.mjs +30 -0
  258. package/templates/page.mjs +52 -0
  259. package/templates/slide.mjs +46 -0
  260. package/tools/embed_fonts.py +154 -0
  261. package/wand.js +179 -0
  262. package/README.md +0 -3
@@ -0,0 +1,18 @@
1
+ <!-- @license lucide-static v0.469.0 - ISC -->
2
+ <svg
3
+ class="lucide lucide-users"
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ width="24"
6
+ height="24"
7
+ viewBox="0 0 24 24"
8
+ fill="none"
9
+ stroke="currentColor"
10
+ stroke-width="2"
11
+ stroke-linecap="round"
12
+ stroke-linejoin="round"
13
+ >
14
+ <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
15
+ <circle cx="9" cy="7" r="4" />
16
+ <path d="M22 21v-2a4 4 0 0 0-3-3.87" />
17
+ <path d="M16 3.13a4 4 0 0 1 0 7.75" />
18
+ </svg>
@@ -0,0 +1,16 @@
1
+ <!-- @license lucide-static v0.469.0 - ISC -->
2
+ <svg
3
+ class="lucide lucide-wallet"
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ width="24"
6
+ height="24"
7
+ viewBox="0 0 24 24"
8
+ fill="none"
9
+ stroke="currentColor"
10
+ stroke-width="2"
11
+ stroke-linecap="round"
12
+ stroke-linejoin="round"
13
+ >
14
+ <path d="M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1" />
15
+ <path d="M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4" />
16
+ </svg>
@@ -0,0 +1,22 @@
1
+ <!-- @license lucide-static v0.469.0 - ISC -->
2
+ <svg
3
+ class="lucide lucide-wand-sparkles"
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ width="24"
6
+ height="24"
7
+ viewBox="0 0 24 24"
8
+ fill="none"
9
+ stroke="currentColor"
10
+ stroke-width="2"
11
+ stroke-linecap="round"
12
+ stroke-linejoin="round"
13
+ >
14
+ <path d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72" />
15
+ <path d="m14 7 3 3" />
16
+ <path d="M5 6v4" />
17
+ <path d="M19 14v4" />
18
+ <path d="M10 2v2" />
19
+ <path d="M7 8H3" />
20
+ <path d="M21 16h-4" />
21
+ <path d="M11 3H9" />
22
+ </svg>
@@ -0,0 +1,17 @@
1
+ <!-- @license lucide-static v0.469.0 - ISC -->
2
+ <svg
3
+ class="lucide lucide-workflow"
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ width="24"
6
+ height="24"
7
+ viewBox="0 0 24 24"
8
+ fill="none"
9
+ stroke="currentColor"
10
+ stroke-width="2"
11
+ stroke-linecap="round"
12
+ stroke-linejoin="round"
13
+ >
14
+ <rect width="8" height="8" x="3" y="3" rx="2" />
15
+ <path d="M7 11v4a2 2 0 0 0 2 2h4" />
16
+ <rect width="8" height="8" x="13" y="13" rx="2" />
17
+ </svg>
@@ -0,0 +1,15 @@
1
+ <!-- @license lucide-static v0.469.0 - ISC -->
2
+ <svg
3
+ class="lucide lucide-zap"
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ width="24"
6
+ height="24"
7
+ viewBox="0 0 24 24"
8
+ fill="none"
9
+ stroke="currentColor"
10
+ stroke-width="2"
11
+ stroke-linecap="round"
12
+ stroke-linejoin="round"
13
+ >
14
+ <path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z" />
15
+ </svg>
@@ -0,0 +1,87 @@
1
+ /**
2
+ * lib/glyphs.js — the named icon set.
3
+ *
4
+ * The old library was 36 PNGs named after their extraction order, half baked
5
+ * white and half baked accent. That produced two standing bugs: a white glyph on
6
+ * a light tile is invisible, and nobody could pick an icon by meaning because
7
+ * the names carried none.
8
+ *
9
+ * These are Lucide's single-colour stroke SVGs. `stroke="currentColor"` means
10
+ * the kit substitutes the theme's colour at build time, so polarity cannot be
11
+ * wrong. They go into the deck as vector — pptxgenjs writes the OOXML
12
+ * `asvg:svgBlip` extension with a raster fallback — so nothing is rasterised
13
+ * and the kit keeps needing no native dependency.
14
+ */
15
+
16
+ const fs = require("fs");
17
+ const path = require("path");
18
+
19
+ const DIR = path.join(__dirname, "..", "assets", "glyphs");
20
+ let INDEX = null, BUNDLE = null;
21
+
22
+ function index() {
23
+ if (!INDEX) INDEX = JSON.parse(fs.readFileSync(path.join(DIR, "index.json"), "utf8"));
24
+ return INDEX;
25
+ }
26
+
27
+ /**
28
+ * 75 SVGs is 75 files, and a packaged skill is capped at 200 of them. The
29
+ * packager collapses them into one `glyphs.json`; loose files still win when
30
+ * they are there, so the repo keeps its readable one-file-per-icon layout.
31
+ */
32
+ function bundle() {
33
+ if (BUNDLE === null) {
34
+ const f = path.join(DIR, "glyphs.json");
35
+ BUNDLE = fs.existsSync(f) ? JSON.parse(fs.readFileSync(f, "utf8")) : false;
36
+ }
37
+ return BUNDLE;
38
+ }
39
+
40
+ /** The raw SVG source for a glyph. */
41
+ function svg(name) {
42
+ const b = bundle();
43
+ if (b && b[name]) return b[name];
44
+ return fs.readFileSync(path.join(DIR, name + ".svg"), "utf8");
45
+ }
46
+
47
+ /** Every glyph name, for validation and for `wand.js glyphs`. */
48
+ const names = () => index().icons;
49
+ const has = (name) => typeof name === "string" && index().icons.includes(name);
50
+
51
+ const cache = new Map();
52
+
53
+ /**
54
+ * A recoloured glyph as a data URI, ready for `addImage({ data })`.
55
+ * Returns null for anything that isn't a known glyph name, so callers can fall
56
+ * back to the legacy PNG path.
57
+ */
58
+ function dataUri(name, color) {
59
+ if (!has(name)) return null;
60
+ const key = `${name}|${color}`;
61
+ if (cache.has(key)) return cache.get(key);
62
+ const src = svg(name).replace(/currentColor/g, "#" + String(color).replace(/^#/, ""));
63
+ const uri = "image/svg+xml;base64," + Buffer.from(src, "utf8").toString("base64");
64
+ cache.set(key, uri);
65
+ return uri;
66
+ }
67
+
68
+ /** Nearest names, for a useful error when someone mistypes one. */
69
+ function suggest(name, n = 4) {
70
+ const q = String(name || "").toLowerCase();
71
+ return index().icons
72
+ .map((g) => [g, g.includes(q) || q.includes(g) ? 0 : distance(q, g)])
73
+ .sort((a, b) => a[1] - b[1]).slice(0, n).map((r) => r[0]);
74
+ }
75
+
76
+ function distance(a, b) {
77
+ const d = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]);
78
+ for (let j = 0; j <= b.length; j++) d[0][j] = j;
79
+ for (let i = 1; i <= a.length; i++) {
80
+ for (let j = 1; j <= b.length; j++) {
81
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
82
+ }
83
+ }
84
+ return d[a.length][b.length];
85
+ }
86
+
87
+ module.exports = { names, has, svg, dataUri, suggest, index, DIR };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * lib/measure.js — real text metrics against the actual Geist faces.
3
+ *
4
+ * Why this exists: §9 says LibreOffice ignores embedded fonts and substitutes
5
+ * DejaVu, so QA renders over-report overflow and lose all weight information.
6
+ * That makes visual QA unreliable for exactly the thing it looks like it's
7
+ * checking. This module measures the real font, so overflow is a build-time
8
+ * error rather than a judgement call about a screenshot.
9
+ */
10
+
11
+ const path = require("path");
12
+ const FONTS = path.join(__dirname, "..", "fonts");
13
+
14
+ /* family + style -> ttf stem, mirroring embed_fonts.py */
15
+ const FACE = {
16
+ "Geist": { r: "Geist-Regular", b: "Geist-Bold", i: "Geist-Italic", bi: "Geist-BoldItalic" },
17
+ "Geist Medium": { r: "Geist-Medium", b: "Geist-SemiBold", i: "Geist-MediumItalic", bi: "Geist-SemiBoldItalic" },
18
+ "Geist SemiBold": { r: "Geist-SemiBold", b: "Geist-ExtraBold", i: "Geist-SemiBoldItalic", bi: "Geist-ExtraBoldItalic" },
19
+ "Geist ExtraBold": { r: "Geist-ExtraBold", b: "Geist-ExtraBold", i: "Geist-ExtraBoldItalic", bi: "Geist-ExtraBoldItalic" },
20
+ "Geist Light": { r: "Geist-Light", b: "Geist-Medium", i: "Geist-LightItalic", bi: "Geist-MediumItalic" },
21
+ };
22
+
23
+ let FONTKIT = null;
24
+ // Below this size, measure without kerning. PowerPoint kerns only from the
25
+ // `kern` threshold in the deck's default text style — pptxgenjs writes 1200,
26
+ // 12pt — so a deck kit passes 12 and its body text measures the way it is
27
+ // drawn. 0, the default, kerns everything, as this module always has.
28
+ let KERN_FROM = 0;
29
+ const cache = new Map();
30
+ function face(family = "Geist", { bold = false, italic = false } = {}) {
31
+ if (!FONTKIT) throw new Error("measure.js was loaded without fontkit — require it through the kit's lib/shared.js");
32
+ const set = FACE[family] || FACE["Geist"];
33
+ const stem = set[bold && italic ? "bi" : bold ? "b" : italic ? "i" : "r"];
34
+ if (!cache.has(stem)) cache.set(stem, FONTKIT.openSync(path.join(FONTS, stem + ".ttf")));
35
+ return cache.get(stem);
36
+ }
37
+
38
+ /** Width of a single line, in inches. */
39
+ function widthIn(text, { family, fontSize, bold, italic } = {}) {
40
+ if (!text) return 0;
41
+ const f = face(family, { bold, italic });
42
+ const run = fontSize < KERN_FROM ? f.layout(String(text), { kern: false }) : f.layout(String(text));
43
+ return (run.advanceWidth / f.unitsPerEm) * (fontSize / 72);
44
+ }
45
+
46
+ /**
47
+ * Greedy word wrap at a given box width. Returns the lines it would occupy.
48
+ * Honours explicit \n. Matches how PowerPoint breaks closely enough to catch
49
+ * the failures that matter (a line too many, a word that can't fit at all).
50
+ */
51
+ function wrap(text, boxWidthIn, opts = {}) {
52
+ const out = [];
53
+ for (const para of String(text).split("\n")) {
54
+ const words = para.split(/\s+/).filter(Boolean);
55
+ if (!words.length) { out.push(""); continue; }
56
+ let line = words[0];
57
+ for (let i = 1; i < words.length; i++) {
58
+ const test = line + " " + words[i];
59
+ if (widthIn(test, opts) <= boxWidthIn) line = test;
60
+ else { out.push(line); line = words[i]; }
61
+ }
62
+ out.push(line);
63
+ }
64
+ return out;
65
+ }
66
+
67
+ /** Rendered height in inches for text wrapped into a box. */
68
+ function heightIn(text, boxWidthIn, opts = {}) {
69
+ const leading = opts.lineSpacingMultiple || 1.2;
70
+ return wrap(text, boxWidthIn, opts).length * (opts.fontSize / 72) * leading;
71
+ }
72
+
73
+ /**
74
+ * Does this text fit the box? Returns a verdict object rather than a boolean so
75
+ * callers can report *why*.
76
+ */
77
+ function fits(text, boxWidthIn, boxHeightIn, opts = {}) {
78
+ const lines = wrap(text, boxWidthIn, opts);
79
+ const h = heightIn(text, boxWidthIn, opts);
80
+ const widest = Math.max(...lines.map((l) => widthIn(l, opts)));
81
+ return {
82
+ ok: h <= boxHeightIn + 1e-6 && widest <= boxWidthIn + 1e-6,
83
+ lines: lines.length,
84
+ heightIn: +h.toFixed(3),
85
+ widestIn: +widest.toFixed(3),
86
+ overflowIn: +Math.max(0, h - boxHeightIn).toFixed(3),
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Largest font size at or below `fontSize` that fits the box, stepping down in
92
+ * `step` pt increments but never below `min`. Use this instead of hardcoding a
93
+ * length threshold — it works for any string, in any language.
94
+ */
95
+ function fitSize(text, boxWidthIn, boxHeightIn, opts = {}) {
96
+ const { fontSize, min = 7, step = 0.5 } = opts;
97
+ for (let s = fontSize; s >= min; s -= step) {
98
+ if (fits(text, boxWidthIn, boxHeightIn, { ...opts, fontSize: s }).ok) return s;
99
+ }
100
+ return min;
101
+ }
102
+
103
+ /** Line count only — the common case for "will this title be 2 lines or 3?". */
104
+ const lineCount = (text, boxWidthIn, opts = {}) => wrap(text, boxWidthIn, opts).length;
105
+
106
+ /**
107
+ * This module lives in brand/, outside any kit's `node_modules`, so Node cannot
108
+ * resolve `fontkit` from here. The kit that loads it hands its own copy in.
109
+ * Explicit beats a resolution trick that works until someone moves a directory.
110
+ */
111
+ module.exports = function create(fontkit, { kernFrom = 0 } = {}) {
112
+ FONTKIT = fontkit;
113
+ KERN_FROM = kernFrom;
114
+ return { widthIn, wrap, heightIn, fits, fitSize, lineCount, face };
115
+ };