mochi-avatar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/BRAND.md +85 -0
  2. package/LICENSE.md +104 -0
  3. package/README.md +135 -0
  4. package/dist/canvas2d/avatar.d.ts +264 -0
  5. package/dist/canvas2d/avatar.d.ts.map +1 -0
  6. package/dist/canvas2d/avatar.js +811 -0
  7. package/dist/canvas2d/avatar.js.map +1 -0
  8. package/dist/canvas2d/face.d.ts +74 -0
  9. package/dist/canvas2d/face.d.ts.map +1 -0
  10. package/dist/canvas2d/face.js +298 -0
  11. package/dist/canvas2d/face.js.map +1 -0
  12. package/dist/canvas2d/paths.d.ts +13 -0
  13. package/dist/canvas2d/paths.d.ts.map +1 -0
  14. package/dist/canvas2d/paths.js +24 -0
  15. package/dist/canvas2d/paths.js.map +1 -0
  16. package/dist/characters/colourways.d.ts +78 -0
  17. package/dist/characters/colourways.d.ts.map +1 -0
  18. package/dist/characters/colourways.js +76 -0
  19. package/dist/characters/colourways.js.map +1 -0
  20. package/dist/characters/index.d.ts +8 -0
  21. package/dist/characters/index.d.ts.map +1 -0
  22. package/dist/characters/index.js +8 -0
  23. package/dist/characters/index.js.map +1 -0
  24. package/dist/characters/mochi.d.ts +19 -0
  25. package/dist/characters/mochi.d.ts.map +1 -0
  26. package/dist/characters/mochi.js +66 -0
  27. package/dist/characters/mochi.js.map +1 -0
  28. package/dist/core/colour.d.ts +25 -0
  29. package/dist/core/colour.d.ts.map +1 -0
  30. package/dist/core/colour.js +37 -0
  31. package/dist/core/colour.js.map +1 -0
  32. package/dist/core/envelope.d.ts +153 -0
  33. package/dist/core/envelope.d.ts.map +1 -0
  34. package/dist/core/envelope.js +141 -0
  35. package/dist/core/envelope.js.map +1 -0
  36. package/dist/core/geometry.d.ts +106 -0
  37. package/dist/core/geometry.d.ts.map +1 -0
  38. package/dist/core/geometry.js +157 -0
  39. package/dist/core/geometry.js.map +1 -0
  40. package/dist/core/idle.d.ts +163 -0
  41. package/dist/core/idle.d.ts.map +1 -0
  42. package/dist/core/idle.js +262 -0
  43. package/dist/core/idle.js.map +1 -0
  44. package/dist/core/layout.d.ts +142 -0
  45. package/dist/core/layout.d.ts.map +1 -0
  46. package/dist/core/layout.js +172 -0
  47. package/dist/core/layout.js.map +1 -0
  48. package/dist/core/lens.d.ts +47 -0
  49. package/dist/core/lens.d.ts.map +1 -0
  50. package/dist/core/lens.js +67 -0
  51. package/dist/core/lens.js.map +1 -0
  52. package/dist/core/looks.d.ts +77 -0
  53. package/dist/core/looks.d.ts.map +1 -0
  54. package/dist/core/looks.js +168 -0
  55. package/dist/core/looks.js.map +1 -0
  56. package/dist/core/motion.d.ts +168 -0
  57. package/dist/core/motion.d.ts.map +1 -0
  58. package/dist/core/motion.js +527 -0
  59. package/dist/core/motion.js.map +1 -0
  60. package/dist/core/mouth.d.ts +93 -0
  61. package/dist/core/mouth.d.ts.map +1 -0
  62. package/dist/core/mouth.js +92 -0
  63. package/dist/core/mouth.js.map +1 -0
  64. package/dist/core/plain.d.ts +23 -0
  65. package/dist/core/plain.d.ts.map +1 -0
  66. package/dist/core/plain.js +63 -0
  67. package/dist/core/plain.js.map +1 -0
  68. package/dist/core/spec.d.ts +156 -0
  69. package/dist/core/spec.d.ts.map +1 -0
  70. package/dist/core/spec.js +236 -0
  71. package/dist/core/spec.js.map +1 -0
  72. package/dist/core/spring.d.ts +53 -0
  73. package/dist/core/spring.d.ts.map +1 -0
  74. package/dist/core/spring.js +68 -0
  75. package/dist/core/spring.js.map +1 -0
  76. package/dist/core/vocabulary.d.ts +182 -0
  77. package/dist/core/vocabulary.d.ts.map +1 -0
  78. package/dist/core/vocabulary.js +66 -0
  79. package/dist/core/vocabulary.js.map +1 -0
  80. package/dist/element/dough-avatar.d.ts +34 -0
  81. package/dist/element/dough-avatar.d.ts.map +1 -0
  82. package/dist/element/dough-avatar.js +189 -0
  83. package/dist/element/dough-avatar.js.map +1 -0
  84. package/dist/element/index.d.ts +3 -0
  85. package/dist/element/index.d.ts.map +1 -0
  86. package/dist/element/index.js +35 -0
  87. package/dist/element/index.js.map +1 -0
  88. package/dist/index.d.ts +38 -0
  89. package/dist/index.d.ts.map +1 -0
  90. package/dist/index.js +42 -0
  91. package/dist/index.js.map +1 -0
  92. package/dist/svg/silhouette.d.ts +97 -0
  93. package/dist/svg/silhouette.d.ts.map +1 -0
  94. package/dist/svg/silhouette.js +143 -0
  95. package/dist/svg/silhouette.js.map +1 -0
  96. package/package.json +90 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAGhC,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA;AAGnC,cAAc,qBAAqB,CAAA;AAGnC,cAAc,uBAAuB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * A soft-body companion character engine.
