wand-kit 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitkeep +0 -0
- package/PROMPT.md +255 -0
- package/SKILL.md +117 -0
- package/START-HERE.md +16 -0
- package/brands/current/assets/bg-cover-dark.png +0 -0
- package/brands/current/assets/bg-overlay-dark.png +0 -0
- package/brands/current/assets/bg-statement-dark.png +0 -0
- package/brands/current/assets/closing-art.png +0 -0
- package/brands/current/assets/cover-art.png +0 -0
- package/brands/current/assets/divider-art.png +0 -0
- package/brands/current/assets/glyphs/README.md +29 -0
- package/brands/current/assets/glyphs/activity.svg +15 -0
- package/brands/current/assets/glyphs/atom.svg +17 -0
- package/brands/current/assets/glyphs/badge-check.svg +16 -0
- package/brands/current/assets/glyphs/bell.svg +16 -0
- package/brands/current/assets/glyphs/blocks.svg +16 -0
- package/brands/current/assets/glyphs/book-open.svg +16 -0
- package/brands/current/assets/glyphs/bot.svg +20 -0
- package/brands/current/assets/glyphs/boxes.svg +26 -0
- package/brands/current/assets/glyphs/brain.svg +23 -0
- package/brands/current/assets/glyphs/briefcase.svg +16 -0
- package/brands/current/assets/glyphs/building-2.svg +21 -0
- package/brands/current/assets/glyphs/calendar.svg +18 -0
- package/brands/current/assets/glyphs/chart-column.svg +18 -0
- package/brands/current/assets/glyphs/chart-line.svg +16 -0
- package/brands/current/assets/glyphs/chart-pie.svg +16 -0
- package/brands/current/assets/glyphs/circle-check.svg +16 -0
- package/brands/current/assets/glyphs/clipboard-check.svg +17 -0
- package/brands/current/assets/glyphs/clock.svg +16 -0
- package/brands/current/assets/glyphs/cloud.svg +15 -0
- package/brands/current/assets/glyphs/code.svg +15 -0
- package/brands/current/assets/glyphs/coins.svg +18 -0
- package/brands/current/assets/glyphs/compass.svg +16 -0
- package/brands/current/assets/glyphs/cpu.svg +24 -0
- package/brands/current/assets/glyphs/database.svg +17 -0
- package/brands/current/assets/glyphs/eye.svg +16 -0
- package/brands/current/assets/glyphs/file-check.svg +17 -0
- package/brands/current/assets/glyphs/filter.svg +15 -0
- package/brands/current/assets/glyphs/fingerprint.svg +23 -0
- package/brands/current/assets/glyphs/flag.svg +16 -0
- package/brands/current/assets/glyphs/gauge.svg +16 -0
- package/brands/current/assets/glyphs/gavel.svg +19 -0
- package/brands/current/assets/glyphs/git-branch.svg +18 -0
- package/brands/current/assets/glyphs/globe.svg +17 -0
- package/brands/current/assets/glyphs/graduation-cap.svg +17 -0
- package/brands/current/assets/glyphs/handshake.svg +19 -0
- package/brands/current/assets/glyphs/hexagon.svg +15 -0
- package/brands/current/assets/glyphs/index.json +177 -0
- package/brands/current/assets/glyphs/infinity.svg +15 -0
- package/brands/current/assets/glyphs/key.svg +17 -0
- package/brands/current/assets/glyphs/landmark.svg +20 -0
- package/brands/current/assets/glyphs/layers.svg +17 -0
- package/brands/current/assets/glyphs/lightbulb.svg +17 -0
- package/brands/current/assets/glyphs/link.svg +16 -0
- package/brands/current/assets/glyphs/list-checks.svg +19 -0
- package/brands/current/assets/glyphs/lock.svg +16 -0
- package/brands/current/assets/glyphs/map.svg +17 -0
- package/brands/current/assets/glyphs/megaphone.svg +16 -0
- package/brands/current/assets/glyphs/message-square.svg +15 -0
- package/brands/current/assets/glyphs/milestone.svg +17 -0
- package/brands/current/assets/glyphs/network.svg +19 -0
- package/brands/current/assets/glyphs/orbit.svg +19 -0
- package/brands/current/assets/glyphs/package.svg +18 -0
- package/brands/current/assets/glyphs/plug.svg +18 -0
- package/brands/current/assets/glyphs/puzzle.svg +15 -0
- package/brands/current/assets/glyphs/radar.svg +22 -0
- package/brands/current/assets/glyphs/receipt.svg +17 -0
- package/brands/current/assets/glyphs/refresh-cw.svg +18 -0
- package/brands/current/assets/glyphs/rocket.svg +18 -0
- package/brands/current/assets/glyphs/route.svg +17 -0
- package/brands/current/assets/glyphs/scale.svg +19 -0
- package/brands/current/assets/glyphs/search.svg +16 -0
- package/brands/current/assets/glyphs/server.svg +18 -0
- package/brands/current/assets/glyphs/settings.svg +16 -0
- package/brands/current/assets/glyphs/share-2.svg +19 -0
- package/brands/current/assets/glyphs/shield-check.svg +16 -0
- package/brands/current/assets/glyphs/shuffle.svg +19 -0
- package/brands/current/assets/glyphs/sparkles.svg +19 -0
- package/brands/current/assets/glyphs/square-stack.svg +17 -0
- package/brands/current/assets/glyphs/target.svg +17 -0
- package/brands/current/assets/glyphs/trending-up.svg +16 -0
- package/brands/current/assets/glyphs/triangle-alert.svg +17 -0
- package/brands/current/assets/glyphs/trophy.svg +20 -0
- package/brands/current/assets/glyphs/users.svg +18 -0
- package/brands/current/assets/glyphs/wallet.svg +16 -0
- package/brands/current/assets/glyphs/wand-sparkles.svg +22 -0
- package/brands/current/assets/glyphs/workflow.svg +17 -0
- package/brands/current/assets/glyphs/zap.svg +15 -0
- package/brands/current/assets/mark-dark.png +0 -0
- package/brands/current/assets/mark-white.png +0 -0
- package/brands/current/assets/orb-cover.png +0 -0
- package/brands/current/assets/orb-glow.png +0 -0
- package/brands/current/assets/wordmark-dark.png +0 -0
- package/brands/current/assets/wordmark-white.png +0 -0
- package/brands/current/fonts/Geist-Bold.ttf +0 -0
- package/brands/current/fonts/Geist-BoldItalic.ttf +0 -0
- package/brands/current/fonts/Geist-ExtraBold.ttf +0 -0
- package/brands/current/fonts/Geist-ExtraBoldItalic.ttf +0 -0
- package/brands/current/fonts/Geist-Italic.ttf +0 -0
- package/brands/current/fonts/Geist-Light.ttf +0 -0
- package/brands/current/fonts/Geist-LightItalic.ttf +0 -0
- package/brands/current/fonts/Geist-Medium.ttf +0 -0
- package/brands/current/fonts/Geist-MediumItalic.ttf +0 -0
- package/brands/current/fonts/Geist-Regular.ttf +0 -0
- package/brands/current/fonts/Geist-SemiBold.ttf +0 -0
- package/brands/current/fonts/Geist-SemiBoldItalic.ttf +0 -0
- package/brands/current/lib/glyphs.js +87 -0
- package/brands/current/lib/measure.js +115 -0
- package/brands/current/lib/pdf.js +603 -0
- package/brands/current/lib/ttf.py +167 -0
- package/brands/current/lib/wandxml.py +37 -0
- package/brands/current/tokens.json +206 -0
- package/brands/next/assets/closing-art.png +0 -0
- package/brands/next/assets/cover-art.png +0 -0
- package/brands/next/assets/divider-art.png +0 -0
- package/brands/next/assets/glyphs/README.md +29 -0
- package/brands/next/assets/glyphs/activity.svg +15 -0
- package/brands/next/assets/glyphs/atom.svg +17 -0
- package/brands/next/assets/glyphs/badge-check.svg +16 -0
- package/brands/next/assets/glyphs/bell.svg +16 -0
- package/brands/next/assets/glyphs/blocks.svg +16 -0
- package/brands/next/assets/glyphs/book-open.svg +16 -0
- package/brands/next/assets/glyphs/bot.svg +20 -0
- package/brands/next/assets/glyphs/boxes.svg +26 -0
- package/brands/next/assets/glyphs/brain.svg +23 -0
- package/brands/next/assets/glyphs/briefcase.svg +16 -0
- package/brands/next/assets/glyphs/building-2.svg +21 -0
- package/brands/next/assets/glyphs/calendar.svg +18 -0
- package/brands/next/assets/glyphs/chart-column.svg +18 -0
- package/brands/next/assets/glyphs/chart-line.svg +16 -0
- package/brands/next/assets/glyphs/chart-pie.svg +16 -0
- package/brands/next/assets/glyphs/circle-check.svg +16 -0
- package/brands/next/assets/glyphs/clipboard-check.svg +17 -0
- package/brands/next/assets/glyphs/clock.svg +16 -0
- package/brands/next/assets/glyphs/cloud.svg +15 -0
- package/brands/next/assets/glyphs/code.svg +15 -0
- package/brands/next/assets/glyphs/coins.svg +18 -0
- package/brands/next/assets/glyphs/compass.svg +16 -0
- package/brands/next/assets/glyphs/cpu.svg +24 -0
- package/brands/next/assets/glyphs/database.svg +17 -0
- package/brands/next/assets/glyphs/eye.svg +16 -0
- package/brands/next/assets/glyphs/file-check.svg +17 -0
- package/brands/next/assets/glyphs/filter.svg +15 -0
- package/brands/next/assets/glyphs/fingerprint.svg +23 -0
- package/brands/next/assets/glyphs/flag.svg +16 -0
- package/brands/next/assets/glyphs/gauge.svg +16 -0
- package/brands/next/assets/glyphs/gavel.svg +19 -0
- package/brands/next/assets/glyphs/git-branch.svg +18 -0
- package/brands/next/assets/glyphs/globe.svg +17 -0
- package/brands/next/assets/glyphs/graduation-cap.svg +17 -0
- package/brands/next/assets/glyphs/handshake.svg +19 -0
- package/brands/next/assets/glyphs/hexagon.svg +15 -0
- package/brands/next/assets/glyphs/index.json +177 -0
- package/brands/next/assets/glyphs/infinity.svg +15 -0
- package/brands/next/assets/glyphs/key.svg +17 -0
- package/brands/next/assets/glyphs/landmark.svg +20 -0
- package/brands/next/assets/glyphs/layers.svg +17 -0
- package/brands/next/assets/glyphs/lightbulb.svg +17 -0
- package/brands/next/assets/glyphs/link.svg +16 -0
- package/brands/next/assets/glyphs/list-checks.svg +19 -0
- package/brands/next/assets/glyphs/lock.svg +16 -0
- package/brands/next/assets/glyphs/map.svg +17 -0
- package/brands/next/assets/glyphs/megaphone.svg +16 -0
- package/brands/next/assets/glyphs/message-square.svg +15 -0
- package/brands/next/assets/glyphs/milestone.svg +17 -0
- package/brands/next/assets/glyphs/network.svg +19 -0
- package/brands/next/assets/glyphs/orbit.svg +19 -0
- package/brands/next/assets/glyphs/package.svg +18 -0
- package/brands/next/assets/glyphs/plug.svg +18 -0
- package/brands/next/assets/glyphs/puzzle.svg +15 -0
- package/brands/next/assets/glyphs/radar.svg +22 -0
- package/brands/next/assets/glyphs/receipt.svg +17 -0
- package/brands/next/assets/glyphs/refresh-cw.svg +18 -0
- package/brands/next/assets/glyphs/rocket.svg +18 -0
- package/brands/next/assets/glyphs/route.svg +17 -0
- package/brands/next/assets/glyphs/scale.svg +19 -0
- package/brands/next/assets/glyphs/search.svg +16 -0
- package/brands/next/assets/glyphs/server.svg +18 -0
- package/brands/next/assets/glyphs/settings.svg +16 -0
- package/brands/next/assets/glyphs/share-2.svg +19 -0
- package/brands/next/assets/glyphs/shield-check.svg +16 -0
- package/brands/next/assets/glyphs/shuffle.svg +19 -0
- package/brands/next/assets/glyphs/sparkles.svg +19 -0
- package/brands/next/assets/glyphs/square-stack.svg +17 -0
- package/brands/next/assets/glyphs/target.svg +17 -0
- package/brands/next/assets/glyphs/trending-up.svg +16 -0
- package/brands/next/assets/glyphs/triangle-alert.svg +17 -0
- package/brands/next/assets/glyphs/trophy.svg +20 -0
- package/brands/next/assets/glyphs/users.svg +18 -0
- package/brands/next/assets/glyphs/wallet.svg +16 -0
- package/brands/next/assets/glyphs/wand-sparkles.svg +22 -0
- package/brands/next/assets/glyphs/workflow.svg +17 -0
- package/brands/next/assets/glyphs/zap.svg +15 -0
- package/brands/next/assets/layers-dark.png +0 -0
- package/brands/next/assets/layers-light.png +0 -0
- package/brands/next/assets/ornaments/burst-dark.png +0 -0
- package/brands/next/assets/ornaments/burst-light.png +0 -0
- package/brands/next/assets/ornaments/chain-dark.png +0 -0
- package/brands/next/assets/ornaments/chain-light.png +0 -0
- package/brands/next/assets/ornaments/coil-dark.png +0 -0
- package/brands/next/assets/ornaments/coil-light.png +0 -0
- package/brands/next/assets/ornaments/cone-dark.png +0 -0
- package/brands/next/assets/ornaments/cone-light.png +0 -0
- package/brands/next/assets/ornaments/diamond-dark.png +0 -0
- package/brands/next/assets/ornaments/diamond-light.png +0 -0
- package/brands/next/assets/ornaments/fan-dark.png +0 -0
- package/brands/next/assets/ornaments/fan-light.png +0 -0
- package/brands/next/assets/ornaments/globe-dark.png +0 -0
- package/brands/next/assets/ornaments/globe-light.png +0 -0
- package/brands/next/assets/ornaments/orbit-dark.png +0 -0
- package/brands/next/assets/ornaments/orbit-light.png +0 -0
- package/brands/next/assets/ornaments/rings-dark.png +0 -0
- package/brands/next/assets/ornaments/rings-light.png +0 -0
- package/brands/next/assets/ornaments/sphere-dark.png +0 -0
- package/brands/next/assets/ornaments/sphere-light.png +0 -0
- package/brands/next/assets/ornaments/sun-dark.png +0 -0
- package/brands/next/assets/ornaments/sun-light.png +0 -0
- package/brands/next/assets/photos/atrium.jpg +0 -0
- package/brands/next/assets/photos/cantilever.jpg +0 -0
- package/brands/next/assets/photos/corridor.jpg +0 -0
- package/brands/next/assets/photos/dome.jpg +0 -0
- package/brands/next/assets/photos/facade.jpg +0 -0
- package/brands/next/assets/photos/glass.jpg +0 -0
- package/brands/next/assets/photos/stairs.jpg +0 -0
- package/brands/next/assets/photos/tower.jpg +0 -0
- package/brands/next/assets/photos/walk.jpg +0 -0
- package/brands/next/assets/spark-gold.png +0 -0
- package/brands/next/assets/statement-art.png +0 -0
- package/brands/next/assets/wordmark-dark.png +0 -0
- package/brands/next/assets/wordmark-light.png +0 -0
- package/brands/next/assets/wordmark.svg +7 -0
- package/brands/next/fonts/FKRomanStandard-Light.ttf +0 -0
- package/brands/next/fonts/OFL-OverusedGrotesk.txt +93 -0
- package/brands/next/fonts/OverusedGrotesk-Italic.ttf +0 -0
- package/brands/next/fonts/OverusedGrotesk-Medium.ttf +0 -0
- package/brands/next/fonts/OverusedGrotesk-MediumItalic.ttf +0 -0
- package/brands/next/fonts/OverusedGrotesk-Roman.ttf +0 -0
- package/brands/next/fonts/OverusedGrotesk-SemiBold.ttf +0 -0
- package/brands/next/fonts/OverusedGrotesk-SemiBoldItalic.ttf +0 -0
- package/brands/next/fonts/faces.json +27 -0
- package/brands/next/lib/glyphs.js +89 -0
- package/brands/next/lib/measure.js +121 -0
- package/brands/next/lib/pdf.js +597 -0
- package/brands/next/lib/ttf.py +169 -0
- package/brands/next/lib/wandxml.py +37 -0
- package/brands/next/tokens.json +174 -0
- package/examples/gallery/job.mjs +83 -0
- package/examples/overview/job.mjs +99 -0
- package/lib/brand.js +135 -0
- package/lib/components.js +313 -0
- package/lib/content.js +66 -0
- package/lib/engine.js +280 -0
- package/lib/html_export.js +71 -0
- package/lib/pptx.js +446 -0
- package/lib/surfaces.js +62 -0
- package/package.json +17 -4
- package/templates/card.mjs +30 -0
- package/templates/page.mjs +52 -0
- package/templates/slide.mjs +46 -0
- package/tools/embed_fonts.py +154 -0
- package/wand.js +179 -0
- package/README.md +0 -3
|
@@ -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,174 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_": "Canonical brand values, measured from the website's CSS (wand-ai-staging.webflow.io, 2026-10-05). BRAND.md documents usage; the deck kit, the art generator and the gates all read this file, so a palette change happens here once. Derived values are the site's opacity tokens flattened onto the ground they sit on, so a .pptx carries solid colours a gate can check. `roles`, `faces`, `shape` and `art` were added for the studio kit (power-prompts/studio), which draws both brands from one component set; the decks-next kit reads only `swatch` and `derived`.",
|
|
3
|
+
"swatch": {
|
|
4
|
+
"violet100": "504666",
|
|
5
|
+
"violet200": "3A314E",
|
|
6
|
+
"violet300": "261D3D",
|
|
7
|
+
"gold100": "B79A5E",
|
|
8
|
+
"gold200": "917741",
|
|
9
|
+
"gold300": "786336",
|
|
10
|
+
"beige100": "F1EFEB",
|
|
11
|
+
"beige200": "EBE8E2",
|
|
12
|
+
"beige300": "D5CEC2",
|
|
13
|
+
"darkGrey": "2D2D2D",
|
|
14
|
+
"white": "FFFFFF"
|
|
15
|
+
},
|
|
16
|
+
"derived": {
|
|
17
|
+
"bodyLight": {
|
|
18
|
+
"hex": "686766",
|
|
19
|
+
"from": "darkGrey @ 70% on beige100 - the site's text-2"
|
|
20
|
+
},
|
|
21
|
+
"mutedLight": {
|
|
22
|
+
"hex": "7B7B79",
|
|
23
|
+
"from": "darkGrey @ 60% on beige100"
|
|
24
|
+
},
|
|
25
|
+
"footLight": {
|
|
26
|
+
"hex": "8F8E8C",
|
|
27
|
+
"from": "darkGrey @ 50% on beige100"
|
|
28
|
+
},
|
|
29
|
+
"bodyDark": {
|
|
30
|
+
"hex": "BEBBC5",
|
|
31
|
+
"from": "white @ 70% on violet300 - the site's is-o70-w"
|
|
32
|
+
},
|
|
33
|
+
"bodyDarkAlt": {
|
|
34
|
+
"hex": "C4C1CA",
|
|
35
|
+
"from": "white @ 70% on violet200, lists inside dark cards"
|
|
36
|
+
},
|
|
37
|
+
"footDark": {
|
|
38
|
+
"hex": "888394",
|
|
39
|
+
"from": "white @ 45% on violet300"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"name": "next",
|
|
43
|
+
"title": "Wand, the website's brand (FK Roman, Overused Grotesk)",
|
|
44
|
+
"roles": {
|
|
45
|
+
"light": {
|
|
46
|
+
"bg": "F1EFEB",
|
|
47
|
+
"paper": "EBE8E2",
|
|
48
|
+
"ink": "2D2D2D",
|
|
49
|
+
"text": "686766",
|
|
50
|
+
"soft": "7B7B79",
|
|
51
|
+
"muted": "8F8E8C",
|
|
52
|
+
"accent": "B79A5E",
|
|
53
|
+
"accentText": "917741",
|
|
54
|
+
"label": "917741",
|
|
55
|
+
"emph": "3A314E",
|
|
56
|
+
"emphInk": "FFFFFF",
|
|
57
|
+
"emphText": "BEBBC5",
|
|
58
|
+
"emphAccent": "B79A5E",
|
|
59
|
+
"stroke": "EBE8E2",
|
|
60
|
+
"rule": "D5CEC2",
|
|
61
|
+
"track": "D5CEC2",
|
|
62
|
+
"surface": "EBE8E2"
|
|
63
|
+
},
|
|
64
|
+
"dark": {
|
|
65
|
+
"bg": "261D3D",
|
|
66
|
+
"paper": "3A314E",
|
|
67
|
+
"ink": "FFFFFF",
|
|
68
|
+
"text": "BEBBC5",
|
|
69
|
+
"soft": "C4C1CA",
|
|
70
|
+
"muted": "888394",
|
|
71
|
+
"accent": "B79A5E",
|
|
72
|
+
"accentText": "B79A5E",
|
|
73
|
+
"label": "B79A5E",
|
|
74
|
+
"emph": "504666",
|
|
75
|
+
"emphInk": "FFFFFF",
|
|
76
|
+
"emphText": "C4C1CA",
|
|
77
|
+
"emphAccent": "B79A5E",
|
|
78
|
+
"stroke": "3A314E",
|
|
79
|
+
"rule": "504666",
|
|
80
|
+
"track": "504666",
|
|
81
|
+
"surface": "3A314E"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"faces": {
|
|
85
|
+
"display": {
|
|
86
|
+
"family": "FK Roman Standard Light",
|
|
87
|
+
"weight": 300,
|
|
88
|
+
"tracking": -0.07,
|
|
89
|
+
"leading": 1.06
|
|
90
|
+
},
|
|
91
|
+
"body": {
|
|
92
|
+
"family": "Overused Grotesk",
|
|
93
|
+
"weight": 400,
|
|
94
|
+
"tracking": -0.02,
|
|
95
|
+
"leading": 1.5
|
|
96
|
+
},
|
|
97
|
+
"label": {
|
|
98
|
+
"family": "FK Roman Standard Light",
|
|
99
|
+
"weight": 300,
|
|
100
|
+
"tracking": 0.08,
|
|
101
|
+
"leading": 1.2,
|
|
102
|
+
"caps": true
|
|
103
|
+
},
|
|
104
|
+
"files": [
|
|
105
|
+
{
|
|
106
|
+
"family": "FK Roman Standard Light",
|
|
107
|
+
"weight": 300,
|
|
108
|
+
"italic": false,
|
|
109
|
+
"file": "FKRomanStandard-Light.ttf",
|
|
110
|
+
"pptx": "FK Roman Standard Light"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"family": "Overused Grotesk",
|
|
114
|
+
"weight": 400,
|
|
115
|
+
"italic": false,
|
|
116
|
+
"file": "OverusedGrotesk-Roman.ttf",
|
|
117
|
+
"pptx": "Overused Grotesk"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"family": "Overused Grotesk",
|
|
121
|
+
"weight": 400,
|
|
122
|
+
"italic": true,
|
|
123
|
+
"file": "OverusedGrotesk-Italic.ttf",
|
|
124
|
+
"pptx": "Overused Grotesk"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"family": "Overused Grotesk",
|
|
128
|
+
"weight": 500,
|
|
129
|
+
"italic": false,
|
|
130
|
+
"file": "OverusedGrotesk-Medium.ttf",
|
|
131
|
+
"pptx": "Overused Grotesk Medium"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"family": "Overused Grotesk",
|
|
135
|
+
"weight": 500,
|
|
136
|
+
"italic": true,
|
|
137
|
+
"file": "OverusedGrotesk-MediumItalic.ttf",
|
|
138
|
+
"pptx": "Overused Grotesk Medium"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"family": "Overused Grotesk",
|
|
142
|
+
"weight": 600,
|
|
143
|
+
"italic": false,
|
|
144
|
+
"file": "OverusedGrotesk-SemiBold.ttf",
|
|
145
|
+
"pptx": "Overused Grotesk SemiBold"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"family": "Overused Grotesk",
|
|
149
|
+
"weight": 600,
|
|
150
|
+
"italic": true,
|
|
151
|
+
"file": "OverusedGrotesk-SemiBoldItalic.ttf",
|
|
152
|
+
"pptx": "Overused Grotesk SemiBold"
|
|
153
|
+
}
|
|
154
|
+
],
|
|
155
|
+
"license": "FK Roman Standard: commercial, the website's webfont converted for testing (see fonts/LICENSE-FONTS.md). Overused Grotesk: SIL OFL 1.1"
|
|
156
|
+
},
|
|
157
|
+
"shape": {
|
|
158
|
+
"radius": 0,
|
|
159
|
+
"cardStroke": 0,
|
|
160
|
+
"emphStroke": 0
|
|
161
|
+
},
|
|
162
|
+
"art": {
|
|
163
|
+
"cover": "cover-art.png",
|
|
164
|
+
"overlay": "cover-art.png",
|
|
165
|
+
"statement": "statement-art.png",
|
|
166
|
+
"closing": "closing-art.png",
|
|
167
|
+
"coverArt": "cover-art.png",
|
|
168
|
+
"divider": "divider-art.png",
|
|
169
|
+
"markOnLight": "spark-gold.png",
|
|
170
|
+
"markOnDark": "spark-gold.png",
|
|
171
|
+
"wordmarkOnLight": "wordmark-dark.png",
|
|
172
|
+
"wordmarkOnDark": "wordmark-light.png"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gallery — every component, light and dark, on the Letter surface, in
|
|
3
|
+
* the brand named by WAND_GALLERY_BRAND (node wand.js gallery --brand next).
|
|
4
|
+
* It is the regression for the component library: a change to components.js
|
|
5
|
+
* or to a brand's tokens is reviewed here once, on one sheet, before any job.
|
|
6
|
+
*/
|
|
7
|
+
import { DEFAULT_BRAND } from "../../lib/brand.js";
|
|
8
|
+
|
|
9
|
+
const brand = process.env.WAND_GALLERY_BRAND || DEFAULT_BRAND;
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
name: `Gallery ${brand}`,
|
|
13
|
+
file: `gallery-${brand}`,
|
|
14
|
+
brand,
|
|
15
|
+
surface: process.env.WAND_GALLERY_SURFACE || "letter",
|
|
16
|
+
tag: "Wand studio",
|
|
17
|
+
footer: `Component gallery · ${brand} brand`,
|
|
18
|
+
css: ``,
|
|
19
|
+
pages: (c) => {
|
|
20
|
+
const PHASES = ["Foundation", "Beachhead", "Controlled expansion", "Enterprise scale"];
|
|
21
|
+
const body = (dark) => `
|
|
22
|
+
${c.sec("01", `Type and <em>sections</em>`)}
|
|
23
|
+
${c.lead("The standfirst: one sentence that carries the page's claim, set larger than the body.")}
|
|
24
|
+
${c.body("Running text at the surface's body size: a measure near 75 characters, ragged right, nothing under the minimum size, hierarchy from the face and the scale rather than from bold.")}
|
|
25
|
+
${c.sec("02", `Cards and <em>grids</em>`)}
|
|
26
|
+
${c.cols(3, [
|
|
27
|
+
c.card({ tag: "Layer 01", title: "Real agents", body: ["Persistent identity, layered memory, objectives and a full action history."] }),
|
|
28
|
+
c.card({ tag: "Layer 02", title: "An agentic workforce", body: ["Hired, onboarded, reviewed and retired like people, in one registry."] }),
|
|
29
|
+
c.card({ tag: "Layer 03", title: "Wand OS", emph: true, body: ["The control plane: policy, identity, audit and approval."] }),
|
|
30
|
+
])}
|
|
31
|
+
${c.cols(3, [
|
|
32
|
+
c.card({ cls: "fn", title: "Customer service", level: "h5", body: ["Changes, refunds, baggage and disruption cases resolved end to end."], out: "Serve growth without proportional hiring" }),
|
|
33
|
+
c.card({ cls: "fn", title: "Maintenance", level: "h5", body: ["Manuals, ADs and service bulletins turned into work-card packages."], out: "Absorb a new fleet type" }),
|
|
34
|
+
c.card({ cls: "fn", title: "Revenue accounting", level: "h5", body: ["Ticket sales, BSP, card settlements and refunds matched; leakage flagged."], out: "Protect margins as revenue scales" }),
|
|
35
|
+
])}
|
|
36
|
+
${c.stats([{ v: "$1.68T", k: "assets under management", hero: true }, { v: "150+", k: "countries served" }, { v: "+43.2%", k: "12-month total return" }])}
|
|
37
|
+
${c.sec("03", `Chips, phases and <em>flows</em>`)}
|
|
38
|
+
${c.strip(PHASES, { on: 1 })}
|
|
39
|
+
${c.phase(0, PHASES[0], "One registry, one identity")}
|
|
40
|
+
${c.flow({ label: "With Wand · AI as labor", nodes: [["Agent", "Notices", "a"], ["Agent", "Plans", "a"], ["Agent", "Executes", "a"], ["Agent", "Grades", "a"], ["Human", "Approves", "p"]],
|
|
41
|
+
note: "<b>Capacity = people + agents.</b> The person moves from every step to the one that needs judgment." })}
|
|
42
|
+
${c.cols(2, [c.list(["One registry for people and agents", "Objectives, KPIs and reviews"]), `<div class="card sm">${c.tile("shield-check")}${c.h5("Governance")}<p>A glyph on a tile, recoloured through the theme.</p></div>`], { stretch: false })}
|
|
43
|
+
`;
|
|
44
|
+
const body2 = (dark) => `
|
|
45
|
+
${c.sec("04", `Tables and <em>figures</em>`)}
|
|
46
|
+
${c.table({ head: ["Phase", "Foundation", "First deploy", "Expansion", "Scaling"], emph: 0, rows: [
|
|
47
|
+
["Agentic GDP", { v: "0%", meter: true }, { v: "8%", meter: true }, { v: "20%", meter: true }, { v: "40%", meter: true }],
|
|
48
|
+
["Process penetration", { v: "0%", meter: true }, { v: "5%", meter: true }, { v: "18%", meter: true }, { v: "55%", meter: true }],
|
|
49
|
+
] })}
|
|
50
|
+
${c.table({ head: ["Total return", "BEN", "BLK", "Edge: BEN"], cls: "price", rows: [
|
|
51
|
+
["Trailing 12 months", { v: "+43.2%", cls: "strong" }, "+7.2%", { v: "+36 pts", cls: "accent" }],
|
|
52
|
+
["Closing price", { v: "$31.21", cls: "strong" }, "$1,046.49", { v: "n/a", cls: "accent" }],
|
|
53
|
+
] })}
|
|
54
|
+
${c.cols(3, [
|
|
55
|
+
c.card({ cls: "proof", tag: "Global asset manager", title: "RFP response", level: "h5", body: `${c.delta("120 hrs", "10 hrs")}<p>More volume with a smaller team.</p>` }),
|
|
56
|
+
c.card({ cls: "proof", tag: "Global law firm", title: "Contract review", level: "h5", body: `${c.delta("2–3 hrs", "15 min")}<p>Nine times the volume, same team.</p>` }),
|
|
57
|
+
c.card({ cls: "proof", tag: "Government agency", title: "Visa adjudication", level: "h5", body: `${c.delta("4–6 hrs", "50 min")}<p>500,000 staff hours a year released.</p>` }),
|
|
58
|
+
])}
|
|
59
|
+
${c.note(`<b>Small print.</b> Sources, footnotes and the distribution line are set in the soft colour at the note size.`)}
|
|
60
|
+
${c.sec("05", `Quotes, callouts and <em>steps</em>`)}
|
|
61
|
+
${c.cols(2, [
|
|
62
|
+
c.quote({ text: "Wand helps us move beyond isolated AI tools into a connected agentic workforce, a new operating model for scaling intelligence across the organization.", by: ["Jenny Johnson", "CEO, Franklin Templeton"] }),
|
|
63
|
+
c.quote({ text: "With strong governance in place, we are demonstrating that AI can deliver secure, scalable, and measurable value.", by: ["Vasundhara Chetluru", "Head of AI Platform, Franklin Templeton"], emph: true }),
|
|
64
|
+
])}
|
|
65
|
+
${c.callout({ tag: "Why identity-first matters in asset management", text: "Treating agents as governed identities, not anonymous scripts, is what lets a regulated firm scale AI safely." })}
|
|
66
|
+
${c.steps([
|
|
67
|
+
{ title: "Working session", when: "Within 3 weeks", body: "Two hours with the functions that own the shortlist." },
|
|
68
|
+
{ title: "Deployment posture", when: "Weeks 3–5", body: "Environment, policies and approval gates agreed." },
|
|
69
|
+
{ title: "First agents live", when: "Within 90 days", body: "Real work on real data, measured against the baseline.", emph: true },
|
|
70
|
+
])}
|
|
71
|
+
`;
|
|
72
|
+
return [
|
|
73
|
+
c.cover({ label: "Wand studio", title: `The component <em>gallery</em>`, lede: `Every component on the ${brand} brand, light and dark, on one surface.`,
|
|
74
|
+
body: "Review this sheet after any change to components.js or a brand's tokens. What reads wrong here reads wrong in every job.",
|
|
75
|
+
meta: `${brand} brand · ${process.env.WAND_GALLERY_SURFACE || "letter"} surface` }),
|
|
76
|
+
c.page({ eyebrow: "Light ground", body: body(false) }),
|
|
77
|
+
c.page({ eyebrow: "Light ground", body: body2(false) }),
|
|
78
|
+
c.page({ eyebrow: "Dark ground", dark: true, body: body(true) }),
|
|
79
|
+
c.page({ eyebrow: "Dark ground", dark: true, body: body2(true) }),
|
|
80
|
+
c.closing({ pair: "Wand studio", title: "The component gallery", lede: "A closing page: the lockup over the closing art." }),
|
|
81
|
+
];
|
|
82
|
+
},
|
|
83
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Agentic Workforce for Aviation: a cover and two pages, US Letter, on the
|
|
3
|
+
* current brand. Authored copy (no content file), so coverage is skipped and
|
|
4
|
+
* the review reads the argument instead. The regression for the page surface:
|
|
5
|
+
* three sections a page, the chip strip, the five-node flow, cards in threes
|
|
6
|
+
* and fours, next-step cards with arrows.
|
|
7
|
+
*/
|
|
8
|
+
const FUNCTIONS = [
|
|
9
|
+
["Customer service", "Changes, refunds, baggage and disruption cases resolved end to end, in every language.", "Serve growth without proportional hiring"],
|
|
10
|
+
["Reservations & ticketing", "Reprotection, name corrections, group bookings, PNR cleanup, reissues and exchanges.", "More volume per employee"],
|
|
11
|
+
["Dynamic pricing", "Competitor fares and demand watched continuously; fare updates prepared and loaded.", "Maximise yield on every route"],
|
|
12
|
+
["Flight ops support", "Briefing packs, crew currency and duty limits, and turnaround milestones tracked.", "Grow the network, not the ops headcount"],
|
|
13
|
+
["Maintenance", "Manuals, ADs and service bulletins turned into work-card packages; records kept audit-ready.", "Absorb a new fleet type"],
|
|
14
|
+
["Revenue accounting", "Ticket sales, BSP, card settlements and refunds matched; leakage flagged.", "Protect margins as revenue scales"],
|
|
15
|
+
["Finance operations", "Supplier, lessor and fuel invoices processed and reconciled against contract and uplift data.", "A lean finance function"],
|
|
16
|
+
["Marketing", "Route-launch campaigns built, launched and monitored across segments and channels.", "Fill seats on every new route"],
|
|
17
|
+
["Software development", "AI dev pods ship booking-site features and keep the integration stack patched.", "Ship at challenger speed"],
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export default {
|
|
21
|
+
name: "The Agentic Workforce for Aviation",
|
|
22
|
+
file: "Wand_Agentic_Workforce_for_Aviation",
|
|
23
|
+
brand: process.env.WAND_BRAND || "current",
|
|
24
|
+
surface: "letter",
|
|
25
|
+
tag: "The Agentic Workforce",
|
|
26
|
+
footer: "Wand AI · Strictly confidential, prepared for airline leadership",
|
|
27
|
+
denylist: ["Israir", "Sirkis"],
|
|
28
|
+
css: `
|
|
29
|
+
.cover .top{margin-top:4%}
|
|
30
|
+
.models{display:flex;flex-direction:column;gap:1em}
|
|
31
|
+
.models .pair{display:flex;align-items:stretch;gap:.9em}
|
|
32
|
+
.models .pair .arrow{align-self:center;width:calc(var(--m-arrow) * 1.25);height:calc(var(--m-arrow) * 1.25);fill:var(--accent)}
|
|
33
|
+
.models .card{flex:1}
|
|
34
|
+
.models .card.emph{background:var(--emph)}
|
|
35
|
+
.kpis{row-gap:calc(var(--m-col-gap) * .9)}
|
|
36
|
+
.kpi{display:flex;gap:.8em;align-items:flex-start;padding-top:.7em;border-top:1px solid var(--rule)}
|
|
37
|
+
.kpi .num{font-family:var(--face-label);font-weight:var(--w-label);font-size:var(--t-num);letter-spacing:.2em;color:var(--label);padding-top:.25em}
|
|
38
|
+
.kpi h5{margin-bottom:.25em}
|
|
39
|
+
.kpi p{font-size:var(--t-sm);line-height:1.5;color:var(--text)}
|
|
40
|
+
`,
|
|
41
|
+
pages: (c) => [
|
|
42
|
+
c.cover({
|
|
43
|
+
label: "Wand × Aviation",
|
|
44
|
+
title: `The Agentic Workforce<br>for <em>Aviation</em>`,
|
|
45
|
+
lede: "Airlines are adding aircraft, routes and long-haul flying at record pace. The back office is the same size.",
|
|
46
|
+
body: "Contact centre, reservations, operations and finance are still sized for the airline of a few years ago. Without another kind of worker, a carrier either hires into its margin or lets service crack exactly when the market is watching.",
|
|
47
|
+
extra: `<div class="models">${c.tag("Two models")}<div class="pair">
|
|
48
|
+
${c.card({ tag: "Today · every airline", title: "Human-centric", body: `<div class="sub">100% human</div><p>Humans think, decide and execute. AI shows up in pockets, as tools. Sophisticated, but slow, costly and capped.</p>` })}
|
|
49
|
+
${c.arrow}
|
|
50
|
+
${c.card({ tag: "The target model", title: "Dual workforce", emph: true, body: `<div class="sub">Humans + AI agents</div><p>One workforce, two kinds of colleagues. Agent workers carry the volume growth, so the team that built the airline stays lean and fast.</p>` })}
|
|
51
|
+
</div>${c.p("Overview for airline leadership · October 2026 · Strictly confidential", "meta")}</div>`,
|
|
52
|
+
}),
|
|
53
|
+
|
|
54
|
+
c.page({ eyebrow: "The platform", body: `
|
|
55
|
+
${c.sec("01", `The <em>Ceiling</em>`)}
|
|
56
|
+
${c.body("Copilots make tasks faster. AI labor does the work itself. That is the difference between an efficiency programme and a capacity programme.")}
|
|
57
|
+
${c.flow({ label: "Today · AI as a tool", nodes: [["Person", "Notices", "p"], ["Person", "Gathers", "p"], ["Person", "Instructs", "p"], ["AI", "Drafts", "a"], ["Person", "Checks", "p"]],
|
|
58
|
+
note: "<b>Capacity = your people.</b> Four of five steps wait on a person; that person is the ceiling." })}
|
|
59
|
+
${c.flow({ label: "With Wand · AI as labor", nodes: [["Agent", "Notices", "a"], ["Agent", "Plans", "a"], ["Agent", "Executes", "a"], ["Agent", "Grades", "a"], ["Human", "Approves", "p"]],
|
|
60
|
+
note: "<b>Capacity = people + agents.</b> The person moves from every step to the one that needs judgment." })}
|
|
61
|
+
${c.sec("02", `Three <em>Layers</em>`)}
|
|
62
|
+
${c.body("Model-agnostic and cloud-agnostic: Wand runs inside the airline's own environment, on its own models, behind its own controls.")}
|
|
63
|
+
${c.cols(3, [
|
|
64
|
+
c.card({ tag: "Layer 01", title: "Real agents", body: ["Persistent identity, memory, objectives and full action history. They do not wait for a prompt; they notice, plan, act and grade their own work."] }),
|
|
65
|
+
c.card({ tag: "Layer 02", title: "An agentic workforce", body: ["Agents are hired, onboarded, reviewed and retired like people, in one registry that holds humans and agents together."] }),
|
|
66
|
+
c.card({ tag: "Layer 03", title: "Wand OS", emph: true, body: ["The control plane the workforce reports into: policy, identity, audit and approval, integrated with booking, ops, maintenance and finance."] }),
|
|
67
|
+
])}
|
|
68
|
+
${c.sec("03", `Four <em>Controls</em>`)}
|
|
69
|
+
${c.body("In aviation, governance is the deployment prerequisite, which is why we built it first.")}
|
|
70
|
+
${c.cols(4, [
|
|
71
|
+
c.card({ tag: "01", title: "Policy", level: "h5", body: ["Rules the platform enforces, not guidance a model is asked to follow."] }),
|
|
72
|
+
c.card({ tag: "02", title: "Identity", level: "h5", body: ["Every agent has its own identity and permissions. No shared accounts."] }),
|
|
73
|
+
c.card({ tag: "03", title: "Audit", level: "h5", body: ["Every action written to an immutable record an auditor can replay."] }),
|
|
74
|
+
c.card({ tag: "04", title: "Human approval", level: "h5", body: ["Named people approve at the gates you set, per process."] }),
|
|
75
|
+
])}
|
|
76
|
+
${c.note("Built for civil aviation authorities, FAA, DOT and TSA, EASA and EU obligations: every agent action is attributable, bounded and reconstructable.")}
|
|
77
|
+
` }),
|
|
78
|
+
|
|
79
|
+
c.page({ eyebrow: "In the airline", body: `
|
|
80
|
+
${c.sec("04", `Where This <em>Lands</em>`)}
|
|
81
|
+
${c.cols(3, FUNCTIONS.map(([t, b, o]) => c.card({ cls: "fn", title: t, level: "h5", body: [b], out: o })))}
|
|
82
|
+
${c.sec("05", `Measured <em>against a baseline</em>`)}
|
|
83
|
+
${c.cols(3, [
|
|
84
|
+
["01", "Volume per FTE", "Cases handled per person per day, before and after. The capacity number."],
|
|
85
|
+
["02", "Cycle time", "End-to-end elapsed time per case, including the escalations."],
|
|
86
|
+
["03", "Error rate against review", "How often a human reviewer disagrees with the agent. The quality gate that matters most."],
|
|
87
|
+
["04", "Escalation rate", "Share of cases routed to a person. Falls as the agent learns; never reaches zero by design."],
|
|
88
|
+
["05", "Cost per transaction", "Fully loaded, agent and human combined. The number that shows up in the operating line."],
|
|
89
|
+
["06", "Time to production", "Weeks from kickoff to the first agent doing real work on real data."],
|
|
90
|
+
].map(([n, t, b]) => `<div class="kpi"><span class="num">${n}</span><div>${c.h5(t)}<p>${b}</p></div></div>`), { cls: "kpis" })}
|
|
91
|
+
${c.sec("06", `Three <em>Steps</em>`)}
|
|
92
|
+
${c.steps([
|
|
93
|
+
{ title: "Working session", when: "Within 3 weeks", body: "Two hours with the functions that own the shortlist. One process chosen, its baseline written down." },
|
|
94
|
+
{ title: "Deployment posture", when: "Weeks 3–5", body: "Environment, policies and approval gates agreed with your technology and compliance teams." },
|
|
95
|
+
{ title: "First agents live", when: "Within 90 days", body: "Real work on real data, inside your environment, measured against the baseline.", emph: true },
|
|
96
|
+
])}
|
|
97
|
+
` }),
|
|
98
|
+
],
|
|
99
|
+
};
|