@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,125 @@
1
+ /**
2
+ * What the control panel can change.
3
+ *
4
+ * Generators read every value through a trait key, so a control is just a way of
5
+ * fixing some keys (see `Overrides`). Each avatar type describes its own controls
6
+ * from its current traits, which is what lets them appear and change with the avatar.
7
+ */
8
+ import { type Overrides, type Traits } from "./traits.ts";
9
+ /** A change to the overrides. `undefined` hands a key back to the name. */
10
+ export type Patch = Record<string, number | string | undefined>;
11
+ export interface Option {
12
+ id: string;
13
+ label: string;
14
+ color?: string;
15
+ set: Patch;
16
+ }
17
+ export interface ChoiceControl {
18
+ type: "choice";
19
+ id: string;
20
+ label: string;
21
+ layout: "segmented" | "list" | "swatches" | "switch";
22
+ options: Option[];
23
+ selected: string;
24
+ /** Offers any colour too: the override it writes, and the colour showing now. */
25
+ custom?: {
26
+ id: string;
27
+ color: string;
28
+ };
29
+ }
30
+ export interface SliderControl {
31
+ type: "slider";
32
+ id: string;
33
+ label: string;
34
+ /** The position from 0 to 1, which is exactly what the generator reads. */
35
+ value: number;
36
+ display: string;
37
+ /** The shown number at either end, so its box can be typed in and stepped in its own units. */
38
+ range?: [number, number];
39
+ /** Words for either end of the slider, and a line saying what the current setting means. */
40
+ ends?: [string, string];
41
+ note?: string;
42
+ }
43
+ /** Several colours set together, each by hand. */
44
+ export interface ColorsControl {
45
+ type: "colors";
46
+ id: string;
47
+ label: string;
48
+ colors: {
49
+ id: string;
50
+ color: string;
51
+ }[];
52
+ }
53
+ /** Words of the visitor's own. Left empty, the generator's are shown as the placeholder. */
54
+ export interface TextControl {
55
+ type: "text";
56
+ id: string;
57
+ label: string;
58
+ value: string;
59
+ placeholder: string;
60
+ maxLength: number;
61
+ }
62
+ /** A colour field as in a design tool: hex and opacity, each under its own key. */
63
+ export interface FillControl {
64
+ type: "fill";
65
+ id: string;
66
+ label: string;
67
+ color: string;
68
+ /** From 0 to 1. */
69
+ opacity: number;
70
+ keys: {
71
+ color: string;
72
+ opacity: string;
73
+ };
74
+ /** Colours offered in its picker. */
75
+ presets: string[];
76
+ }
77
+ export type Control = ChoiceControl | SliderControl | ColorsControl | TextControl | FillControl;
78
+ export interface Section {
79
+ title: string;
80
+ controls: Control[];
81
+ }
82
+ export type ControlBuilder = (name: string, overrides: Overrides) => Section[];
83
+ /**
84
+ * Lifts the controls named in `ids` out of their sections, in that order, and leaves
85
+ * the rest behind. Controls come and go with the avatar, so a missing id is skipped.
86
+ */
87
+ export declare function splitEssentials(sections: Section[], ids: string[]): {
88
+ essentials: Control[];
89
+ advanced: Section[];
90
+ };
91
+ export interface Entry {
92
+ label: string;
93
+ /** How much of the range this option takes, matching the generator's odds. */
94
+ weight?: number;
95
+ color?: string;
96
+ }
97
+ type Layout = ChoiceControl["layout"];
98
+ /** Which entry a position lands on, for generators that pick by weight. */
99
+ export declare const bandAt: (entries: {
100
+ weight?: number;
101
+ }[], position: number) => number;
102
+ /** A value the generator picks from a list, under one key. */
103
+ export declare function choice(t: Traits, key: string, label: string, layout: Layout, entries: Entry[]): ChoiceControl;
104
+ /** A whole-avatar turn in degrees: a small one from the name, or any at all set by hand. */
105
+ export declare function turnOf(t: Traits, natural: number): number;
106
+ export declare function turnSlider(t: Traits, natural: number): SliderControl;
107
+ /** Makes each of a control's options also drop the colour picked by hand for `key`. */
108
+ export declare const clearingColor: (control: ChoiceControl, key: string) => ChoiceControl;
109
+ /** Swatches the generator picks from under one key, and any other colour by hand. */
110
+ export declare function palette(t: Traits, key: string, label: string, colors: string[]): ChoiceControl;
111
+ /** The same, for a colour no name picks: everything starts on the first, whatever its name. */
112
+ export declare function fixedPalette(t: Traits, key: string, label: string, colors: string[]): ChoiceControl;
113
+ /** A list that may not appear at all: the generator rolls `chanceKey`, then picks from `kindKey`. */
114
+ export declare function optional(t: Traits, chanceKey: string, percent: number, kindKey: string, label: string, layout: Layout, entries: Entry[]): ChoiceControl;
115
+ /** Something the generator either includes or leaves out. */
116
+ export declare function toggle(t: Traits, key: string, percent: number, label: string): ChoiceControl;
117
+ /** Normally worked out from the avatar, and can be fixed by hand. `current` is what it worked out. */
118
+ export declare function automatic(t: Traits, key: string, label: string, labels: string[], current: string): ChoiceControl;
119
+ /** A continuous trait, shown in the units it ends up in. */
120
+ export declare function slider(t: Traits, key: string, label: string, min: number, max: number, unit?: string): SliderControl;
121
+ /** The part of the art's variants a control offers, keeping their odds. */
122
+ export declare const fromVariants: (variants: object, label: (name: string, i: number) => string) => Entry[];
123
+ /** `bigPupils` to `Big pupils`, for names taken from the art. */
124
+ export declare const spaced: (name: string) => string;
125
+ export {};
@@ -0,0 +1,220 @@
1
+ /**
2
+ * What the control panel can change.
3
+ *
4
+ * Generators read every value through a trait key, so a control is just a way of
5
+ * fixing some keys (see `Overrides`). Each avatar type describes its own controls
6
+ * from its current traits, which is what lets them appear and change with the avatar.
7
+ */
8
+ import { hexToInt } from "./colors.js";
9
+ import { colorKey } from "./traits.js";
10
+ /**
11
+ * Lifts the controls named in `ids` out of their sections, in that order, and leaves
12
+ * the rest behind. Controls come and go with the avatar, so a missing id is skipped.
13
+ */
14
+ export function splitEssentials(sections, ids) {
15
+ const all = sections.flatMap((section) => section.controls);
16
+ const essentials = ids.flatMap((id) => all.filter((control) => control.id === id));
17
+ const rest = sections.map((section) => ({
18
+ ...section,
19
+ controls: section.controls.filter((control) => !essentials.includes(control)),
20
+ }));
21
+ return { essentials, advanced: regroup(rest) };
22
+ }
23
+ /**
24
+ * More options, in the same groups and order for every type. Possibilities leads, with no
25
+ * heading of its own, as it governs all the rest.
26
+ */
27
+ const GROUPS = ["", "Shape", "Face", "Details", "Colour", "Container"];
28
+ /** Where a type's own sections land when nothing about a control says otherwise. */
29
+ const FROM_SECTION = {
30
+ Colour: "Colour",
31
+ Paint: "Colour",
32
+ Eyes: "Face",
33
+ Face: "Face",
34
+ Words: "Details",
35
+ Parts: "Details",
36
+ Accessories: "Details",
37
+ Hair: "Details",
38
+ Lines: "Details",
39
+ Reactions: "Details",
40
+ Container: "Container",
41
+ };
42
+ /** A control's group, read from what it sets. The type's own sections are only a fallback. */
43
+ function groupOf(control, section) {
44
+ const id = control.id;
45
+ if (id === "variance")
46
+ return "";
47
+ if (/container/.test(id))
48
+ return "Container";
49
+ // A type's Colour section holds only colours, so its word is taken as is.
50
+ if (FROM_SECTION[section] === "Colour")
51
+ return "Colour";
52
+ if (/colou?r|paint|tile|fill|skin|palette|^hue|^tone|ink|fur|^glow|frost|^pool|grain|blend|^mask|speckle/.test(id))
53
+ return "Colour";
54
+ if (/hover|effect|hat|glasses|earring|beard|^hair|mark|spots|^top$|^side|straps|studs|topper|holes|wobble|weight|gesture|title|lumps|clothing/.test(id))
55
+ return "Details";
56
+ if (/eye|brow|mouth|grin|teeth|^face|mood|feeling|expression|emotion|cheek|nose|blush|iris|formation|gaze|patch/.test(id))
57
+ return "Face";
58
+ if (/^pose|mirrored|roundness|angle|^body|^head|width|waist|softness|turn|depth|fluff|stretch/.test(id))
59
+ return "Shape";
60
+ return FROM_SECTION[section] ?? (GROUPS.includes(section) ? section : "Shape");
61
+ }
62
+ function regroup(sections) {
63
+ const groups = new Map();
64
+ for (const section of sections) {
65
+ for (const control of section.controls) {
66
+ const group = groupOf(control, section.title);
67
+ groups.set(group, [...(groups.get(group) ?? []), control]);
68
+ }
69
+ }
70
+ const rank = (title) => (GROUPS.includes(title) ? GROUPS.indexOf(title) : GROUPS.length);
71
+ return [...groups]
72
+ .map(([title, controls]) => ({ title, controls }))
73
+ .sort((a, b) => rank(a.title) - rank(b.title));
74
+ }
75
+ /** Just below 1, because a position of exactly 1 is past the end of every list. */
76
+ const TOP = 0.999;
77
+ /** Splits [0, 1) in proportion to the weights. Each band's middle selects that entry. */
78
+ function bands(entries) {
79
+ const total = entries.reduce((sum, entry) => sum + (entry.weight ?? 1), 0);
80
+ let start = 0;
81
+ return entries.map((entry) => {
82
+ const width = (entry.weight ?? 1) / total;
83
+ const band = { ...entry, start, end: start + width, middle: start + width / 2 };
84
+ start += width;
85
+ return band;
86
+ });
87
+ }
88
+ /** Which entry a position lands on, for generators that pick by weight. */
89
+ export const bandAt = (entries, position) => {
90
+ const found = bands(entries).findIndex((band) => position < band.end);
91
+ return found === -1 ? entries.length - 1 : found;
92
+ };
93
+ /** A value the generator picks from a list, under one key. */
94
+ export function choice(t, key, label, layout, entries) {
95
+ return {
96
+ type: "choice",
97
+ id: key,
98
+ label,
99
+ layout,
100
+ options: bands(entries).map((band, i) => ({
101
+ id: String(i),
102
+ label: band.label,
103
+ color: band.color,
104
+ set: { [key]: band.middle },
105
+ })),
106
+ selected: String(bandAt(entries, t(key))),
107
+ };
108
+ }
109
+ /** A whole-avatar turn in degrees: a small one from the name, or any at all set by hand. */
110
+ export function turnOf(t, natural) {
111
+ const set = t.forced("angle");
112
+ return set === undefined ? (t("angle") * 2 - 1) * natural : set * 360 - 180;
113
+ }
114
+ export function turnSlider(t, natural) {
115
+ const turn = turnOf(t, natural);
116
+ return { type: "slider", id: "angle", label: "Angle", value: (turn + 180) / 360, display: `${Math.round(turn)}°`, range: [-180, 180] };
117
+ }
118
+ /** Makes each of a control's options also drop the colour picked by hand for `key`. */
119
+ export const clearingColor = (control, key) => ({
120
+ ...control,
121
+ options: control.options.map((option) => ({ ...option, set: { ...option.set, [colorKey(key)]: undefined } })),
122
+ });
123
+ /** Swatches the generator picks from under one key, and any other colour by hand. */
124
+ export function palette(t, key, label, colors) {
125
+ const control = clearingColor(choice(t, key, label, "swatches", colors.map((color) => ({ label: color, color }))), key);
126
+ const picked = t.color(key);
127
+ return {
128
+ ...control,
129
+ selected: picked ? "custom" : control.selected,
130
+ custom: { id: colorKey(key), color: picked ?? control.options[Number(control.selected)].color },
131
+ };
132
+ }
133
+ /** The same, for a colour no name picks: everything starts on the first, whatever its name. */
134
+ export function fixedPalette(t, key, label, colors) {
135
+ const id = colorKey(key);
136
+ const current = t.color(key) ?? colors[0];
137
+ return {
138
+ type: "choice",
139
+ id: key,
140
+ label,
141
+ layout: "swatches",
142
+ options: colors.map((color, i) => ({
143
+ id: color,
144
+ label: color,
145
+ color,
146
+ set: { [id]: i === 0 ? undefined : hexToInt(color) },
147
+ })),
148
+ selected: current,
149
+ custom: { id, color: current },
150
+ };
151
+ }
152
+ /** A list that may not appear at all: the generator rolls `chanceKey`, then picks from `kindKey`. */
153
+ export function optional(t, chanceKey, percent, kindKey, label, layout, entries) {
154
+ const present = t(chanceKey) * 100 < percent;
155
+ return {
156
+ type: "choice",
157
+ id: chanceKey,
158
+ label,
159
+ layout,
160
+ options: [
161
+ { id: "none", label: "None", set: { [chanceKey]: TOP } },
162
+ ...bands(entries).map((band, i) => ({
163
+ id: String(i),
164
+ label: band.label,
165
+ color: band.color,
166
+ set: { [chanceKey]: 0, [kindKey]: band.middle },
167
+ })),
168
+ ],
169
+ selected: present ? String(bandAt(entries, t(kindKey))) : "none",
170
+ };
171
+ }
172
+ /** Something the generator either includes or leaves out. */
173
+ export function toggle(t, key, percent, label) {
174
+ return {
175
+ type: "choice",
176
+ id: key,
177
+ label,
178
+ layout: "switch",
179
+ options: [
180
+ { id: "on", label: "On", set: { [key]: 0 } },
181
+ { id: "off", label: "Off", set: { [key]: TOP } },
182
+ ],
183
+ selected: t(key) * 100 < percent ? "on" : "off",
184
+ };
185
+ }
186
+ /** Normally worked out from the avatar, and can be fixed by hand. `current` is what it worked out. */
187
+ export function automatic(t, key, label, labels, current) {
188
+ const forced = t.forced(key);
189
+ return {
190
+ type: "choice",
191
+ id: key,
192
+ label,
193
+ layout: "list",
194
+ options: [
195
+ { id: "auto", label: `Auto (${current})`, set: { [key]: undefined } },
196
+ ...labels.map((entry, i) => ({ id: String(i), label: entry, set: { [key]: (i + 0.5) / labels.length } })),
197
+ ],
198
+ selected: forced === undefined ? "auto" : String(Math.floor(forced * labels.length)),
199
+ };
200
+ }
201
+ /** A continuous trait, shown in the units it ends up in. */
202
+ export function slider(t, key, label, min, max, unit = "") {
203
+ const value = t(key);
204
+ const shown = min + value * (max - min);
205
+ return {
206
+ type: "slider",
207
+ id: key,
208
+ label,
209
+ value,
210
+ display: `${Number.isInteger(min) && Number.isInteger(max) && max - min > 10 ? Math.round(shown) : shown.toFixed(1)}${unit}`,
211
+ range: [min, max],
212
+ };
213
+ }
214
+ /** The part of the art's variants a control offers, keeping their odds. */
215
+ export const fromVariants = (variants, label) => Object.entries(variants).map(([name, variant], i) => ({ label: label(name, i), weight: variant.weight ?? 1 }));
216
+ /** `bigPupils` to `Big pupils`, for names taken from the art. */
217
+ export const spaced = (name) => {
218
+ const words = name.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase();
219
+ return words.charAt(0).toUpperCase() + words.slice(1);
220
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * What Possibilities calms on a critter. Low keeps the eight mildest feelings, one for each
3
+ * variant, and draws the eyes' size, gap and height and the mouth's size towards their middle.
4
+ * Indices are into `FEELINGS`, mildest 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,18 @@
1
+ /**
2
+ * What Possibilities calms on a critter. Low keeps the eight mildest feelings, one for each
3
+ * variant, and draws the eyes' size, gap and height and the mouth's size towards their middle.
4
+ * Indices are into `FEELINGS`, mildest first.
5
+ */
6
+ export const CALMING = {
7
+ features: /^eye\.(size|gap|y)$|^mouth\.(size|y)$|^nose\.size$/,
8
+ lists: {
9
+ // Happy, Grin, Joy, Sleepy, Smug, Surprised, Bored, Grumpy, then Sad and Angry.
10
+ feeling: [10, 8, [0, 4, 7, 3, 6, 2, 9, 1, 5, 8]],
11
+ },
12
+ rarer: ["spots", "blush"],
13
+ };
14
+ export const NOTES = [
15
+ "happy, sleepy and smug faces, the eyes and mouth in their usual places.",
16
+ "more feelings, bigger or smaller eyes and mouths.",
17
+ "every feeling, sad and angry too, at any size.",
18
+ ];
@@ -0,0 +1,2 @@
1
+ import { type ControlBuilder } from "../controls.ts";
2
+ export declare const controls: ControlBuilder;
@@ -0,0 +1,49 @@
1
+ import { choice, palette, slider, toggle } from "../controls.js";
2
+ import { narrowed, possibilitiesSlider } from "../possibilities.js";
3
+ import { CALMING, NOTES } from "./calming.js";
4
+ import { BLUSH_PERCENT, BROWS, COLOURS, critterTraits, EYE_STYLES, FEELINGS, IRISES, KINDS, kindOf, SPOTS_PERCENT, styledOf } from "./index.js";
5
+ const titled = (word) => word[0].toUpperCase() + word.slice(1);
6
+ /** A choice that shows what's drawn: the creature's own until one is picked. */
7
+ const own = (control, selected) => ({ ...control, selected: String(selected) });
8
+ export const controls = (name, overrides) => {
9
+ const t = critterTraits(name, overrides);
10
+ const kind = kindOf(name, overrides);
11
+ const styled = styledOf(name, overrides);
12
+ // A bill or a beak stands in for the mouth, and a cyclops has no gap between its eyes.
13
+ const mouthed = kind.feature !== "bill" && kind.feature !== "beak";
14
+ return [
15
+ {
16
+ title: "Critter",
17
+ controls: [
18
+ // Showing the creature drawn, which for a variant is a step on from its name's own.
19
+ { ...choice(t, "kind", "Creature", "list", KINDS.map(({ name: label }) => ({ label }))), selected: String(KINDS.indexOf(kind)) },
20
+ narrowed(choice(t, "feeling", "Feeling", "list", FEELINGS.map(({ name: label }) => ({ label }))), t, CALMING),
21
+ possibilitiesSlider(t, NOTES),
22
+ slider(t, "eye.size", "Eye size", 0, 100, "%"),
23
+ ...(kind.single ? [] : [slider(t, "eye.gap", "Eye gap", 0, 100, "%")]),
24
+ slider(t, "eye.y", "Eye height", -100, 100, "%"),
25
+ ...(mouthed ? [slider(t, "mouth.size", "Mouth size", 0, 100, "%")] : []),
26
+ own(choice(t, "eye.style", "Eyes", "list", EYE_STYLES.map((style) => ({ label: titled(style) }))), EYE_STYLES.indexOf(styled.eyes)),
27
+ own(choice(t, "brows", "Brows", "list", BROWS.map((style) => ({ label: titled(style) }))), BROWS.indexOf(styled.brows)),
28
+ {
29
+ ...choice(t, "patch", "Eye patches", "list", [{ label: "On" }, { label: "Off" }]),
30
+ selected: styled.patch ? "0" : "1",
31
+ options: [
32
+ { id: "0", label: "On", set: { patch: 0 } },
33
+ { id: "1", label: "Off", set: { patch: 0.999 } },
34
+ ],
35
+ layout: "switch",
36
+ },
37
+ toggle(t, "spots", SPOTS_PERCENT, "Spots"),
38
+ toggle(t, "blush", BLUSH_PERCENT, "Blush"),
39
+ ],
40
+ },
41
+ {
42
+ title: "Colour",
43
+ controls: [
44
+ palette(t, "body", "Colour", COLOURS),
45
+ palette(t, "iris", "Iris", IRISES),
46
+ ],
47
+ },
48
+ ];
49
+ };
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Critters: original creatures in the style of close-up flat face tiles. The face fills the
3
+ * tile and runs off its edges, in one bold body colour with a darker tone for lids, brows and
4
+ * creases and a lighter one for patches, muzzles and jaws. Eyes are big whites with an iris
5
+ * and a glint, or glossy beads, under lids that set the mood. Each type is a creature of its
6
+ * own, as the animals are, and every type can feel every feeling.
7
+ *
8
+ * Every part is drawn from numbers in the 100×100 frame, so the live avatar can move its lids,
9
+ * pupils and brows, and the thumbnail is the very same picture at rest.
10
+ */
11
+ import { type Overrides, type Traits } from "../traits.ts";
12
+ /** Bold flat body colours. Neighbours are unlike. */
13
+ export declare const COLOURS: string[];
14
+ /** Rich iris colours, as the references' are, rather than a dull tint of the body's. */
15
+ export declare const IRISES: string[];
16
+ type Mouth = "smile" | "grin" | "laugh" | "flat" | "frown" | "o" | "smirk" | "grit";
17
+ /** A feeling, as lids, brows, pupils and a mouth. The same feeling reads on every creature. */
18
+ interface Feeling {
19
+ name: string;
20
+ /** How far the upper lid is down, from 0 (wide open) to 1 (shut). */
21
+ lid: number;
22
+ /** The lid's tilt in degrees: above 0 lowers it towards the nose, as in anger. */
23
+ tilt: number;
24
+ /** How far the brows are raised, and their tilt as for the lid. */
25
+ lift: number;
26
+ browTilt: number;
27
+ /** The pupils' size, and where they look, as shares of their reach. */
28
+ pupil: number;
29
+ look: [number, number];
30
+ mouth: Mouth;
31
+ /** Shut in happiness, as two arcs. */
32
+ closed?: boolean;
33
+ }
34
+ /** Neighbours are as unlike as they can be. */
35
+ export declare const FEELINGS: Feeling[];
36
+ type EyeStyle = "white" | "bead";
37
+ type Brows = "arcs" | "pads" | "wedges" | "none";
38
+ type Feature = "nose" | "bear" | "bill" | "beak" | "jaw" | "none";
39
+ interface Kind {
40
+ name: string;
41
+ eyes: EyeStyle;
42
+ /** One eye or two, their radius, height and how far each is from the middle. */
43
+ single?: boolean;
44
+ r: number;
45
+ y: number;
46
+ gap: number;
47
+ /** A lighter patch round each eye, as a share of its radius. */
48
+ patch?: number;
49
+ brows: Brows;
50
+ feature: Feature;
51
+ /** Its mouth's height and half-width, and whether its smiles are toothy. */
52
+ mouthY: number;
53
+ mouthW: number;
54
+ toothy: boolean;
55
+ }
56
+ /** The creatures, each its own build. Neighbours are as unlike as they can be. */
57
+ export declare const KINDS: Kind[];
58
+ export declare const critterTraits: (name: string, overrides?: Overrides) => Traits;
59
+ /**
60
+ * Its creature. Variants step on through them from the name's own, as the animals' are each a
61
+ * different animal, keeping to those Possibilities offers. Set by hand, it's that one.
62
+ */
63
+ export declare function kindOf(name: string, overrides?: Overrides): Kind;
64
+ export declare const feelingOf: (t: Traits) => Feeling;
65
+ export declare const EYE_STYLES: EyeStyle[];
66
+ export declare const BROWS: Brows[];
67
+ /** How often a name is spotted or blushes, in percent. */
68
+ export declare const SPOTS_PERCENT = 25;
69
+ export declare const BLUSH_PERCENT = 30;
70
+ /**
71
+ * Its creature as drawn: its own build, with its eyes, brows and patches as picked by hand if
72
+ * they are. Unpicked, every creature keeps its own, as each is built to.
73
+ */
74
+ export declare function styledOf(name: string, overrides?: Overrides): Kind;
75
+ /** Its colours: the body, and the tones every part is drawn in, all from the body's. */
76
+ export declare function coloursOf(t: Traits): {
77
+ body: string;
78
+ lid: string;
79
+ shade: string;
80
+ dark: string;
81
+ light: string;
82
+ iris: string;
83
+ cream: string;
84
+ plate: string;
85
+ comb: string;
86
+ beak: string;
87
+ bill: string;
88
+ };
89
+ export interface Critter {
90
+ kind: string;
91
+ feeling: string;
92
+ body: string;
93
+ /** Drawn behind the eyes: patches, muzzles, jaws and noses. */
94
+ back: string;
95
+ eyes: {
96
+ left: string;
97
+ right: string;
98
+ };
99
+ brows: string;
100
+ mouth: string;
101
+ /** The bill or beak, drawn over the mouth. */
102
+ front: string;
103
+ blink: {
104
+ period: number;
105
+ delay: number;
106
+ };
107
+ }
108
+ /** `id` keeps each critter's clips its own where many share a page. */
109
+ export declare function createCritter(name: string, overrides: Overrides | undefined, id?: string): Critter;
110
+ export declare const CORNER = 22.22;
111
+ /** The critter at rest, for thumbnails. */
112
+ export declare function critterSvg(name: string, overrides?: Overrides): string;
113
+ export {};