3
+ *
4
+ * The shape is a superellipse ovoid with independent exponents above and below
5
+ * its widest point; it squashes area-preservingly, leans by a shear pinned at
6
+ * its contact point, breathes on a one-sided curve and settles on a
7
+ * second-order spring. A face rides on it at a configurable grip, so it deforms
8
+ * with the body without being printed on it.
9
+ *
10
+ * Everything in `core` is pure arithmetic returning points and numbers. Nothing
11
+ * there imports a canvas, a document, or a platform. `canvas2d` turns those
12
+ * points into fills on any 2D context — a browser canvas, an OffscreenCanvas in
13
+ * a worker, or `@napi-rs/canvas` under Node. `svg` emits the same silhouette as
14
+ * a vector, so an icon pipeline and the running app cannot disagree about the
15
+ * shape.
16
+ *
17
+ * A face is DATA — see `FaceSpec` — so a design is a JSON file rather than a
18
+ * fork. `PLAIN` is the default; it is intended to be replaced.
19
+ */
20
+ // --- core: the character as arithmetic -------------------------------------
21
+ export * from './core/spec.js';
22
+ export * from './core/plain.js';
23
+ export * from './core/vocabulary.js';
24
+ export * from './core/layout.js';
25
+ export * from './core/geometry.js';
26
+ export * from './core/lens.js';
27
+ export * from './core/looks.js';
28
+ export * from './core/idle.js';
29
+ export * from './core/spring.js';
30
+ export * from './core/motion.js';
31
+ export * from './core/envelope.js';
32
+ export * from './core/mouth.js';
33
+ export * from './core/colour.js';
34
+ // --- canvas2d: points into pixels ------------------------------------------
35
+ export * from './canvas2d/avatar.js';
36
+ export * from './canvas2d/face.js';
37
+ export * from './canvas2d/paths.js';
38
+ // --- svg: the same silhouette, as a vector ---------------------------------
39
+ export * from './svg/silhouette.js';
40
+ // --- characters: NOT MIT. See LICENSE.md ------------------------------------
41
+ export * from './characters/index.js';
42
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,8EAA8E;AAC9E,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAEhC,8EAA8E;AAC9E,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA;AAEnC,8EAA8E;AAC9E,cAAc,qBAAqB,CAAA;AAEnC,+EAA+E;AAC/E,cAAc,uBAAuB,CAAA"}
@@ -0,0 +1,97 @@
1
+ import type { FaceSpec } from '../core/spec.js';
2
+ import { type BodyShape, type Point } from '../core/geometry.js';
3
+ /**
4
+ * Her, as a vector, from the same geometry that paints her.
5
+ *
6
+ * ## The claim this exists to make true
7
+ *
8
+ * `electron-builder.yml` says it plainly: *"the generator that produced them did
9
+ * not survive the rewrite, so `resources/` is hand-maintained"*. And
10
+ * `shipped-icons.test.ts` says what that cost: the handoff claimed the mark and
11
+ * the mochi "cannot drift", and the test's own header answers that the claim
12
+ * "was not true, it was merely unfalsified".
13
+ *
14
+ * Measuring drift is the second-best answer. This is the first: every shipped
15
+ * asset is rasterised from an SVG emitted **by `domeOutline`**, which is the
16
+ * function `paint` calls to draw her on screen. Change her shape and the icons
17
+ * change with her, because there is one shape and this is it.
18
+ *
19
+ * ## Why SVG rather than emitting PNGs from the canvas directly
20
+ *
21
+ * The rig could rasterise these itself -- `@napi-rs/canvas` is already here and
22
+ * `paint` already knows how. Three things a vector step buys that a direct
23
+ * raster does not:
24
+ *
25
+ * 1. **A source somebody can open.** A designer can look at the shape, and a
26
+ * diff can show what changed about it, without decoding a PNG.
27
+ * 2. **One geometry, many rasterisers.** macOS wants `.icns`, Windows wants
28
+ * `.ico`, the web wants whatever it wants; every one of those pipelines
29
+ * takes SVG, and none of them take a `CanvasRenderingContext2D`.
30
+ * 3. **Resolution is a parameter rather than a re-render.** The 16px mark and
31
+ * the 1024px icon come out of the same document, so they cannot disagree
32
+ * about her proportions -- which is the exact failure the profile test in
33
+ * `shipped-icons.test.ts` was written to detect.
34
+ *
35
+ * ## What is NOT drawn
36
+ *
37
+ * No eyes, no mouth, no cheeks. Every shipped asset is her SILHOUETTE, measured
38
+ * off the existing artwork: a face at 16px is four grey pixels, and the mark has
39
+ * to read at the size a menu bar gives it. `looks.ts` deforms a face; this draws
40
+ * a body, and the two have never been the same picture.
41
+ */
42
+ /** How one asset is coloured and how much of its square she fills. */
43
+ export interface Treatment {
44
+ /** The lit fill. */
45
+ readonly body: string;
46
+ /** The fill behind it, offset by `shadowX`/`shadowY`. See `paintBody`. */
47
+ readonly shadow: string;
48
+ /**
49
+ * A vertical gradient behind her, or null for transparency.
50
+ *
51
+ * `size` and `radius` describe a PLATE rather than a full-bleed square, and
52
+ * they exist because leaving them out lost an asset. `icons/dock.png` was
53
+ * hand-drawn as an 852px rounded plate inside a 1024 canvas with transparent
54
+ * margin — the pre-masked tile `dock.setIcon` needs, because an image set at
55
+ * runtime gets no squircle from the system. When every asset was moved to
56
+ * this generator the plate became `<rect width="px" height="px">`, a
57
+ * full-bleed square, and the mark it framed went from 62% of a plate to 51%
58
+ * of a tile without a single number changing.
59
+ *
60
+ * Both default to the full square, so every other treatment is untouched.
61
+ */
62
+ readonly background: {
63
+ readonly from: string;
64
+ readonly to: string;
65
+ /** Side of the plate as a fraction of the square. 1 is full bleed. */
66
+ readonly size?: number;
67
+ /** Corner radius as a fraction of the PLATE, not of the square. */
68
+ readonly radius?: number;
69
+ } | null;
70
+ /**
71
+ * Her width as a fraction of the square.
72
+ *
73
+ * Measured off the shipped artwork rather than chosen: the app icon has her
74
+ * at 0.64 of its width and the Windows tray mark at 0.91, because a mark in a
75
+ * 16px strip has no room for margin and an app icon on a gradient needs one.
76
+ * One number per treatment, so a change of mind is a change of number.
77
+ */
78
+ readonly width: number;
79
+ }
80
+ /**
81
+ * Her outline as an SVG path, centred in a `px` square at the given width.
82
+ *
83
+ * Exported because the tests measure it: a profile taken from these points can
84
+ * be compared with one taken from the rig's own `domeOutline` without going
85
+ * near a rasteriser, which is a comparison with no tolerance in it at all.
86
+ */
87
+ export declare function outlineFor(face: FaceSpec, px: number, width: number): {
88
+ readonly points: readonly Point[];
89
+ readonly shape: BodyShape;
90
+ readonly scale: number;
91
+ readonly offset: {
92
+ readonly dx: number;
93
+ readonly dy: number;
94
+ };
95
+ };
96
+ export declare function mochiSvg(face: FaceSpec, px: number, treatment: Treatment): string;
97
+ //# sourceMappingURL=silhouette.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"silhouette.d.ts","sourceRoot":"","sources":["../../src/svg/silhouette.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAe,KAAK,SAAS,EAAE,KAAK,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,sEAAsE;AACtE,MAAM,WAAW,SAAS;IACxB,oBAAoB;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,UAAU,EAAE;QACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;QACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;QACnB,sEAAsE;QACtE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;QACtB,mEAAmE;QACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KACzB,GAAG,IAAI,CAAA;IACR;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAaD;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,QAAQ,EACd,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,GACZ;IACD,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAA;IACjC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9D,CAiDA;AAsDD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,MAAM,CA8BjF"}
@@ -0,0 +1,143 @@
1
+ import { domeOutline } from '../core/geometry.js';
2
+ /** Two decimals is a tenth of a pixel at 1024, and shortens the file by half. */
3
+ function round(value) {
4
+ return Math.round(value * 100) / 100;
5
+ }
6
+ function toD(points) {
7
+ return `${points
8
+ .map((point, index) => `${index === 0 ? 'M' : 'L'}${round(point.x)} ${round(point.y)}`)
9
+ .join('')}Z`;
10
+ }
11
+ /**
12
+ * Her outline as an SVG path, centred in a `px` square at the given width.
13
+ *
14
+ * Exported because the tests measure it: a profile taken from these points can
15
+ * be compared with one taken from the rig's own `domeOutline` without going
16
+ * near a rasteriser, which is a comparison with no tolerance in it at all.
17
+ */
18
+ export function outlineFor(face, px, width) {
19
+ /*
20
+ `width` is her SILHOUETTE, and the offset copy does not extend it.
21
+
22
+ That is only true because the lit copy is clipped to the outline — see
23
+ `mochiSvg`. An earlier draft drew it unclipped and had to solve for the
24
+ overshoot here, which produced a mark 4% too wide and the wrong shape
25
+ entirely: measured, 1.23 wide-to-tall against the rig's 1.28.
26
+
27
+ Clipped, her extent is exactly the outline, so this is the plain division it
28
+ looks like. Checked against the shipped 1024, whose mark is 0.6396 of the
29
+ square wide and 0.499 tall — 0.64 and 0.64/1.2821, to three decimals.
30
+ */
31
+ const scale = (px * width) / face.bodyW;
32
+ const shape = {
33
+ halfWidth: (face.bodyW / 2) * scale,
34
+ height: face.bodyH * scale,
35
+ waist: face.waist,
36
+ upperShoulder: face.upperShoulder,
37
+ lowerShoulder: face.lowerShoulder,
38
+ // Upright. A lean is something a MOTION does to her; an icon is her at rest,
39
+ // and a mark that leaned would look like a mistake rather than a pose.
40
+ lean: 0,
41
+ };
42
+ const dx = face.shadowX * shape.halfWidth * 2;
43
+ const dy = face.shadowY * shape.height;
44
+ /*
45
+ Centred, and SNAPPED to whole pixels.
46
+
47
+ Centring alone puts her baseline at a fraction — 53.875 in a 64px square —
48
+ so the bottom row of the dome is a partial coverage the rasteriser
49
+ antialiases away, and her base reads narrower than it is. That is not
50
+ theoretical: `window-64.png` was the one asset of thirty-three that failed
51
+ the profile check against the rig, by 0.112 against a tolerance of 0.1, at
52
+ the 5% height band which is exactly where her base is.
53
+
54
+ Snapping costs at most half a pixel of centring and buys a crisp edge at
55
+ every size. It matters most at 16 and 22, where half a pixel is a twentieth
56
+ of the mark.
57
+ */
58
+ const originX = Math.round(px / 2);
59
+ const originY = Math.round(px / 2 + shape.height / 2);
60
+ // Local space is +y up with the base at 0; SVG is +y down, like the canvas.
61
+ const points = domeOutline(shape).map((point) => ({
62
+ x: originX + point.x,
63
+ y: originY - point.y,
64
+ }));
65
+ return { points, shape, scale, offset: { dx, dy } };
66
+ }
67
+ /**
68
+ * One asset, as an SVG document.
69
+ *
70
+ * The body is drawn TWICE and the pair is CLIPPED to the first, which is not
71
+ * decoration and is not optional -- it is exactly what `paint` does. The
72
+ * silhouette is clipped before `paintBody` is called, so the lit copy moving up
73
+ * and to the right does not make her bigger: it uncovers a crescent of shadow
74
+ * along her lower left and is cut off everywhere else.
75
+ *
76
+ * Getting this wrong is not subtle and was caught by measurement rather than by
77
+ * eye. Unclipped, the offset copy extends her bounding box by the offset, and
78
+ * the mark comes out **1.23 wide-to-tall against the rig's 1.28** -- outside the
79
+ * tolerance `shipped-icons.test.ts` allows, on the one axis a mark is most
80
+ * recognisable by. Two paths and a `clipPath` reproduce her; two paths alone
81
+ * reproduce something else.
82
+ */
83
+ /**
84
+ * The plate she sits on: the whole square, or a rounded tile inside it.
85
+ *
86
+ * Centred, and the original artwork was not quite — its margins were 86 either
87
+ * side and 99 over 74 top to bottom, so the plate sat 12px low in a 1024
88
+ * square. That is a tenth of a percent of the tile and reproducing it would be
89
+ * reproducing a wobble rather than a decision.
90
+ */
91
+ function plateRect(px, background) {
92
+ if (background === null)
93
+ return '';
94
+ /*
95
+ THE SIZE TAKES THE PARITY OF THE TILE, so the margins are equal.
96
+
97
+ Coordinates in the emitted SVG are integers. `at = round((px - size) / 2)`
98
+ with an odd remainder puts the plate half a pixel off centre and leaves
99
+ margins that differ by one — `px = 16` with `size = 13` gives 2 above and 1
100
+ below. The docblock above says "Centred", and at the sizes a tray icon is
101
+ drawn at, one pixel of the sixteen is a visible lean.
102
+
103
+ Losing a pixel of SIZE is the cheaper correction than gaining a pixel of
104
+ offset: the plate is a proportion of the tile and one pixel narrower is
105
+ within the rounding that already happened, while an off-centre plate is a
106
+ different shape from the one asked for.
107
+ */
108
+ const wanted = Math.round(px * (background.size ?? 1));
109
+ const size = Math.max(0, (px - wanted) % 2 === 0 ? wanted : wanted - 1);
110
+ const at = (px - size) / 2;
111
+ const radius = Math.round(size * (background.radius ?? 0));
112
+ return (`<rect x="${String(at)}" y="${String(at)}" ` +
113
+ `width="${String(size)}" height="${String(size)}" ` +
114
+ (radius > 0 ? `rx="${String(radius)}" ry="${String(radius)}" ` : '') +
115
+ `fill="url(#bg)"/>`);
116
+ }
117
+ export function mochiSvg(face, px, treatment) {
118
+ const { points, offset } = outlineFor(face, px, treatment.width);
119
+ const d = toD(points);
120
+ const dx = round(offset.dx);
121
+ // Canvas and SVG both grow y downward and the light comes from above, so the
122
+ // lit copy moves toward smaller y.
123
+ const dy = round(offset.dy);
124
+ const gradient = treatment.background === null
125
+ ? ''
126
+ : `<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">` +
127
+ `<stop offset="0" stop-color="${treatment.background.from}"/>` +
128
+ `<stop offset="1" stop-color="${treatment.background.to}"/>` +
129
+ `</linearGradient>`;
130
+ // OUTSIDE the clip: the plate is what she sits on, and clipping it to her
131
+ // would produce an icon that is only her, on nothing.
132
+ const plate = plateRect(px, treatment.background);
133
+ return (`<svg xmlns="http://www.w3.org/2000/svg" width="${String(px)}" height="${String(px)}" ` +
134
+ `viewBox="0 0 ${String(px)} ${String(px)}">` +
135
+ `<defs>${gradient}<clipPath id="her"><path d="${d}"/></clipPath></defs>` +
136
+ plate +
137
+ `<g clip-path="url(#her)">` +
138
+ `<path d="${d}" fill="${treatment.shadow}"/>` +
139
+ `<path d="${d}" fill="${treatment.body}" transform="translate(${String(dx)} ${String(-dy)})"/>` +
140
+ `</g>` +
141
+ `</svg>`);
142
+ }
143
+ //# sourceMappingURL=silhouette.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"silhouette.js","sourceRoot":"","sources":["../../src/svg/silhouette.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "mochi-avatar",
3
+ "version": "1.0.0",
4
+ "description": "Mochi \u2014 a soft-body companion character, and the engine that draws her. Superellipse geometry, area-preserving squash, spring physics and an idle layer. Canvas2D, SVG, or one custom element. Zero runtime dependencies.",
5
+ "license": "SEE LICENSE IN LICENSE.md",
6
+ "author": "HANDO K.K.",
7
+ "homepage": "https://github.com/xiaolai/mochi#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/xiaolai/mochi.git"
11
+ },
12
+ "bugs": "https://github.com/xiaolai/mochi/issues",
13
+ "type": "module",
14
+ "sideEffects": [
15
+ "./dist/element/index.js"
16
+ ],
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "./element": {
25
+ "types": "./dist/element/index.d.ts",
26
+ "default": "./dist/element/index.js"
27
+ },
28
+ "./characters": {
29
+ "types": "./dist/characters/index.d.ts",
30
+ "default": "./dist/characters/index.js"
31
+ },
32
+ "./core/*": {
33
+ "types": "./dist/core/*.d.ts",
34
+ "default": "./dist/core/*.js"
35
+ },
36
+ "./canvas2d/*": {
37
+ "types": "./dist/canvas2d/*.d.ts",
38
+ "default": "./dist/canvas2d/*.js"
39
+ },
40
+ "./svg/*": {
41
+ "types": "./dist/svg/*.d.ts",
42
+ "default": "./dist/svg/*.js"
43
+ },
44
+ "./package.json": "./package.json"
45
+ },
46
+ "files": [
47
+ "dist",
48
+ "README.md",
49
+ "LICENSE.md",
50
+ "BRAND.md"
51
+ ],
52
+ "keywords": [
53
+ "avatar",
54
+ "character",
55
+ "mascot",
56
+ "canvas",
57
+ "svg",
58
+ "custom-element",
59
+ "web-component",
60
+ "superellipse",
61
+ "spring",
62
+ "animation"
63
+ ],
64
+ "engines": {
65
+ "node": ">=20"
66
+ },
67
+ "packageManager": "pnpm@10.33.0",
68
+ "scripts": {
69
+ "build": "tsc -p tsconfig.build.json",
70
+ "test": "vitest run",
71
+ "test:watch": "vitest",
72
+ "test:coverage": "vitest run --coverage",
73
+ "typecheck": "tsc --noEmit",
74
+ "lint": "oxlint --type-aware",
75
+ "format": "prettier --write --cache .",
76
+ "format:check": "prettier --check --cache .",
77
+ "verify": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm test && pnpm build",
78
+ "prepublishOnly": "pnpm verify"
79
+ },
80
+ "devDependencies": {
81
+ "@napi-rs/canvas": "^1.0.6",
82
+ "@types/node": "^26.1.2",
83
+ "@vitest/coverage-v8": "^4.1.10",
84
+ "oxlint": "1.77.0",
85
+ "oxlint-tsgolint": "7.0.2001",
86
+ "prettier": "3.9.6",
87
+ "typescript": "^7.0.2",
88
+ "vitest": "^4.1.10"
89
+ }
90
+ }