wand-kit 0.3.2 → 0.5.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 (162) hide show
  1. package/PROMPT.md +221 -19
  2. package/SKILL.md +100 -20
  3. package/START-HERE.md +12 -1
  4. package/brands/current/tokens.json +10 -2
  5. package/brands/interim/assets/avatar.svg +1 -0
  6. package/brands/interim/assets/closing-art.png +0 -0
  7. package/brands/interim/assets/cover-art.png +0 -0
  8. package/brands/interim/assets/cover-portrait-art.png +0 -0
  9. package/brands/interim/assets/divider-art.png +0 -0
  10. package/brands/interim/assets/glyphs/README.md +29 -0
  11. package/brands/interim/assets/glyphs/activity.svg +15 -0
  12. package/brands/interim/assets/glyphs/atom.svg +17 -0
  13. package/brands/interim/assets/glyphs/badge-check.svg +16 -0
  14. package/brands/interim/assets/glyphs/bell.svg +16 -0
  15. package/brands/interim/assets/glyphs/blocks.svg +16 -0
  16. package/brands/interim/assets/glyphs/book-open.svg +16 -0
  17. package/brands/interim/assets/glyphs/bot.svg +20 -0
  18. package/brands/interim/assets/glyphs/boxes.svg +26 -0
  19. package/brands/interim/assets/glyphs/brain.svg +23 -0
  20. package/brands/interim/assets/glyphs/briefcase.svg +16 -0
  21. package/brands/interim/assets/glyphs/building-2.svg +21 -0
  22. package/brands/interim/assets/glyphs/calendar.svg +18 -0
  23. package/brands/interim/assets/glyphs/chart-column.svg +18 -0
  24. package/brands/interim/assets/glyphs/chart-line.svg +16 -0
  25. package/brands/interim/assets/glyphs/chart-pie.svg +16 -0
  26. package/brands/interim/assets/glyphs/circle-check.svg +16 -0
  27. package/brands/interim/assets/glyphs/clipboard-check.svg +17 -0
  28. package/brands/interim/assets/glyphs/clock.svg +16 -0
  29. package/brands/interim/assets/glyphs/cloud.svg +15 -0
  30. package/brands/interim/assets/glyphs/code.svg +15 -0
  31. package/brands/interim/assets/glyphs/coins.svg +18 -0
  32. package/brands/interim/assets/glyphs/compass.svg +16 -0
  33. package/brands/interim/assets/glyphs/cpu.svg +24 -0
  34. package/brands/interim/assets/glyphs/database.svg +17 -0
  35. package/brands/interim/assets/glyphs/eye.svg +16 -0
  36. package/brands/interim/assets/glyphs/file-check.svg +17 -0
  37. package/brands/interim/assets/glyphs/filter.svg +15 -0
  38. package/brands/interim/assets/glyphs/fingerprint.svg +23 -0
  39. package/brands/interim/assets/glyphs/flag.svg +16 -0
  40. package/brands/interim/assets/glyphs/gauge.svg +16 -0
  41. package/brands/interim/assets/glyphs/gavel.svg +19 -0
  42. package/brands/interim/assets/glyphs/git-branch.svg +18 -0
  43. package/brands/interim/assets/glyphs/globe.svg +17 -0
  44. package/brands/interim/assets/glyphs/graduation-cap.svg +17 -0
  45. package/brands/interim/assets/glyphs/handshake.svg +19 -0
  46. package/brands/interim/assets/glyphs/hexagon.svg +15 -0
  47. package/brands/interim/assets/glyphs/index.json +177 -0
  48. package/brands/interim/assets/glyphs/infinity.svg +15 -0
  49. package/brands/interim/assets/glyphs/key.svg +17 -0
  50. package/brands/interim/assets/glyphs/landmark.svg +20 -0
  51. package/brands/interim/assets/glyphs/layers.svg +17 -0
  52. package/brands/interim/assets/glyphs/lightbulb.svg +17 -0
  53. package/brands/interim/assets/glyphs/link.svg +16 -0
  54. package/brands/interim/assets/glyphs/list-checks.svg +19 -0
  55. package/brands/interim/assets/glyphs/lock.svg +16 -0
  56. package/brands/interim/assets/glyphs/map.svg +17 -0
  57. package/brands/interim/assets/glyphs/megaphone.svg +16 -0
  58. package/brands/interim/assets/glyphs/message-square.svg +15 -0
  59. package/brands/interim/assets/glyphs/milestone.svg +17 -0
  60. package/brands/interim/assets/glyphs/network.svg +19 -0
  61. package/brands/interim/assets/glyphs/orbit.svg +19 -0
  62. package/brands/interim/assets/glyphs/package.svg +18 -0
  63. package/brands/interim/assets/glyphs/plug.svg +18 -0
  64. package/brands/interim/assets/glyphs/puzzle.svg +15 -0
  65. package/brands/interim/assets/glyphs/radar.svg +22 -0
  66. package/brands/interim/assets/glyphs/receipt.svg +17 -0
  67. package/brands/interim/assets/glyphs/refresh-cw.svg +18 -0
  68. package/brands/interim/assets/glyphs/rocket.svg +18 -0
  69. package/brands/interim/assets/glyphs/route.svg +17 -0
  70. package/brands/interim/assets/glyphs/scale.svg +19 -0
  71. package/brands/interim/assets/glyphs/search.svg +16 -0
  72. package/brands/interim/assets/glyphs/server.svg +18 -0
  73. package/brands/interim/assets/glyphs/settings.svg +16 -0
  74. package/brands/interim/assets/glyphs/share-2.svg +19 -0
  75. package/brands/interim/assets/glyphs/shield-check.svg +16 -0
  76. package/brands/interim/assets/glyphs/shuffle.svg +19 -0
  77. package/brands/interim/assets/glyphs/sparkles.svg +19 -0
  78. package/brands/interim/assets/glyphs/square-stack.svg +17 -0
  79. package/brands/interim/assets/glyphs/target.svg +17 -0
  80. package/brands/interim/assets/glyphs/trending-up.svg +16 -0
  81. package/brands/interim/assets/glyphs/triangle-alert.svg +17 -0
  82. package/brands/interim/assets/glyphs/trophy.svg +20 -0
  83. package/brands/interim/assets/glyphs/users.svg +18 -0
  84. package/brands/interim/assets/glyphs/wallet.svg +16 -0
  85. package/brands/interim/assets/glyphs/wand-sparkles.svg +22 -0
  86. package/brands/interim/assets/glyphs/workflow.svg +17 -0
  87. package/brands/interim/assets/glyphs/zap.svg +15 -0
  88. package/brands/interim/assets/mark.svg +1 -0
  89. package/brands/interim/assets/social/composition.json +6833 -0
  90. package/brands/interim/assets/spark-lavender.png +0 -0
  91. package/brands/interim/assets/spark-violet.png +0 -0
  92. package/brands/interim/assets/statement-art.png +0 -0
  93. package/brands/interim/assets/story/agent-alpha.png +0 -0
  94. package/brands/interim/assets/story/agent.png +0 -0
  95. package/brands/interim/assets/story/economy-alpha.png +0 -0
  96. package/brands/interim/assets/story/economy.png +0 -0
  97. package/brands/interim/assets/story/index.json +214 -0
  98. package/brands/interim/assets/story/learning-alpha.png +0 -0
  99. package/brands/interim/assets/story/learning.png +0 -0
  100. package/brands/interim/assets/story/organization-alpha.png +0 -0
  101. package/brands/interim/assets/story/organization.png +0 -0
  102. package/brands/interim/assets/story/worker-alpha.png +0 -0
  103. package/brands/interim/assets/story/worker-light.png +0 -0
  104. package/brands/interim/assets/story/worker.png +0 -0
  105. package/brands/interim/assets/wordmark-on-dark.png +0 -0
  106. package/brands/interim/assets/wordmark-on-dark.svg +1 -0
  107. package/brands/interim/assets/wordmark-on-light.png +0 -0
  108. package/brands/interim/assets/wordmark-on-light.svg +1 -0
  109. package/brands/interim/fonts/Geist-Book.ttf +0 -0
  110. package/brands/interim/fonts/Geist-Medium.ttf +0 -0
  111. package/brands/interim/fonts/Geist-Regular.ttf +0 -0
  112. package/brands/interim/fonts/Geist-SemiBold.ttf +0 -0
  113. package/brands/interim/fonts/Newsreader-Italic.ttf +0 -0
  114. package/brands/interim/fonts/Newsreader-Medium.ttf +0 -0
  115. package/brands/interim/fonts/Newsreader-MediumItalic.ttf +0 -0
  116. package/brands/interim/fonts/Newsreader-Regular.ttf +0 -0
  117. package/brands/interim/fonts/OFL-Geist.txt +93 -0
  118. package/brands/interim/fonts/OFL-Newsreader.txt +93 -0
  119. package/brands/interim/fonts/faces.json +12 -0
  120. package/brands/interim/lib/composition.js +169 -0
  121. package/brands/interim/lib/composition.mjs +101 -0
  122. package/brands/interim/lib/glyphs.js +89 -0
  123. package/brands/interim/lib/measure.js +121 -0
  124. package/brands/interim/lib/pdf.js +597 -0
  125. package/brands/interim/lib/ttf.py +169 -0
  126. package/brands/interim/lib/wandxml.py +37 -0
  127. package/brands/interim/tokens.json +299 -0
  128. package/brands/next/tokens.json +14 -2
  129. package/examples/social/social.json +23 -0
  130. package/lib/audit.js +142 -0
  131. package/lib/brand.js +12 -7
  132. package/lib/components.js +42 -10
  133. package/lib/engine.js +72 -9
  134. package/lib/library.js +180 -0
  135. package/lib/pptx.js +1 -1
  136. package/lib/social.js +366 -0
  137. package/lib/talk.js +37 -4
  138. package/library/index.json +629 -0
  139. package/library/interim/avatar-800x800.png +0 -0
  140. package/library/interim/icons/apple-touch-icon.png +0 -0
  141. package/library/interim/icons/favicon-16.png +0 -0
  142. package/library/interim/icons/favicon-32.png +0 -0
  143. package/library/interim/icons/favicon-48.png +0 -0
  144. package/library/interim/icons/favicon.ico +0 -0
  145. package/library/interim/icons/favicon.svg +1 -0
  146. package/library/interim/icons/icon-192.png +0 -0
  147. package/library/interim/icons/icon-512.png +0 -0
  148. package/library/interim/icons/icon-tags.html +5 -0
  149. package/library/interim/icons/site.webmanifest +19 -0
  150. package/library/interim/linkedin-banner-1584x396.png +0 -0
  151. package/library/interim/linkedin-company-1128x191.png +0 -0
  152. package/library/interim/open-agent-discovery-share-1200x630.png +0 -0
  153. package/library/interim/open-agent-discovery-share-head-tags.html +17 -0
  154. package/library/interim/wand-ai-share-1200x630.png +0 -0
  155. package/library/interim/wand-ai-share-head-tags.html +17 -0
  156. package/library/interim/x-header-1500x500.png +0 -0
  157. package/package.json +2 -2
  158. package/templates/card.mjs +6 -2
  159. package/templates/page.mjs +1 -1
  160. package/templates/slide.mjs +1 -1
  161. package/templates/storyboard.mjs +1 -1
  162. package/wand.js +65 -6
