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.
- package/PROMPT.md +221 -19
- package/SKILL.md +100 -20
- package/START-HERE.md +12 -1
- package/brands/current/tokens.json +10 -2
- package/brands/interim/assets/avatar.svg +1 -0
- package/brands/interim/assets/closing-art.png +0 -0
- package/brands/interim/assets/cover-art.png +0 -0
- package/brands/interim/assets/cover-portrait-art.png +0 -0
- package/brands/interim/assets/divider-art.png +0 -0
- package/brands/interim/assets/glyphs/README.md +29 -0
- package/brands/interim/assets/glyphs/activity.svg +15 -0
- package/brands/interim/assets/glyphs/atom.svg +17 -0
- package/brands/interim/assets/glyphs/badge-check.svg +16 -0
- package/brands/interim/assets/glyphs/bell.svg +16 -0
- package/brands/interim/assets/glyphs/blocks.svg +16 -0
- package/brands/interim/assets/glyphs/book-open.svg +16 -0
- package/brands/interim/assets/glyphs/bot.svg +20 -0
- package/brands/interim/assets/glyphs/boxes.svg +26 -0
- package/brands/interim/assets/glyphs/brain.svg +23 -0
- package/brands/interim/assets/glyphs/briefcase.svg +16 -0
- package/brands/interim/assets/glyphs/building-2.svg +21 -0
- package/brands/interim/assets/glyphs/calendar.svg +18 -0
- package/brands/interim/assets/glyphs/chart-column.svg +18 -0
- package/brands/interim/assets/glyphs/chart-line.svg +16 -0
- package/brands/interim/assets/glyphs/chart-pie.svg +16 -0
- package/brands/interim/assets/glyphs/circle-check.svg +16 -0
- package/brands/interim/assets/glyphs/clipboard-check.svg +17 -0
- package/brands/interim/assets/glyphs/clock.svg +16 -0
- package/brands/interim/assets/glyphs/cloud.svg +15 -0
- package/brands/interim/assets/glyphs/code.svg +15 -0
- package/brands/interim/assets/glyphs/coins.svg +18 -0
- package/brands/interim/assets/glyphs/compass.svg +16 -0
- package/brands/interim/assets/glyphs/cpu.svg +24 -0
- package/brands/interim/assets/glyphs/database.svg +17 -0
- package/brands/interim/assets/glyphs/eye.svg +16 -0
- package/brands/interim/assets/glyphs/file-check.svg +17 -0
- package/brands/interim/assets/glyphs/filter.svg +15 -0
- package/brands/interim/assets/glyphs/fingerprint.svg +23 -0
- package/brands/interim/assets/glyphs/flag.svg +16 -0
- package/brands/interim/assets/glyphs/gauge.svg +16 -0
- package/brands/interim/assets/glyphs/gavel.svg +19 -0
- package/brands/interim/assets/glyphs/git-branch.svg +18 -0
- package/brands/interim/assets/glyphs/globe.svg +17 -0
- package/brands/interim/assets/glyphs/graduation-cap.svg +17 -0
- package/brands/interim/assets/glyphs/handshake.svg +19 -0
- package/brands/interim/assets/glyphs/hexagon.svg +15 -0
- package/brands/interim/assets/glyphs/index.json +177 -0
- package/brands/interim/assets/glyphs/infinity.svg +15 -0
- package/brands/interim/assets/glyphs/key.svg +17 -0
- package/brands/interim/assets/glyphs/landmark.svg +20 -0
- package/brands/interim/assets/glyphs/layers.svg +17 -0
- package/brands/interim/assets/glyphs/lightbulb.svg +17 -0
- package/brands/interim/assets/glyphs/link.svg +16 -0
- package/brands/interim/assets/glyphs/list-checks.svg +19 -0
- package/brands/interim/assets/glyphs/lock.svg +16 -0
- package/brands/interim/assets/glyphs/map.svg +17 -0
- package/brands/interim/assets/glyphs/megaphone.svg +16 -0
- package/brands/interim/assets/glyphs/message-square.svg +15 -0
- package/brands/interim/assets/glyphs/milestone.svg +17 -0
- package/brands/interim/assets/glyphs/network.svg +19 -0
- package/brands/interim/assets/glyphs/orbit.svg +19 -0
- package/brands/interim/assets/glyphs/package.svg +18 -0
- package/brands/interim/assets/glyphs/plug.svg +18 -0
- package/brands/interim/assets/glyphs/puzzle.svg +15 -0
- package/brands/interim/assets/glyphs/radar.svg +22 -0
- package/brands/interim/assets/glyphs/receipt.svg +17 -0
- package/brands/interim/assets/glyphs/refresh-cw.svg +18 -0
- package/brands/interim/assets/glyphs/rocket.svg +18 -0
- package/brands/interim/assets/glyphs/route.svg +17 -0
- package/brands/interim/assets/glyphs/scale.svg +19 -0
- package/brands/interim/assets/glyphs/search.svg +16 -0
- package/brands/interim/assets/glyphs/server.svg +18 -0
- package/brands/interim/assets/glyphs/settings.svg +16 -0
- package/brands/interim/assets/glyphs/share-2.svg +19 -0
- package/brands/interim/assets/glyphs/shield-check.svg +16 -0
- package/brands/interim/assets/glyphs/shuffle.svg +19 -0
- package/brands/interim/assets/glyphs/sparkles.svg +19 -0
- package/brands/interim/assets/glyphs/square-stack.svg +17 -0
- package/brands/interim/assets/glyphs/target.svg +17 -0
- package/brands/interim/assets/glyphs/trending-up.svg +16 -0
- package/brands/interim/assets/glyphs/triangle-alert.svg +17 -0
- package/brands/interim/assets/glyphs/trophy.svg +20 -0
- package/brands/interim/assets/glyphs/users.svg +18 -0
- package/brands/interim/assets/glyphs/wallet.svg +16 -0
- package/brands/interim/assets/glyphs/wand-sparkles.svg +22 -0
- package/brands/interim/assets/glyphs/workflow.svg +17 -0
- package/brands/interim/assets/glyphs/zap.svg +15 -0
- package/brands/interim/assets/mark.svg +1 -0
- package/brands/interim/assets/social/composition.json +6833 -0
- package/brands/interim/assets/spark-lavender.png +0 -0
- package/brands/interim/assets/spark-violet.png +0 -0
- package/brands/interim/assets/statement-art.png +0 -0
- package/brands/interim/assets/story/agent-alpha.png +0 -0
- package/brands/interim/assets/story/agent.png +0 -0
- package/brands/interim/assets/story/economy-alpha.png +0 -0
- package/brands/interim/assets/story/economy.png +0 -0
- package/brands/interim/assets/story/index.json +214 -0
- package/brands/interim/assets/story/learning-alpha.png +0 -0
- package/brands/interim/assets/story/learning.png +0 -0
- package/brands/interim/assets/story/organization-alpha.png +0 -0
- package/brands/interim/assets/story/organization.png +0 -0
- package/brands/interim/assets/story/worker-alpha.png +0 -0
- package/brands/interim/assets/story/worker-light.png +0 -0
- package/brands/interim/assets/story/worker.png +0 -0
- package/brands/interim/assets/wordmark-on-dark.png +0 -0
- package/brands/interim/assets/wordmark-on-dark.svg +1 -0
- package/brands/interim/assets/wordmark-on-light.png +0 -0
- package/brands/interim/assets/wordmark-on-light.svg +1 -0
- package/brands/interim/fonts/Geist-Book.ttf +0 -0
- package/brands/interim/fonts/Geist-Medium.ttf +0 -0
- package/brands/interim/fonts/Geist-Regular.ttf +0 -0
- package/brands/interim/fonts/Geist-SemiBold.ttf +0 -0
- package/brands/interim/fonts/Newsreader-Italic.ttf +0 -0
- package/brands/interim/fonts/Newsreader-Medium.ttf +0 -0
- package/brands/interim/fonts/Newsreader-MediumItalic.ttf +0 -0
- package/brands/interim/fonts/Newsreader-Regular.ttf +0 -0
- package/brands/interim/fonts/OFL-Geist.txt +93 -0
- package/brands/interim/fonts/OFL-Newsreader.txt +93 -0
- package/brands/interim/fonts/faces.json +12 -0
- package/brands/interim/lib/composition.js +169 -0
- package/brands/interim/lib/composition.mjs +101 -0
- package/brands/interim/lib/glyphs.js +89 -0
- package/brands/interim/lib/measure.js +121 -0
- package/brands/interim/lib/pdf.js +597 -0
- package/brands/interim/lib/ttf.py +169 -0
- package/brands/interim/lib/wandxml.py +37 -0
- package/brands/interim/tokens.json +299 -0
- package/brands/next/tokens.json +14 -2
- package/examples/social/social.json +23 -0
- package/lib/audit.js +142 -0
- package/lib/brand.js +12 -7
- package/lib/components.js +42 -10
- package/lib/engine.js +72 -9
- package/lib/library.js +180 -0
- package/lib/pptx.js +1 -1
- package/lib/social.js +366 -0
- package/lib/talk.js +37 -4
- package/library/index.json +629 -0
- package/library/interim/avatar-800x800.png +0 -0
- package/library/interim/icons/apple-touch-icon.png +0 -0
- package/library/interim/icons/favicon-16.png +0 -0
- package/library/interim/icons/favicon-32.png +0 -0
- package/library/interim/icons/favicon-48.png +0 -0
- package/library/interim/icons/favicon.ico +0 -0
- package/library/interim/icons/favicon.svg +1 -0
- package/library/interim/icons/icon-192.png +0 -0
- package/library/interim/icons/icon-512.png +0 -0
- package/library/interim/icons/icon-tags.html +5 -0
- package/library/interim/icons/site.webmanifest +19 -0
- package/library/interim/linkedin-banner-1584x396.png +0 -0
- package/library/interim/linkedin-company-1128x191.png +0 -0
- package/library/interim/open-agent-discovery-share-1200x630.png +0 -0
- package/library/interim/open-agent-discovery-share-head-tags.html +17 -0
- package/library/interim/wand-ai-share-1200x630.png +0 -0
- package/library/interim/wand-ai-share-head-tags.html +17 -0
- package/library/interim/x-header-1500x500.png +0 -0
- package/package.json +2 -2
- package/templates/card.mjs +6 -2
- package/templates/page.mjs +1 -1
- package/templates/slide.mjs +1 -1
- package/templates/storyboard.mjs +1 -1
- package/wand.js +65 -6
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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>
|
|
Binary file
|
|
@@ -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>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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); });
|