@doan-labs/peek 1.0.0 → 2.0.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/LICENSE +21 -0
- package/README.md +64 -1
- package/dist/bloc.d.ts +2 -0
- package/dist/core.d.ts +9 -0
- package/dist/draw.d.ts +24 -2
- package/dist/identity.d.ts +4 -1
- package/dist/identity.js +22 -2
- package/dist/index.d.ts +1 -7
- package/dist/index.js +719 -142
- package/dist/peek.svelte +75 -0
- package/dist/peek.svelte.d.ts +5 -0
- package/dist/svelte-peek.d.ts +14 -0
- package/dist/svelte-peek.js +2205 -0
- package/dist/svelte.d.ts +3 -0
- package/dist/svelte.js +1650 -0
- package/dist/svg.d.ts +3 -1
- package/dist/svg.js +737 -166
- package/dist/tables.d.ts +67 -1
- package/dist/vue.d.ts +17 -0
- package/dist/vue.js +2302 -0
- package/package.json +30 -5
- package/src/bloc.ts +326 -0
- package/src/core.ts +39 -0
- package/src/draw.ts +522 -109
- package/src/identity.ts +11 -0
- package/src/index.ts +1 -26
- package/src/peek.svelte +75 -0
- package/src/peek.svelte.d.ts +5 -0
- package/src/peek.tsx +1 -1
- package/src/svelte-peek.ts +18 -0
- package/src/svelte.ts +3 -0
- package/src/svg.ts +30 -2
- package/src/tables.ts +74 -2
- package/src/vue.ts +133 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doan-labs/peek",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -18,6 +18,16 @@
|
|
|
18
18
|
"types": "./dist/identity.d.ts",
|
|
19
19
|
"import": "./dist/identity.js",
|
|
20
20
|
"default": "./dist/identity.js"
|
|
21
|
+
},
|
|
22
|
+
"./vue": {
|
|
23
|
+
"types": "./dist/vue.d.ts",
|
|
24
|
+
"import": "./dist/vue.js",
|
|
25
|
+
"default": "./dist/vue.js"
|
|
26
|
+
},
|
|
27
|
+
"./svelte": {
|
|
28
|
+
"types": "./dist/svelte.d.ts",
|
|
29
|
+
"svelte": "./dist/svelte.js",
|
|
30
|
+
"default": "./dist/svelte.js"
|
|
21
31
|
}
|
|
22
32
|
},
|
|
23
33
|
"scripts": {
|
|
@@ -27,18 +37,24 @@
|
|
|
27
37
|
"prepack": "bun run build"
|
|
28
38
|
},
|
|
29
39
|
"peerDependencies": {
|
|
30
|
-
"react": ">=19"
|
|
40
|
+
"react": ">=19",
|
|
41
|
+
"svelte": ">=5.20",
|
|
42
|
+
"vue": ">=3.5"
|
|
31
43
|
},
|
|
32
44
|
"devDependencies": {
|
|
33
|
-
"@peek/typescript-config": "
|
|
45
|
+
"@peek/typescript-config": "0.0.0",
|
|
34
46
|
"@types/bun": "latest",
|
|
35
47
|
"@types/react": "^19.2.0",
|
|
36
48
|
"@types/react-dom": "^19.2.0",
|
|
37
49
|
"react": "^19.2.0",
|
|
38
50
|
"react-dom": "^19.2.0",
|
|
39
|
-
"
|
|
51
|
+
"svelte": "^5.20",
|
|
52
|
+
"typescript": "^6.0.2",
|
|
53
|
+
"vue": "^3.5"
|
|
40
54
|
},
|
|
41
|
-
"description": "Deterministic SVG avatars from a name, with expressions and
|
|
55
|
+
"description": "Deterministic SVG avatars from a name, with expressions and components for React, Vue and Svelte.",
|
|
56
|
+
"license": "MIT",
|
|
57
|
+
"author": "Doan Labs",
|
|
42
58
|
"homepage": "https://peek.doan-labs.com",
|
|
43
59
|
"repository": {
|
|
44
60
|
"type": "git",
|
|
@@ -49,6 +65,8 @@
|
|
|
49
65
|
"avatar",
|
|
50
66
|
"svg",
|
|
51
67
|
"react",
|
|
68
|
+
"vue",
|
|
69
|
+
"svelte",
|
|
52
70
|
"deterministic",
|
|
53
71
|
"peek"
|
|
54
72
|
],
|
|
@@ -56,6 +74,7 @@
|
|
|
56
74
|
"dist",
|
|
57
75
|
"src/*.ts",
|
|
58
76
|
"src/*.tsx",
|
|
77
|
+
"src/*.svelte",
|
|
59
78
|
"!src/*.test.ts",
|
|
60
79
|
"README.md"
|
|
61
80
|
],
|
|
@@ -65,6 +84,12 @@
|
|
|
65
84
|
"peerDependenciesMeta": {
|
|
66
85
|
"react": {
|
|
67
86
|
"optional": true
|
|
87
|
+
},
|
|
88
|
+
"svelte": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
91
|
+
"vue": {
|
|
92
|
+
"optional": true
|
|
68
93
|
}
|
|
69
94
|
}
|
|
70
95
|
}
|
package/src/bloc.ts
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Bloc, from the Bloc sheet (direction A, palette P2): every face rebuilt
|
|
3
|
+
* from a square, a circle and a bar. The same four silhouettes, the body cut
|
|
4
|
+
* in two down the middle (strong on the left, light on the right), one ink
|
|
5
|
+
* bar for both brows, and the trait as a block over the upper right outline.
|
|
6
|
+
* No round corners, no gradients, no outlines.
|
|
7
|
+
*
|
|
8
|
+
* Expressions come from the same channels as peek, read as three moulds:
|
|
9
|
+
* round eyes and a whole bar at rest, a level cut from below or above for
|
|
10
|
+
* the smiling and the drowsy, a tilted cut and a bar broken in two for the
|
|
11
|
+
* angry, the sad and the confused. The bar is always two halves, so it
|
|
12
|
+
* breaks and mends continuously when animated.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
clamp,
|
|
17
|
+
depth,
|
|
18
|
+
f2,
|
|
19
|
+
type Node,
|
|
20
|
+
n,
|
|
21
|
+
type Parts,
|
|
22
|
+
seat,
|
|
23
|
+
smooth,
|
|
24
|
+
} from './draw'
|
|
25
|
+
import { BLOC, COLORS, FACES, type Face, type FaceGeom, INK } from './tables'
|
|
26
|
+
|
|
27
|
+
const RAD = Math.PI / 180
|
|
28
|
+
|
|
29
|
+
/* Peek's silhouettes; the triangle's round joins are cut sharp and it grows
|
|
30
|
+
* to the 300 box the circle and the diamond fill, its base unmoved. */
|
|
31
|
+
const TRI = [
|
|
32
|
+
[170, 12.8],
|
|
33
|
+
[320, 312.8],
|
|
34
|
+
[20, 312.8],
|
|
35
|
+
] as const
|
|
36
|
+
const BODY: Record<Face, { body: FaceGeom['body']; crown: FaceGeom['crown'] }> =
|
|
37
|
+
{
|
|
38
|
+
diamond: FACES.diamond,
|
|
39
|
+
semicircle: FACES.semicircle,
|
|
40
|
+
circle: FACES.circle,
|
|
41
|
+
triangle: {
|
|
42
|
+
body: { d: `M${TRI.map((p) => p.join(' ')).join('L')}Z` },
|
|
43
|
+
crown: { poly: TRI, r: 0 },
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Bloc's own ramps: lines thicken 2.6x below 30px, eyes and trait grow
|
|
48
|
+
const strokeFor = (px: number) =>
|
|
49
|
+
px >= 96 ? 1 : px >= 56 ? 1.25 : px >= 40 ? 1.6 : px >= 30 ? 2 : 2.6
|
|
50
|
+
const boostFor = (px: number) => (px < 30 ? 1.25 : px < 56 ? 1.12 : 1)
|
|
51
|
+
|
|
52
|
+
/** A point turned by `deg` about the origin, y down. */
|
|
53
|
+
const turn = (x: number, y: number, deg: number) => {
|
|
54
|
+
const c = Math.cos(deg * RAD)
|
|
55
|
+
const s = Math.sin(deg * RAD)
|
|
56
|
+
return [x * c - y * s, x * s + y * c] as const
|
|
57
|
+
}
|
|
58
|
+
const poly = (pts: readonly (readonly [number, number])[]) =>
|
|
59
|
+
`M${pts.map(([x, y]) => `${f2(x)} ${f2(y)}`).join('L')}Z`
|
|
60
|
+
|
|
61
|
+
export function blocParts({ who, pose, o, defs, g }: Parts): [Node[], Node[]] {
|
|
62
|
+
const sh: FaceGeom = FACES[who.face]
|
|
63
|
+
const { body, crown } = BODY[who.face]
|
|
64
|
+
const id = o.id
|
|
65
|
+
const P = who.persona
|
|
66
|
+
const spread = P.spread
|
|
67
|
+
const st = strokeFor(o.size)
|
|
68
|
+
const up = boostFor(o.size)
|
|
69
|
+
const strong = BLOC[who.color]
|
|
70
|
+
// the light half goes deep on light grounds, so the body never sinks in
|
|
71
|
+
const light =
|
|
72
|
+
o.frame === 'bone' || o.frame === 'paper'
|
|
73
|
+
? COLORS[who.color].deep
|
|
74
|
+
: COLORS[who.color].body
|
|
75
|
+
const lineW = sh.brow.w * st
|
|
76
|
+
|
|
77
|
+
// the body: one block, its right half cut out in the light ink
|
|
78
|
+
defs.push(
|
|
79
|
+
n('clipPath', 'half', { id: `${id}-r` }, [
|
|
80
|
+
n('rect', 'half.shape', { x: 170, y: -30, width: 200, height: 400 }),
|
|
81
|
+
]),
|
|
82
|
+
)
|
|
83
|
+
const block = (key: string, fill: string, clip?: string) =>
|
|
84
|
+
'd' in body
|
|
85
|
+
? n('path', key, { d: body.d, fill, 'clip-path': clip })
|
|
86
|
+
: n('circle', key, {
|
|
87
|
+
cx: body.circle[0],
|
|
88
|
+
cy: body.circle[1],
|
|
89
|
+
r: body.circle[2],
|
|
90
|
+
fill,
|
|
91
|
+
'clip-path': clip,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// eyes: a paper disc with its pupil, a solid bead, or a rimmed ring
|
|
95
|
+
const eyeS = g('eyeS')
|
|
96
|
+
const r0 = Math.sqrt(sh.rx * sh.ry) * up
|
|
97
|
+
const lid0 = g('lid')
|
|
98
|
+
const lidAsym = g('lidAsym')
|
|
99
|
+
const lower = clamp(g('lower'), 0, 1)
|
|
100
|
+
const pupil = g('pupil')
|
|
101
|
+
const gx = clamp(g('gx'), -1.1, 1.1)
|
|
102
|
+
const gy = clamp(g('gy'), -1.1, 1.1)
|
|
103
|
+
const tilt = g('lidTilt')
|
|
104
|
+
const bead = who.eyes === 'bead'
|
|
105
|
+
const eyes = sh.eyes.map(([ex, ey], i) => {
|
|
106
|
+
const side = i === 0 ? -1 : 1
|
|
107
|
+
const kk = `eye${i}`
|
|
108
|
+
let lid = clamp(lid0 + (i === 0 ? lidAsym : -lidAsym * 0.3), 0, 1)
|
|
109
|
+
lid = lid + (1 - lid) * pose.blink
|
|
110
|
+
// peek rests its lid a tenth down; a Bloc eye rests whole
|
|
111
|
+
const cut = clamp((lid - 0.1) / 0.9, 0, 1)
|
|
112
|
+
const r = bead ? r0 * 0.7 * eyeS * (0.4 + 0.6 * pupil) : r0 * eyeS
|
|
113
|
+
const [tx, ty] = bead
|
|
114
|
+
? [ex + side * spread + gx * 6, ey + gy * 6]
|
|
115
|
+
: [ex + side * spread, ey]
|
|
116
|
+
// the cuts: a straight lid from above, tilted, and a level one below
|
|
117
|
+
const a = side * tilt
|
|
118
|
+
const edge = -r - 1 + cut * (2 * r + 2)
|
|
119
|
+
const floor = r + 1 - lower * r * 2.2
|
|
120
|
+
const W = r + 2
|
|
121
|
+
const lidAt = (x: number) =>
|
|
122
|
+
x * Math.tan(a * RAD) + edge / Math.cos(a * RAD)
|
|
123
|
+
const [t0, t1] = [lidAt(-W), lidAt(W)]
|
|
124
|
+
defs.push(
|
|
125
|
+
n('clipPath', `${kk}.clip`, { id: `${id}-e${i}` }, [
|
|
126
|
+
n('path', `${kk}.clip.shape`, {
|
|
127
|
+
d: poly([
|
|
128
|
+
[-W, t0],
|
|
129
|
+
[W, t1],
|
|
130
|
+
[W, Math.max(floor, t1)],
|
|
131
|
+
[-W, Math.max(floor, t0)],
|
|
132
|
+
]),
|
|
133
|
+
}),
|
|
134
|
+
]),
|
|
135
|
+
)
|
|
136
|
+
const pr = sh.pr * pupil * up
|
|
137
|
+
const px = gx * Math.max(0, r - pr) * 0.85
|
|
138
|
+
const py = gy * Math.max(0, r - pr) * 0.75
|
|
139
|
+
const rim = Math.min(r * 0.27 * Math.min(st, 1.6), r * 0.4)
|
|
140
|
+
const inner: Node[] = bead
|
|
141
|
+
? [n('circle', `${kk}.bead`, { cx: 0, cy: 0, r: f2(r), fill: INK.ink })]
|
|
142
|
+
: [
|
|
143
|
+
n('circle', `${kk}.white`, {
|
|
144
|
+
cx: 0,
|
|
145
|
+
cy: 0,
|
|
146
|
+
r: f2(r),
|
|
147
|
+
fill: INK.paper,
|
|
148
|
+
}),
|
|
149
|
+
n('circle', `${kk}.pupil`, {
|
|
150
|
+
cx: f2(px),
|
|
151
|
+
cy: f2(py),
|
|
152
|
+
r: f2(pr),
|
|
153
|
+
fill: INK.ink,
|
|
154
|
+
}),
|
|
155
|
+
]
|
|
156
|
+
if (who.eyes === 'ring')
|
|
157
|
+
inner.push(
|
|
158
|
+
n('circle', `${kk}.rim`, {
|
|
159
|
+
cx: 0,
|
|
160
|
+
cy: 0,
|
|
161
|
+
r: f2(r - rim / 2),
|
|
162
|
+
fill: 'none',
|
|
163
|
+
stroke: INK.ink,
|
|
164
|
+
'stroke-width': f2(rim),
|
|
165
|
+
}),
|
|
166
|
+
)
|
|
167
|
+
// closed: a bar where the eye was
|
|
168
|
+
const co = smooth(0.86, 1, lid)
|
|
169
|
+
const yy = Math.min(edge, r) - lineW / 2
|
|
170
|
+
return n('g', kk, { transform: `translate(${f2(tx)} ${f2(ty)})` }, [
|
|
171
|
+
n('g', `${kk}.inner`, { 'clip-path': `url(#${id}-e${i})` }, inner),
|
|
172
|
+
n('path', `${kk}.closed`, {
|
|
173
|
+
d: `M${f2(-r * 0.95)} ${f2(yy)}H${f2(r * 0.95)}`,
|
|
174
|
+
transform: `rotate(${f2(a * 0.6)})`,
|
|
175
|
+
stroke: INK.ink,
|
|
176
|
+
'stroke-width': f2(lineW * 1.4),
|
|
177
|
+
opacity: co > 0.01 ? f2(co) : 0,
|
|
178
|
+
}),
|
|
179
|
+
])
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
// the bar: both brows in one, from one eye's outer edge to the other's.
|
|
183
|
+
// The persona slants it; the expression's tilt breaks it into a V or a
|
|
184
|
+
// caret; asymmetry lifts its right end. It never leaves the body.
|
|
185
|
+
const bt = pose.browTilt
|
|
186
|
+
const basym = g('browAsym')
|
|
187
|
+
const th = lineW * 2 * g('browW')
|
|
188
|
+
const hw =
|
|
189
|
+
sh.eyes[1][0] - 170 + spread + Math.sqrt(sh.rx * sh.ry) * P.mul.eyeS * up
|
|
190
|
+
const gap = smooth(0, 8, Math.abs(bt)) * th * 0.9
|
|
191
|
+
const slant = P.add.browTilt * 0.3 - Math.atan((1.3 * basym) / (2 * hw)) / RAD
|
|
192
|
+
const y0 = sh.brow.y + g('browY') - (eyeS - 1) * sh.ry * 0.9 - 0.35 * basym
|
|
193
|
+
// `len` shortens the bar from its outer ends, 1 is full length
|
|
194
|
+
const halves = (dy: number, len = 1) =>
|
|
195
|
+
[-1, 1].map((s) => {
|
|
196
|
+
const [ox, oy] = turn((s * gap) / 2, 0, slant)
|
|
197
|
+
const x0 = 170 + ox
|
|
198
|
+
const yy = y0 + dy + oy
|
|
199
|
+
const ang = slant + s * bt
|
|
200
|
+
const [ux, uy] = turn(s * (hw * len - gap / 2), 0, ang)
|
|
201
|
+
const [nx, ny] = turn(0, th / 2, ang)
|
|
202
|
+
return [
|
|
203
|
+
[x0 - nx, yy - ny],
|
|
204
|
+
[x0 + ux - nx, yy + uy - ny],
|
|
205
|
+
[x0 + ux + nx, yy + uy + ny],
|
|
206
|
+
[x0 + nx, yy + ny],
|
|
207
|
+
] as const
|
|
208
|
+
})
|
|
209
|
+
const inside = (dy: number, len = 1) =>
|
|
210
|
+
halves(dy, len).every((q) =>
|
|
211
|
+
q.every(([x, y]) => depth(crown, x, y) + 2 < 0),
|
|
212
|
+
)
|
|
213
|
+
// pushed down until it fits; if even that fails, it grows shorter
|
|
214
|
+
let lo = 0
|
|
215
|
+
let hi = 0
|
|
216
|
+
if (!inside(0)) {
|
|
217
|
+
hi = 60
|
|
218
|
+
for (let k = 0; k < 12; k++) {
|
|
219
|
+
const mid = (lo + hi) / 2
|
|
220
|
+
if (inside(mid)) hi = mid
|
|
221
|
+
else lo = mid
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
let len = 1
|
|
225
|
+
if (!inside(hi)) {
|
|
226
|
+
let short = 0
|
|
227
|
+
for (let k = 0; k < 12; k++) {
|
|
228
|
+
const mid = (short + len) / 2
|
|
229
|
+
if (inside(hi, mid)) short = mid
|
|
230
|
+
else len = mid
|
|
231
|
+
}
|
|
232
|
+
len = short
|
|
233
|
+
}
|
|
234
|
+
const bars = halves(hi, len).map((q, i) =>
|
|
235
|
+
n('path', `bar${i}`, { d: poly(q), fill: INK.ink }),
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
// mouth: poly a turned square, round a half disc, line a bar, box a
|
|
239
|
+
// block. Diamonds and triangles get a bigger one, it vanished at 16px.
|
|
240
|
+
const m = sh.mouth
|
|
241
|
+
const big =
|
|
242
|
+
(who.face === 'diamond' || who.face === 'triangle' ? 1.25 : 1) * up
|
|
243
|
+
const mw = m.w * Math.max(0.05, g('mw')) * big
|
|
244
|
+
const mt = g('mt') * m.d * big
|
|
245
|
+
const mb = g('mb') * m.d * big
|
|
246
|
+
const mid = (mt + mb) / 2
|
|
247
|
+
const kb = 4 / 3
|
|
248
|
+
let d: string
|
|
249
|
+
if (who.mouth === 'poly') {
|
|
250
|
+
const s = Math.max(mw * 0.9, (mb - mt) / 2)
|
|
251
|
+
d = poly([
|
|
252
|
+
[-s, mid],
|
|
253
|
+
[0, mid - s],
|
|
254
|
+
[s, mid],
|
|
255
|
+
[0, mid + s],
|
|
256
|
+
])
|
|
257
|
+
} else if (who.mouth === 'box') {
|
|
258
|
+
const h = Math.max(mb - mt, lineW * 1.6) / 2
|
|
259
|
+
const w = mw * 0.85
|
|
260
|
+
d = poly([
|
|
261
|
+
[-w, mid - h],
|
|
262
|
+
[w, mid - h],
|
|
263
|
+
[w, mid + h],
|
|
264
|
+
[-w, mid + h],
|
|
265
|
+
])
|
|
266
|
+
} else if (who.mouth === 'line') {
|
|
267
|
+
const h = (lineW * 1.4) / 2
|
|
268
|
+
const w = mw * 1.5
|
|
269
|
+
const y = mid * 0.5
|
|
270
|
+
d = poly([
|
|
271
|
+
[-w, y - h],
|
|
272
|
+
[w, y - h],
|
|
273
|
+
[w, y + h],
|
|
274
|
+
[-w, y + h],
|
|
275
|
+
])
|
|
276
|
+
} else {
|
|
277
|
+
d = `M${f2(-mw)} 0C${f2(-mw)} ${f2(mb * kb)} ${f2(mw)} ${f2(mb * kb)} ${f2(mw)} 0C${f2(mw)} ${f2(mt * kb)} ${f2(-mw)} ${f2(mt * kb)} ${f2(-mw)} 0Z`
|
|
278
|
+
}
|
|
279
|
+
const mouth = n('path', 'mouth', {
|
|
280
|
+
d,
|
|
281
|
+
transform: `translate(${f2(m.x + g('mx'))} ${f2(m.y + g('my'))}) rotate(${f2(g('mk'))})`,
|
|
282
|
+
fill: INK.ink,
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
// the trait, as a block over the upper right outline. Perky lifts it
|
|
286
|
+
// clear, low slides it down the side.
|
|
287
|
+
const hair = g('hair')
|
|
288
|
+
const s = seat(crown, Math.max(0, hair) * 8, 110 + Math.max(0, -hair) * 40)
|
|
289
|
+
const shape =
|
|
290
|
+
who.trait === 'square'
|
|
291
|
+
? n('rect', 'trait.shape', { x: -21, y: -21, width: 42, height: 42 })
|
|
292
|
+
: who.trait === 'dot'
|
|
293
|
+
? n('circle', 'trait.shape', { cx: 0, cy: 0, r: 22 })
|
|
294
|
+
: who.trait === 'ring'
|
|
295
|
+
? n('circle', 'trait.shape', {
|
|
296
|
+
cx: 0,
|
|
297
|
+
cy: 0,
|
|
298
|
+
r: 17.5,
|
|
299
|
+
fill: 'none',
|
|
300
|
+
stroke: strong,
|
|
301
|
+
'stroke-width': 13,
|
|
302
|
+
})
|
|
303
|
+
: who.trait === 'fin'
|
|
304
|
+
? n('path', 'trait.shape', { d: 'M-27 27V-27A54 54 0 0 1 27 27Z' })
|
|
305
|
+
: // peak stands as a bar, upright and short of the brow bar's length
|
|
306
|
+
n('rect', 'trait.shape', {
|
|
307
|
+
x: -16,
|
|
308
|
+
y: -30,
|
|
309
|
+
width: 32,
|
|
310
|
+
height: 60,
|
|
311
|
+
})
|
|
312
|
+
const trait = n(
|
|
313
|
+
'g',
|
|
314
|
+
'trait',
|
|
315
|
+
{
|
|
316
|
+
transform: `translate(${f2(s.x)} ${f2(s.y + pose.lag)}) scale(${f2(up)})`,
|
|
317
|
+
fill: strong,
|
|
318
|
+
},
|
|
319
|
+
[shape],
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
return [
|
|
323
|
+
[block('body', strong), block('body.light', light, `url(#${id}-r)`), trait],
|
|
324
|
+
[...eyes, ...bars, mouth],
|
|
325
|
+
]
|
|
326
|
+
}
|
package/src/core.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* Everything but a component: each framework entry re-exports this. */
|
|
2
|
+
export type { Frame, Gaze, Node } from './draw'
|
|
3
|
+
/** Low level: the drawing tree that toSvg and <Peek> both render. */
|
|
4
|
+
export { draw } from './draw'
|
|
5
|
+
export type { Axes, Identity, Persona } from './identity'
|
|
6
|
+
export { AXES, identify, LISTS, tidy } from './identity'
|
|
7
|
+
export type { PeekOptions } from './svg'
|
|
8
|
+
export { settle, toSvg } from './svg'
|
|
9
|
+
export type {
|
|
10
|
+
AccessorySlot,
|
|
11
|
+
Axis,
|
|
12
|
+
Brows,
|
|
13
|
+
Channel,
|
|
14
|
+
Cheeks,
|
|
15
|
+
Color,
|
|
16
|
+
Expression,
|
|
17
|
+
Eyes,
|
|
18
|
+
Eyewear,
|
|
19
|
+
Face,
|
|
20
|
+
Headwear,
|
|
21
|
+
Mouth,
|
|
22
|
+
Neckwear,
|
|
23
|
+
Trait,
|
|
24
|
+
Variant,
|
|
25
|
+
} from './tables'
|
|
26
|
+
export {
|
|
27
|
+
ACCESSORIES,
|
|
28
|
+
BLOC,
|
|
29
|
+
COLORS,
|
|
30
|
+
EXPRESSIONS,
|
|
31
|
+
FACES,
|
|
32
|
+
GROUPS,
|
|
33
|
+
HAZE,
|
|
34
|
+
LATEST,
|
|
35
|
+
PARTS,
|
|
36
|
+
VARIANTS,
|
|
37
|
+
VERSIONS,
|
|
38
|
+
WHEEL,
|
|
39
|
+
} from './tables'
|