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.
- package/BRAND.md +85 -0
- package/LICENSE.md +104 -0
- package/README.md +135 -0
- package/dist/canvas2d/avatar.d.ts +264 -0
- package/dist/canvas2d/avatar.d.ts.map +1 -0
- package/dist/canvas2d/avatar.js +811 -0
- package/dist/canvas2d/avatar.js.map +1 -0
- package/dist/canvas2d/face.d.ts +74 -0
- package/dist/canvas2d/face.d.ts.map +1 -0
- package/dist/canvas2d/face.js +298 -0
- package/dist/canvas2d/face.js.map +1 -0
- package/dist/canvas2d/paths.d.ts +13 -0
- package/dist/canvas2d/paths.d.ts.map +1 -0
- package/dist/canvas2d/paths.js +24 -0
- package/dist/canvas2d/paths.js.map +1 -0
- package/dist/characters/colourways.d.ts +78 -0
- package/dist/characters/colourways.d.ts.map +1 -0
- package/dist/characters/colourways.js +76 -0
- package/dist/characters/colourways.js.map +1 -0
- package/dist/characters/index.d.ts +8 -0
- package/dist/characters/index.d.ts.map +1 -0
- package/dist/characters/index.js +8 -0
- package/dist/characters/index.js.map +1 -0
- package/dist/characters/mochi.d.ts +19 -0
- package/dist/characters/mochi.d.ts.map +1 -0
- package/dist/characters/mochi.js +66 -0
- package/dist/characters/mochi.js.map +1 -0
- package/dist/core/colour.d.ts +25 -0
- package/dist/core/colour.d.ts.map +1 -0
- package/dist/core/colour.js +37 -0
- package/dist/core/colour.js.map +1 -0
- package/dist/core/envelope.d.ts +153 -0
- package/dist/core/envelope.d.ts.map +1 -0
- package/dist/core/envelope.js +141 -0
- package/dist/core/envelope.js.map +1 -0
- package/dist/core/geometry.d.ts +106 -0
- package/dist/core/geometry.d.ts.map +1 -0
- package/dist/core/geometry.js +157 -0
- package/dist/core/geometry.js.map +1 -0
- package/dist/core/idle.d.ts +163 -0
- package/dist/core/idle.d.ts.map +1 -0
- package/dist/core/idle.js +262 -0
- package/dist/core/idle.js.map +1 -0
- package/dist/core/layout.d.ts +142 -0
- package/dist/core/layout.d.ts.map +1 -0
- package/dist/core/layout.js +172 -0
- package/dist/core/layout.js.map +1 -0
- package/dist/core/lens.d.ts +47 -0
- package/dist/core/lens.d.ts.map +1 -0
- package/dist/core/lens.js +67 -0
- package/dist/core/lens.js.map +1 -0
- package/dist/core/looks.d.ts +77 -0
- package/dist/core/looks.d.ts.map +1 -0
- package/dist/core/looks.js +168 -0
- package/dist/core/looks.js.map +1 -0
- package/dist/core/motion.d.ts +168 -0
- package/dist/core/motion.d.ts.map +1 -0
- package/dist/core/motion.js +527 -0
- package/dist/core/motion.js.map +1 -0
- package/dist/core/mouth.d.ts +93 -0
- package/dist/core/mouth.d.ts.map +1 -0
- package/dist/core/mouth.js +92 -0
- package/dist/core/mouth.js.map +1 -0
- package/dist/core/plain.d.ts +23 -0
- package/dist/core/plain.d.ts.map +1 -0
- package/dist/core/plain.js +63 -0
- package/dist/core/plain.js.map +1 -0
- package/dist/core/spec.d.ts +156 -0
- package/dist/core/spec.d.ts.map +1 -0
- package/dist/core/spec.js +236 -0
- package/dist/core/spec.js.map +1 -0
- package/dist/core/spring.d.ts +53 -0
- package/dist/core/spring.d.ts.map +1 -0
- package/dist/core/spring.js +68 -0
- package/dist/core/spring.js.map +1 -0
- package/dist/core/vocabulary.d.ts +182 -0
- package/dist/core/vocabulary.d.ts.map +1 -0
- package/dist/core/vocabulary.js +66 -0
- package/dist/core/vocabulary.js.map +1 -0
- package/dist/element/dough-avatar.d.ts +34 -0
- package/dist/element/dough-avatar.d.ts.map +1 -0
- package/dist/element/dough-avatar.js +189 -0
- package/dist/element/dough-avatar.js.map +1 -0
- package/dist/element/index.d.ts +3 -0
- package/dist/element/index.d.ts.map +1 -0
- package/dist/element/index.js +35 -0
- package/dist/element/index.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/svg/silhouette.d.ts +97 -0
- package/dist/svg/silhouette.d.ts.map +1 -0
- package/dist/svg/silhouette.js +143 -0
- package/dist/svg/silhouette.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How big she is, and how much room her deformation needs.
|
|
3
|
+
*
|
|
4
|
+
* ## The direction of the dependency
|
|
5
|
+
*
|
|
6
|
+
* The body is the fixed thing and the CANVAS follows from it. That is the
|
|
7
|
+
* inverse of how this started: the window was 280x280 and the rig fitted her
|
|
8
|
+
* into a fraction of it, which meant the real size was an emergent property of
|
|
9
|
+
* two constants in different files, and "make her smaller" was a change to a
|
|
10
|
+
* fraction rather than to a size. Now `bodyW x bodyH` design units times a
|
|
11
|
+
* scale IS the size, and everything else is computed from it.
|
|
12
|
+
*
|
|
13
|
+
* Host-agnostic: nothing here knows about a window, a screen, or a desktop.
|
|
14
|
+
* What sizes an OS window from these numbers lives in the host application.
|
|
15
|
+
*/
|
|
16
|
+
import { type FaceSpec } from './spec.js';
|
|
17
|
+
/**
|
|
18
|
+
* The furthest the squash channel may travel, in either direction.
|
|
19
|
+
*
|
|
20
|
+
* Lives here rather than in the rig because it is a SIZE fact before it is an
|
|
21
|
+
* animation fact: the window has to be big enough for the most deformed frame
|
|
22
|
+
* that can ever be drawn, so whoever sizes the window needs this number. The
|
|
23
|
+
* rig imports it back for the clamp that makes it true.
|
|
24
|
+
*
|
|
25
|
+
* What it cost to leave unbounded: sleepy posture at the top of a breath
|
|
26
|
+
* widened her past the window edge, and the part that did not fit was painted
|
|
27
|
+
* as two flat vertical sides.
|
|
28
|
+
*/
|
|
29
|
+
export declare const SQUASH_LIMIT = 0.26;
|
|
30
|
+
/** The most any look may lean. `looks.ts` is checked against this by a test. */
|
|
31
|
+
export declare const LEAN_LIMIT = 0.1;
|
|
32
|
+
/** How much of the half-width a full lean displaces the apex by. */
|
|
33
|
+
export declare const SHEAR_GAIN = 0.55;
|
|
34
|
+
/**
|
|
35
|
+
* Clearance around her worst case, in DESIGN UNITS.
|
|
36
|
+
*
|
|
37
|
+
* In her own units rather than pixels, so it scales with her: at 200% the gap
|
|
38
|
+
* around her looks the same as at 50%, which a pixel margin would not.
|
|
39
|
+
*/
|
|
40
|
+
export declare const BREATHING_UNITS = 8;
|
|
41
|
+
/**
|
|
42
|
+
* Pixels per design unit at 100%.
|
|
43
|
+
*
|
|
44
|
+
* 0.94 puts her resting body at 94 x 73 CSS pixels, which is the chosen
|
|
45
|
+
* default size. Expressed as a scale rather than as the width itself because
|
|
46
|
+
* `bodyW` belongs to the FACE — every avatar declares its own proportions, and
|
|
47
|
+
* a default stated as "94 wide" would silently mean something different for a
|
|
48
|
+
* face that is not 100 units across.
|
|
49
|
+
*
|
|
50
|
+
* 100% is the default rather than an extreme, so the 50–200% range gives room
|
|
51
|
+
* in both directions. She was previously drawn at twice this, which is what
|
|
52
|
+
* 200% now restores.
|
|
53
|
+
*/
|
|
54
|
+
export declare const BASE_UNIT_SCALE = 0.94;
|
|
55
|
+
/**
|
|
56
|
+
* How far into a canvas the character stands, by default.
|
|
57
|
+
*
|
|
58
|
+
* A DEFAULT the host is expected to replace. The number came from one
|
|
59
|
+
* application's window and means nothing here; it survives only as the fallback
|
|
60
|
+
* for a caller that never says where the ground is. Anything with its own
|
|
61
|
+
* layout should call `setFeet` and ignore it.
|
|
62
|
+
*
|
|
63
|
+
* Why the stance is settable at all rather than fixed: a host may not be free
|
|
64
|
+
* to place its window where it likes. macOS pins a window's top edge to the
|
|
65
|
+
* work area, so a fixed standing height meant the character could never be
|
|
66
|
+
* carried close to the top of the display — dragged upward, she stopped dead in
|
|
67
|
+
* mid-air. Letting the host move the stance instead is what fixed that, and it
|
|
68
|
+
* is the general case, not a macOS special case.
|
|
69
|
+
*/
|
|
70
|
+
export declare const FEET_FROM_TOP = 340;
|
|
71
|
+
/**
|
|
72
|
+
* Where she stands inside a canvas of a given height.
|
|
73
|
+
*
|
|
74
|
+
* ONE rule, called by the rig that draws her and by the code that anchors the
|
|
75
|
+
* bubble to her. They used to compute it separately — one as a fraction of the
|
|
76
|
+
* canvas and one as an offset from its bottom — which agree only when the
|
|
77
|
+
* canvas happens to be exactly her layout's height.
|
|
78
|
+
*/
|
|
79
|
+
export declare function feetY(cssHeight: number, clearance: number, feetFromTop?: number): number;
|
|
80
|
+
/**
|
|
81
|
+
* What the size setting accepts, as a percentage of `BASE_UNIT_SCALE`.
|
|
82
|
+
*
|
|
83
|
+
* DERIVED from `FACE_BOUNDS.size`, which is where a user-supplied value is
|
|
84
|
+
* refused. That table's own comment states the rule — "One table, read by BOTH
|
|
85
|
+
* the validator and the tuner's sliders. Two copies would let the editor offer
|
|
86
|
+
* a value the loader rejects" — and there had come to be four copies of these
|
|
87
|
+
* three numbers: here, in `persona-change.ts` as `SIZE_BAND`, and in the
|
|
88
|
+
* shelf's size control as `BAND`.
|
|
89
|
+
*
|
|
90
|
+
* The `fallback` is genuinely this module's own: `FACE_BOUNDS` says what is
|
|
91
|
+
* PERMITTED, and what to do with a value that is not a number at all is a
|
|
92
|
+
* question only `clampSizePercent` asks.
|
|
93
|
+
*/
|
|
94
|
+
export declare const SIZE_PERCENT: {
|
|
95
|
+
readonly min: number;
|
|
96
|
+
readonly max: number;
|
|
97
|
+
readonly step: number;
|
|
98
|
+
readonly fallback: 100;
|
|
99
|
+
};
|
|
100
|
+
export interface AvatarLayout {
|
|
101
|
+
/** Pixels per design unit. */
|
|
102
|
+
readonly scale: number;
|
|
103
|
+
/** The window, and therefore the canvas, in CSS pixels. */
|
|
104
|
+
readonly width: number;
|
|
105
|
+
readonly height: number;
|
|
106
|
+
/** Where her base sits, as a fraction of the height. */
|
|
107
|
+
readonly ground: number;
|
|
108
|
+
/** Her resting body, in CSS pixels. What a person means by "how big is she". */
|
|
109
|
+
readonly bodyWidth: number;
|
|
110
|
+
readonly bodyHeight: number;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The widest and tallest she can ever be drawn, in design units.
|
|
114
|
+
*
|
|
115
|
+
* Deliberately conservative on the lean: the shear displaces the apex, where
|
|
116
|
+
* the body is narrowest, so adding it to the full width over-estimates. A
|
|
117
|
+
* window a few pixels too large is invisible; one a few pixels too small
|
|
118
|
+
* crops her, and that is the failure this whole calculation exists to prevent.
|
|
119
|
+
*/
|
|
120
|
+
export declare function worstCaseUnits(face: FaceSpec): {
|
|
121
|
+
width: number;
|
|
122
|
+
height: number;
|
|
123
|
+
};
|
|
124
|
+
/** Clamp a stored percentage to something renderable. */
|
|
125
|
+
export declare function clampSizePercent(value: unknown): number;
|
|
126
|
+
/**
|
|
127
|
+
* Everything both processes need, from a face and a size.
|
|
128
|
+
*
|
|
129
|
+
* Rounded to whole pixels because a BrowserWindow cannot be 293.7 wide — and
|
|
130
|
+
* if main rounds while the renderer does not, her base drifts off the ground
|
|
131
|
+
* line by a fraction of a pixel and the two disagree about where she is.
|
|
132
|
+
*/
|
|
133
|
+
export declare function layoutFor(face: FaceSpec, sizePercent: number): AvatarLayout;
|
|
134
|
+
/**
|
|
135
|
+
* The largest scale whose WORST CASE still fits a canvas somebody else sized.
|
|
136
|
+
*
|
|
137
|
+
* For the tuner, which lays out its own grid of cells and cannot be told what
|
|
138
|
+
* size to be. The app never uses this — it sizes the window from `layoutFor`,
|
|
139
|
+
* which is the whole point of the inversion.
|
|
140
|
+
*/
|
|
141
|
+
export declare function fitToCanvas(face: FaceSpec, cssWidth: number, cssHeight: number): number;
|
|
142
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/core/layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAA;AACtD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,OAAO,CAAA;AAEhC,gFAAgF;AAChF,eAAO,MAAM,UAAU,MAAM,CAAA;AAE7B,oEAAoE;AACpE,eAAO,MAAM,UAAU,OAAO,CAAA;AAE9B;;;;;GAKG;AACH,eAAO,MAAM,eAAe,IAAI,CAAA;AAEhC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,OAAO,CAAA;AAEnC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,MAAM,CAAA;AAEhC;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,SAAgB,GAAG,MAAM,CAU/F;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY;;;;uBAAoC,GAAG;CAAW,CAAA;AAE3E,MAAM,WAAW,YAAY;IAC3B,8BAA8B;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,gFAAgF;IAChF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC5B;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAMhF;AAED,yDAAyD;AACzD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGvD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,GAAG,YAAY,CAc3E;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAevF"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How big she is, and how much room her deformation needs.
|
|
3
|
+
*
|
|
4
|
+
* ## The direction of the dependency
|
|
5
|
+
*
|
|
6
|
+
* The body is the fixed thing and the CANVAS follows from it. That is the
|
|
7
|
+
* inverse of how this started: the window was 280x280 and the rig fitted her
|
|
8
|
+
* into a fraction of it, which meant the real size was an emergent property of
|
|
9
|
+
* two constants in different files, and "make her smaller" was a change to a
|
|
10
|
+
* fraction rather than to a size. Now `bodyW x bodyH` design units times a
|
|
11
|
+
* scale IS the size, and everything else is computed from it.
|
|
12
|
+
*
|
|
13
|
+
* Host-agnostic: nothing here knows about a window, a screen, or a desktop.
|
|
14
|
+
* What sizes an OS window from these numbers lives in the host application.
|
|
15
|
+
*/
|
|
16
|
+
import { FACE_BOUNDS } from './spec.js';
|
|
17
|
+
/**
|
|
18
|
+
* The furthest the squash channel may travel, in either direction.
|
|
19
|
+
*
|
|
20
|
+
* Lives here rather than in the rig because it is a SIZE fact before it is an
|
|
21
|
+
* animation fact: the window has to be big enough for the most deformed frame
|
|
22
|
+
* that can ever be drawn, so whoever sizes the window needs this number. The
|
|
23
|
+
* rig imports it back for the clamp that makes it true.
|
|
24
|
+
*
|
|
25
|
+
* What it cost to leave unbounded: sleepy posture at the top of a breath
|
|
26
|
+
* widened her past the window edge, and the part that did not fit was painted
|
|
27
|
+
* as two flat vertical sides.
|
|
28
|
+
*/
|
|
29
|
+
export const SQUASH_LIMIT = 0.26;
|
|
30
|
+
/** The most any look may lean. `looks.ts` is checked against this by a test. */
|
|
31
|
+
export const LEAN_LIMIT = 0.1;
|
|
32
|
+
/** How much of the half-width a full lean displaces the apex by. */
|
|
33
|
+
export const SHEAR_GAIN = 0.55;
|
|
34
|
+
/**
|
|
35
|
+
* Clearance around her worst case, in DESIGN UNITS.
|
|
36
|
+
*
|
|
37
|
+
* In her own units rather than pixels, so it scales with her: at 200% the gap
|
|
38
|
+
* around her looks the same as at 50%, which a pixel margin would not.
|
|
39
|
+
*/
|
|
40
|
+
export const BREATHING_UNITS = 8;
|
|
41
|
+
/**
|
|
42
|
+
* Pixels per design unit at 100%.
|
|
43
|
+
*
|
|
44
|
+
* 0.94 puts her resting body at 94 x 73 CSS pixels, which is the chosen
|
|
45
|
+
* default size. Expressed as a scale rather than as the width itself because
|
|
46
|
+
* `bodyW` belongs to the FACE — every avatar declares its own proportions, and
|
|
47
|
+
* a default stated as "94 wide" would silently mean something different for a
|
|
48
|
+
* face that is not 100 units across.
|
|
49
|
+
*
|
|
50
|
+
* 100% is the default rather than an extreme, so the 50–200% range gives room
|
|
51
|
+
* in both directions. She was previously drawn at twice this, which is what
|
|
52
|
+
* 200% now restores.
|
|
53
|
+
*/
|
|
54
|
+
export const BASE_UNIT_SCALE = 0.94;
|
|
55
|
+
/**
|
|
56
|
+
* How far into a canvas the character stands, by default.
|
|
57
|
+
*
|
|
58
|
+
* A DEFAULT the host is expected to replace. The number came from one
|
|
59
|
+
* application's window and means nothing here; it survives only as the fallback
|
|
60
|
+
* for a caller that never says where the ground is. Anything with its own
|
|
61
|
+
* layout should call `setFeet` and ignore it.
|
|
62
|
+
*
|
|
63
|
+
* Why the stance is settable at all rather than fixed: a host may not be free
|
|
64
|
+
* to place its window where it likes. macOS pins a window's top edge to the
|
|
65
|
+
* work area, so a fixed standing height meant the character could never be
|
|
66
|
+
* carried close to the top of the display — dragged upward, she stopped dead in
|
|
67
|
+
* mid-air. Letting the host move the stance instead is what fixed that, and it
|
|
68
|
+
* is the general case, not a macOS special case.
|
|
69
|
+
*/
|
|
70
|
+
export const FEET_FROM_TOP = 340;
|
|
71
|
+
/**
|
|
72
|
+
* Where she stands inside a canvas of a given height.
|
|
73
|
+
*
|
|
74
|
+
* ONE rule, called by the rig that draws her and by the code that anchors the
|
|
75
|
+
* bubble to her. They used to compute it separately — one as a fraction of the
|
|
76
|
+
* canvas and one as an offset from its bottom — which agree only when the
|
|
77
|
+
* canvas happens to be exactly her layout's height.
|
|
78
|
+
*/
|
|
79
|
+
export function feetY(cssHeight, clearance, feetFromTop = FEET_FROM_TOP) {
|
|
80
|
+
// Clamped, so a canvas smaller than the standing height still rests her on
|
|
81
|
+
// something rather than dropping her through the floor. The tuner sizes its
|
|
82
|
+
// own cells and is the caller that hits this.
|
|
83
|
+
//
|
|
84
|
+
// `clearance` is in PIXELS and therefore scaled — `BREATHING_UNITS` is in her
|
|
85
|
+
// design units, and passing it raw put her 4px low at 50% and 4px high at
|
|
86
|
+
// 150%, which is the whole distance between resting on the ground and
|
|
87
|
+
// hovering over it.
|
|
88
|
+
return Math.min(feetFromTop, cssHeight - clearance);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* What the size setting accepts, as a percentage of `BASE_UNIT_SCALE`.
|
|
92
|
+
*
|
|
93
|
+
* DERIVED from `FACE_BOUNDS.size`, which is where a user-supplied value is
|
|
94
|
+
* refused. That table's own comment states the rule — "One table, read by BOTH
|
|
95
|
+
* the validator and the tuner's sliders. Two copies would let the editor offer
|
|
96
|
+
* a value the loader rejects" — and there had come to be four copies of these
|
|
97
|
+
* three numbers: here, in `persona-change.ts` as `SIZE_BAND`, and in the
|
|
98
|
+
* shelf's size control as `BAND`.
|
|
99
|
+
*
|
|
100
|
+
* The `fallback` is genuinely this module's own: `FACE_BOUNDS` says what is
|
|
101
|
+
* PERMITTED, and what to do with a value that is not a number at all is a
|
|
102
|
+
* question only `clampSizePercent` asks.
|
|
103
|
+
*/
|
|
104
|
+
export const SIZE_PERCENT = { ...FACE_BOUNDS.size, fallback: 100 };
|
|
105
|
+
/**
|
|
106
|
+
* The widest and tallest she can ever be drawn, in design units.
|
|
107
|
+
*
|
|
108
|
+
* Deliberately conservative on the lean: the shear displaces the apex, where
|
|
109
|
+
* the body is narrowest, so adding it to the full width over-estimates. A
|
|
110
|
+
* window a few pixels too large is invisible; one a few pixels too small
|
|
111
|
+
* crops her, and that is the failure this whole calculation exists to prevent.
|
|
112
|
+
*/
|
|
113
|
+
export function worstCaseUnits(face) {
|
|
114
|
+
const width = face.bodyW * (1 + SQUASH_LIMIT);
|
|
115
|
+
return {
|
|
116
|
+
width: width + width * LEAN_LIMIT * SHEAR_GAIN,
|
|
117
|
+
height: face.bodyH / (1 - SQUASH_LIMIT),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Clamp a stored percentage to something renderable. */
|
|
121
|
+
export function clampSizePercent(value) {
|
|
122
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
123
|
+
return SIZE_PERCENT.fallback;
|
|
124
|
+
return Math.min(SIZE_PERCENT.max, Math.max(SIZE_PERCENT.min, value));
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Everything both processes need, from a face and a size.
|
|
128
|
+
*
|
|
129
|
+
* Rounded to whole pixels because a BrowserWindow cannot be 293.7 wide — and
|
|
130
|
+
* if main rounds while the renderer does not, her base drifts off the ground
|
|
131
|
+
* line by a fraction of a pixel and the two disagree about where she is.
|
|
132
|
+
*/
|
|
133
|
+
export function layoutFor(face, sizePercent) {
|
|
134
|
+
const scale = BASE_UNIT_SCALE * (clampSizePercent(sizePercent) / 100);
|
|
135
|
+
const worst = worstCaseUnits(face);
|
|
136
|
+
const width = Math.round((worst.width + BREATHING_UNITS * 2) * scale);
|
|
137
|
+
const height = Math.round((worst.height + BREATHING_UNITS * 2) * scale);
|
|
138
|
+
return {
|
|
139
|
+
scale,
|
|
140
|
+
width,
|
|
141
|
+
height,
|
|
142
|
+
// Her base sits one clearance up from the bottom edge.
|
|
143
|
+
ground: (height - BREATHING_UNITS * scale) / height,
|
|
144
|
+
bodyWidth: face.bodyW * scale,
|
|
145
|
+
bodyHeight: face.bodyH * scale,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The largest scale whose WORST CASE still fits a canvas somebody else sized.
|
|
150
|
+
*
|
|
151
|
+
* For the tuner, which lays out its own grid of cells and cannot be told what
|
|
152
|
+
* size to be. The app never uses this — it sizes the window from `layoutFor`,
|
|
153
|
+
* which is the whole point of the inversion.
|
|
154
|
+
*/
|
|
155
|
+
export function fitToCanvas(face, cssWidth, cssHeight) {
|
|
156
|
+
// A canvas has to be a positive finite size to be fitted into. It used to
|
|
157
|
+
// take whatever it was given, and every bad answer flowed downstream in a
|
|
158
|
+
// shape nothing tested for: zero produced scale 0, which makes the renderer's
|
|
159
|
+
// ground calculation `0 / 0`, so she was positioned at NaN and vanished with
|
|
160
|
+
// no error; a negative width produced a negative scale, which mirrors her.
|
|
161
|
+
//
|
|
162
|
+
// A zero-sized canvas is not exotic -- an element that is display:none, or
|
|
163
|
+
// measured a frame before layout, reports exactly that.
|
|
164
|
+
if (!isPositive(cssWidth) || !isPositive(cssHeight))
|
|
165
|
+
return 0;
|
|
166
|
+
const worst = worstCaseUnits(face);
|
|
167
|
+
return Math.min(cssWidth / (worst.width + BREATHING_UNITS * 2), cssHeight / (worst.height + BREATHING_UNITS * 2));
|
|
168
|
+
}
|
|
169
|
+
function isPositive(value) {
|
|
170
|
+
return Number.isFinite(value) && value > 0;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/core/layout.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lens: the region between an upper edge and a lower edge.
|
|
3
|
+
*
|
|
4
|
+
* One primitive for both the eyes and the mouth, and that unification is the
|
|
5
|
+
* design rather than a saving. Because both edges are independently signed, a
|
|
6
|
+
* single shape covers every state the face needs:
|
|
7
|
+
*
|
|
8
|
+
* upper +, lower + a dot, or an open mouth
|
|
9
|
+
* upper ~0, lower ~0 a closed sliver -- a blink
|
|
10
|
+
* upper +, lower - a crescent -- the ^ ^ happy eye
|
|
11
|
+
* upper -, lower + a frown
|
|
12
|
+
*
|
|
13
|
+
* A NEGATIVE lower is the trick worth naming: it curves the bottom edge upward
|
|
14
|
+
* past the baseline, which is what turns an eye into a smile without a second
|
|
15
|
+
* asset and without a special case. Lip-sync and expression therefore drive one
|
|
16
|
+
* code path, so a mouth cannot be open according to one and shut according to
|
|
17
|
+
* the other.
|
|
18
|
+
*
|
|
19
|
+
* Each edge is half a SUPERELLIPSE, not a quadratic. The first version used
|
|
20
|
+
* quadratics and every eye came out a pointed almond, because two quadratics
|
|
21
|
+
* meeting at the corners always meet at an angle -- there is no value of the
|
|
22
|
+
* parameters that produces a round dot. A superellipse at `roundness` 2 is an
|
|
23
|
+
* ellipse, so the two halves join with a shared vertical tangent and the corner
|
|
24
|
+
* disappears; below 2 it sharpens toward a leaf, above 2 it squares off.
|
|
25
|
+
*
|
|
26
|
+
* Local space, origin at the lens centre, +y UP -- the same convention as
|
|
27
|
+
* geometry.ts.
|
|
28
|
+
*/
|
|
29
|
+
import type { Point } from './geometry.js';
|
|
30
|
+
export interface LensShape {
|
|
31
|
+
readonly halfWidth: number;
|
|
32
|
+
/** How far the top edge bulges above the baseline. Negative dips it below. */
|
|
33
|
+
readonly upper: number;
|
|
34
|
+
/** How far the bottom edge dips below the baseline. Negative lifts it above. */
|
|
35
|
+
readonly lower: number;
|
|
36
|
+
/** Radians. Mirror it per side so both inner corners move the same way. */
|
|
37
|
+
readonly tilt: number;
|
|
38
|
+
/** Superellipse exponent. 2 is an ellipse; below sharpens, above squares off. */
|
|
39
|
+
readonly roundness: number;
|
|
40
|
+
}
|
|
41
|
+
export declare function lensOutline(lens: LensShape, steps?: number): Point[];
|
|
42
|
+
/**
|
|
43
|
+
* Peak vertical extent, used to offset gaze so the pupil moves within the eye
|
|
44
|
+
* rather than by a fraction of nothing when the lid is nearly shut.
|
|
45
|
+
*/
|
|
46
|
+
export declare function lensHeight(lens: LensShape): number;
|
|
47
|
+
//# sourceMappingURL=lens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lens.d.ts","sourceRoot":"","sources":["../../src/core/lens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AAE1C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,iFAAiF;IACjF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAMD,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,SAAK,GAAG,KAAK,EAAE,CA4BhE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAElD"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lens: the region between an upper edge and a lower edge.
|
|
3
|
+
*
|
|
4
|
+
* One primitive for both the eyes and the mouth, and that unification is the
|
|
5
|
+
* design rather than a saving. Because both edges are independently signed, a
|
|
6
|
+
* single shape covers every state the face needs:
|
|
7
|
+
*
|
|
8
|
+
* upper +, lower + a dot, or an open mouth
|
|
9
|
+
* upper ~0, lower ~0 a closed sliver -- a blink
|
|
10
|
+
* upper +, lower - a crescent -- the ^ ^ happy eye
|
|
11
|
+
* upper -, lower + a frown
|
|
12
|
+
*
|
|
13
|
+
* A NEGATIVE lower is the trick worth naming: it curves the bottom edge upward
|
|
14
|
+
* past the baseline, which is what turns an eye into a smile without a second
|
|
15
|
+
* asset and without a special case. Lip-sync and expression therefore drive one
|
|
16
|
+
* code path, so a mouth cannot be open according to one and shut according to
|
|
17
|
+
* the other.
|
|
18
|
+
*
|
|
19
|
+
* Each edge is half a SUPERELLIPSE, not a quadratic. The first version used
|
|
20
|
+
* quadratics and every eye came out a pointed almond, because two quadratics
|
|
21
|
+
* meeting at the corners always meet at an angle -- there is no value of the
|
|
22
|
+
* parameters that produces a round dot. A superellipse at `roundness` 2 is an
|
|
23
|
+
* ellipse, so the two halves join with a shared vertical tangent and the corner
|
|
24
|
+
* disappears; below 2 it sharpens toward a leaf, above 2 it squares off.
|
|
25
|
+
*
|
|
26
|
+
* Local space, origin at the lens centre, +y UP -- the same convention as
|
|
27
|
+
* geometry.ts.
|
|
28
|
+
*/
|
|
29
|
+
function rotate(x, y, sin, cos) {
|
|
30
|
+
return { x: x * cos - y * sin, y: x * sin + y * cos };
|
|
31
|
+
}
|
|
32
|
+
export function lensOutline(lens, steps = 28) {
|
|
33
|
+
const hw = Math.max(0, lens.halfWidth);
|
|
34
|
+
const k = Math.max(0.5, lens.roundness);
|
|
35
|
+
const power = 2 / k;
|
|
36
|
+
const sin = Math.sin(lens.tilt);
|
|
37
|
+
const cos = Math.cos(lens.tilt);
|
|
38
|
+
const points = [];
|
|
39
|
+
// Right, over the top, to left; then left, under the bottom, back to right.
|
|
40
|
+
// Parametrised by angle rather than by x so the samples stay evenly spread
|
|
41
|
+
// around the curve instead of bunching at the ends.
|
|
42
|
+
const half = (extent, from, to) => {
|
|
43
|
+
for (let i = 0; i <= steps; i++) {
|
|
44
|
+
const t = from + (to - from) * (i / steps);
|
|
45
|
+
const c = Math.cos(t);
|
|
46
|
+
const s = Math.sin(t);
|
|
47
|
+
const x = hw * Math.sign(c) * Math.pow(Math.abs(c), power);
|
|
48
|
+
const y = extent * Math.sign(s) * Math.pow(Math.abs(s), power);
|
|
49
|
+
points.push(rotate(x, y, sin, cos));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
// Over the top with `upper`, then under the bottom with `lower`. On the lower
|
|
53
|
+
// sweep sin(t) is negative, so the formula already flips the sign -- passing
|
|
54
|
+
// `lower` unnegated is what makes a POSITIVE lower dip below the baseline and
|
|
55
|
+
// a negative one bow above it.
|
|
56
|
+
half(lens.upper, 0, Math.PI);
|
|
57
|
+
half(lens.lower, Math.PI, Math.PI * 2);
|
|
58
|
+
return points;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Peak vertical extent, used to offset gaze so the pupil moves within the eye
|
|
62
|
+
* rather than by a fraction of nothing when the lid is nearly shut.
|
|
63
|
+
*/
|
|
64
|
+
export function lensHeight(lens) {
|
|
65
|
+
return Math.max(Math.abs(lens.upper), Math.abs(lens.lower));
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=lens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lens.js","sourceRoot":"","sources":["../../src/core/lens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAgBH,SAAS,MAAM,CAAC,CAAS,EAAE,CAAS,EAAE,GAAW,EAAE,GAAW;IAC5D,OAAO,EAAE,CAAC,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,CAAA;AACvD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAe,EAAE,KAAK,GAAG,EAAE;IACrD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;IACtC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;IACvC,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,CAAA;IACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAE/B,MAAM,MAAM,GAAY,EAAE,CAAA;IAC1B,4EAA4E;IAC5E,2EAA2E;IAC3E,oDAAoD;IACpD,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,IAAY,EAAE,EAAU,EAAQ,EAAE;QAC9D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAChC,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAA;YAC1C,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YACrB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YACrB,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;YAC1D,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;YAC9D,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;QACrC,CAAC;IACH,CAAC,CAAA;IACD,8EAA8E;IAC9E,6EAA6E;IAC7E,8EAA8E;IAC9E,+BAA+B;IAC/B,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAA;IAC5B,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;IACtC,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAAe;IACxC,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;AAC7D,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How each emotion deforms the neutral face.
|
|
3
|
+
*
|
|
4
|
+
* Every Emotion gets an entry, enforced by `Record<Emotion, Look>`. Letting one
|
|
5
|
+
* fall through to neutral would make `caps.presetExpressions` a lie: the
|
|
6
|
+
* backend would claim to support an expression it renders identically to no
|
|
7
|
+
* expression at all, and the caller has no way to discover that.
|
|
8
|
+
*
|
|
9
|
+
* Values are multipliers on the tuned base geometry rather than absolute sizes,
|
|
10
|
+
* so retuning the face in the tuner does not invalidate the table.
|
|
11
|
+
*/
|
|
12
|
+
import { type Emotion } from './vocabulary.js';
|
|
13
|
+
export interface Look {
|
|
14
|
+
/** Multiplies the eye's upper arc. */
|
|
15
|
+
readonly eyeUpper: number;
|
|
16
|
+
/** Multiplies the eye's lower arc. NEGATIVE turns the eye into a crescent -- see lens.ts. */
|
|
17
|
+
readonly eyeLower: number;
|
|
18
|
+
readonly eyeWidth: number;
|
|
19
|
+
/** Radians, mirrored per side. Positive brings the inner corners down. */
|
|
20
|
+
readonly eyeTilt: number;
|
|
21
|
+
readonly mouthUpper: number;
|
|
22
|
+
readonly mouthLower: number;
|
|
23
|
+
readonly mouthWidth: number;
|
|
24
|
+
/**
|
|
25
|
+
* 0..1 opacity of the mouth. 0 removes it entirely.
|
|
26
|
+
*
|
|
27
|
+
* Only `sleepy` uses it, and only for the resting pose: a sleeping mochi is a
|
|
28
|
+
* shape with two closed eyes, and a mouth left on it reads as awake-but-quiet
|
|
29
|
+
* rather than asleep.
|
|
30
|
+
*
|
|
31
|
+
* This does NOT weaken the layering rule, which says no layer above the
|
|
32
|
+
* mouth may overwrite it, because a gesture holding her jaw shut while audio
|
|
33
|
+
* is playing reads as broken. `paintMouth` therefore takes the LOUDER of this
|
|
34
|
+
* and whatever is actually driving the mouth -- so anything making a sound
|
|
35
|
+
* wins, and the expression can only hide a mouth that is already still.
|
|
36
|
+
*/
|
|
37
|
+
readonly mouthAlpha: number;
|
|
38
|
+
/** 0..1 extra cheek colour, on top of the resting amount. */
|
|
39
|
+
readonly cheek: number;
|
|
40
|
+
/**
|
|
41
|
+
* 0..1 opacity of the catchlight -- the "her eyes lit up" block.
|
|
42
|
+
*
|
|
43
|
+
* A property of the EMOTION rather than a timer, which is what makes "her
|
|
44
|
+
* eyes lit up" mean something: it appears when she is delighted or startled
|
|
45
|
+
* and is absent the rest of the time, so seeing it carries information. A
|
|
46
|
+
* highlight that were simply always on would be a shading detail, and one
|
|
47
|
+
* that blinked on a schedule would be noise.
|
|
48
|
+
*
|
|
49
|
+
* It blends with intensity like everything else here, so the waking perk --
|
|
50
|
+
* `surprised` at 0.6 for 1.4 seconds, in `face.ts` -- shows a partial glint
|
|
51
|
+
* rather than a full one.
|
|
52
|
+
*
|
|
53
|
+
* **It does not fade.** This entry used to say it did, and it was wrong
|
|
54
|
+
* twice over: nothing set the perk at all, and only `squash` runs through a
|
|
55
|
+
* spring -- every other channel of a `Look` is applied on the frame it is
|
|
56
|
+
* set. A hold ENDS an expression; it does not ease it out. Anyone wanting a
|
|
57
|
+
* fade has to interpolate the look itself, which is a change to this whole
|
|
58
|
+
* pipeline rather than a value in this table.
|
|
59
|
+
*/
|
|
60
|
+
readonly sparkle: number;
|
|
61
|
+
/** -1..1 gaze bias, added to whatever the cursor is doing. Thinking looks up and away. */
|
|
62
|
+
readonly gazeX: number;
|
|
63
|
+
readonly gazeY: number;
|
|
64
|
+
/** Postural squash. Surprise stretches her up; sleepy lets her spread. */
|
|
65
|
+
readonly squash: number;
|
|
66
|
+
readonly lean: number;
|
|
67
|
+
}
|
|
68
|
+
export declare const NEUTRAL: Look;
|
|
69
|
+
export declare const LOOKS: Readonly<Record<Emotion, Look>>;
|
|
70
|
+
/**
|
|
71
|
+
* Neutral blended toward the named emotion by intensity.
|
|
72
|
+
*
|
|
73
|
+
* Intensity 0 is exactly neutral for every emotion, which is what lets a signal
|
|
74
|
+
* decay smoothly rather than snapping when it expires.
|
|
75
|
+
*/
|
|
76
|
+
export declare function blendLook(emotion: Emotion, intensity: number): Look;
|
|
77
|
+
//# sourceMappingURL=looks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"looks.d.ts","sourceRoot":"","sources":["../../src/core/looks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAEvD,MAAM,WAAW,IAAI;IACnB,sCAAsC;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,6FAA6F;IAC7F,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,0FAA0F;IAC1F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,eAAO,MAAM,OAAO,EAAE,IAerB,CAAA;AAED,eAAO,MAAM,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAsHjD,CAAA;AAID;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAWnE"}
|