@originkit/avatars 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.
Files changed (171) hide show
  1. package/GUIDE.md +44 -0
  2. package/LICENSE +58 -0
  3. package/README.md +141 -2
  4. package/bin/avatars.js +65 -0
  5. package/dist/art.d.ts +43 -0
  6. package/dist/art.js +35 -0
  7. package/dist/auras/calming.d.ts +18 -0
  8. package/dist/auras/calming.js +30 -0
  9. package/dist/auras/controls.d.ts +2 -0
  10. package/dist/auras/controls.js +60 -0
  11. package/dist/auras/index.d.ts +119 -0
  12. package/dist/auras/index.js +355 -0
  13. package/dist/auras/skies.d.ts +13 -0
  14. package/dist/auras/skies.js +22 -0
  15. package/dist/blobs/color.d.ts +19 -0
  16. package/dist/blobs/color.js +113 -0
  17. package/dist/blobs/controls.d.ts +2 -0
  18. package/dist/blobs/controls.js +77 -0
  19. package/dist/blobs/index.d.ts +5 -0
  20. package/dist/blobs/index.js +107 -0
  21. package/dist/blobs/paths.d.ts +33 -0
  22. package/dist/blobs/paths.js +102 -0
  23. package/dist/blobs/shapes.d.ts +46 -0
  24. package/dist/blobs/shapes.js +186 -0
  25. package/dist/blobs/variance.d.ts +22 -0
  26. package/dist/blobs/variance.js +42 -0
  27. package/dist/bots/attachments.d.ts +24 -0
  28. package/dist/bots/attachments.js +119 -0
  29. package/dist/bots/calming.d.ts +4 -0
  30. package/dist/bots/calming.js +19 -0
  31. package/dist/bots/controls.d.ts +2 -0
  32. package/dist/bots/controls.js +78 -0
  33. package/dist/bots/faces.d.ts +23 -0
  34. package/dist/bots/faces.js +122 -0
  35. package/dist/bots/heads.d.ts +15 -0
  36. package/dist/bots/heads.js +66 -0
  37. package/dist/bots/index.d.ts +5 -0
  38. package/dist/bots/index.js +47 -0
  39. package/dist/bots/palette.d.ts +15 -0
  40. package/dist/bots/palette.js +30 -0
  41. package/dist/bots/paths.d.ts +25 -0
  42. package/dist/bots/paths.js +89 -0
  43. package/dist/bots/trim.d.ts +12 -0
  44. package/dist/bots/trim.js +37 -0
  45. package/dist/bubbles/calming.d.ts +7 -0
  46. package/dist/bubbles/calming.js +19 -0
  47. package/dist/bubbles/controls.d.ts +2 -0
  48. package/dist/bubbles/controls.js +69 -0
  49. package/dist/bubbles/index.d.ts +65 -0
  50. package/dist/bubbles/index.js +279 -0
  51. package/dist/buddies/calming.d.ts +8 -0
  52. package/dist/buddies/calming.js +18 -0
  53. package/dist/buddies/controls.d.ts +2 -0
  54. package/dist/buddies/controls.js +35 -0
  55. package/dist/buddies/index.d.ts +113 -0
  56. package/dist/buddies/index.js +363 -0
  57. package/dist/colors.d.ts +20 -0
  58. package/dist/colors.js +41 -0
  59. package/dist/combinations.d.ts +9 -0
  60. package/dist/combinations.js +28 -0
  61. package/dist/container.d.ts +14 -0
  62. package/dist/container.js +67 -0
  63. package/dist/controls.d.ts +125 -0
  64. package/dist/controls.js +220 -0
  65. package/dist/critters/calming.d.ts +8 -0
  66. package/dist/critters/calming.js +18 -0
  67. package/dist/critters/controls.d.ts +2 -0
  68. package/dist/critters/controls.js +49 -0
  69. package/dist/critters/index.d.ts +113 -0
  70. package/dist/critters/index.js +307 -0
  71. package/dist/doodles/calming.d.ts +7 -0
  72. package/dist/doodles/calming.js +18 -0
  73. package/dist/doodles/controls.d.ts +2 -0
  74. package/dist/doodles/controls.js +50 -0
  75. package/dist/doodles/index.d.ts +66 -0
  76. package/dist/doodles/index.js +236 -0
  77. package/dist/faces/adventurer-neutral.json +3168 -0
  78. package/dist/faces/calming.d.ts +8 -0
  79. package/dist/faces/calming.js +20 -0
  80. package/dist/faces/controls.d.ts +2 -0
  81. package/dist/faces/controls.js +38 -0
  82. package/dist/faces/index.d.ts +44 -0
  83. package/dist/faces/index.js +108 -0
  84. package/dist/glances/calming.d.ts +8 -0
  85. package/dist/glances/calming.js +16 -0
  86. package/dist/glances/controls.d.ts +2 -0
  87. package/dist/glances/controls.js +27 -0
  88. package/dist/glances/index.d.ts +54 -0
  89. package/dist/glances/index.js +79 -0
  90. package/dist/glyphs/calming.d.ts +8 -0
  91. package/dist/glyphs/calming.js +17 -0
  92. package/dist/glyphs/controls.d.ts +2 -0
  93. package/dist/glyphs/controls.js +55 -0
  94. package/dist/glyphs/index.d.ts +62 -0
  95. package/dist/glyphs/index.js +197 -0
  96. package/dist/grins/calming.d.ts +8 -0
  97. package/dist/grins/calming.js +21 -0
  98. package/dist/grins/controls.d.ts +2 -0
  99. package/dist/grins/controls.js +33 -0
  100. package/dist/grins/index.d.ts +81 -0
  101. package/dist/grins/index.js +240 -0
  102. package/dist/gummies/art.d.ts +77 -0
  103. package/dist/gummies/art.js +168 -0
  104. package/dist/gummies/calming.d.ts +8 -0
  105. package/dist/gummies/calming.js +21 -0
  106. package/dist/gummies/controls.d.ts +2 -0
  107. package/dist/gummies/controls.js +53 -0
  108. package/dist/gummies/index.d.ts +37 -0
  109. package/dist/gummies/index.js +283 -0
  110. package/dist/history.d.ts +21 -0
  111. package/dist/history.js +48 -0
  112. package/dist/index.d.ts +13 -0
  113. package/dist/index.js +16 -0
  114. package/dist/kinds.d.ts +30 -0
  115. package/dist/kinds.js +266 -0
  116. package/dist/kitties/calming.d.ts +8 -0
  117. package/dist/kitties/calming.js +19 -0
  118. package/dist/kitties/controls.d.ts +2 -0
  119. package/dist/kitties/controls.js +43 -0
  120. package/dist/kitties/index.d.ts +181 -0
  121. package/dist/kitties/index.js +673 -0
  122. package/dist/knits/calming.d.ts +7 -0
  123. package/dist/knits/calming.js +19 -0
  124. package/dist/knits/controls.d.ts +2 -0
  125. package/dist/knits/controls.js +32 -0
  126. package/dist/knits/index.d.ts +74 -0
  127. package/dist/knits/index.js +445 -0
  128. package/dist/mallows/calming.d.ts +8 -0
  129. package/dist/mallows/calming.js +19 -0
  130. package/dist/mallows/controls.d.ts +2 -0
  131. package/dist/mallows/controls.js +45 -0
  132. package/dist/mallows/index.d.ts +111 -0
  133. package/dist/mallows/index.js +454 -0
  134. package/dist/peeks/calming.d.ts +8 -0
  135. package/dist/peeks/calming.js +21 -0
  136. package/dist/peeks/controls.d.ts +2 -0
  137. package/dist/peeks/controls.js +54 -0
  138. package/dist/peeks/index.d.ts +80 -0
  139. package/dist/peeks/index.js +225 -0
  140. package/dist/pixels/calming.d.ts +8 -0
  141. package/dist/pixels/calming.js +22 -0
  142. package/dist/pixels/controls.d.ts +3 -0
  143. package/dist/pixels/controls.js +82 -0
  144. package/dist/pixels/index.d.ts +34 -0
  145. package/dist/pixels/index.js +174 -0
  146. package/dist/pixels/palette.d.ts +29 -0
  147. package/dist/pixels/palette.js +107 -0
  148. package/dist/pixels/sprites.d.ts +42 -0
  149. package/dist/pixels/sprites.js +154 -0
  150. package/dist/possibilities.d.ts +39 -0
  151. package/dist/possibilities.js +88 -0
  152. package/dist/shapes/calming.d.ts +7 -0
  153. package/dist/shapes/calming.js +19 -0
  154. package/dist/shapes/controls.d.ts +2 -0
  155. package/dist/shapes/controls.js +41 -0
  156. package/dist/shapes/index.d.ts +44 -0
  157. package/dist/shapes/index.js +104 -0
  158. package/dist/share.d.ts +21 -0
  159. package/dist/share.js +83 -0
  160. package/dist/traits.d.ts +34 -0
  161. package/dist/traits.js +58 -0
  162. package/dist/types.d.ts +99 -0
  163. package/dist/types.js +1 -0
  164. package/dist/variants.d.ts +13 -0
  165. package/dist/variants.js +43 -0
  166. package/mcp/server.js +7 -0
  167. package/mcp/tools.js +70 -0
  168. package/package.json +68 -4
  169. package/react/index.d.ts +28 -0
  170. package/react/index.js +869 -0
  171. package/react/styles.js +1 -0
