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
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ttf.py — the ~1% of TrueType that embed_fonts.py needs, with no fontTools.
|
|
3
|
+
|
|
4
|
+
Reads `head` and `OS/2` for the fields an EOT header carries, and rewrites the
|
|
5
|
+
`name` table so a face reports the family we address it by. Weight in this
|
|
6
|
+
system is carried by the family name — `Geist SemiBold` regular, not `Geist`
|
|
7
|
+
bold — so retagging is not cosmetic: without it PowerPoint resolves four of the
|
|
8
|
+
five families to the same face.
|
|
9
|
+
|
|
10
|
+
Everything else in the font is copied through untouched.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import struct
|
|
14
|
+
|
|
15
|
+
_HEAD_CHECKSUM_ADJ = 8 # offset of checkSumAdjustment within `head`
|
|
16
|
+
_MAGIC = 0xB1B0AFBA
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TTFError(ValueError):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TTF:
|
|
24
|
+
def __init__(self, data):
|
|
25
|
+
if len(data) < 12:
|
|
26
|
+
raise TTFError("not a font: too short")
|
|
27
|
+
tag = data[:4]
|
|
28
|
+
if tag not in (b"\x00\x01\x00\x00", b"true", b"ttcf", b"OTTO"):
|
|
29
|
+
raise TTFError(f"unrecognised sfnt tag {tag!r}")
|
|
30
|
+
if tag == b"ttcf":
|
|
31
|
+
raise TTFError("font collections (.ttc) are not supported — pass a single face")
|
|
32
|
+
self.data = data
|
|
33
|
+
self.sfnt = tag
|
|
34
|
+
(num,) = struct.unpack(">H", data[4:6])
|
|
35
|
+
self.tables = {}
|
|
36
|
+
for i in range(num):
|
|
37
|
+
off = 12 + i * 16
|
|
38
|
+
t, csum, o, ln = struct.unpack(">4sIII", data[off:off + 16])
|
|
39
|
+
self.tables[t] = (o, ln, csum)
|
|
40
|
+
|
|
41
|
+
# -- reading -----------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
def table(self, tag):
|
|
44
|
+
if isinstance(tag, str):
|
|
45
|
+
tag = tag.encode()
|
|
46
|
+
if tag not in self.tables:
|
|
47
|
+
raise TTFError(f"font has no {tag.decode()} table")
|
|
48
|
+
o, ln, _ = self.tables[tag]
|
|
49
|
+
return self.data[o:o + ln]
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def check_sum_adjustment(self):
|
|
53
|
+
return struct.unpack(">I", self.table("head")[8:12])[0]
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def os2(self):
|
|
57
|
+
"""The OS/2 fields the EOT header needs."""
|
|
58
|
+
t = self.table("OS/2")
|
|
59
|
+
version = struct.unpack(">H", t[0:2])[0]
|
|
60
|
+
d = {
|
|
61
|
+
"version": version,
|
|
62
|
+
"usWeightClass": struct.unpack(">H", t[4:6])[0],
|
|
63
|
+
"panose": t[32:42],
|
|
64
|
+
"ulUnicodeRange": struct.unpack(">IIII", t[42:58]),
|
|
65
|
+
"fsSelection": struct.unpack(">H", t[62:64])[0],
|
|
66
|
+
# the font's own embedding permission; the EOT header must not claim more
|
|
67
|
+
"fsType": struct.unpack(">H", t[8:10])[0],
|
|
68
|
+
}
|
|
69
|
+
# code page ranges only exist from version 1
|
|
70
|
+
d["ulCodePageRange"] = struct.unpack(">II", t[78:86]) if version >= 1 and len(t) >= 86 else (0, 0)
|
|
71
|
+
return d
|
|
72
|
+
|
|
73
|
+
def names(self):
|
|
74
|
+
"""{nameID: string} for the Windows/Unicode records, best effort."""
|
|
75
|
+
t = self.table("name")
|
|
76
|
+
count, storage = struct.unpack(">HH", t[2:6])
|
|
77
|
+
out = {}
|
|
78
|
+
for i in range(count):
|
|
79
|
+
p = 6 + i * 12
|
|
80
|
+
plat, enc, lang, nid, ln, off = struct.unpack(">HHHHHH", t[p:p + 12])
|
|
81
|
+
raw = t[storage + off: storage + off + ln]
|
|
82
|
+
try:
|
|
83
|
+
s = raw.decode("utf-16-be") if plat == 3 or (plat == 0) else raw.decode("latin-1")
|
|
84
|
+
except UnicodeDecodeError:
|
|
85
|
+
continue
|
|
86
|
+
out.setdefault(nid, s)
|
|
87
|
+
return out
|
|
88
|
+
|
|
89
|
+
# -- writing -----------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
def retagged(self, family, subfamily, full, postscript):
|
|
92
|
+
"""Return new font bytes with name IDs 1/2/4/6 replaced."""
|
|
93
|
+
replace = {1: family, 2: subfamily, 4: full, 6: postscript}
|
|
94
|
+
name = _rebuild_name(self.table("name"), replace)
|
|
95
|
+
return _reassemble(self, {b"name": name})
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _rebuild_name(t, replace):
|
|
99
|
+
count, storage = struct.unpack(">HH", t[2:6])
|
|
100
|
+
records, strings, cursor = [], bytearray(), 0
|
|
101
|
+
|
|
102
|
+
for i in range(count):
|
|
103
|
+
p = 6 + i * 12
|
|
104
|
+
plat, enc, lang, nid, ln, off = struct.unpack(">HHHHHH", t[p:p + 12])
|
|
105
|
+
if nid in replace:
|
|
106
|
+
s = replace[nid]
|
|
107
|
+
raw = s.encode("utf-16-be") if plat in (0, 3) else s.encode("latin-1", "replace")
|
|
108
|
+
else:
|
|
109
|
+
raw = t[storage + off: storage + off + ln]
|
|
110
|
+
records.append((plat, enc, lang, nid, len(raw), cursor))
|
|
111
|
+
strings += raw
|
|
112
|
+
cursor += len(raw)
|
|
113
|
+
|
|
114
|
+
# sort order is required by the spec: platform, encoding, language, name
|
|
115
|
+
records.sort(key=lambda r: (r[0], r[1], r[2], r[3]))
|
|
116
|
+
head = struct.pack(">HHH", 0, len(records), 6 + len(records) * 12)
|
|
117
|
+
body = b"".join(struct.pack(">HHHHHH", *r) for r in records)
|
|
118
|
+
return head + body + bytes(strings)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _checksum(b):
|
|
122
|
+
if len(b) % 4:
|
|
123
|
+
b = b + b"\0" * (4 - len(b) % 4)
|
|
124
|
+
return sum(struct.unpack(f">{len(b)//4}I", b)) & 0xFFFFFFFF
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _reassemble(font, overrides):
|
|
128
|
+
"""Rewrite the whole sfnt with some tables replaced, fixing every offset,
|
|
129
|
+
every table checksum and the head checkSumAdjustment."""
|
|
130
|
+
tags = sorted(font.tables)
|
|
131
|
+
parts = {}
|
|
132
|
+
for t in tags:
|
|
133
|
+
parts[t] = overrides.get(t, font.table(t))
|
|
134
|
+
|
|
135
|
+
# head's checkSumAdjustment must be zero while the file checksum is taken
|
|
136
|
+
head = bytearray(parts[b"head"])
|
|
137
|
+
head[_HEAD_CHECKSUM_ADJ:_HEAD_CHECKSUM_ADJ + 4] = b"\0\0\0\0"
|
|
138
|
+
parts[b"head"] = bytes(head)
|
|
139
|
+
|
|
140
|
+
n = len(tags)
|
|
141
|
+
# binary-search fields the spec wants present; readers ignore them but
|
|
142
|
+
# validators complain when they are wrong
|
|
143
|
+
entry_selector = max(n.bit_length() - 1, 0)
|
|
144
|
+
search_range = (1 << entry_selector) * 16
|
|
145
|
+
out = bytearray(struct.pack(">4sHHHH", font.sfnt, n, search_range,
|
|
146
|
+
entry_selector, n * 16 - search_range))
|
|
147
|
+
|
|
148
|
+
offset = 12 + n * 16
|
|
149
|
+
directory, body = bytearray(), bytearray()
|
|
150
|
+
for t in tags:
|
|
151
|
+
d = parts[t]
|
|
152
|
+
directory += struct.pack(">4sIII", t, _checksum(d), offset, len(d))
|
|
153
|
+
body += d
|
|
154
|
+
pad = (-len(d)) % 4
|
|
155
|
+
body += b"\0" * pad
|
|
156
|
+
offset += len(d) + pad
|
|
157
|
+
|
|
158
|
+
out += directory + body
|
|
159
|
+
adj = (_MAGIC - _checksum(bytes(out))) & 0xFFFFFFFF
|
|
160
|
+
|
|
161
|
+
head_off = struct.unpack(">I", out[12 + tags.index(b"head") * 16 + 8:
|
|
162
|
+
12 + tags.index(b"head") * 16 + 12])[0]
|
|
163
|
+
out[head_off + _HEAD_CHECKSUM_ADJ: head_off + _HEAD_CHECKSUM_ADJ + 4] = struct.pack(">I", adj)
|
|
164
|
+
return bytes(out)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def load(path):
|
|
168
|
+
with open(path, "rb") as f:
|
|
169
|
+
return TTF(f.read())
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
wandxml.py — XML parsing that works with nothing installed.
|
|
3
|
+
|
|
4
|
+
The kit has to run wherever someone unzips it. `defusedxml` is the better
|
|
5
|
+
parser and we use it when it's there, but requiring a pip install to get past
|
|
6
|
+
Phase 0 means the engine is unusable in exactly the situation it was built for:
|
|
7
|
+
a fresh container with a zip and a .pptx in it.
|
|
8
|
+
|
|
9
|
+
The stdlib fallback is guarded against the one attack that matters for a parser
|
|
10
|
+
pointed at an untrusted .pptx — entity expansion — by refusing any document with
|
|
11
|
+
a DOCTYPE. Office files never have one.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from defusedxml import minidom as _md
|
|
16
|
+
HARDENED = True
|
|
17
|
+
except ImportError: # stdlib fallback
|
|
18
|
+
from xml.dom import minidom as _md
|
|
19
|
+
HARDENED = False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class UnsafeXML(ValueError):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def parse(data):
|
|
27
|
+
"""Parse .pptx part bytes (or str) into a minidom Document."""
|
|
28
|
+
probe = data[:8192]
|
|
29
|
+
if isinstance(probe, bytes):
|
|
30
|
+
probe = probe.decode("utf-8", "ignore")
|
|
31
|
+
if not HARDENED and ("<!DOCTYPE" in probe or "<!ENTITY" in probe):
|
|
32
|
+
raise UnsafeXML(
|
|
33
|
+
"document declares a DOCTYPE or ENTITY. Office parts never do. "
|
|
34
|
+
"Refusing to parse it with the stdlib parser — `pip install defusedxml` "
|
|
35
|
+
"if this file is genuinely trusted."
|
|
36
|
+
)
|
|
37
|
+
return _md.parseString(data)
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_": "Canonical brand values of the interim wand.ai look, in the shape the studio kit reads (swatch, derived, roles, faces, shape, art). Two sources: the live site (build oval-mobile-20261020-r1, read 2026-10-21: its CSS custom properties, the hero's constants, the marks' colours) and the social design approved on this brand on 2026-10-22 (X header, LinkedIn banners, avatar), which wins where they differ: the dark ground, ink on dark, the display and phrase weights, the spark, the avatar's tile. BRAND.md documents usage; the `from` map says where each value sits.",
|
|
3
|
+
"name": "interim",
|
|
4
|
+
"title": "Wand, the interim website's brand (Geist, Newsreader)",
|
|
5
|
+
"source": {
|
|
6
|
+
"site": "https://wand.ai/",
|
|
7
|
+
"build": "oval-mobile-20261020-r1",
|
|
8
|
+
"read": "2026-10-21",
|
|
9
|
+
"files": [
|
|
10
|
+
"site.20261020-r1.css",
|
|
11
|
+
"scale-story.20261020-r1.css",
|
|
12
|
+
"wand-refinement-20261020.20261020-r1.css",
|
|
13
|
+
"chapter-navigation.20261020-r1.css",
|
|
14
|
+
"assets/wand-icon-dark-v4.svg",
|
|
15
|
+
"assets/wand-wordmark-light-violet.svg",
|
|
16
|
+
"scale-story.desktop-wheel-20261020-r4.js"
|
|
17
|
+
],
|
|
18
|
+
"approved": {
|
|
19
|
+
"date": "2026-10-22",
|
|
20
|
+
"files": [
|
|
21
|
+
"X header 1500x500",
|
|
22
|
+
"LinkedIn banner 1584x396",
|
|
23
|
+
"LinkedIn company banner 1512x256",
|
|
24
|
+
"avatar 800",
|
|
25
|
+
"LinkedIn profile mock-up"
|
|
26
|
+
],
|
|
27
|
+
"kept": "runs/2026-10-22-wand-og/ref",
|
|
28
|
+
"measured": "assets/social/composition.json",
|
|
29
|
+
"precedence": "the approved design wins over the site where they differ"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"swatch": {
|
|
33
|
+
"night": "0B0913",
|
|
34
|
+
"ground": "14101C",
|
|
35
|
+
"nightDeep": "08060D",
|
|
36
|
+
"nightSoft": "100C19",
|
|
37
|
+
"plum": "2D1D40",
|
|
38
|
+
"ink": "221B2D",
|
|
39
|
+
"paper": "FDFCF9",
|
|
40
|
+
"stone": "F5F3EF",
|
|
41
|
+
"lilac": "F2EAFA",
|
|
42
|
+
"lavender": "D6BFF5",
|
|
43
|
+
"violet": "C2A1EF",
|
|
44
|
+
"violetStrong": "6D41B3",
|
|
45
|
+
"phraseLight": "765098",
|
|
46
|
+
"eyebrowLight": "836494",
|
|
47
|
+
"muted": "6E6577",
|
|
48
|
+
"mutedLight": "B6ABCA",
|
|
49
|
+
"storyInk": "F4EEF8",
|
|
50
|
+
"storyText": "C1B5CD",
|
|
51
|
+
"storyMuted": "B5A6C5",
|
|
52
|
+
"storyDetail": "9C8DAB",
|
|
53
|
+
"emphasis": "D4BCE8",
|
|
54
|
+
"kicker": "C7B7D6",
|
|
55
|
+
"lineLight": "D8CEDF",
|
|
56
|
+
"spark": "C2A1EF",
|
|
57
|
+
"sparkOnLight": "765098",
|
|
58
|
+
"wordmarkOnLight": "240647",
|
|
59
|
+
"tile": "261D3D",
|
|
60
|
+
"white": "FFFFFF",
|
|
61
|
+
"inkDark": "F7F5F9"
|
|
62
|
+
},
|
|
63
|
+
"from": {
|
|
64
|
+
"night": "--night: the site's html ground, its hero and <meta theme-color>; no longer the brand's dark ground (see ground)",
|
|
65
|
+
"nightDeep": "--night-deep: the footer",
|
|
66
|
+
"nightSoft": "the mobile navigation, the product menu, .contact-next",
|
|
67
|
+
"plum": ".button-dark",
|
|
68
|
+
"ink": "--ink: body colour on light",
|
|
69
|
+
"paper": "--paper",
|
|
70
|
+
"stone": "--stone: the body ground",
|
|
71
|
+
"lilac": ".nav-cta ground; the editorial cards run #e9e0f1 to #f6f0fa",
|
|
72
|
+
"lavender": "--lavender",
|
|
73
|
+
"violet": "--violet: h1 em, h2 em on dark; the current nav item's underline",
|
|
74
|
+
"violetStrong": "--violet-strong",
|
|
75
|
+
"phraseLight": ".light-section h1 em, h2 em",
|
|
76
|
+
"eyebrowLight": ".eyebrow on light (chapter-navigation); 4.5:1 on stone by a hair under AA, so the kit's small muted text takes phraseLight",
|
|
77
|
+
"muted": "--muted: body text on light",
|
|
78
|
+
"mutedLight": "--muted-light",
|
|
79
|
+
"storyInk": ".scale-story --story-ink: the hero's text",
|
|
80
|
+
"storyText": ".scale-story-caption > p",
|
|
81
|
+
"storyMuted": ".scale-story --story-muted",
|
|
82
|
+
"storyDetail": ".scale-story-detail",
|
|
83
|
+
"emphasis": ".scale-story-title em: the hero's italic phrase",
|
|
84
|
+
"kicker": ".scale-story-kicker",
|
|
85
|
+
"lineLight": "the light sections' hairline (#d8cedf)",
|
|
86
|
+
"spark": "the approved wordmark and avatar: the spark is the phrase's violet (the site's spark was B6A2FF)",
|
|
87
|
+
"sparkOnLight": "the approved rule carried to the light ground: the spark is the phrase's colour (765098 on light; the site's was 6D4CF5)",
|
|
88
|
+
"wordmarkOnLight": "the letters in wand-wordmark-dark.svg",
|
|
89
|
+
"tile": "the approved avatar's disc (the site's favicon tile was 280845)",
|
|
90
|
+
"ground": "the approved social design (2026-10-22), flat on every export: the dark ground, in place of the site's night",
|
|
91
|
+
"inkDark": "the approved design's 'A new workforce.': ink on dark, in place of the site's story ink"
|
|
92
|
+
},
|
|
93
|
+
"derived": {
|
|
94
|
+
"hairlineDark": {
|
|
95
|
+
"hex": "322F39",
|
|
96
|
+
"from": "white @ 13% on the ground: --dark-line #ffffff21"
|
|
97
|
+
},
|
|
98
|
+
"strokeDark": {
|
|
99
|
+
"hex": "26232E",
|
|
100
|
+
"from": "white @ 8% on the ground: the card borders #ffffff14"
|
|
101
|
+
},
|
|
102
|
+
"surfaceDark": {
|
|
103
|
+
"hex": "21192E",
|
|
104
|
+
"from": "the site's dark card ground (#21192e)"
|
|
105
|
+
},
|
|
106
|
+
"paperDark": {
|
|
107
|
+
"hex": "191322",
|
|
108
|
+
"from": "cards on dark: the ground lifted as the site lifts nightSoft off night (+5, +3, +6)"
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"glow": {
|
|
112
|
+
"_": "the hero's ambient light: the site's two radial gradients as hex + alpha, and its hairlines; a layout positions them",
|
|
113
|
+
"a": {
|
|
114
|
+
"hex": "B183EB",
|
|
115
|
+
"alpha": 0.09,
|
|
116
|
+
"from": "radial-gradient(ellipse at 75% 24%, #b183eb17, transparent 60%)"
|
|
117
|
+
},
|
|
118
|
+
"b": {
|
|
119
|
+
"hex": "654393",
|
|
120
|
+
"alpha": 0.094,
|
|
121
|
+
"from": "radial-gradient(ellipse at 88% 25%, #65439318, transparent 52%)"
|
|
122
|
+
},
|
|
123
|
+
"hairline": {
|
|
124
|
+
"hex": "FFFFFF",
|
|
125
|
+
"alpha": 0.13,
|
|
126
|
+
"from": "--dark-line: #ffffff21"
|
|
127
|
+
},
|
|
128
|
+
"kickerDot": {
|
|
129
|
+
"hex": "D8BBEE",
|
|
130
|
+
"halo": "CDA4EA",
|
|
131
|
+
"from": ".scale-story-kicker > span"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"roles": {
|
|
135
|
+
"light": {
|
|
136
|
+
"bg": "F5F3EF",
|
|
137
|
+
"paper": "FDFCF9",
|
|
138
|
+
"ink": "221B2D",
|
|
139
|
+
"text": "6E6577",
|
|
140
|
+
"soft": "6E6577",
|
|
141
|
+
"muted": "765098",
|
|
142
|
+
"accent": "6D41B3",
|
|
143
|
+
"accentText": "6D41B3",
|
|
144
|
+
"label": "765098",
|
|
145
|
+
"emph": "14101C",
|
|
146
|
+
"emphInk": "F4EEF8",
|
|
147
|
+
"emphText": "C1B5CD",
|
|
148
|
+
"emphAccent": "C2A1EF",
|
|
149
|
+
"stroke": "D8CEDF",
|
|
150
|
+
"rule": "D8CEDF",
|
|
151
|
+
"track": "D8CEDF",
|
|
152
|
+
"surface": "F2EAFA",
|
|
153
|
+
"emphLabel": "C7B7D6",
|
|
154
|
+
"second": "765098",
|
|
155
|
+
"secondSm": "765098",
|
|
156
|
+
"gradeLow": "14101C",
|
|
157
|
+
"gradeHigh": "F5F3EF"
|
|
158
|
+
},
|
|
159
|
+
"dark": {
|
|
160
|
+
"bg": "14101C",
|
|
161
|
+
"paper": "191322",
|
|
162
|
+
"ink": "F7F5F9",
|
|
163
|
+
"text": "C1B5CD",
|
|
164
|
+
"soft": "B5A6C5",
|
|
165
|
+
"muted": "9C8DAB",
|
|
166
|
+
"accent": "D4BCE8",
|
|
167
|
+
"accentText": "C2A1EF",
|
|
168
|
+
"label": "C7B7D6",
|
|
169
|
+
"emph": "2D1D40",
|
|
170
|
+
"emphInk": "F9F4FF",
|
|
171
|
+
"emphText": "C1B5CD",
|
|
172
|
+
"emphAccent": "C2A1EF",
|
|
173
|
+
"stroke": "26232E",
|
|
174
|
+
"rule": "322F39",
|
|
175
|
+
"track": "322F39",
|
|
176
|
+
"surface": "21192E",
|
|
177
|
+
"emphLabel": "C7B7D6",
|
|
178
|
+
"second": "C2A1EF",
|
|
179
|
+
"secondSm": "B5A6C5",
|
|
180
|
+
"gradeLow": "14101C",
|
|
181
|
+
"gradeHigh": "F5F3EF"
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
"faces": {
|
|
185
|
+
"display": {
|
|
186
|
+
"family": "Geist",
|
|
187
|
+
"weight": 500,
|
|
188
|
+
"tracking": 0,
|
|
189
|
+
"leading": 1.025,
|
|
190
|
+
"_": "approved 2026-10-22: Geist Medium, untracked (stems 17.6 units at 140, as exported). The site's hero title was Geist 450 at -0.063em; its leading, 1.025, stays, since the approved set shows no wrapped title (its lead and phrase sit 1.11 apart, a lockup the composition carries)",
|
|
191
|
+
"coverTracking": 0
|
|
192
|
+
},
|
|
193
|
+
"body": {
|
|
194
|
+
"family": "Geist",
|
|
195
|
+
"weight": 400,
|
|
196
|
+
"tracking": 0,
|
|
197
|
+
"leading": 1.6
|
|
198
|
+
},
|
|
199
|
+
"label": {
|
|
200
|
+
"family": "Geist",
|
|
201
|
+
"weight": 500,
|
|
202
|
+
"tracking": 0.11,
|
|
203
|
+
"leading": 1.5,
|
|
204
|
+
"caps": true
|
|
205
|
+
},
|
|
206
|
+
"phrase": {
|
|
207
|
+
"family": "Newsreader",
|
|
208
|
+
"weight": 500,
|
|
209
|
+
"italic": false,
|
|
210
|
+
"tracking": 0,
|
|
211
|
+
"artItalic": true,
|
|
212
|
+
"_": "approved 2026-10-22: Newsreader Medium, optical size 72, untracked, the italic on the pages that carry the art (the hero's phrase); roman elsewhere, as the site's h1 em. The site set it at 400, -0.049em"
|
|
213
|
+
},
|
|
214
|
+
"files": [
|
|
215
|
+
{
|
|
216
|
+
"family": "Geist",
|
|
217
|
+
"weight": 400,
|
|
218
|
+
"italic": false,
|
|
219
|
+
"file": "Geist-Regular.ttf",
|
|
220
|
+
"pptx": "Geist"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"family": "Geist",
|
|
224
|
+
"weight": 450,
|
|
225
|
+
"italic": false,
|
|
226
|
+
"file": "Geist-Book.ttf",
|
|
227
|
+
"pptx": "Geist Book"
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"family": "Geist",
|
|
231
|
+
"weight": 500,
|
|
232
|
+
"italic": false,
|
|
233
|
+
"file": "Geist-Medium.ttf",
|
|
234
|
+
"pptx": "Geist Medium"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"family": "Geist",
|
|
238
|
+
"weight": 600,
|
|
239
|
+
"italic": false,
|
|
240
|
+
"file": "Geist-SemiBold.ttf",
|
|
241
|
+
"pptx": "Geist SemiBold"
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"family": "Newsreader",
|
|
245
|
+
"weight": 400,
|
|
246
|
+
"italic": false,
|
|
247
|
+
"file": "Newsreader-Regular.ttf",
|
|
248
|
+
"pptx": "Newsreader"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"family": "Newsreader",
|
|
252
|
+
"weight": 400,
|
|
253
|
+
"italic": true,
|
|
254
|
+
"file": "Newsreader-Italic.ttf",
|
|
255
|
+
"pptx": "Newsreader"
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
"family": "Newsreader",
|
|
259
|
+
"weight": 500,
|
|
260
|
+
"italic": false,
|
|
261
|
+
"file": "Newsreader-Medium.ttf",
|
|
262
|
+
"pptx": "Newsreader Medium"
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"family": "Newsreader",
|
|
266
|
+
"weight": 500,
|
|
267
|
+
"italic": true,
|
|
268
|
+
"file": "Newsreader-MediumItalic.ttf",
|
|
269
|
+
"pptx": "Newsreader Medium"
|
|
270
|
+
}
|
|
271
|
+
],
|
|
272
|
+
"license": "Geist and Newsreader: SIL OFL 1.1, static instances of the Google Fonts variable sources (see fonts/LICENSE-FONTS.md)"
|
|
273
|
+
},
|
|
274
|
+
"shape": {
|
|
275
|
+
"radius": 1,
|
|
276
|
+
"cardStroke": 1,
|
|
277
|
+
"emphStroke": 0,
|
|
278
|
+
"artShade": 0,
|
|
279
|
+
"_": "artShade: the dark art pages carry no shade over their art, as the approved composition carries none over its trails and ring; the statement and closing art bake in the 30% the kit used to lay over them (tools/art.mjs)"
|
|
280
|
+
},
|
|
281
|
+
"art": {
|
|
282
|
+
"cover": "cover-art.png",
|
|
283
|
+
"coverPortrait": "cover-portrait-art.png",
|
|
284
|
+
"overlay": "cover-art.png",
|
|
285
|
+
"statement": "statement-art.png",
|
|
286
|
+
"closing": "closing-art.png",
|
|
287
|
+
"coverArt": "cover-art.png",
|
|
288
|
+
"divider": "divider-art.png",
|
|
289
|
+
"markOnLight": "spark-violet.png",
|
|
290
|
+
"markOnDark": "spark-lavender.png",
|
|
291
|
+
"wordmarkOnLight": "wordmark-on-light.png",
|
|
292
|
+
"wordmarkOnDark": "wordmark-on-dark.png",
|
|
293
|
+
"wordmarkSvgOnLight": "wordmark-on-light.svg",
|
|
294
|
+
"wordmarkSvgOnDark": "wordmark-on-dark.svg",
|
|
295
|
+
"mark": "mark.svg",
|
|
296
|
+
"avatar": "avatar.svg",
|
|
297
|
+
"composition": "social/composition.json"
|
|
298
|
+
}
|
|
299
|
+
}
|
package/brands/next/tokens.json
CHANGED
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
"hex": "C4C1CA",
|
|
35
35
|
"from": "white @ 70% on violet200, lists inside dark cards"
|
|
36
36
|
},
|
|
37
|
+
"secondDark": {
|
|
38
|
+
"hex": "9D99A8",
|
|
39
|
+
"from": "white @ 55% on violet300 - a title's quiet second tone on dark (5.7:1)"
|
|
40
|
+
},
|
|
37
41
|
"footDark": {
|
|
38
42
|
"hex": "888394",
|
|
39
43
|
"from": "white @ 45% on violet300"
|
|
@@ -60,7 +64,11 @@
|
|
|
60
64
|
"rule": "D5CEC2",
|
|
61
65
|
"track": "D5CEC2",
|
|
62
66
|
"surface": "EBE8E2",
|
|
63
|
-
"emphLabel": "B79A5E"
|
|
67
|
+
"emphLabel": "B79A5E",
|
|
68
|
+
"second": "7B7B79",
|
|
69
|
+
"secondSm": "686766",
|
|
70
|
+
"gradeLow": "261D3D",
|
|
71
|
+
"gradeHigh": "F1EFEB"
|
|
64
72
|
},
|
|
65
73
|
"dark": {
|
|
66
74
|
"bg": "261D3D",
|
|
@@ -80,7 +88,11 @@
|
|
|
80
88
|
"rule": "504666",
|
|
81
89
|
"track": "504666",
|
|
82
90
|
"surface": "3A314E",
|
|
83
|
-
"emphLabel": "C4C1CA"
|
|
91
|
+
"emphLabel": "C4C1CA",
|
|
92
|
+
"second": "9D99A8",
|
|
93
|
+
"secondSm": "BEBBC5",
|
|
94
|
+
"gradeLow": "261D3D",
|
|
95
|
+
"gradeHigh": "F1EFEB"
|
|
84
96
|
}
|
|
85
97
|
},
|
|
86
98
|
"faces": {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wand-social",
|
|
3
|
+
"brand": "interim",
|
|
4
|
+
"site": "Wand",
|
|
5
|
+
"line": { "lead": "A new workforce.", "phrase": "A new operating era." },
|
|
6
|
+
"upload": "https://wand.ai/assets/og/20261023/",
|
|
7
|
+
"cards": [
|
|
8
|
+
{ "kind": "linkedin-banner" },
|
|
9
|
+
{ "kind": "x-header" },
|
|
10
|
+
{ "kind": "linkedin-company" },
|
|
11
|
+
{ "kind": "share", "id": "wand-ai-share", "url": "https://wand.ai/", "title": "Wand AI", "type": "website",
|
|
12
|
+
"description": "Autonomous AI workers. One operating system for people and agents. The foundation for a new era of enterprise and national capacity.",
|
|
13
|
+
"alt": "Wand. A new workforce. A new operating era." },
|
|
14
|
+
{ "kind": "share", "id": "open-agent-discovery-share", "url": "https://wand.ai/insights/open-agent-discovery-nandaindex.html",
|
|
15
|
+
"lead": "An Open Agentic Web", "phrase": "Needs Open Discovery", "type": "article",
|
|
16
|
+
"title": "An Open Agentic Web Needs Open Discovery | Wand AI",
|
|
17
|
+
"description": "Why interoperable agent discovery matters for the emerging agentic economy, and how NandaIndex complements governed AI workforces.",
|
|
18
|
+
"alt": "Wand. An Open Agentic Web Needs Open Discovery." },
|
|
19
|
+
{ "kind": "square" },
|
|
20
|
+
{ "kind": "avatar" },
|
|
21
|
+
{ "kind": "icons", "upload": "/assets/20261022/" }
|
|
22
|
+
]
|
|
23
|
+
}
|