@originkit/avatars 0.0.0-stage → 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.
Files changed (168) hide show
  1. package/GUIDE.md +44 -0
  2. package/LICENSE +58 -0
  3. package/README.md +119 -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 +128 -0
  64. package/dist/controls.js +170 -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 +11 -0
  165. package/dist/variants.js +41 -0
  166. package/mcp/server.js +7 -0
  167. package/mcp/tools.js +70 -0
  168. package/package.json +54 -4
@@ -0,0 +1,8 @@
1
+ /**
2
+ * What Possibilities calms on a face. Eight mouths stay at the lowest, one for each variant.
3
+ * The art lists its parts in no useful order, and the first brows are the angriest, so the
4
+ * calm ones are named here, chosen by looking at each: round open eyes, gentle brows, smiles.
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,20 @@
1
+ /**
2
+ * What Possibilities calms on a face. Eight mouths stay at the lowest, one for each variant.
3
+ * The art lists its parts in no useful order, and the first brows are the angriest, so the
4
+ * calm ones are named here, chosen by looking at each: round open eyes, gentle brows, smiles.
5
+ */
6
+ export const CALMING = {
7
+ lists: {
8
+ mouth: [30, 8, [0, 24, 4, 25, 1, 8, 2, 6, 22, 3]],
9
+ "mouth.hover": [30, 8, [0, 24, 4, 25, 1, 8, 2, 6, 22, 3]],
10
+ eyes: [26, 3, [23, 24, 25, 0, 1, 2, 7]],
11
+ eyebrows: [15, 2, [8, 9, 10, 11, 12, 14, 4, 5]],
12
+ "glasses.variant": [5, 1],
13
+ },
14
+ rarer: ["glasses"],
15
+ };
16
+ export const NOTES = [
17
+ "a few calm eyes and brows, simple mouths and hardly ever glasses.",
18
+ "more eyes, brows and mouths, and glasses now and then.",
19
+ "every eye, brow, mouth and pair of glasses.",
20
+ ];
@@ -0,0 +1,2 @@
1
+ import { type ControlBuilder } from "../controls.ts";
2
+ export declare const controls: ControlBuilder;
@@ -0,0 +1,38 @@
1
+ import { automatic, choice, fromVariants, optional, palette, spaced } from "../controls.js";
2
+ import { calmed, narrowed, possibilitiesSlider } from "../possibilities.js";
3
+ import { createTraits } from "../traits.js";
4
+ import { CALMING, NOTES } from "./calming.js";
5
+ import definition from "./adventurer-neutral.json" with { type: "json" };
6
+ import { createFace, EFFECTS, SKINS } from "./index.js";
7
+ const { eyes, eyebrows, mouth, glasses } = definition.components;
8
+ const numbered = (label) => (_, i) => `${label} ${i + 1}`;
9
+ export const controls = (name, overrides) => {
10
+ const t = calmed(createTraits(name, overrides), CALMING);
11
+ const list = (control) => narrowed(control, t, CALMING);
12
+ const effect = createFace(name, "", overrides).effect;
13
+ return [
14
+ {
15
+ title: "Face",
16
+ controls: [
17
+ palette(t, "skin", "Colour", SKINS),
18
+ possibilitiesSlider(t, NOTES),
19
+ list(choice(t, "eyes", "Eyes", "list", fromVariants(eyes.variants, numbered("Eyes")))),
20
+ list(choice(t, "eyebrows", "Brows", "list", fromVariants(eyebrows.variants, numbered("Brows")))),
21
+ list(choice(t, "mouth", "Mouth", "list", fromVariants(mouth.variants, numbered("Mouth")))),
22
+ ],
23
+ },
24
+ {
25
+ title: "Accessories",
26
+ controls: [
27
+ optional(t, "glasses", glasses.probability, "glasses.variant", "Glasses", "list", fromVariants(glasses.variants, (variant, i) => (variant === "variant01" ? "Shades" : `Style ${i}`))),
28
+ ],
29
+ },
30
+ {
31
+ title: "Reactions",
32
+ controls: [
33
+ list(choice(t, "mouth.hover", "On hover", "list", fromVariants(mouth.variants, numbered("Mouth")))),
34
+ automatic(t, "effect.forced", "Tap effect", EFFECTS.map(spaced), spaced(effect)),
35
+ ],
36
+ },
37
+ ];
38
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Faces from "Adventurer Neutral" by Lisa Wischofsky (CC BY 4.0), drawn from its
3
+ * definition in `adventurer-neutral.json`. CC BY needs a visible credit wherever
4
+ * this is published (see LICENSE). The changes are the separated pupils and the
5
+ * reactions below.
6
+ */
7
+ import { type Variant } from "../art.ts";
8
+ import { type Overrides } from "../traits.ts";
9
+ type Part = "eyes" | "eyebrows" | "mouth" | "glasses";
10
+ export declare const SKINS: string[];
11
+ export declare const EFFECTS: readonly ["confetti", "hearts", "stars", "shine", "snooze", "storm"];
12
+ export type Effect = (typeof EFFECTS)[number];
13
+ /**
14
+ * Draws a pair of eyes with the pupils free to move, keeping the art's drawing
15
+ * order. Each pupil gets a `data-pupils` group, clipped to the whites so it can
16
+ * never slide out of the eye. A white straight after a pupil is a glint on it, and
17
+ * travels with it. Any other white belongs to an eye.
18
+ */
19
+ export declare function drawEyes(variant: Variant, colors: Record<string, string>, idPrefix: string): {
20
+ markup: string;
21
+ open: boolean;
22
+ };
23
+ export interface Face {
24
+ skin: string;
25
+ at: Record<Part, string>;
26
+ eyes: string;
27
+ eyebrows: string;
28
+ glasses: string | null;
29
+ /** The mouth at rest, while hovered, and while reacting to a tap. */
30
+ mouths: {
31
+ rest: string;
32
+ hover: string;
33
+ tap: string;
34
+ };
35
+ /** Whether there are pupils to move. Closed eyes have none. */
36
+ eyesOpen: boolean;
37
+ effect: Effect;
38
+ /** Seconds, so a row of faces does not blink as one. */
39
+ blinkDelay: number;
40
+ }
41
+ export declare function createFace(name: string, idPrefix: string, overrides?: Overrides): Face;
42
+ /** The face at rest, as a picture. */
43
+ export declare function faceSvg(name: string, overrides?: Overrides): string;
44
+ export {};
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Faces from "Adventurer Neutral" by Lisa Wischofsky (CC BY 4.0), drawn from its
3
+ * definition in `adventurer-neutral.json`. CC BY needs a visible credit wherever
4
+ * this is published (see LICENSE). The changes are the separated pupils and the
5
+ * reactions below.
6
+ */
7
+ import { calmed } from "../possibilities.js";
8
+ import { CALMING } from "./calming.js";
9
+ import { draw, pickVariant } from "../art.js";
10
+ import { createTraits } from "../traits.js";
11
+ import definition from "./adventurer-neutral.json" with { type: "json" };
12
+ const components = definition.components;
13
+ /** Where the art places each part on its 400×400 canvas. */
14
+ const placement = Object.fromEntries(definition.canvas.elements.map((element) => [element.name, element.attributes.transform]));
15
+ const INK = Object.fromEntries(Object.entries(definition.colors)
16
+ .filter(([name]) => name !== "background")
17
+ .map(([name, color]) => [name, color.values[0]]));
18
+ export const SKINS = definition.colors.background.values;
19
+ export const EFFECTS = ["confetti", "hearts", "stars", "shine", "snooze", "storm"];
20
+ const hasTag = (variant, tag) => variant.tags?.includes(tag) ?? false;
21
+ const fillOf = (element) => {
22
+ const fill = element.attributes?.fill;
23
+ return typeof fill === "object" ? fill.name : null;
24
+ };
25
+ const tagged = (variants, tag) => Object.keys(variants).filter((name) => hasTag(variants[name], tag));
26
+ /**
27
+ * Draws a pair of eyes with the pupils free to move, keeping the art's drawing
28
+ * order. Each pupil gets a `data-pupils` group, clipped to the whites so it can
29
+ * never slide out of the eye. A white straight after a pupil is a glint on it, and
30
+ * travels with it. Any other white belongs to an eye.
31
+ */
32
+ export function drawEyes(variant, colors, idPrefix) {
33
+ const clip = `${idPrefix}whites`;
34
+ const whites = [];
35
+ const pieces = [];
36
+ let onPupil = false;
37
+ for (const element of variant.elements) {
38
+ const fill = fillOf(element);
39
+ const moves = fill === "eyes" || (onPupil && fill === "sclera");
40
+ if (fill === "sclera" && !moves)
41
+ whites.push(element);
42
+ pieces.push({ element, moves });
43
+ onPupil = moves;
44
+ }
45
+ const open = whites.length > 0 && pieces.some((piece) => piece.moves);
46
+ const markup = pieces
47
+ .map(({ element, moves }) => {
48
+ const art = draw(element, colors, idPrefix);
49
+ return open && moves ? `<g clip-path="url(#${clip})"><g data-pupils="">${art}</g></g>` : art;
50
+ })
51
+ .join("");
52
+ const clipPath = `<clipPath id="${clip}">${whites.map((element) => draw(element, colors, idPrefix)).join("")}</clipPath>`;
53
+ return { markup: open ? clipPath + markup : markup, open };
54
+ }
55
+ /** A face's tap effect follows from how it was generated. */
56
+ function chooseEffect(t, eyesOpen, restingMouth, glasses) {
57
+ const forced = t.forced("effect.forced");
58
+ if (forced !== undefined)
59
+ return EFFECTS[Math.floor(forced * EFFECTS.length)];
60
+ if (glasses && hasTag(glasses, "eyewear:sunglasses"))
61
+ return "shine";
62
+ if (!eyesOpen)
63
+ return "snooze";
64
+ if (hasTag(restingMouth, "mood:negative"))
65
+ return "storm";
66
+ return t.pick("effect", ["confetti", "hearts", "stars"]);
67
+ }
68
+ export function createFace(name, idPrefix, overrides) {
69
+ const t = calmed(createTraits(name, overrides), CALMING);
70
+ const render = (variant) => variant.elements.map((element) => draw(element, INK, idPrefix)).join("");
71
+ const eyes = drawEyes(components.eyes.variants[pickVariant(t, "eyes", components.eyes.variants)], INK, idPrefix);
72
+ const eyebrows = components.eyebrows.variants[pickVariant(t, "eyebrows", components.eyebrows.variants)];
73
+ const glasses = t("glasses") * 100 < components.glasses.probability
74
+ ? components.glasses.variants[pickVariant(t, "glasses.variant", components.glasses.variants)]
75
+ : null;
76
+ const mouthVariants = components.mouth.variants;
77
+ const restingMouth = pickVariant(t, "mouth", mouthVariants);
78
+ const happy = tagged(mouthVariants, "mood:positive");
79
+ const unhappy = tagged(mouthVariants, "mood:negative");
80
+ const effect = chooseEffect(t, eyes.open, mouthVariants[restingMouth], glasses);
81
+ const tapMouths = effect === "storm" ? unhappy : happy;
82
+ return {
83
+ skin: t.color("skin") ?? t.pick("skin", SKINS),
84
+ at: placement,
85
+ eyes: eyes.markup,
86
+ eyebrows: render(eyebrows),
87
+ glasses: glasses && render(glasses),
88
+ mouths: {
89
+ rest: render(mouthVariants[restingMouth]),
90
+ hover: render(mouthVariants[pickVariant(t, "mouth.hover", mouthVariants, happy)]),
91
+ tap: render(mouthVariants[pickVariant(t, "mouth.tap", mouthVariants, tapMouths)]),
92
+ },
93
+ eyesOpen: eyes.open,
94
+ effect,
95
+ blinkDelay: t.num("blink", 0, 4),
96
+ };
97
+ }
98
+ /** The face at rest, as a picture. */
99
+ export function faceSvg(name, overrides) {
100
+ const face = createFace(name, "f-", overrides);
101
+ return (`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" fill="none">` +
102
+ `<rect width="400" height="400" rx="96" fill="${face.skin}"/>` +
103
+ `<g transform="${face.at.eyes}">${face.eyes}</g>` +
104
+ `<g transform="${face.at.eyebrows}">${face.eyebrows}</g>` +
105
+ `<g transform="${face.at.mouth}">${face.mouths.rest}</g>` +
106
+ (face.glasses ? `<g transform="${face.at.glasses}">${face.glasses}</g>` : "") +
107
+ `</svg>`);
108
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * What Possibilities calms on a glance. Low keeps the eight plainest eyes for each side, one for
3
+ * each variant, and draws the size, the gap, the height and the tilt towards their middle.
4
+ * Indices are into `EYES`, 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,16 @@
1
+ /**
2
+ * What Possibilities calms on a glance. Low keeps the eight plainest eyes for each side, one for
3
+ * each variant, and draws the size, the gap, the height and the tilt towards their middle.
4
+ * Indices are into `EYES`, plainest first.
5
+ */
6
+ // Full, Lid, Sleepy, Smile, Pill, Oval, Dot, Dash, then Dome, Ring, Slant, Moon and Squint.
7
+ const PLAINEST = [13, 8, [0, 1, 4, 2, 3, 12, 9, 6, 7, 5, 8, 10, 11]];
8
+ export const CALMING = {
9
+ features: /^eye\.(size|gap|y)$|^tilt$/,
10
+ lists: { "eye.left": PLAINEST, "eye.right": PLAINEST },
11
+ };
12
+ export const NOTES = [
13
+ "plain circles, lids, smiles and pills, upright and evenly spaced.",
14
+ "more shapes, a little tilted, set wider or closer.",
15
+ "every eye, up to moons, rings, slants and squints, at any size, gap and tilt.",
16
+ ];
@@ -0,0 +1,2 @@
1
+ import { type ControlBuilder } from "../controls.ts";
2
+ export declare const controls: ControlBuilder;
@@ -0,0 +1,27 @@
1
+ import { choice, palette, slider } from "../controls.js";
2
+ import { narrowed, possibilitiesSlider } from "../possibilities.js";
3
+ import { CALMING, NOTES } from "./calming.js";
4
+ import { EYES, glanceTraits, INKS, rightOf, TILES } from "./index.js";
5
+ const named = EYES.map(({ name: label }) => ({ label }));
6
+ export const controls = (name, overrides) => {
7
+ const t = glanceTraits(name, overrides);
8
+ return [
9
+ {
10
+ title: "Eyes",
11
+ controls: [
12
+ narrowed(choice(t, "eye.left", "Left eye", "list", named), t, CALMING),
13
+ // Showing what's drawn, which for a matching pair is the left eye's shape.
14
+ { ...narrowed(choice(t, "eye.right", "Right eye", "list", named), t, CALMING), selected: String(EYES.indexOf(rightOf(t))) },
15
+ possibilitiesSlider(t, NOTES),
16
+ slider(t, "eye.size", "Eye size", 0, 100, "%"),
17
+ slider(t, "eye.gap", "Eye gap", 0, 100, "%"),
18
+ slider(t, "eye.y", "Eye height", 0, 100, "%"),
19
+ slider(t, "tilt", "Tilt", -6, 6, "°"),
20
+ ],
21
+ },
22
+ {
23
+ title: "Colour",
24
+ controls: [palette(t, "tile", "Colour", TILES), palette(t, "ink", "Eyes", INKS)],
25
+ },
26
+ ];
27
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Glances: just two eyes on a dark tile, after the references. Each eye is one bold flat
3
+ * shape: a full circle, a half-closed lid, a sleepy lid, an arched smile, and more in the same
4
+ * language. Each eye picks its own, so a pair can match or not, as a lid beside a full circle
5
+ * reads as a knowing look. The eyes are what moves: they follow the pointer, glance about,
6
+ * blink, and pop when tapped.
7
+ */
8
+ import { type Overrides, type Traits } from "../traits.ts";
9
+ interface Eye {
10
+ name: string;
11
+ /** Markup around the eye's middle at radius `r`, as the left eye. The right is its mirror. */
12
+ draw(r: number): string;
13
+ }
14
+ /** Measured off the references and kept in their language. Neighbours are as unlike as they can be. */
15
+ export declare const EYES: Eye[];
16
+ /** Dark tiles, the first the references'. Neighbours are unlike. */
17
+ export declare const TILES: string[];
18
+ /** Eye colours, the first the references'. */
19
+ export declare const INKS: string[];
20
+ export declare const glanceTraits: (name: string, overrides?: Overrides) => Traits;
21
+ export declare const leftOf: (t: Traits) => Eye;
22
+ /** The right eye: the left's own shape for a matching pair, unless one is picked for it. */
23
+ export declare const rightOf: (t: Traits) => Eye;
24
+ export declare function coloursOf(t: Traits): {
25
+ tile: string;
26
+ ink: string;
27
+ };
28
+ export interface Glance {
29
+ tile: string;
30
+ ink: string;
31
+ /** Where the eyes sit and their tilt, and each eye placed in it. */
32
+ face: string;
33
+ left: {
34
+ at: string;
35
+ shape: string;
36
+ };
37
+ right: {
38
+ at: string;
39
+ shape: string;
40
+ };
41
+ /** How long each blink cycle is, when it starts, and whether it blinks twice. */
42
+ blink: {
43
+ period: number;
44
+ delay: number;
45
+ twice: boolean;
46
+ };
47
+ }
48
+ export declare function createGlance(name: string, overrides?: Overrides): Glance;
49
+ /** Every eye shares these, so a stroked shape is round at its ends and corners. */
50
+ export declare const EYE_STYLE: (ink: string) => string;
51
+ export declare const CORNER = 22.22;
52
+ /** The eyes at rest, open, for thumbnails. */
53
+ export declare function glanceSvg(name: string, overrides?: Overrides): string;
54
+ export {};
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Glances: just two eyes on a dark tile, after the references. Each eye is one bold flat
3
+ * shape: a full circle, a half-closed lid, a sleepy lid, an arched smile, and more in the same
4
+ * language. Each eye picks its own, so a pair can match or not, as a lid beside a full circle
5
+ * reads as a knowing look. The eyes are what moves: they follow the pointer, glance about,
6
+ * blink, and pop when tapped.
7
+ */
8
+ import { calmed } from "../possibilities.js";
9
+ import { createTraits } from "../traits.js";
10
+ import { CALMING } from "./calming.js";
11
+ const round2 = (value) => Math.round(value * 100) / 100;
12
+ const f = round2;
13
+ /** Measured off the references and kept in their language. Neighbours are as unlike as they can be. */
14
+ export const EYES = [
15
+ // The reference's open eye.
16
+ { name: "Full", draw: (r) => `<circle r="${f(r)}"/>` },
17
+ // The reference's half-closed one: the lower half, its flat top through the middle.
18
+ { name: "Lid", draw: (r) => `<path d="M${f(-r)} 0A${f(r)} ${f(r)} 0 0 0 ${f(r)} 0Z"/>` },
19
+ // The reference's arched smile, eyes closed in happiness.
20
+ { name: "Smile", draw: (r) => `<path d="M${f(-1.05 * r)} ${f(0.75 * r)}Q0 ${f(-0.3 * r)} ${f(1.05 * r)} ${f(0.75 * r)}Q0 ${f(0.25 * r)} ${f(-1.05 * r)} ${f(0.75 * r)}Z" stroke-width="${f(r * 0.1)}"/>` },
21
+ { name: "Pill", draw: (r) => `<rect x="${f(-0.46 * r)}" y="${f(-0.95 * r)}" width="${f(0.92 * r)}" height="${f(1.9 * r)}" rx="${f(0.46 * r)}"/>` },
22
+ // The sleepy reference's: more than half, its lid just above the middle, its corners rounded.
23
+ {
24
+ name: "Sleepy",
25
+ draw: (r) => {
26
+ const [top, side] = [-0.2 * r, 0.98 * r];
27
+ return `<path d="M${f(-side)} ${f(top)}H${f(side)}A${f(r)} ${f(r)} 0 0 1 ${f(-side)} ${f(top)}Z" stroke-width="${f(r * 0.16)}"/>`;
28
+ },
29
+ },
30
+ { name: "Ring", draw: (r) => `<circle r="${f(r * 0.78)}" fill="none" stroke-width="${f(r * 0.42)}"/>` },
31
+ { name: "Dash", draw: (r) => `<rect x="${f(-0.95 * r)}" y="${f(0.1 * r)}" width="${f(1.9 * r)}" height="${f(0.44 * r)}" rx="${f(0.22 * r)}"/>` },
32
+ // An upper half, the eye looking up from under a lower lid.
33
+ { name: "Dome", draw: (r) => `<path d="M${f(-r)} ${f(0.4 * r)}A${f(r)} ${f(r)} 0 0 1 ${f(r)} ${f(0.4 * r)}Z"/>` },
34
+ // A lid tilted down towards the nose, as a frown.
35
+ { name: "Slant", draw: (r) => `<path d="M${f(-r)} 0A${f(r)} ${f(r)} 0 0 0 ${f(r)} 0Z" transform="rotate(20)"/>` },
36
+ { name: "Dot", draw: (r) => `<circle r="${f(r * 0.5)}"/>` },
37
+ // A crescent moon, open towards the nose.
38
+ { name: "Moon", draw: (r) => `<path d="M${f(0.2 * r)} ${f(-r)}A${f(r)} ${f(r)} 0 1 0 ${f(0.2 * r)} ${f(r)}A${f(0.72 * r)} ${f(r)} 0 1 1 ${f(0.2 * r)} ${f(-r)}Z"/>` },
39
+ // Squeezed tight, a chevron pointing at the nose.
40
+ { name: "Squint", draw: (r) => `<path d="M${f(-0.55 * r)} ${f(-0.6 * r)}L${f(0.5 * r)} 0L${f(-0.55 * r)} ${f(0.6 * r)}" fill="none" stroke-width="${f(r * 0.38)}"/>` },
41
+ { name: "Oval", draw: (r) => `<ellipse rx="${f(r * 0.68)}" ry="${f(r * 1.08)}"/>` },
42
+ ];
43
+ /** Dark tiles, the first the references'. Neighbours are unlike. */
44
+ export const TILES = ["#000000", "#1d1330", "#0e2620", "#2a1019", "#0f1b3b", "#15151c", "#262019", "#101418"];
45
+ /** Eye colours, the first the references'. */
46
+ export const INKS = ["#ffffff", "#fff3d6", "#cbfff0", "#ffd3ea", "#ffe08a", "#aee9ff"];
47
+ /** How often a name's eyes are a matching pair, in percent. The rest are each their own, as in the knowing look. */
48
+ const PAIR_PERCENT = 55;
49
+ export const glanceTraits = (name, overrides) => calmed(createTraits(name, overrides), CALMING);
50
+ export const leftOf = (t) => t.pick("eye.left", EYES);
51
+ /** The right eye: the left's own shape for a matching pair, unless one is picked for it. */
52
+ export const rightOf = (t) => t.forced("eye.right") === undefined && t("pair") * 100 < PAIR_PERCENT ? leftOf(t) : t.pick("eye.right", EYES);
53
+ export function coloursOf(t) {
54
+ return { tile: t.color("tile") ?? t.pick("tile", TILES), ink: t.color("ink") ?? t.pick("ink", INKS) };
55
+ }
56
+ export function createGlance(name, overrides) {
57
+ const t = glanceTraits(name, overrides);
58
+ const { tile, ink } = coloursOf(t);
59
+ const r = t.num("eye.size", 11, 14.5);
60
+ const gap = t.num("eye.gap", 16, 22);
61
+ const place = (side, eye) => ({ at: `translate(${f(side * gap)} 0)${side > 0 ? " scale(-1 1)" : ""}`, shape: eye.draw(r) });
62
+ return {
63
+ tile,
64
+ ink,
65
+ face: `translate(50 ${f(t.num("eye.y", 42, 52))}) rotate(${f(t.jitter("tilt", 6))})`,
66
+ left: place(-1, leftOf(t)),
67
+ right: place(1, rightOf(t)),
68
+ blink: { period: f(t.num("blink.period", 3.4, 6)), delay: f(t.num("blink.delay", 0, 3)), twice: t("blink.twice") < 0.4 },
69
+ };
70
+ }
71
+ /** Every eye shares these, so a stroked shape is round at its ends and corners. */
72
+ export const EYE_STYLE = (ink) => `fill="${ink}" stroke="${ink}" stroke-width="0" stroke-linecap="round" stroke-linejoin="round"`;
73
+ export const CORNER = 22.22;
74
+ /** The eyes at rest, open, for thumbnails. */
75
+ export function glanceSvg(name, overrides) {
76
+ const g = createGlance(name, overrides);
77
+ return (`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect width="100" height="100" rx="${CORNER}" fill="${g.tile}"/>` +
78
+ `<g transform="${g.face}" ${EYE_STYLE(g.ink)}><g transform="${g.left.at}">${g.left.shape}</g><g transform="${g.right.at}">${g.right.shape}</g></g></svg>`);
79
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * What Possibilities calms on a glyph. Low keeps eight shapes, one for each variant, leaving
3
+ * out only the spikiest, the burst, and draws its proportions, tilt and eyes towards their
4
+ * middle. Indices are into `SHAPES`, 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,17 @@
1
+ /**
2
+ * What Possibilities calms on a glyph. Low keeps eight shapes, one for each variant, leaving
3
+ * out only the spikiest, the burst, and draws its proportions, tilt and eyes towards their
4
+ * middle. Indices are into `SHAPES`, plainest first.
5
+ */
6
+ export const CALMING = {
7
+ features: /^(waist|tilt|body\.size|eye\.(size|gap))$|^(sparkle|burst|flower|cloud)\.n$/,
8
+ lists: {
9
+ // Cloud, Chip, Diamond, Star, Wedge, Flower, Sparkle, Cross, then Burst.
10
+ shape: [9, 8, [5, 7, 6, 2, 1, 4, 0, 8, 3]],
11
+ },
12
+ };
13
+ export const NOTES = [
14
+ "soft shapes like clouds, chips and stars, upright, even and middling in size.",
15
+ "more lean and size, and every point and lobe count.",
16
+ "every shape, up to bursts, at any lean and size.",
17
+ ];
@@ -0,0 +1,2 @@
1
+ import { type ControlBuilder } from "../controls.ts";
2
+ export declare const controls: ControlBuilder;
@@ -0,0 +1,55 @@
1
+ import { automatic, choice, clearingColor, palette, slider, } from "../controls.js";
2
+ import { calmed, narrowed, possibilitiesSlider } from "../possibilities.js";
3
+ import { colorKey, createTraits } from "../traits.js";
4
+ import { CALMING, NOTES } from "./calming.js";
5
+ import { EYE_KINDS, eyeKindOf, FILLS, fillOf, shapeOf, SHAPES, tilesFor } from "./index.js";
6
+ /** Weighted like the generator, so it can't use `palette`. */
7
+ function fillControl(t) {
8
+ const picked = t.color("fill");
9
+ const control = clearingColor(choice(t, "fill", "Fill", "swatches", FILLS.map(({ color, weight }) => ({ label: color, weight, color }))), "fill");
10
+ return {
11
+ ...control,
12
+ selected: picked ? "custom" : control.selected,
13
+ custom: { id: colorKey("fill"), color: picked ?? FILLS[Number(control.selected)].color },
14
+ };
15
+ }
16
+ export const controls = (name, overrides) => {
17
+ const t = calmed(createTraits(name, overrides), CALMING);
18
+ const shape = shapeOf(t);
19
+ const { bright } = fillOf(t);
20
+ const count = shape.count
21
+ ? [
22
+ choice(t, shape.count.key, shape.count.label, "list", Array.from({ length: shape.count.max - shape.count.min + 1 }, (_, i) => ({ label: String(shape.count.min + i) }))),
23
+ ]
24
+ : [];
25
+ return [
26
+ {
27
+ title: "Colour",
28
+ controls: [fillControl(t), palette(t, "tile", "Tile", tilesFor(bright))],
29
+ },
30
+ {
31
+ title: "Shape",
32
+ controls: [
33
+ possibilitiesSlider(t, NOTES),
34
+ narrowed(choice(t, "shape", "Shape", "list", SHAPES.map(({ name: label }) => ({ label }))), t, CALMING),
35
+ ...count,
36
+ // Pixel shapes stay on their grid, so they only come in whole steps of width.
37
+ ...(shape.pixel
38
+ ? [slider(t, "waist", "Width", 0, 100, "%")]
39
+ : [
40
+ slider(t, "body.size", "Body size", 0, 100, "%"),
41
+ slider(t, "tilt", "Tilt", -12, 12, "°"),
42
+ slider(t, "waist", "Waist", 0, 100, "%"),
43
+ ]),
44
+ ],
45
+ },
46
+ {
47
+ title: "Eyes",
48
+ controls: [
49
+ automatic(t, "eyes.kind", "Eyes", EYE_KINDS, eyeKindOf(t, shape)),
50
+ slider(t, "eye.size", "Eye size", 0, 100, "%"),
51
+ slider(t, "eye.gap", "Gap", 0, 100, "%"),
52
+ ],
53
+ },
54
+ ];
55
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Glyphs: a solid silhouette with two eyes, on a rounded tile. Smooth shapes are curves
3
+ * between tips around the centre, and pixel shapes are rows of cells. The art is markup
4
+ * rather than colours, so a tap can move the silhouette and leave the eyes.
5
+ */
6
+ import { type Overrides, type Traits } from "../traits.ts";
7
+ interface Paint {
8
+ color: string;
9
+ weight?: number;
10
+ /** A bright ink, which goes on a dark tile so it has something to stand out against. */
11
+ bright?: boolean;
12
+ }
13
+ export declare const TILES: string[];
14
+ /** The tiles behind a bright ink. */
15
+ export declare const NIGHTS: string[];
16
+ /** Most glyphs are black or a bright ink, with the other dark inks as the rare ones. */
17
+ export declare const FILLS: Paint[];
18
+ export declare const EFFECTS: readonly ["spin", "pop", "drop", "hop"];
19
+ export type Effect = (typeof EFFECTS)[number];
20
+ interface Shape {
21
+ name: string;
22
+ pixel: boolean;
23
+ /** How it idles once selected. The motion suits the silhouette, so it comes with it. */
24
+ effect: Effect;
25
+ /** A count the visitor can choose, where the shape has one. */
26
+ count?: {
27
+ key: string;
28
+ label: string;
29
+ min: number;
30
+ max: number;
31
+ };
32
+ eyeY?: number;
33
+ draw(t: Traits, count: number): string;
34
+ }
35
+ export declare const SHAPES: Shape[];
36
+ export declare const EYE_KINDS: string[];
37
+ export declare const shapeOf: (t: Traits) => Shape;
38
+ /** What the name, or the panel, has filled the glyph with. */
39
+ export declare function fillOf(t: Traits): {
40
+ bright: boolean;
41
+ color: string;
42
+ };
43
+ /** The tiles that suit the fill, so a name can never end up black on black. */
44
+ export declare const tilesFor: (bright: boolean) => string[];
45
+ /** Follows the rest of the glyph: pixel shapes get square eyes, and bright inks pills. */
46
+ export declare function eyeKindOf(t: Traits, shape: Shape): string;
47
+ export declare const TILE_SHAPE = "<rect x=\"5\" y=\"5\" width=\"90\" height=\"90\" rx=\"20\"/>";
48
+ /** Frames the tile exactly, so it sits the same size as the other flat avatars. */
49
+ export declare const VIEW_BOX = "5 5 90 90";
50
+ export interface Glyph {
51
+ tile: string;
52
+ /** The silhouette, which is what moves. */
53
+ body: string;
54
+ eyes: string;
55
+ pixel: boolean;
56
+ effect: Effect;
57
+ blinkDelay: number;
58
+ }
59
+ export declare function createGlyph(name: string, overrides?: Overrides): Glyph;
60
+ /** The glyph at rest, for thumbnails. */
61
+ export declare const glyphSvg: (name: string, overrides?: Overrides) => string;
62
+ export {};