@@ -0,0 +1,214 @@
1
+ {
2
+ "_": "Crops of the live hero's canvas (scale-story.desktop-wheel-20261020-r4.js, captured 2026-10-21 at its 1.8x cap, 2300x1652, captions suppressed) at each story stage. `<stage>` is RGBA on the night ground, edges feathered over 140px; `<stage>-alpha` is the same frame captured without the script's ground fill, for light grounds. `body` is the art's solid extent inside the crop (luminance > 40, or alpha > 120); a layout fits that box, not the file.",
3
+ "ground": "0B0913",
4
+ "worker": {
5
+ "file": "worker.png",
6
+ "size": [
7
+ 1126,
8
+ 1593
9
+ ],
10
+ "body": [
11
+ 220,
12
+ 220,
13
+ 906,
14
+ 1373
15
+ ],
16
+ "ring": [
17
+ 221,
18
+ 221,
19
+ 905,
20
+ 1372
21
+ ]
22
+ },
23
+ "worker-alpha": {
24
+ "file": "worker-alpha.png",
25
+ "size": [
26
+ 1125,
27
+ 1592
28
+ ],
29
+ "body": [
30
+ 220,
31
+ 220,
32
+ 905,
33
+ 1372
34
+ ],
35
+ "ring": [
36
+ 220,
37
+ 221,
38
+ 904,
39
+ 1372
40
+ ]
41
+ },
42
+ "learning": {
43
+ "file": "learning.png",
44
+ "size": [
45
+ 2206,
46
+ 1593
47
+ ],
48
+ "body": [
49
+ 220,
50
+ 220,
51
+ 2206,
52
+ 1373
53
+ ],
54
+ "ring": [
55
+ 828,
56
+ 224,
57
+ 1512,
58
+ 1370
59
+ ]
60
+ },
61
+ "learning-alpha": {
62
+ "file": "learning-alpha.png",
63
+ "size": [
64
+ 2153,
65
+ 1592
66
+ ],
67
+ "body": [
68
+ 220,
69
+ 220,
70
+ 2064,
71
+ 1372
72
+ ],
73
+ "ring": [
74
+ 775,
75
+ 224,
76
+ 1459,
77
+ 1370
78
+ ]
79
+ },
80
+ "organization": {
81
+ "file": "organization.png",
82
+ "size": [
83
+ 2147,
84
+ 1652
85
+ ],
86
+ "body": [
87
+ 28,
88
+ 192,
89
+ 1927,
90
+ 1601
91
+ ],
92
+ "ring": [
93
+ 789,
94
+ 193,
95
+ 1927,
96
+ 1225
97
+ ]
98
+ },
99
+ "organization-alpha": {
100
+ "file": "organization-alpha.png",
101
+ "size": [
102
+ 1715,
103
+ 1652
104
+ ],
105
+ "body": [
106
+ 220,
107
+ 188,
108
+ 1495,
109
+ 1600
110
+ ],
111
+ "ring": [
112
+ 218,
113
+ 188,
114
+ 1495,
115
+ 1312
116
+ ]
117
+ },
118
+ "economy": {
119
+ "file": "economy.png",
120
+ "size": [
121
+ 2037,
122
+ 1652
123
+ ],
124
+ "body": [
125
+ 220,
126
+ 21,
127
+ 1817,
128
+ 1550
129
+ ],
130
+ "ring": [
131
+ 325,
132
+ 112,
133
+ 1580,
134
+ 1454
135
+ ]
136
+ },
137
+ "economy-alpha": {
138
+ "file": "economy-alpha.png",
139
+ "size": [
140
+ 1930,
141
+ 1652
142
+ ],
143
+ "body": [
144
+ 220,
145
+ 33,
146
+ 1710,
147
+ 1543
148
+ ],
149
+ "ring": [
150
+ 233,
151
+ 111,
152
+ 1593,
153
+ 1473
154
+ ]
155
+ },
156
+ "agent": {
157
+ "file": "agent.png",
158
+ "size": [
159
+ 1190,
160
+ 1400
161
+ ],
162
+ "body": [
163
+ 220,
164
+ 52,
165
+ 970,
166
+ 1225
167
+ ],
168
+ "ring": [
169
+ 224,
170
+ 53,
171
+ 969,
172
+ 1224
173
+ ]
174
+ },
175
+ "agent-alpha": {
176
+ "file": "agent-alpha.png",
177
+ "size": [
178
+ 1190,
179
+ 1400
180
+ ],
181
+ "body": [
182
+ 220,
183
+ 53,
184
+ 970,
185
+ 1224
186
+ ],
187
+ "ring": [
188
+ 220,
189
+ 53,
190
+ 969,
191
+ 1224
192
+ ]
193
+ },
194
+ "worker-light": {
195
+ "file": "worker-light.png",
196
+ "size": [
197
+ 1125,
198
+ 1592
199
+ ],
200
+ "body": [
201
+ 220,
202
+ 220,
203
+ 905,
204
+ 1372
205
+ ],
206
+ "ring": [
207
+ 220,
208
+ 221,
209
+ 904,
210
+ 1372
211
+ ],
212
+ "_": "worker-alpha.png with its translucent pixels tinted toward violetStrong (tools/tint-light.py), for the composition on light"
213
+ }
214
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -131 3120 999" role="img" aria-label="Wand"><path d="M0 235.79A7.79 7.79 0 0 1 7.79 228L152.47 228A7.79 7.79 0 0 1 160.25 235.79L160.25 589.17A120.25 120.25 0 0 0 400.75 589.17L400.75 235.79A7.79 7.79 0 0 1 408.53 228L553.21 228A7.79 7.79 0 0 1 561 235.79L561 589.17A120.25 120.25 0 0 0 801.49 589.17L801.49 324.02A7.79 7.79 0 0 1 809.28 316.24L810.14 316.24Q814.03 316.24 816.68 319.09L839.27 343.38A57.97 57.97 0 0 0 924.18 343.38L946.77 319.09Q949.42 316.24 953.31 316.24L953.96 316.24A7.79 7.79 0 0 1 961.75 324.02L961.75 589.17A280.5 280.5 0 0 1 487.02 791.55Q480.87 785.46 474.72 791.55A280.5 280.5 0 0 1 0 589.17ZM1068.5 548A320 320 0 1 1 1708.5 548A320 320 0 1 1 1068.5 548ZM1555 228L1703 228A6 6 0 0 1 1709 234L1709 861A6 6 0 0 1 1703 867L1555 867A6 6 0 0 1 1549 861L1549 234A6 6 0 0 1 1555 228ZM1549 547.5A160.5 160.5 0 1 0 1228 547.5A160.5 160.5 0 1 0 1549 547.5ZM1816 861L1816 505.5A277.5 277.5 0 0 1 2371 505.5L2371 861A6 6 0 0 1 2365 867L2219 867A6 6 0 0 1 2213 861L2213 515A119.5 119.5 0 0 0 1974 515L1974 861A6 6 0 0 1 1968 867L1822 867A6 6 0 0 1 1816 861ZM2478 547A319.5 319.5 0 1 1 3117 547A319.5 319.5 0 1 1 2478 547ZM2963 2L3113.5 2A6 6 0 0 1 3119.5 8L3119.5 861A6 6 0 0 1 3113.5 867L2963 867A6 6 0 0 1 2957 861L2957 8A6 6 0 0 1 2963 2ZM2957 575.43L2957 531.57A161 161 0 1 0 2957 575.43Z" fill="#ffffff"/><path d="M871.04 -125.15A7.53 7.53 0 0 1 886.41 -125.15A193.62 193.62 0 0 0 1072.35 60.79A7.53 7.53 0 0 1 1072.35 76.15A193.62 193.62 0 0 0 886.41 262.09A7.53 7.53 0 0 1 871.04 262.09A193.62 193.62 0 0 0 685.11 76.15A7.53 7.53 0 0 1 685.11 60.79A193.62 193.62 0 0 0 871.04 -125.15Z" fill="#c2a1ef"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -131 3120 999" role="img" aria-label="Wand"><path d="M0 235.79A7.79 7.79 0 0 1 7.79 228L152.47 228A7.79 7.79 0 0 1 160.25 235.79L160.25 589.17A120.25 120.25 0 0 0 400.75 589.17L400.75 235.79A7.79 7.79 0 0 1 408.53 228L553.21 228A7.79 7.79 0 0 1 561 235.79L561 589.17A120.25 120.25 0 0 0 801.49 589.17L801.49 324.02A7.79 7.79 0 0 1 809.28 316.24L810.14 316.24Q814.03 316.24 816.68 319.09L839.27 343.38A57.97 57.97 0 0 0 924.18 343.38L946.77 319.09Q949.42 316.24 953.31 316.24L953.96 316.24A7.79 7.79 0 0 1 961.75 324.02L961.75 589.17A280.5 280.5 0 0 1 487.02 791.55Q480.87 785.46 474.72 791.55A280.5 280.5 0 0 1 0 589.17ZM1068.5 548A320 320 0 1 1 1708.5 548A320 320 0 1 1 1068.5 548ZM1555 228L1703 228A6 6 0 0 1 1709 234L1709 861A6 6 0 0 1 1703 867L1555 867A6 6 0 0 1 1549 861L1549 234A6 6 0 0 1 1555 228ZM1549 547.5A160.5 160.5 0 1 0 1228 547.5A160.5 160.5 0 1 0 1549 547.5ZM1816 861L1816 505.5A277.5 277.5 0 0 1 2371 505.5L2371 861A6 6 0 0 1 2365 867L2219 867A6 6 0 0 1 2213 861L2213 515A119.5 119.5 0 0 0 1974 515L1974 861A6 6 0 0 1 1968 867L1822 867A6 6 0 0 1 1816 861ZM2478 547A319.5 319.5 0 1 1 3117 547A319.5 319.5 0 1 1 2478 547ZM2963 2L3113.5 2A6 6 0 0 1 3119.5 8L3119.5 861A6 6 0 0 1 3113.5 867L2963 867A6 6 0 0 1 2957 861L2957 8A6 6 0 0 1 2963 2ZM2957 575.43L2957 531.57A161 161 0 1 0 2957 575.43Z" fill="#240647"/><path d="M871.04 -125.15A7.53 7.53 0 0 1 886.41 -125.15A193.62 193.62 0 0 0 1072.35 60.79A7.53 7.53 0 0 1 1072.35 76.15A193.62 193.62 0 0 0 886.41 262.09A7.53 7.53 0 0 1 871.04 262.09A193.62 193.62 0 0 0 685.11 76.15A7.53 7.53 0 0 1 685.11 60.79A193.62 193.62 0 0 0 871.04 -125.15Z" fill="#765098"/></svg>
@@ -0,0 +1,93 @@
1
+ Copyright 2024 The Geist Project Authors (https://github.com/vercel/geist-font.git)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ https://openfontlicense.org
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,93 @@
1
+ Copyright 2020 The Newsreader Project Authors (http://github.com/productiontype/Newsreader)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,12 @@
1
+ {
2
+ "_": "The one table that maps a PowerPoint family name to the files that draw it; measure.js, pdf.js, embed_fonts.py and brandlint read it. Weight rides on the family name (\"Geist Medium\" is the display's 500 since the approved design of 2026-10-22), so a renderer that matches by name has one face per name. `tracking` is letter-spacing in em: the display and phrase untracked, as approved; the label's caps add their own 0.11em where they are set.",
3
+ "default": "Geist",
4
+ "families": {
5
+ "Geist": { "role": "body", "r": "Geist-Regular", "b": "Geist-SemiBold", "i": "Geist-Regular", "bi": "Geist-SemiBold", "tracking": 0, "license": "SIL OFL 1.1, see OFL-Geist.txt" },
6
+ "Geist Book": { "role": "site display (450)", "r": "Geist-Book", "b": "Geist-SemiBold", "i": "Geist-Book", "bi": "Geist-SemiBold", "tracking": -0.063, "license": "SIL OFL 1.1, see OFL-Geist.txt" },
7
+ "Geist Medium": { "role": "display, label", "r": "Geist-Medium", "b": "Geist-SemiBold", "i": "Geist-Medium", "bi": "Geist-SemiBold", "tracking": 0, "license": "SIL OFL 1.1, see OFL-Geist.txt" },
8
+ "Geist SemiBold": { "role": "bold", "r": "Geist-SemiBold", "b": "Geist-SemiBold", "i": "Geist-SemiBold", "bi": "Geist-SemiBold", "tracking": 0, "license": "SIL OFL 1.1, see OFL-Geist.txt" },
9
+ "Newsreader": { "role": "site phrase (400)", "r": "Newsreader-Regular", "b": "Newsreader-Regular", "i": "Newsreader-Italic", "bi": "Newsreader-Italic", "tracking": -0.049, "license": "SIL OFL 1.1, see OFL-Newsreader.txt" },
10
+ "Newsreader Medium": { "role": "phrase", "r": "Newsreader-Medium", "b": "Newsreader-Medium", "i": "Newsreader-MediumItalic", "bi": "Newsreader-MediumItalic", "tracking": 0, "license": "SIL OFL 1.1, see OFL-Newsreader.txt" }
11
+ }
12
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * lib/composition.js — draws one window of the approved social composition onto a canvas.
3
+ *
4
+ * A browser script. The page that loads it sets `window.CARD` (lib/composition.mjs
5
+ * writes such pages): the composition from assets/social/composition.json, the
6
+ * palette resolved to hex for one ground, the wordmark's paths, the ring's file,
7
+ * and the window, a scale `s` (px per master unit) with the master point at the
8
+ * card's top-left (`x0`, `y0`). Everything is drawn in master units, so the art,
9
+ * the lockup and the stroke widths scale together and no part is re-proportioned
10
+ * per format. `lockup: false` draws the art alone, for a page that sets its own
11
+ * title over it. A fit gate is written as JSON into <script id="gate">.
12
+ */
13
+ (async function () {
14
+ const C = window.CARD, D = C.composition, P = C.palette;
15
+ const dpr = window.devicePixelRatio || 1;
16
+ const cv = document.getElementById("card");
17
+ cv.width = Math.round(C.w * dpr); cv.height = Math.round(C.h * dpr);
18
+ cv.style.width = C.w + "px"; cv.style.height = C.h + "px";
19
+ const ctx = cv.getContext("2d");
20
+
21
+ await Promise.all((C.fonts || []).map((f) => document.fonts.load(f)));
22
+ const ring = await new Promise((ok, no) => { const i = new Image(); i.onload = () => ok(i); i.onerror = () => no(new Error("ring image did not load: " + C.ringSrc)); i.src = C.ringSrc; });
23
+
24
+ const hex = (h) => [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
25
+ const rgba = (c, a) => `rgba(${c.map((v) => Math.round(Math.max(0, Math.min(255, v)))).join(",")},${Math.max(0, Math.min(1, a)).toFixed(4)})`;
26
+ const mix = (a, b, t) => a.map((v, i) => v + (b[i] - v) * t);
27
+
28
+ // master -> device pixels
29
+ const k = C.s * dpr;
30
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
31
+ ctx.fillStyle = "#" + P.ground; ctx.fillRect(0, 0, cv.width, cv.height);
32
+ ctx.setTransform(k, 0, 0, k, -C.x0 * k, -C.y0 * k);
33
+ const toPx = (x, y) => [(x - C.x0) * C.s, (y - C.y0) * C.s];
34
+ const left = C.x0, top = C.y0, right = C.x0 + C.w / C.s, bottom = C.y0 + C.h / C.s;
35
+
36
+ // ---- the planet: a disc of ground over a light that falls off outside its edge
37
+ const pl = D.planet, glowEnd = pl.glow[pl.glow.length - 1][0], light = hex(P.light);
38
+ const g = ctx.createRadialGradient(pl.c[0], pl.c[1], pl.r, pl.c[0], pl.c[1], pl.r + glowEnd);
39
+ for (const [t, a] of pl.glow) g.addColorStop(t / glowEnd, rgba(light, a * P.glowGain));
40
+ ctx.fillStyle = g; ctx.fillRect(left, top, right - left, bottom - top);
41
+ ctx.beginPath(); ctx.arc(pl.c[0], pl.c[1], pl.r, 0, Math.PI * 2);
42
+ ctx.fillStyle = "#" + P.planet; ctx.fill();
43
+
44
+ // ---- trails: y(x) polylines, tinted and faded along x by the measured stops
45
+ const tr = D.trails, head = hex(P.trailHead), tail = hex(P.trailTail);
46
+ for (const t of tr.list) {
47
+ const x0 = t.points[0][0], x1 = t.points[t.points.length - 1][0];
48
+ const grad = ctx.createLinearGradient(x0, 0, x1, 0);
49
+ for (const [x, kk, tt] of t.stops) {
50
+ if (x < x0 || x > x1) continue;
51
+ grad.addColorStop((x - x0) / (x1 - x0), rgba(mix(head, tail, Math.max(0, tt)), Math.min(1, kk / tr.width) * P.trailGain));
52
+ }
53
+ ctx.beginPath();
54
+ t.points.forEach(([x, y], i) => (i ? ctx.lineTo(x, y) : ctx.moveTo(x, y)));
55
+ ctx.lineWidth = tr.width; ctx.lineCap = "round"; ctx.lineJoin = "round";
56
+ ctx.strokeStyle = grad; ctx.stroke();
57
+ }
58
+
59
+ // ---- dots: a core in a gaussian glow
60
+ const dt = D.dots, R = dt.glowSigma * 3;
61
+ for (const d of dt.list) {
62
+ const gg = ctx.createRadialGradient(d.x, d.y, 0, d.x, d.y, R);
63
+ for (let i = 0; i <= 12; i++) { const r = (i / 12) * R; gg.addColorStop(i / 12, rgba(hex(P.dotGlow), dt.glowAlpha * P.dotGain * Math.exp(-(r * r) / (2 * dt.glowSigma * dt.glowSigma)) * (i === 12 ? 0 : 1))); }
64
+ ctx.fillStyle = gg; ctx.beginPath(); ctx.arc(d.x, d.y, R, 0, Math.PI * 2); ctx.fill();
65
+ ctx.fillStyle = "#" + P.dot; ctx.beginPath(); ctx.arc(d.x, d.y, dt.core, 0, Math.PI * 2); ctx.fill();
66
+ }
67
+
68
+ // ---- the ring, over the trails: they show through its opening
69
+ const rg = D.ring, [lx0, ly0, lx1, ly1] = rg.lumBox, [bx0, by0, bx1, by1] = rg.box, rdx = (C.ring && C.ring.dx) || 0, rdy = (C.ring && C.ring.dy) || 0;
70
+ const sc = ((bx1 - bx0) / (lx1 - lx0) + (by1 - by0) / (ly1 - ly0)) / 2;
71
+ ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = "high";
72
+ ctx.drawImage(ring, bx0 + rdx - lx0 * sc, by0 + rdy - ly0 * sc, ring.naturalWidth * sc, ring.naturalHeight * sc);
73
+ const ringBox = [bx0 + rdx, by0 + rdy, bx1 + rdx, by1 + rdy];
74
+
75
+ // ---- a photograph over the art (`C.photo`: a cut-out with its own transparency, filling the card): the brand's one
76
+ // grade, as the kit's photo() lays it (blacks lift toward gradeLow at 42%, highlights settle toward gradeHigh at 55%),
77
+ // so a person cut from daylight sits in the scene. A profile photo that continues the banner is this over the banner's art.
78
+ if (C.photo) {
79
+ const im = await new Promise((ok, no) => { const i = new Image(); i.onload = () => ok(i); i.onerror = () => no(new Error("photo did not load: " + C.photo.src)); i.src = C.photo.src; });
80
+ const layer = () => { const c = document.createElement("canvas"); c.width = cv.width; c.height = cv.height; return [c, c.getContext("2d")]; };
81
+ const [cut, c0] = layer(), sc2 = Math.max(cut.width / im.naturalWidth, cut.height / im.naturalHeight);
82
+ c0.imageSmoothingQuality = "high";
83
+ c0.drawImage(im, (cut.width - im.naturalWidth * sc2) / 2, (cut.height - im.naturalHeight * sc2) / 2, im.naturalWidth * sc2, im.naturalHeight * sc2);
84
+ // the grade by blending, not by reading pixels back (a file:// image taints the canvas): lighten toward the low
85
+ // colour, darken toward the high, each clipped to the cut-out's own shape
86
+ const [g, g0] = layer();
87
+ g0.drawImage(cut, 0, 0);
88
+ for (const [op, colour, a] of [["lighten", C.photo.low, 0.42], ["darken", C.photo.high, 0.55]]) {
89
+ g0.globalCompositeOperation = op; g0.fillStyle = rgba(hex(colour), a); g0.fillRect(0, 0, g.width, g.height);
90
+ g0.globalCompositeOperation = "destination-in"; g0.drawImage(cut, 0, 0);
91
+ }
92
+ ctx.save(); ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.drawImage(g, 0, 0); ctx.restore();
93
+ }
94
+
95
+ // ---- the lockup, over everything: the wordmark (letters and its approved spark, as the SVG draws them) and two lines.
96
+ // In place, it sits where the approved exports set it, at the art's scale. `C.set` sets it on its own instead, for a
97
+ // card that must survive a crop or a thumbnail: { size (the lead's px) or fit: { width, max } (the largest size whose
98
+ // block fits `width`, up to `max`), x (the lead's ink left; the centre axis when centred; omitted, the block is centred
99
+ // on the card), top (the spark's tip) or middle (the block's centre), align "left" | "centre", indent (em, the phrase's
100
+ // step; the approved 2.49 by default), wordmark (its scale alone, 1 by default) }.
101
+ const boxes = {};
102
+ let lockPx = null, typePx = +(D.lockup.lead.size * C.s).toFixed(1);
103
+ if (C.lockup !== false) {
104
+ const L = D.lockup, W = C.paths, S = C.set || null, wk = L.wordmark.height / W.lettersH;
105
+ const ox = S ? 0 : (C.lockup && C.lockup.dx) || 0, oy = S ? 0 : (C.lockup && C.lockup.dy) || 0;
106
+ const align = (S && S.align) || "left", centre = S && align === "centre" ? L.lead.inkLeft : null;
107
+ const indent = S && S.indent != null ? S.indent : (L.phrase.inkLeft - L.lead.inkLeft) / L.lead.size;
108
+ // the wordmark, scaled alone if asked, its baseline and left edge held
109
+ const wmS = (S && S.wordmark) || 1, wkS = wk * wmS, wW = W.lettersW * wkS, wH = L.wordmark.height * wmS;
110
+ const wl = centre != null ? centre - wW / 2 : L.wordmark.centerX + ox - (W.lettersW * wk) / 2, wt = L.wordmark.top + oy + L.wordmark.height - wH;
111
+ boxes.wordmark = [wl, wt + (W.sparkTop - W.lettersTop) * wkS, wl + wW, wt + wH];
112
+ // the two lines, measured before anything is drawn (metrics are in the font's own px, whatever the transform)
113
+ const lines = [["lead", { ...L.lead, ...(C.text && C.text.lead ? { text: C.text.lead } : {}) }, P.ink, L.lead.inkLeft + ox],
114
+ ["phrase", { ...L.phrase, ...(C.text && C.text.phrase ? { text: C.text.phrase } : {}) }, P.phrase, S ? L.lead.inkLeft + indent * L.lead.size : L.phrase.inkLeft + ox]];
115
+ const font = (spec) => { ctx.font = `${spec.italic ? "italic " : ""}${spec.weight} ${spec.size}px "${spec.family}"`; ctx.letterSpacing = (spec.tracking || 0) * spec.size + "px"; };
116
+ const set = lines.map(([key, spec, colour, left]) => {
117
+ font(spec);
118
+ const m = ctx.measureText(spec.text), w = m.actualBoundingBoxLeft + m.actualBoundingBoxRight;
119
+ const inkLeft = centre != null ? centre - w / 2 : left, y = spec.baseline + oy;
120
+ boxes[key] = [inkLeft, y - m.actualBoundingBoxAscent, inkLeft + w, y + m.actualBoundingBoxDescent];
121
+ return { key, spec, colour, x: inkLeft + m.actualBoundingBoxLeft, y };
122
+ });
123
+ const all = Object.values(boxes);
124
+ const blk = [Math.min(...all.map((b) => b[0])), boxes.wordmark[1], Math.max(...all.map((b) => b[2])), Math.max(boxes.lead[3], boxes.phrase[3])];
125
+ // where the block lands: in place, the art's own transform; set, a transform of its own
126
+ let toCard;
127
+ if (S) {
128
+ const u = S.fit ? Math.min(S.fit.max / L.lead.size, S.fit.width / (blk[2] - blk[0])) : S.size / L.lead.size;
129
+ const X0 = S.x == null ? C.w / 2 - ((blk[2] - blk[0]) * u) / 2 : S.x - ((centre != null ? centre : L.lead.inkLeft) - blk[0]) * u;
130
+ const Y0 = S.middle != null ? S.middle - ((blk[3] - blk[1]) * u) / 2 : S.top;
131
+ ctx.setTransform(dpr * u, 0, 0, dpr * u, dpr * (X0 - blk[0] * u), dpr * (Y0 - blk[1] * u));
132
+ toCard = (b) => [X0 + (b[0] - blk[0]) * u, Y0 + (b[1] - blk[1]) * u, X0 + (b[2] - blk[0]) * u, Y0 + (b[3] - blk[1]) * u];
133
+ typePx = +(L.lead.size * u).toFixed(1);
134
+ } else {
135
+ toCard = (b) => { const [a, c] = toPx(b[0], b[1]), [e, f] = toPx(b[2], b[3]); return [a, c, e, f]; };
136
+ }
137
+ ctx.save();
138
+ ctx.translate(wl, wt - W.lettersTop * wkS); ctx.scale(wkS, wkS);
139
+ ctx.fillStyle = "#" + P.wordmark; ctx.fill(new Path2D(W.letters));
140
+ ctx.fillStyle = "#" + P.spark; ctx.fill(new Path2D(W.spark));
141
+ ctx.restore();
142
+ for (const l of set) { font(l.spec); ctx.fillStyle = "#" + l.colour; ctx.fillText(l.spec.text, l.x, l.y); }
143
+ for (const k2 of Object.keys(boxes)) boxes[k2] = toCard(boxes[k2]).map((v) => Math.round(v * 10) / 10);
144
+ lockPx = toCard(blk).map((v) => Math.round(v * 10) / 10);
145
+ ctx.setTransform(k, 0, 0, k, -C.x0 * k, -C.y0 * k);
146
+ }
147
+
148
+ // ---- gate: the rules every card keeps (px, at 1x)
149
+ const px = (b) => { const [a, c] = toPx(b[0], b[1]), [e, f] = toPx(b[2], b[3]); return [a, c, e, f].map((v) => Math.round(v * 10) / 10); };
150
+ const r = C.rules || {}, reasons = [];
151
+ const inside = (b, m) => b[0] >= m - 0.5 && b[1] >= m - 0.5 && b[2] <= C.w - m + 0.5 && b[3] <= C.h - m + 0.5;
152
+ const pRing = px(ringBox);
153
+ if (lockPx) {
154
+ if (r.margin != null && !inside(lockPx, r.margin)) reasons.push(`the lockup comes within ${r.margin}px of an edge`);
155
+ if (pRing[0] < lockPx[2] + (r.gap || 0) && pRing[2] > lockPx[0] && pRing[1] < lockPx[3] && pRing[3] > lockPx[1]) reasons.push("the ring crowds the lockup");
156
+ if (r.minType && typePx < r.minType) reasons.push(`type at ${typePx}px is under the ${r.minType}px floor`);
157
+ // a square crop from the centre (Slack's and WhatsApp's thumbnails) must keep the whole lockup
158
+ if (r.squareSafe != null && C.w > C.h) {
159
+ const sx0 = (C.w - C.h) / 2 + r.squareSafe, sx1 = (C.w + C.h) / 2 - r.squareSafe;
160
+ if (lockPx[0] < sx0 - 0.5 || lockPx[2] > sx1 + 0.5) reasons.push(`the lockup leaves the centre square (x ${sx0} to ${sx1})`);
161
+ }
162
+ }
163
+ if (r.ringWhole && !inside(pRing, r.ringMargin || 0)) reasons.push("the ring is cut by an edge");
164
+ const gate = { ok: !reasons.length, reasons, s: C.s, typePx, lockup: lockPx, ring: pRing,
165
+ dots: dt.list.map((d) => toPx(d.x, d.y).map(Math.round)), boxes };
166
+ const s = document.createElement("script"); s.type = "application/json"; s.id = "gate"; s.textContent = JSON.stringify(gate);
167
+ document.body.appendChild(s);
168
+ document.body.dataset.done = "1";
169
+ })().catch((e) => { const s = document.createElement("script"); s.type = "application/json"; s.id = "gate"; s.textContent = JSON.stringify({ ok: false, reasons: ["compose failed: " + e] }); document.body.appendChild(s); });