@doan-labs/peek 0.1.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/src/tables.ts ADDED
@@ -0,0 +1,414 @@
1
+ /*
2
+ * The peek style's tables. Faces, inks and expressions are transcribed from
3
+ * the Peek v1.2 page (peek.html); the new part types are drawn in the same
4
+ * language and driven by the same channels.
5
+ *
6
+ * Every list is append-only and ordered: object key order is list order, and
7
+ * the hash indexes into it. Never reorder or remove an entry. A version pins
8
+ * how much of each list it may pick from (VERSIONS), so growing a list means
9
+ * a new version, never a new result for an old one.
10
+ */
11
+
12
+ type Pt = readonly [number, number]
13
+
14
+ /** Structure inks: never a body color. Signal stays in the interface. */
15
+ export const INK = {
16
+ ink: '#1A1918',
17
+ bone: '#F3F0E8',
18
+ paper: '#FFFDF8',
19
+ } as const
20
+
21
+ export type FaceGeom = {
22
+ /** `stroke` rounds the corners: a round-join outline in the body ink. */
23
+ body:
24
+ | { readonly d: string; readonly stroke?: number }
25
+ | { readonly circle: readonly [number, number, number] }
26
+ bottom: number
27
+ /** How far the body sinks below the floor at rest. */
28
+ sink: number
29
+ eyes: readonly [Pt, Pt]
30
+ rx: number
31
+ ry: number
32
+ pr: number
33
+ brow: {
34
+ readonly y: number
35
+ readonly hw: number
36
+ readonly arch: number
37
+ readonly w: number
38
+ }
39
+ cheeks: readonly [Pt, Pt]
40
+ crx: number
41
+ cry: number
42
+ mouth: {
43
+ readonly x: number
44
+ readonly y: number
45
+ readonly w: number
46
+ readonly d: number
47
+ readonly sw: number
48
+ }
49
+ /**
50
+ * The crown: the visible outline above the eyes, as a convex polygon grown
51
+ * by `r` (a point grown by r is a circle). `poly[0]` is the top and the
52
+ * polygon runs clockwise. Every trait sits on this outline, so any trait
53
+ * fits any face.
54
+ */
55
+ crown: { readonly poly: readonly Pt[]; readonly r: number }
56
+ }
57
+
58
+ export const FACES = {
59
+ diamond: {
60
+ body: { d: 'M170 20L320 170L170 320L20 170Z' },
61
+ bottom: 320,
62
+ sink: 30,
63
+ eyes: [
64
+ [132.5, 155],
65
+ [207.5, 155],
66
+ ],
67
+ rx: 20,
68
+ ry: 27.5,
69
+ pr: 10,
70
+ brow: { y: 102.5, hw: 18.75, arch: 15, w: 5.62 },
71
+ cheeks: [
72
+ [117.5, 200],
73
+ [222.5, 200],
74
+ ],
75
+ crx: 11.25,
76
+ cry: 5.62,
77
+ mouth: { x: 170, y: 218.75, w: 15, d: 16.25, sw: 4.38 },
78
+ crown: {
79
+ poly: [
80
+ [170, 20],
81
+ [320, 170],
82
+ [170, 320],
83
+ [20, 170],
84
+ ],
85
+ r: 0,
86
+ },
87
+ },
88
+ semicircle: {
89
+ body: { d: 'M0 255A170 170 0 0 1 340 255Z' },
90
+ bottom: 255,
91
+ sink: 0,
92
+ eyes: [
93
+ [102.5, 175],
94
+ [237.5, 175],
95
+ ],
96
+ rx: 30,
97
+ ry: 25,
98
+ pr: 11.25,
99
+ brow: { y: 127.5, hw: 25, arch: 15, w: 5.62 },
100
+ cheeks: [
101
+ [55, 212.5],
102
+ [285, 212.5],
103
+ ],
104
+ crx: 11.25,
105
+ cry: 5.62,
106
+ mouth: { x: 170, y: 221.25, w: 16.25, d: 16.25, sw: 4.38 },
107
+ crown: { poly: [[170, 255]], r: 170 },
108
+ },
109
+ circle: {
110
+ body: { circle: [170, 170, 150] },
111
+ bottom: 320,
112
+ sink: 20,
113
+ eyes: [
114
+ [112.8, 162.2],
115
+ [227.2, 162.2],
116
+ ],
117
+ rx: 29.9,
118
+ ry: 29.9,
119
+ pr: 13,
120
+ brow: { y: 110.2, hw: 23.4, arch: 15.6, w: 5.85 },
121
+ cheeks: [
122
+ [84.2, 203.8],
123
+ [255.8, 203.8],
124
+ ],
125
+ crx: 11.7,
126
+ cry: 5.85,
127
+ mouth: { x: 170, y: 223.3, w: 16.9, d: 16.9, sw: 4.55 },
128
+ crown: { poly: [[170, 170]], r: 150 },
129
+ },
130
+ /* The v2.1 spec (M50 12L92 88H8Z on a 100u grid, 8u round joins, eyes at
131
+ * 62u) scaled x3.4 about the centre. */
132
+ triangle: {
133
+ body: { d: 'M170 40.8L312.8 299.2L27.2 299.2Z', stroke: 27.2 },
134
+ bottom: 312.8,
135
+ sink: 0,
136
+ eyes: [
137
+ [132.6, 210.8],
138
+ [207.4, 210.8],
139
+ ],
140
+ rx: 20.4,
141
+ ry: 27.2,
142
+ pr: 10,
143
+ brow: { y: 166.6, hw: 18.7, arch: 15.3, w: 5.62 },
144
+ cheeks: [
145
+ [98.6, 238],
146
+ [241.4, 238],
147
+ ],
148
+ crx: 11.5,
149
+ cry: 5.75,
150
+ mouth: { x: 170, y: 254, w: 17, d: 16.25, sw: 4.38 },
151
+ crown: {
152
+ poly: [
153
+ [170, 40.8],
154
+ [312.8, 299.2],
155
+ [27.2, 299.2],
156
+ ],
157
+ r: 13.6,
158
+ },
159
+ },
160
+ } as const satisfies Record<string, FaceGeom>
161
+
162
+ /**
163
+ * Body inks, each with a deep partner at the same hue. The body fills the
164
+ * face and the lids, the deep ink carries cheeks and trait. The first four
165
+ * are from v1.2; butter, rose and aqua are provisional, matched to the same
166
+ * OKLCH lightness and chroma.
167
+ */
168
+ export const COLORS = {
169
+ lavender: { body: '#D8CDF0', deep: '#BDAEE6' },
170
+ fog: { body: '#C8D6E8', deep: '#A8BCDC' },
171
+ clay: { body: '#F1CDBF', deep: '#E2A893' },
172
+ mint: { body: '#CFE7D6', deep: '#A5CDB1' },
173
+ butter: { body: '#ECE2B9', deep: '#D9C284' },
174
+ rose: { body: '#EFCAD7', deep: '#DFA4BA' },
175
+ aqua: { body: '#BDE1E5', deep: '#8FC7D1' },
176
+ } as const
177
+
178
+ /** Discrete anatomy. Each part reads every channel its feature owns. */
179
+ export const PARTS = {
180
+ eyes: ['oval', 'bead', 'ring'],
181
+ brows: ['arch', 'bar', 'wedge', 'dash'],
182
+ mouth: ['poly', 'round', 'line', 'box'],
183
+ cheeks: ['oval', 'dots', 'lines'],
184
+ trait: ['square', 'fin', 'ring', 'dot', 'peak'],
185
+ } as const
186
+
187
+ export type Face = keyof typeof FACES
188
+ export type Color = keyof typeof COLORS
189
+ export type Part = keyof typeof PARTS
190
+ export type Eyes = (typeof PARTS.eyes)[number]
191
+ export type Brows = (typeof PARTS.brows)[number]
192
+ export type Mouth = (typeof PARTS.mouth)[number]
193
+ export type Cheeks = (typeof PARTS.cheeks)[number]
194
+ export type Trait = (typeof PARTS.trait)[number]
195
+ export type Axis = 'face' | 'color' | Part
196
+
197
+ /** How much of each list a version may pick from. Append-only too. */
198
+ export const VERSIONS: Record<number, Record<Axis, number>> = {
199
+ 1: { face: 4, color: 7, eyes: 3, brows: 4, mouth: 4, cheeks: 3, trait: 5 },
200
+ }
201
+ export const LATEST = 1
202
+
203
+ /* ---------- state: channels and the 11 expressions ---------- */
204
+
205
+ export const GROUPS = {
206
+ eyes: ['lid', 'lower', 'lidTilt', 'eyeS', 'lidAsym'],
207
+ pupil: ['pupil', 'gx', 'gy', 'shine'],
208
+ brows: ['browY', 'browTilt', 'browArch', 'browW', 'browAsym'],
209
+ mouth: ['mw', 'mt', 'mb', 'mx', 'my', 'mk'],
210
+ body: ['alt', 'rot', 'x', 'sx', 'sy'],
211
+ extra: ['blush', 'hair', 'dim', 'thing'],
212
+ } as const
213
+ export type Group = keyof typeof GROUPS
214
+ export type Channel = (typeof GROUPS)[Group][number]
215
+ export type Channels = Record<Channel, number>
216
+
217
+ const BASE: Channels = {
218
+ alt: 0,
219
+ rot: 0,
220
+ x: 0,
221
+ sx: 1,
222
+ sy: 1,
223
+ lid: 0.1,
224
+ lower: 0,
225
+ lidTilt: 0,
226
+ eyeS: 1,
227
+ lidAsym: 0,
228
+ pupil: 1,
229
+ gx: 0,
230
+ gy: 0,
231
+ shine: 0.7,
232
+ browY: 0,
233
+ browTilt: 0,
234
+ browArch: 0.4,
235
+ browW: 1,
236
+ browAsym: 0,
237
+ mw: 0.72,
238
+ mt: 0,
239
+ mb: 0.5,
240
+ mx: 0,
241
+ my: 0,
242
+ mk: 0,
243
+ blush: 0,
244
+ hair: 0,
245
+ dim: 0,
246
+ thing: 0,
247
+ }
248
+
249
+ const S = (o: Partial<Channels>): Readonly<Channels> => ({ ...BASE, ...o })
250
+ export const EXPRESSIONS = {
251
+ normal: S({}),
252
+ happy: S({
253
+ lid: 0,
254
+ lower: 0.42,
255
+ shine: 1,
256
+ gy: -0.05,
257
+ browY: -5,
258
+ browArch: 1,
259
+ mw: 1,
260
+ mb: 1,
261
+ blush: 1,
262
+ hair: 0.45,
263
+ alt: 0.05,
264
+ }),
265
+ sad: S({
266
+ lid: 0.42,
267
+ lidTilt: 16,
268
+ pupil: 0.95,
269
+ gy: 0.8,
270
+ shine: 0,
271
+ browY: 3,
272
+ browTilt: 14,
273
+ browArch: 0,
274
+ mw: 0.8,
275
+ mt: -0.55,
276
+ mb: -0.55,
277
+ my: 6,
278
+ alt: -0.16,
279
+ sy: 0.985,
280
+ hair: -0.75,
281
+ }),
282
+ angry: S({
283
+ lid: 0.34,
284
+ lidTilt: -22,
285
+ lower: 0.12,
286
+ pupil: 0.82,
287
+ gy: 0.1,
288
+ shine: 0,
289
+ browY: 5,
290
+ browTilt: -21,
291
+ browArch: 0,
292
+ browW: 1.3,
293
+ mw: 0.8,
294
+ mt: -0.08,
295
+ mb: -0.08,
296
+ my: 3,
297
+ sx: 1.02,
298
+ sy: 0.98,
299
+ hair: 0.25,
300
+ }),
301
+ sleepy: S({
302
+ lid: 1,
303
+ lidTilt: 4,
304
+ pupil: 0.9,
305
+ gy: 0.5,
306
+ shine: 0,
307
+ browY: 6,
308
+ browTilt: 3,
309
+ browArch: 0.15,
310
+ browW: 0.9,
311
+ mw: 0.35,
312
+ mt: -0.3,
313
+ mb: 0.3,
314
+ my: 2,
315
+ alt: -1.12,
316
+ rot: -3,
317
+ hair: -1,
318
+ }),
319
+ curious: S({
320
+ lid: 0,
321
+ eyeS: 1.06,
322
+ pupil: 1.05,
323
+ shine: 1,
324
+ browY: -3,
325
+ browAsym: 9,
326
+ browArch: 0.8,
327
+ mw: 0.38,
328
+ mt: -0.36,
329
+ mb: 0.36,
330
+ rot: 6,
331
+ alt: -0.32,
332
+ hair: 0.3,
333
+ thing: 1,
334
+ }),
335
+ surprised: S({
336
+ lid: 0,
337
+ eyeS: 1.2,
338
+ pupil: 0.66,
339
+ shine: 0.8,
340
+ browY: -13,
341
+ browArch: 1,
342
+ mw: 0.55,
343
+ mt: -0.62,
344
+ mb: 0.62,
345
+ alt: 0.36,
346
+ hair: 1,
347
+ }),
348
+ excited: S({
349
+ lid: 0,
350
+ lower: 0.5,
351
+ eyeS: 1.08,
352
+ pupil: 1.08,
353
+ shine: 1,
354
+ browY: -9,
355
+ browArch: 1,
356
+ mw: 1.15,
357
+ mb: 1.3,
358
+ blush: 1,
359
+ hair: 1,
360
+ alt: 0.12,
361
+ }),
362
+ confused: S({
363
+ lid: 0.22,
364
+ lidAsym: 0.28,
365
+ lidTilt: -4,
366
+ pupil: 0.92,
367
+ gx: -0.35,
368
+ gy: -0.45,
369
+ shine: 0.3,
370
+ browY: -2,
371
+ browAsym: 11,
372
+ browTilt: -5,
373
+ browArch: 0.3,
374
+ mw: 0.6,
375
+ mt: -0.1,
376
+ mb: 0.12,
377
+ mx: 5,
378
+ mk: 13,
379
+ rot: -7,
380
+ alt: -0.05,
381
+ hair: 0.1,
382
+ }),
383
+ bored: S({
384
+ lid: 0.5,
385
+ pupil: 0.9,
386
+ gx: -0.55,
387
+ gy: 0.25,
388
+ shine: 0,
389
+ browY: 3,
390
+ browArch: 0.1,
391
+ mw: 0.55,
392
+ mt: 0,
393
+ mb: 0.04,
394
+ mx: -5,
395
+ alt: -0.34,
396
+ hair: -0.45,
397
+ dim: 1,
398
+ }),
399
+ attentive: S({
400
+ lid: 0,
401
+ eyeS: 1.1,
402
+ pupil: 0.8,
403
+ gy: -0.1,
404
+ shine: 1,
405
+ browY: -7,
406
+ browArch: 0.7,
407
+ mw: 0.55,
408
+ mb: 0.4,
409
+ alt: 0.15,
410
+ sy: 1.03,
411
+ hair: 0.65,
412
+ }),
413
+ } as const
414
+ export type Expression = keyof typeof EXPRESSIONS