@@ -0,0 +1,45 @@
1
+ import { containerControls } from "../container.js";
2
+ import { choice, fixedPalette, toggle } from "../controls.js";
3
+ import { narrowed, possibilitiesSlider } from "../possibilities.js";
4
+ import { colorKey } from "../traits.js";
5
+ import { CALMING, NOTES } from "./calming.js";
6
+ import { BLUSH_PERCENT, coloursFor, coloursOf, eyesOf, FEELINGS, mallowTraits, poseOf, TYPES, typeOf } from "./index.js";
7
+ /** Each type's eyes, by whose they are. */
8
+ const EYES = ["Bun's", "Ghost's", "Cloud's"];
9
+ export const controls = (name, overrides) => {
10
+ const t = mallowTraits(name, overrides);
11
+ const type = typeOf(name, overrides);
12
+ const { ink, tint } = coloursOf(t, type);
13
+ return [
14
+ {
15
+ title: "Face",
16
+ controls: [
17
+ // Showing the type drawn, which for a variant is a step on from its name's own.
18
+ { ...choice(t, "type", "Type", "list", TYPES.map(({ name: label }) => ({ label }))), selected: String(TYPES.indexOf(type)) },
19
+ narrowed(choice(t, "face", "Feeling", "list", FEELINGS.map((label) => ({ label }))), t, CALMING),
20
+ // Showing the eyes drawn, which a variant steps on from its name's.
21
+ { ...choice(t, "eyes", "Eyes", "list", EYES.map((label) => ({ label }))), selected: String(TYPES.indexOf(eyesOf(name, overrides, type))) },
22
+ possibilitiesSlider(t, NOTES),
23
+ // Named for the type drawn, as each type holds its arms or hands its own way.
24
+ { ...narrowed(choice(t, "pose", "Pose", "list", type.poses.map(({ name: label }) => ({ label }))), t, CALMING), selected: String(poseOf(name, overrides)) },
25
+ toggle(t, "blush", BLUSH_PERCENT, "Blush"),
26
+ ],
27
+ },
28
+ {
29
+ title: "Colour",
30
+ controls: [
31
+ fixedPalette(t, "colour", "Colour", coloursFor(type)),
32
+ {
33
+ type: "colors",
34
+ id: "mallow.ink",
35
+ label: "Face, pink",
36
+ colors: [
37
+ { id: colorKey("ink"), color: ink },
38
+ { id: colorKey("tint"), color: tint },
39
+ ],
40
+ },
41
+ ],
42
+ },
43
+ { title: "Container", controls: containerControls(t) },
44
+ ];
45
+ };
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Mallows: soft clay characters, each type after a reference, and only that. The Bun is a
3
+ * domed body on two little feet with an arm hanging low on each side and a small dark face.
4
+ * The Ghost is a taller dome with a scalloped hem, plump hands pressed onto its front, and
5
+ * big glossy eyes. The Cloud is puffy lobes on stubby feet, with hands on its front and big
6
+ * white eyes that look about. Every mallow of a type has its body, measured off its reference, and its
7
+ * colour: what changes from name to name is how it feels. In 3D both are matte clay.
8
+ *
9
+ * Everything is drawn as numbers first: the outline as points, the face as exact shapes,
10
+ * so the 3D stage and the still picture draw the very same avatar.
11
+ */
12
+ import { type Overrides, type Traits } from "../traits.ts";
13
+ import type { AvatarParts, Shape } from "../types.ts";
14
+ /** The name's traits, calmed to the Possibilities set, as the panel reads them too. */
15
+ export declare const mallowTraits: (name: string, overrides?: Overrides) => Traits;
16
+ interface Body {
17
+ /** Half its width and height, in its type's units. */
18
+ rx: number;
19
+ ry: number;
20
+ /** How square its dome and its base are: 2 is round. */
21
+ top: number;
22
+ base: number;
23
+ /** How much wider it is at the foot. */
24
+ taper: number;
25
+ /** Where the dips between its feet are, across the base from -1 to 1, how deep, and how wide. */
26
+ dips: number[];
27
+ dip: number;
28
+ width: number;
29
+ /** A cloud is drawn from round lobes instead, as middles and radii in its units, which run into each other. */
30
+ lobes?: [x: number, y: number, r: number][];
31
+ }
32
+ /** An oval piece of clay: its middle as shares of the body's half width and height, its half sizes as shares of the half width, and its turn. */
33
+ type Piece = [x: number, y: number, rx: number, ry: number, turn: number];
34
+ /** Exact shapes in the face's own units, around the body's middle, with y down. */
35
+ interface Face {
36
+ name: string;
37
+ /** What blinks. */
38
+ eyes: Shape[];
39
+ /** What stays put through a blink: the mouth, and any brows. */
40
+ mouth: Shape[];
41
+ /** White glints on the eyes, and marks in the tint, such as a tongue or a blush. */
42
+ shine?: Shape[];
43
+ tint?: Shape[];
44
+ /** The whites of the eyes, which the eyes are pupils on. */
45
+ white?: Shape[];
46
+ }
47
+ /** Where a type's arms or hands are. The first is its reference's, and the rest after its other poses. */
48
+ interface Pose {
49
+ name: string;
50
+ /** Pieces tucked behind the body, such as arms or feet, and pieces pressed onto its front. */
51
+ arms: Piece[];
52
+ hands: Piece[];
53
+ }
54
+ interface Type {
55
+ name: string;
56
+ colour: string;
57
+ ink: string;
58
+ /** A tongue's or a blush's colour. */
59
+ tint: string;
60
+ body: Body;
61
+ poses: Pose[];
62
+ /** Pink cheeks, in the face's units, for when Blush is on. */
63
+ blush: Shape[];
64
+ /** Where its eyes are in its reference's face, in the face's units: across from the middle, up and down, and half their height. */
65
+ eyes: {
66
+ x: number;
67
+ y: number;
68
+ h: number;
69
+ };
70
+ /** The face's unit, as a share of the body's half width. */
71
+ unit: number;
72
+ faces: Face[];
73
+ }
74
+ /** The first is the first reference's. Each type's faces are the same feelings, in the same order. */
75
+ export declare const TYPES: Type[];
76
+ /** The feelings every type has, by name. */
77
+ export declare const FEELINGS: string[];
78
+ /**
79
+ * A name's type. Its variants, named `name#n`, step on through the types from the name's own,
80
+ * as the animals' variants are each a different animal: a strip has every type, no two side by
81
+ * side alike, each with a feeling of its own. A type set by hand is the one drawn, as ever.
82
+ */
83
+ export declare function typeOf(name: string, overrides?: Overrides): Type;
84
+ export declare const faceOf: (t: Traits, type: Type) => Face;
85
+ /**
86
+ * Whose eyes it has. Half of all names have their own type's, and the rest another's. Variants
87
+ * step on through them as through the types, so a strip shows every eye on every body, and low
88
+ * Possibilities keeps each type's own. Picked by hand, they're those. The rest of the face stays its own.
89
+ */
90
+ export declare function eyesOf(name: string, overrides: Overrides | undefined, type: Type): Type;
91
+ /** Every type has as many poses, so the Pose control is the same for all. */
92
+ export declare const POSE_COUNT = 4;
93
+ /**
94
+ * Which pose it's in. Variants step on through them from the name's own, keeping to those that
95
+ * Possibilities offers, so a strip shows them all. Set by hand, it's that one.
96
+ */
97
+ export declare function poseOf(name: string, overrides?: Overrides): number;
98
+ /** How often a mallow blushes, in percent. */
99
+ export declare const BLUSH_PERCENT = 30;
100
+ export declare const blushes: (t: Traits) => boolean;
101
+ /** A type's colours: its own, then the pastels. */
102
+ export declare const coloursFor: (type: Type) => string[];
103
+ export declare function coloursOf(t: Traits, type: Type): {
104
+ body: string;
105
+ ink: string;
106
+ tint: string;
107
+ };
108
+ export declare function mallowParts(name: string, overrides?: Overrides): AvatarParts;
109
+ /** The mallow at rest, for thumbnails and where 3D can't run: lit softly from above, as in 3D. */
110
+ export declare function mallowSvg(name: string, overrides?: Overrides): string;
111
+ export {};
@@ -0,0 +1,454 @@
1
+ /**
2
+ * Mallows: soft clay characters, each type after a reference, and only that. The Bun is a
3
+ * domed body on two little feet with an arm hanging low on each side and a small dark face.
4
+ * The Ghost is a taller dome with a scalloped hem, plump hands pressed onto its front, and
5
+ * big glossy eyes. The Cloud is puffy lobes on stubby feet, with hands on its front and big
6
+ * white eyes that look about. Every mallow of a type has its body, measured off its reference, and its
7
+ * colour: what changes from name to name is how it feels. In 3D both are matte clay.
8
+ *
9
+ * Everything is drawn as numbers first: the outline as points, the face as exact shapes,
10
+ * so the 3D stage and the still picture draw the very same avatar.
11
+ */
12
+ import { mix, WHITE } from "../colors.js";
13
+ import { containerOf, inContainer } from "../container.js";
14
+ import { shapePath } from "../bots/paths.js";
15
+ import { calmed, keptOf, varianceSet } from "../possibilities.js";
16
+ import { createTraits } from "../traits.js";
17
+ import { CALMING } from "./calming.js";
18
+ /** The name's traits, calmed to the Possibilities set, as the panel reads them too. */
19
+ export const mallowTraits = (name, overrides) => calmed(createTraits(name, overrides), CALMING);
20
+ /** Pastels to pick by hand. Each type starts on its own reference's colour instead. */
21
+ const PASTELS = ["#ffd6bf", "#c2eed8", "#fbe8ad", "#f9cddc", "#b5e7e3", "#d9edb4", "#c9d3fb"];
22
+ const oval = (cx, cy, rx, ry) => ({ kind: "superellipse", cx, cy, rx, ry, n: 2, rot: 0 });
23
+ const disc = (cx, cy, r) => ({ kind: "circle", cx, cy, r });
24
+ const line = (r) => ({
25
+ arc: (x1, y1, cx, cy, x2, y2) => ({ kind: "curve", x1, y1, cx, cy, x2, y2, r }),
26
+ bar: (x1, y1, x2, y2) => ({ kind: "rod", x1, y1, x2, y2, r }),
27
+ ring: (cx, cy, rx, ry) => ({ kind: "superellipse", cx, cy, rx, ry, n: 2, rot: 0, stroke: r }),
28
+ });
29
+ /** The same mark on each side, as drawn for the left. */
30
+ const both = (draw) => [draw(-1), draw(1)];
31
+ /** An open mouth with a flat top, bowing down by `depth` in its middle. */
32
+ const open = (y, width, depth) => ({ kind: "crescent", cx: 0, cy: y, width, lift: 0, thickness: depth });
33
+ /* The Bun, in tenths of its reference's pixels. */
34
+ const BUN_LINE = 0.38;
35
+ const BUN_X = 8.55;
36
+ const BUN_Y = -6;
37
+ const bun = line(BUN_LINE);
38
+ const bunOvals = (dy = 0, rx = 1.8, ry = 3) => [oval(-BUN_X, BUN_Y + dy, rx, ry), oval(BUN_X, BUN_Y + dy, rx, ry)];
39
+ /** The reference's smile: a shallow curve whose ends sit level, `bow` above its middle. */
40
+ const bunSmile = (width = 3.8, bow = 2.3, y = -3.3) => bun.arc(-width, y, 0, y + bow * 2, width, y);
41
+ /** A brow over each eye, its inner end `lift` higher than its outer one. */
42
+ const bunBrows = (y, lift) => both((s) => bun.bar(s * (BUN_X + 1.9), y, s * (BUN_X - 1.8), y - lift));
43
+ const BUN_FACES = [
44
+ { name: "Content", eyes: bunOvals(), mouth: [bunSmile()] },
45
+ { name: "Laugh", eyes: both((s) => bun.bar(s * 10.2, -8.2, s * 7.2, -6)).concat(both((s) => bun.bar(s * 7.2, -6, s * 10.2, -3.8))), mouth: [open(-3.6, 3.9, 3.6)] },
46
+ { name: "Sad", eyes: bunOvals(0.6, 1.7, 2.6), mouth: [bun.arc(-3, -0.9, 0, -4.7, 3, -0.9), ...bunBrows(-9.6, 1.4)] },
47
+ { name: "Wow", eyes: bunOvals(-0.3, 2.1, 3.3), mouth: [bun.ring(0, -1.4, 1.4, 1.8)] },
48
+ { name: "Sleepy", eyes: both((s) => bun.arc(s * 10.4, -6, s * 8.55, -3.4, s * 6.7, -6)), mouth: [bunSmile(2.2, 1, -2.8)] },
49
+ { name: "Joy", eyes: bunOvals(-0.3), mouth: [open(-3.4, 3.4, 3.1)] },
50
+ { name: "Grumpy", eyes: bunOvals(0.7, 1.8, 2.3), mouth: [bun.bar(-2.6, -1.8, 2.6, -1.8), ...bunBrows(-10.6, -1.6)] },
51
+ { name: "Wink", eyes: [oval(-BUN_X, BUN_Y, 1.8, 3), bun.arc(6.7, -5.2, 8.55, -8.6, 10.4, -5.2)], mouth: [bunSmile(4.2, 2.6)] },
52
+ { name: "Shy", eyes: bunOvals(0.9, 1.5, 2.4), mouth: [bunSmile(1.8, 0.8, -2.4)] },
53
+ { name: "Bliss", eyes: both((s) => bun.arc(s * 10.4, -5.2, s * 8.55, -8.8, s * 6.7, -5.2)), mouth: [bunSmile()] },
54
+ { name: "Worried", eyes: bunOvals(0.4, 1.7, 2.8), mouth: [bun.arc(-3.2, -2, -1.6, -3.4, 0, -2), bun.arc(0, -2, 1.6, -0.6, 3.2, -2), ...bunBrows(-9.8, 1)] },
55
+ { name: "Cheeky", eyes: bunOvals(), mouth: [bun.arc(-3.2, -2.6, 0.6, -0.4, 3.8, -4.2)] },
56
+ ];
57
+ /* The Ghost, with its body 30 across each side of its middle. */
58
+ const GHOST_X = 10.5;
59
+ const GHOST_Y = -7;
60
+ const ghost = line(0.62);
61
+ const lid = line(0.85);
62
+ /** Big glossy eyes, each with its glint high on the side towards the light. */
63
+ const ghostEyes = (dy = 0, rx = 4.4, ry = 5.6) => {
64
+ const at = (side) => [side * GHOST_X, GHOST_Y + dy];
65
+ return {
66
+ eyes: both((s) => oval(...at(s), rx, ry)),
67
+ shine: both((s) => disc(at(s)[0] + rx * 0.34, at(s)[1] - ry * 0.42, rx * 0.3)),
68
+ };
69
+ };
70
+ const ghostSmile = (width = 2.6, bow = 1.4, y = -2.2) => ghost.arc(-width, y, 0, y + bow * 2, width, y);
71
+ /** Closed eyes, curving up as a smile's do, or down as a sleeper's. */
72
+ const ghostLids = (bow) => both((s) => lid.arc(s * GHOST_X - 3.2, GHOST_Y + bow / 2, s * GHOST_X, GHOST_Y - bow * 1.5, s * GHOST_X + 3.2, GHOST_Y + bow / 2));
73
+ const ghostBrows = (y, lift) => both((s) => ghost.bar(s * (GHOST_X + 3.2), y, s * (GHOST_X - 2.4), y - lift));
74
+ /** A tongue low in an open mouth. */
75
+ const tongue = (y, width, depth) => oval(0, y + depth * 0.62, width * 0.42, depth * 0.27);
76
+ const GHOST_FACES = [
77
+ { name: "Content", ...ghostEyes(), mouth: [ghostSmile()] },
78
+ { name: "Laugh", eyes: ghostLids(2.2), mouth: [open(-2.6, 3.2, 3.4)], tint: [tongue(-2.6, 3.2, 3.4)] },
79
+ { name: "Sad", ...ghostEyes(1, 4, 4.8), mouth: [ghost.arc(-2.4, 0, 0, -3, 2.4, 0), ...ghostBrows(-14.4, 1.4)] },
80
+ { name: "Wow", ...ghostEyes(-0.4, 4.8, 6.2), mouth: [ghost.ring(0, 0.2, 1.5, 1.9)] },
81
+ { name: "Sleepy", eyes: ghostLids(-1.9), mouth: [ghostSmile(1.6, 0.8, -1.4)] },
82
+ { name: "Joy", ...ghostEyes(-0.3), mouth: [open(-2.4, 2.6, 3)], tint: [tongue(-2.4, 2.6, 3)] },
83
+ { name: "Grumpy", ...ghostEyes(0.8, 4.2, 4.6), mouth: [ghost.bar(-2, -1.4, 2, -1.4), ...ghostBrows(-15.6, -1.8)] },
84
+ { name: "Wink", eyes: [oval(-GHOST_X, GHOST_Y, 4.4, 5.6), lid.arc(GHOST_X - 3.2, GHOST_Y + 1.1, GHOST_X, GHOST_Y - 3.3, GHOST_X + 3.2, GHOST_Y + 1.1)], shine: [disc(-GHOST_X + 1.5, GHOST_Y - 2.35, 1.32)], mouth: [ghostSmile(3, 1.7)] },
85
+ { name: "Shy", ...ghostEyes(1, 3.6, 4.6), mouth: [ghostSmile(1.6, 0.7, -1.2)] },
86
+ { name: "Bliss", eyes: ghostLids(2), mouth: [ghostSmile()] },
87
+ { name: "Worried", ...ghostEyes(0.6, 4.2, 5.2), mouth: [ghost.arc(-2.6, -1.2, -1.3, -2.4, 0, -1.2), ghost.arc(0, -1.2, 1.3, 0, 2.6, -1.2), ...ghostBrows(-14.6, 1)] },
88
+ { name: "Cheeky", ...ghostEyes(), mouth: [ghost.arc(-2.6, -1.8, 0.6, 0.4, 3, -3.2)] },
89
+ ];
90
+ /* The Cloud, with its body 30 across each side of its middle. */
91
+ const CLOUD_X = 7.6;
92
+ const CLOUD_Y = -2.5;
93
+ const cloud = line(0.55);
94
+ const brow = line(0.42);
95
+ const cloudLid = line(0.7);
96
+ /**
97
+ * Big white eyes, their pupils looking `look` of the way to the edge of the whites, each with
98
+ * a glint. `only` leaves one side, for a wink.
99
+ */
100
+ const cloudEyes = ([lx, ly], size = 1, only) => {
101
+ const sides = only === undefined ? [-1, 1] : [only];
102
+ const pupil = (s) => [s * CLOUD_X + lx * 2, CLOUD_Y + ly * 2];
103
+ return {
104
+ white: sides.map((s) => oval(s * CLOUD_X, CLOUD_Y, 5.6, 6.2)),
105
+ eyes: sides.map((s) => oval(...pupil(s), 3.3 * size, 3.9 * size)),
106
+ shine: sides.map((s) => disc(pupil(s)[0] + 1.1 * size, pupil(s)[1] - 1.5 * size, 1.05 * size)),
107
+ };
108
+ };
109
+ /** A thin brow above each eye, bowing up, its inner end `lift` higher than its outer one. */
110
+ const cloudBrows = (lift = 0) => both((s) => brow.arc(s * (CLOUD_X + 3.4), CLOUD_Y - 7.6, s * (CLOUD_X + 1.2), CLOUD_Y - 9.8, s * (CLOUD_X - 1.4), CLOUD_Y - 9.6 - lift));
111
+ /** Closed eyes, curving up as a smile's do, or down as a sleeper's. */
112
+ const cloudLids = (bow, sides = [-1, 1]) => sides.map((s) => cloudLid.arc(s * CLOUD_X - 3.4, CLOUD_Y + bow / 2, s * CLOUD_X, CLOUD_Y - bow * 1.5, s * CLOUD_X + 3.4, CLOUD_Y + bow / 2));
113
+ /** The reference's smile, a little lopsided. */
114
+ const cloudSmile = (width = 2.6, bow = 1.1, y = 5.8) => cloud.arc(-width, y, 0.3, y + bow * 2, width + 0.6, y - 0.8);
115
+ /** A wink: one eye open, and the other closed in a smile. */
116
+ const winking = () => {
117
+ const { white, eyes, shine } = cloudEyes([0.3, -0.3], 1, -1);
118
+ return { white, shine, eyes: [...eyes, ...cloudLids(2.2, [1])] };
119
+ };
120
+ const CLOUD_FACES = [
121
+ { name: "Content", ...cloudEyes([0.45, -0.45]), mouth: [cloudSmile(), ...cloudBrows()] },
122
+ { name: "Laugh", eyes: cloudLids(2.4), mouth: [open(4.6, 3, 3.2), ...cloudBrows(0.4)], tint: [tongue(4.6, 3, 3.2)] },
123
+ { name: "Sad", ...cloudEyes([0, 0.55]), mouth: [cloud.arc(-2.4, 7.4, 0, 4.6, 2.4, 7.4), ...cloudBrows(1.6)] },
124
+ { name: "Wow", ...cloudEyes([0, 0], 0.85), mouth: [cloud.ring(0, 7.2, 1.4, 1.8), ...cloudBrows(1)] },
125
+ { name: "Sleepy", eyes: cloudLids(-2.1), mouth: [cloud.arc(-1.4, 5.6, 0, 6.8, 1.4, 5.6), ...cloudBrows()] },
126
+ { name: "Joy", ...cloudEyes([0.2, -0.5]), mouth: [open(4.8, 2.6, 2.9), ...cloudBrows(0.6)], tint: [tongue(4.8, 2.6, 2.9)] },
127
+ { name: "Grumpy", ...cloudEyes([0, 0.2]), mouth: [cloud.bar(-2, 6, 2, 6), ...cloudBrows(-1.8)] },
128
+ { name: "Wink", ...winking(), mouth: [cloudSmile(2.9, 1.3), ...cloudBrows()] },
129
+ { name: "Shy", ...cloudEyes([-0.5, 0.45]), mouth: [cloudSmile(1.6, 0.6), ...cloudBrows(0.6)] },
130
+ { name: "Bliss", eyes: cloudLids(2.2), mouth: [cloudSmile(), ...cloudBrows()] },
131
+ { name: "Worried", ...cloudEyes([0, -0.1]), mouth: [cloud.arc(-2.6, 6.2, -1.3, 5, 0, 6.2), cloud.arc(0, 6.2, 1.3, 7.4, 2.6, 6.2), ...cloudBrows(1.4)] },
132
+ { name: "Cheeky", ...cloudEyes([0.6, 0]), mouth: [cloud.arc(-2.6, 6, 0.6, 8, 3.4, 4.6), ...cloudBrows(-0.6)] },
133
+ ];
134
+ /**
135
+ * The cloud's waving arm, as in its reference: it rises from the right side, tucked into it
136
+ * and blended with it as the bun's arms are, up past the shoulder.
137
+ */
138
+ const WAVING = [0.94, -0.12, 0.34, 0.17, -62];
139
+ /**
140
+ * The ghost's dome is round where the cloud is lobed, so its arm sits higher and nearer upright:
141
+ * its root tucked into the side, the rest clear of the dome, as the reference's waving ghost's is.
142
+ */
143
+ const GHOST_WAVING = [0.98, -0.42, 0.32, 0.16, -75];
144
+ const CLOUD_FEET = [
145
+ [-0.29, 1.02, 0.2, 0.12, 0],
146
+ [0.37, 1.02, 0.21, 0.12, 0],
147
+ ];
148
+ /** The first is the first reference's. Each type's faces are the same feelings, in the same order. */
149
+ export const TYPES = [
150
+ {
151
+ name: "Bun",
152
+ colour: "#c4e2f4",
153
+ ink: "#2e3850",
154
+ // In the reference's pixels, around the body's middle.
155
+ body: { rx: 220, ry: 230, top: 2.15, base: 3, taper: 0.05, dips: [0], dip: 0.1, width: 0.25 },
156
+ tint: "#f2a7b8",
157
+ // Plump ovals hanging down and out, their tops tucked behind the body's sides. Its other
158
+ // poses swing them out, up in a cheer, or one up in a wave.
159
+ poses: [
160
+ { name: "Rest", arms: [-1, 1].map((side) => [side * 1.1, 0.33, 0.35, 0.18, side * 70]), hands: [] },
161
+ { name: "Out", arms: [-1, 1].map((side) => [side * 1.16, 0.22, 0.3, 0.17, side * 25]), hands: [] },
162
+ { name: "Cheer", arms: [-1, 1].map((side) => [side * 1.08, -0.15, 0.33, 0.18, side * -55]), hands: [] },
163
+ { name: "Wave", arms: [[-1.1, 0.33, 0.35, 0.18, -70], [1.08, -0.15, 0.33, 0.18, -55]], hands: [] },
164
+ ],
165
+ blush: both((s) => oval(s * (BUN_X + 1.2), BUN_Y + 5.2, 2.1, 1.05)),
166
+ eyes: { x: BUN_X, y: BUN_Y, h: 3 },
167
+ unit: 1 / 22,
168
+ faces: BUN_FACES,
169
+ },
170
+ {
171
+ name: "Ghost",
172
+ colour: "#c6b6f6",
173
+ ink: "#2b1818",
174
+ tint: "#f07888",
175
+ body: { rx: 30, ry: 33, top: 2.2, base: 4.5, taper: 0.07, dips: [-0.5, 0, 0.5], dip: 0.13, width: 0.19 },
176
+ // Pressed onto its front, the left one over the body's edge as in the reference. The other
177
+ // poses are the reference's smaller ghosts': hands low, hugging its front, or one up in a wave.
178
+ // None reaches the face, which would be painted onto a hand over it.
179
+ poses: [
180
+ { name: "Rest", arms: [], hands: [[-0.8, 0.42, 0.26, 0.21, -28], [0.84, 0.22, 0.22, 0.25, 18]] },
181
+ { name: "Low", arms: [], hands: [[-0.86, 0.6, 0.22, 0.2, -10], [0.88, 0.55, 0.22, 0.2, 10]] },
182
+ { name: "Hug", arms: [], hands: [[-0.55, 0.32, 0.21, 0.19, -25], [0.58, 0.28, 0.2, 0.21, 25]] },
183
+ { name: "Wave", arms: [GHOST_WAVING], hands: [[-0.8, 0.42, 0.26, 0.21, -28]] },
184
+ ],
185
+ blush: both((s) => oval(s * (GHOST_X + 2.6), GHOST_Y + 7, 2.6, 1.3)),
186
+ eyes: { x: GHOST_X, y: GHOST_Y, h: 5.6 },
187
+ unit: 1 / 30,
188
+ faces: GHOST_FACES,
189
+ },
190
+ {
191
+ name: "Cloud",
192
+ colour: "#82b8ee",
193
+ ink: "#1d1515",
194
+ tint: "#ef6f80",
195
+ // Its lobes, after the reference: two big ones on top, a round one each side, and a broad belly.
196
+ body: {
197
+ rx: 30, ry: 27, top: 2, base: 2, taper: 0, dips: [], dip: 0, width: 1,
198
+ lobes: [[-2.4, -9.5, 17], [17.4, -10.3, 10.3], [-18.1, 1.6, 11.8], [21.3, 3.2, 9.5], [-7, 9.2, 17.6], [8, 9.6, 17.4]],
199
+ },
200
+ // Two stubby feet peeking out underneath, and hands on its front. The other poses are the
201
+ // reference's smaller clouds': hands low, one at its chin, or one up in a wave.
202
+ poses: [
203
+ { name: "Rest", arms: CLOUD_FEET, hands: [[-0.43, 0.47, 0.22, 0.2, -30], [0.71, 0.35, 0.19, 0.22, 15]] },
204
+ { name: "Low", arms: CLOUD_FEET, hands: [[-0.55, 0.72, 0.2, 0.18, -10], [0.6, 0.7, 0.2, 0.18, 10]] },
205
+ { name: "Chin", arms: CLOUD_FEET, hands: [[-0.43, 0.47, 0.22, 0.2, -30], [0.16, 0.5, 0.19, 0.17, 5]] },
206
+ { name: "Wave", arms: [...CLOUD_FEET, WAVING], hands: [[-0.43, 0.47, 0.22, 0.2, -30]] },
207
+ ],
208
+ blush: both((s) => oval(s * (CLOUD_X + 3.5), CLOUD_Y + 7, 2.2, 1.1)),
209
+ eyes: { x: CLOUD_X, y: CLOUD_Y, h: 6.2 },
210
+ unit: 1 / 30,
211
+ faces: CLOUD_FACES,
212
+ },
213
+ ];
214
+ /** The feelings every type has, by name. */
215
+ export const FEELINGS = BUN_FACES.map(({ name }) => name);
216
+ /**
217
+ * A name's type. Its variants, named `name#n`, step on through the types from the name's own,
218
+ * as the animals' variants are each a different animal: a strip has every type, no two side by
219
+ * side alike, each with a feeling of its own. A type set by hand is the one drawn, as ever.
220
+ */
221
+ export function typeOf(name, overrides) {
222
+ const { t, step } = named(name, overrides);
223
+ const own = TYPES.indexOf(t.pick("type", TYPES));
224
+ return TYPES[t.forced("type") === undefined ? (own + step) % TYPES.length : own];
225
+ }
226
+ /** The name a variant was made from, its traits, and how many steps on it is: 0 for the name itself. */
227
+ function named(name, overrides) {
228
+ const variant = /^(.*)#(\d+)$/.exec(name);
229
+ const from = variant ? variant[1] : name;
230
+ return { from, t: createTraits(from, overrides), step: variant ? Number(variant[2]) - 1 : 0 };
231
+ }
232
+ export const faceOf = (t, type) => type.faces[Math.floor(t("face") * type.faces.length)];
233
+ /**
234
+ * Whose eyes it has. Half of all names have their own type's, and the rest another's. Variants
235
+ * step on through them as through the types, so a strip shows every eye on every body, and low
236
+ * Possibilities keeps each type's own. Picked by hand, they're those. The rest of the face stays its own.
237
+ */
238
+ export function eyesOf(name, overrides, type) {
239
+ const { t, step } = named(name, overrides);
240
+ const set = t.forced("eyes");
241
+ if (set !== undefined)
242
+ return TYPES[Math.min(TYPES.length - 1, Math.floor(set * TYPES.length))];
243
+ const value = t("eyes");
244
+ const variance = varianceSet(t);
245
+ const kept = variance === undefined ? TYPES.length : keptOf(TYPES.length, 1, variance);
246
+ // How many types on from its own.
247
+ const along = ((value < 0.5 ? 0 : value < 0.75 ? 1 : 2) + step) % kept;
248
+ return TYPES[(TYPES.indexOf(type) + along) % TYPES.length];
249
+ }
250
+ /**
251
+ * An eye's shape from another type's face, moved onto this type's eyes and sized to them, so
252
+ * borrowed eyes sit where its own would and never reach its mouth.
253
+ */
254
+ function borrowed(shape, from, to) {
255
+ const across = shape.kind === "rod" || shape.kind === "curve" ? shape.x1 + shape.x2 : shape.cx;
256
+ const side = across < 0 ? -1 : 1;
257
+ const size = to.eyes.h / from.eyes.h;
258
+ return placed(shape, side * (to.eyes.x - from.eyes.x * size), to.eyes.y - from.eyes.y * size, size);
259
+ }
260
+ /** Every type has as many poses, so the Pose control is the same for all. */
261
+ export const POSE_COUNT = 4;
262
+ /**
263
+ * Which pose it's in. Variants step on through them from the name's own, keeping to those that
264
+ * Possibilities offers, so a strip shows them all. Set by hand, it's that one.
265
+ */
266
+ export function poseOf(name, overrides) {
267
+ const { from, step } = named(name, overrides);
268
+ const t = mallowTraits(from, overrides);
269
+ const own = Math.floor(t("pose") * POSE_COUNT);
270
+ const variance = varianceSet(t);
271
+ if (t.forced("pose") !== undefined)
272
+ return own;
273
+ return (own + step) % (variance === undefined ? POSE_COUNT : keptOf(POSE_COUNT, 2, variance));
274
+ }
275
+ /** How often a mallow blushes, in percent. */
276
+ export const BLUSH_PERCENT = 30;
277
+ export const blushes = (t) => t("blush") * 100 < BLUSH_PERCENT;
278
+ /** A type's colours: its own, then the pastels. */
279
+ export const coloursFor = (type) => [type.colour, ...PASTELS];
280
+ export function coloursOf(t, type) {
281
+ return { body: t.color("colour") ?? type.colour, ink: t.color("ink") ?? type.ink, tint: t.color("tint") ?? type.tint };
282
+ }
283
+ const OUTLINE_POINTS = 240;
284
+ /** How much of the frame the whole mallow spans. */
285
+ const SPAN = 82;
286
+ const round2 = (value) => Math.round(value * 100) / 100;
287
+ const signedPower = (value, power) => Math.sign(value) * Math.abs(value) ** power;
288
+ /** The body's outline, round its own middle with y down. */
289
+ function outlineOf({ rx, ry, top, base, taper, dips, dip, width }) {
290
+ return Array.from({ length: OUTLINE_POINTS }, (_, i) => {
291
+ const angle = (i / OUTLINE_POINTS) * 2 * Math.PI;
292
+ const sin = Math.sin(angle);
293
+ // Each half its own superellipse, so the dome stays round over straight sides.
294
+ const power = sin < 0 ? top : base;
295
+ let x = rx * signedPower(Math.cos(angle), 2 / power);
296
+ let y = ry * signedPower(sin, 2 / power);
297
+ const down = y / ry;
298
+ x *= 1 + taper * down;
299
+ // The dips between the feet, deepest at the very bottom, so the sides keep their line.
300
+ if (down > 0) {
301
+ const across = x / (rx * (1 + taper));
302
+ const near = dips.reduce((sum, at) => sum + Math.exp(-(((across - at) / width) ** 2)), 0);
303
+ y -= ry * dip * Math.min(1, near) * down ** 6;
304
+ }
305
+ return [x, y];
306
+ });
307
+ }
308
+ /** An oval piece, as points round its outline. */
309
+ function pieceOf([x, y, rx, ry, turn], body) {
310
+ const [c, s] = [Math.cos((turn * Math.PI) / 180), Math.sin((turn * Math.PI) / 180)];
311
+ return Array.from({ length: 96 }, (_, i) => {
312
+ const angle = (i / 96) * 2 * Math.PI;
313
+ const [px, py] = [rx * body.rx * Math.cos(angle), ry * body.rx * Math.sin(angle)];
314
+ return [x * body.rx + c * px - s * py, y * body.ry + s * px + c * py];
315
+ });
316
+ }
317
+ /** A face's shape, moved and scaled into place, which keeps a curve a curve. */
318
+ function placed(shape, x, y, size) {
319
+ const to = (px, py) => [round2(x + size * px), round2(y + size * py)];
320
+ const by = (value) => round2(value * size);
321
+ switch (shape.kind) {
322
+ case "superellipse": {
323
+ const [cx, cy] = to(shape.cx, shape.cy);
324
+ return { ...shape, cx, cy, rx: by(shape.rx), ry: by(shape.ry), ...(shape.stroke ? { stroke: by(shape.stroke) } : {}) };
325
+ }
326
+ case "circle": {
327
+ const [cx, cy] = to(shape.cx, shape.cy);
328
+ return { ...shape, cx, cy, r: by(shape.r) };
329
+ }
330
+ case "rod": {
331
+ const [x1, y1] = to(shape.x1, shape.y1);
332
+ const [x2, y2] = to(shape.x2, shape.y2);
333
+ return { ...shape, x1, y1, x2, y2, r: by(shape.r) };
334
+ }
335
+ case "curve": {
336
+ const [x1, y1] = to(shape.x1, shape.y1);
337
+ const [cx, cy] = to(shape.cx, shape.cy);
338
+ const [x2, y2] = to(shape.x2, shape.y2);
339
+ return { ...shape, x1, y1, cx, cy, x2, y2, r: by(shape.r) };
340
+ }
341
+ case "crescent": {
342
+ const [cx, cy] = to(shape.cx, shape.cy);
343
+ return { ...shape, cx, cy, width: by(shape.width), thickness: by(shape.thickness) };
344
+ }
345
+ }
346
+ }
347
+ const pathOf = (points) => `M${points.map(([x, y]) => `${round2(x)} ${round2(y)}`).join("L")}Z`;
348
+ /**
349
+ * A type's body and each pose's pieces in the 100×100 frame, the same for every mallow of it,
350
+ * and where they span. It is framed in its reference's pose, so the body is the same size in
351
+ * every pose, and a raised hand reaches into the margin.
352
+ */
353
+ function figureOf(type) {
354
+ const { body } = type;
355
+ const lobes = body.lobes?.map(([x, y, r]) => pieceOf([x / body.rx, y / body.ry, r / body.rx, r / body.rx, 0], body));
356
+ const outlines = lobes ?? [outlineOf(body)];
357
+ const poses = type.poses.map(({ arms, hands }) => ({ arms: arms.map((piece) => pieceOf(piece, body)), hands: hands.map((piece) => pieceOf(piece, body)) }));
358
+ const all = [...outlines.flat(), ...poses[0].arms.flat(), ...poses[0].hands.flat()];
359
+ const [left, right] = [Math.min(...all.map(([x]) => x)), Math.max(...all.map(([x]) => x))];
360
+ const [high, low] = [Math.min(...all.map(([, y]) => y)), Math.max(...all.map(([, y]) => y))];
361
+ const scale = SPAN / Math.max(right - left, low - high);
362
+ const frame = ([x, y]) => [50 + (x - (left + right) / 2) * scale, 50 + (y - (high + low) / 2) * scale];
363
+ const [x, y] = frame([0, 0]);
364
+ return {
365
+ body: outlines.map((outline) => pathOf(outline.map(frame))),
366
+ poses: poses.map(({ arms, hands }) => ({
367
+ // Puffed up on their own and tucked behind the body, which rounds into them with a soft crease.
368
+ arms: arms.map((piece) => pathOf(piece.map(frame))),
369
+ // Pressed onto its front, where they stand proud of it with a soft crease round them.
370
+ hands: hands.map((piece) => pathOf(piece.map(frame))),
371
+ })),
372
+ middle: { x: round2(x), y: round2(y) },
373
+ unit: type.unit * type.body.rx * scale,
374
+ top: round2(frame([0, high])[1]),
375
+ bottom: round2(frame([0, low])[1]),
376
+ };
377
+ }
378
+ const FIGURES = new Map(TYPES.map((type) => [type, figureOf(type)]));
379
+ function describe(name, overrides) {
380
+ const t = mallowTraits(name, overrides);
381
+ const type = typeOf(name, overrides);
382
+ const figure = FIGURES.get(type);
383
+ const face = faceOf(t, type);
384
+ // Another type's eyes, for the same feeling, if they were picked.
385
+ const from = eyesOf(name, overrides, type);
386
+ const eyeFace = faceOf(t, from);
387
+ const own = (shape) => (from === type ? shape : borrowed(shape, from, type));
388
+ const at = (shape) => placed(shape, figure.middle.x, figure.middle.y, figure.unit);
389
+ return {
390
+ t,
391
+ type,
392
+ figure,
393
+ pose: figure.poses[poseOf(name, overrides)],
394
+ eyes: eyeFace.eyes.map(own).map(at),
395
+ mouth: face.mouth.map(at),
396
+ shine: (eyeFace.shine ?? []).map(own).map(at),
397
+ tint: [...(face.tint ?? []), ...(blushes(t) ? type.blush : [])].map(at),
398
+ white: (eyeFace.white ?? []).map(own).map(at),
399
+ colours: coloursOf(t, type),
400
+ };
401
+ }
402
+ /** Where a shape's middle sits, up and down, which a blink closes towards. */
403
+ const middleOf = (shape) => shape.kind === "rod" || shape.kind === "curve" ? (shape.y1 + shape.y2) / 2 : shape.cy;
404
+ export function mallowParts(name, overrides) {
405
+ const { t, figure, pose, eyes, mouth, shine, tint, white, colours } = describe(name, overrides);
406
+ return {
407
+ // The visor colours the tinted marks, as clay has no glass.
408
+ colors: { body: colours.body, accent: colours.body, visor: colours.tint, light: colours.ink },
409
+ head: figure.body,
410
+ accents: pose.arms,
411
+ visor: [],
412
+ visorShapes: [],
413
+ eyes: [],
414
+ eyeShapes: eyes,
415
+ lights: [],
416
+ lightShapes: mouth,
417
+ chrome: [],
418
+ chromeShapes: [],
419
+ studs: [],
420
+ studShapes: [],
421
+ speckle: 0,
422
+ eyeY: round2(eyes.reduce((sum, shape) => sum + middleOf(shape), 0) / eyes.length),
423
+ painted: true,
424
+ matte: true,
425
+ raised: pose.hands,
426
+ shineShapes: shine,
427
+ tintShapes: tint,
428
+ whiteShapes: white,
429
+ container: containerOf(t),
430
+ };
431
+ }
432
+ /** A face's shape as markup. An oval's outline leaves out its stroke, so it's added here. */
433
+ function markupOf(shape) {
434
+ const stroked = shape.kind === "superellipse" && shape.stroke ? ` fill="none" stroke="currentColor" stroke-width="${round2(shape.stroke * 2)}"` : "";
435
+ return `<path d="${shapePath(shape)}"${stroked}/>`;
436
+ }
437
+ /** The mallow at rest, for thumbnails and where 3D can't run: lit softly from above, as in 3D. */
438
+ export function mallowSvg(name, overrides) {
439
+ const { t, type, figure, pose, eyes, mouth, shine, tint, white, colours } = describe(name, overrides);
440
+ const { body, ink } = colours;
441
+ const clay = (d) => `<path d="${d}"/>`;
442
+ const art = `<defs><linearGradient id="mallow-clay" gradientUnits="userSpaceOnUse" x1="50" y1="${figure.top}" x2="50" y2="${figure.bottom}">` +
443
+ `<stop offset="0" stop-color="${mix(body, WHITE, 0.4)}"/><stop offset="0.55" stop-color="${body}"/>` +
444
+ `<stop offset="1" stop-color="${mix(body, type.ink, 0.12)}"/></linearGradient></defs>` +
445
+ `<g fill="url(#mallow-clay)">${[...pose.arms, ...figure.body].map(clay).join("")}` +
446
+ // Hands sit on the body, so a faint crease round each keeps them apart from it.
447
+ // Lit from the upper left, as in 3D, they catch more light than the body behind them.
448
+ `<g fill="${mix(body, WHITE, 0.22)}" stroke="${mix(body, type.ink, 0.2)}" stroke-width="0.6">${pose.hands.map(clay).join("")}</g></g>` +
449
+ `<g fill="#f2ede0">${white.map(markupOf).join("")}</g>` +
450
+ `<g fill="${ink}" color="${ink}">${[...eyes, ...mouth].map(markupOf).join("")}</g>` +
451
+ `<g fill="${colours.tint}">${tint.map(markupOf).join("")}</g>` +
452
+ `<g fill="#ffffff">${shine.map(markupOf).join("")}</g>`;
453
+ return inContainer(art, containerOf(t), "mallow-card");
454
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * What Possibilities calms on a peek. Low keeps eight bodies and poses, one for each variant,
3
+ * glossy eyes, and draws its size, depth, eyes and turn towards their middle. Indices are
4
+ * into the lists in `index.ts`, plainest first.
5
+ */
6
+ import type { Calming } from "../possibilities.ts";
7
+ export declare const CALMING: Calming;
8
+ export declare const NOTES: [string, string, string];
@@ -0,0 +1,21 @@
1
+ /**
2
+ * What Possibilities calms on a peek. Low keeps eight bodies and poses, one for each variant,
3
+ * glossy eyes, and draws its size, depth, eyes and turn towards their middle. Indices are
4
+ * into the lists in `index.ts`, plainest first.
5
+ */
6
+ export const CALMING = {
7
+ features: /^(body\.size|eye\.(size|ratio|gap)|brow\.y|fluff|tilt|bumps|angle)$/,
8
+ lists: {
9
+ // Dome, Swell, Fluffy, Ears, Tuft, Lumpy, Drips, Horns, then Spiky.
10
+ body: [9, 8, [7, 2, 0, 1, 3, 8, 6, 5, 4]],
11
+ // Corner, Other corner, Rising, Right, Left, Top corner, Top left, Whole, then Hanging.
12
+ pose: [9, 8, [0, 3, 6, 2, 5, 4, 7, 8, 1]],
13
+ // Glossy, then Sleepy and Tiny.
14
+ "eye.kind": [3, 1],
15
+ },
16
+ };
17
+ export const NOTES = [
18
+ "smooth bodies peeking from a corner or the side, glossy eyes, barely turned.",
19
+ "more eye styles, depth and turn.",
20
+ "every body and pose, up to spikes and hanging upside down, at any turn.",
21
+ ];
@@ -0,0 +1,2 @@
1
+ import { type ControlBuilder } from "../controls.ts";
2
+ export declare const controls: ControlBuilder;