bloub-to-vue-and-react 0.1.2

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.
@@ -0,0 +1,79 @@
1
+ import { type ProfileName } from './profiles';
2
+ export interface Point {
3
+ x: number;
4
+ y: number;
5
+ }
6
+ /**
7
+ * 一个剪影 = 一个径向轮廓 r(theta) 加上一个姿势。
8
+ *
9
+ * 一切都通过以相同角度数量采样的轮廓来实现:因此任意两个形状的点都能一一对应,
10
+ * 形变也就归结为半径的线性插值。正是这一点让过渡干净利落,无需路径形变库。
11
+ */
12
+ export interface Silhouette {
13
+ radii: number[];
14
+ /** 轮廓的旋转,单位为弧度 */
15
+ rot: number;
16
+ /** 中心的偏移,单位为球半径 */
17
+ cx: number;
18
+ cy: number;
19
+ /** 挤压与拉伸,应用于屏幕坐标系(旋转之后) */
20
+ sx: number;
21
+ sy: number;
22
+ }
23
+ export declare function silhouette(name: ProfileName, pose?: Partial<Silhouette>): Silhouette;
24
+ /** 完美圆形:用作中性基础(点、气泡、淡入淡出目标)。 */
25
+ export declare function circle(radius: number, pose?: Partial<Silhouette>): Silhouette;
26
+ /** 两个剪影的插值。`out` 会被复用,以避免在 60 fps 下反复分配内存。 */
27
+ export declare function blend(a: Silhouette, b: Silhouette, t: number, out?: Silhouette): Silhouette;
28
+ /** 将剪影投影为屏幕点。`scale` = 以 viewBox 为单位的球半径。 */
29
+ export declare function toPoints(s: Silhouette, scale: number, out?: Point[]): Point[];
30
+ /**
31
+ * 闭合折线 -> Catmull-Rom 三次曲线。
32
+ *
33
+ * 用 64 个点时,居中切线已经足够:即使以 600 px 显示,轮廓
34
+ * 也逐像素光滑,并且路径字符串保持简短。
35
+ */
36
+ export declare function closedPath(pts: Point[], tension?: number): string;
37
+ /**
38
+ * 任意多边形 -> 径向轮廓,通过从 `center` 发射射线实现。
39
+ *
40
+ * 用于构造那些无法自然用 r(theta) 表达的形状("!" 的截锥形竖杠)。
41
+ * 仅在加载时计算一次,绝不在渲染循环中进行。
42
+ */
43
+ export declare function profileFromPolygon(poly: Point[], cx: number, cy: number): number[];
44
+ /** 两个圆的凸包:竖直「!」的截锥形竖杠。 */
45
+ export declare function hullOfCircles(x1: number, y1: number, r1: number, x2: number, y2: number, r2v: number, steps?: number): Point[];
46
+ /**
47
+ * 轮廓在任意方向上的半径,通过在两个相邻采样点之间插值得到。
48
+ *
49
+ * 用于在剪影不再是圆形时,重新校准"放"在身体上的元素(眼睛、通知小圆点):
50
+ * 否则,一个放在 0.62 半径处的眼睛会伸出该方向上边缘只有 0.55 的形状,
51
+ * 从而被遮罩裁剪掉。
52
+ */
53
+ export declare function radiusAtAngle(radii: number[], angle: number): number;
54
+ /**
55
+ * 超椭圆:|x/sx|^n + |y/sy|^n = 1。
56
+ * n = 2 得到椭圆,n ~ 4 得到自定义器中的方圆(squircle)。
57
+ */
58
+ export declare function superellipseProfile(n: number, sx?: number, sy?: number): number[];
59
+ /**
60
+ * 圆盘并集的径向轮廓:r(theta) = 射线/圆交集中最远的那一个。
61
+ * 只要原点位于并集内就是精确的——正是它无需路径布尔运算就能给出云朵的凸起。
62
+ */
63
+ export declare function unionOfCirclesProfile(circles: Array<{
64
+ x: number;
65
+ y: number;
66
+ r: number;
67
+ }>): number[];
68
+ /** 圆角正多边形,内接于 `radius`。 */
69
+ export declare function regularPolygonProfile(sides: number, radius: number, rc: number, rotationDeg?: number): number[];
70
+ /** 精确的闭合折线:保留直线段(与 closedPath 不同)。 */
71
+ export declare function polyPath(pts: Point[], scale?: number): string;
72
+ /** 以原点为中心的胶囊形(跑道形):机器人眼睛的精确形状。 */
73
+ export declare function capsulePath(w: number, h: number): string;
74
+ /**
75
+ * 微笑的嘴:向上弯曲的月牙形。`smile` 是弯曲的深度(正值 = 嘴角上扬),
76
+ * 以半径为单位。厚度为 `h` 的带状区域,其中心线是一条二次曲线,
77
+ * 经过两个嘴角和微笑的凹陷处。
78
+ */
79
+ export declare function smilePath(w: number, h: number, smile: number): string;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * 机器人自定义器提供的形状和颜色。
3
+ *
4
+ * 与动画剪影(`profiles.ts`)不同,这些形状并不是从视频中测量得到的:
5
+ * 它们是根据原版自定义器的网格解析构造出来的。因此这是两个不同的来源,
6
+ * 而且这是有意为之——动画状态必须忠于视频,而基础形状则是用户的选择。
7
+ */
8
+ /**
9
+ * 标识符采用枚举方式,而不是从数组中推导:这样 i18n 层就能在编译期校验
10
+ * 每种形状在三种语言中都有对应翻译(`t(\`shapes.${id}\`)` 只有在键存在时才能编译通过)。
11
+ * 对数组使用 `as const` 也能达到同样效果,但会把 `radii` 变成只读,
12
+ * 而引擎需要原样使用它。
13
+ */
14
+ export type ShapeId = 'cercle' | 'galet' | 'squircle' | 'capsule' | 'triangle' | 'hexagone' | 'nuage' | 'goutte';
15
+ export interface BotShape {
16
+ id: ShapeId;
17
+ radii: number[];
18
+ }
19
+ export declare const SHAPES: BotShape[];
20
+ export declare const SHAPE_BY_ID: Map<string, BotShape>;
21
+ export declare const DEFAULT_SHAPE = "cercle";
22
+ export type ColorId = 'encre' | 'creme' | 'brun' | 'rouge' | 'orange' | 'ambre' | 'vert' | 'turquoise' | 'bleu' | 'violet' | 'rose' | 'gris';
23
+ export interface BotColor {
24
+ id: ColorId;
25
+ hex: string;
26
+ }
27
+ /** 原版自定义器的调色板。 */
28
+ export declare const COLORS: BotColor[];
29
+ export declare const COLOR_BY_ID: Map<string, BotColor>;
30
+ export declare const DEFAULT_COLOR = "encre";
31
+ /** 混合两种十六进制颜色。用于粒子的深度雾化。 */
32
+ export declare function mixHex(from: string, to: string, t: number): string;
@@ -0,0 +1,91 @@
1
+ import { type ArcSpec, type DotRender } from './decor';
2
+ import { type HeadGaze } from './face';
3
+ import { type Silhouette } from './shape';
4
+ export interface EyeCfg {
5
+ /** 局部宽度(胶囊的短轴),以球体半径为单位 */
6
+ w: number;
7
+ /** 局部高度(长轴) */
8
+ h: number;
9
+ /** 1 = 睁开,0 = 闭合 */
10
+ open: number;
11
+ /**
12
+ * 胶囊自身的倾斜,单位为度,正值 = 顶部朝右。在球体的切向标架之后应用。
13
+ * 没有它,两只眼睛就必然朝同一侧倾斜(头部滚转),而愤怒和悲伤这两种
14
+ * 需要互为镜像倾斜的表情就无从实现。
15
+ */
16
+ tilt?: number;
17
+ }
18
+ /** 嘴巴:和眼睛同理,是一个放在球面上的胶囊。 */
19
+ export interface MouthCfg {
20
+ /** 局部宽度(短轴),以球体半径为单位 */
21
+ w: number;
22
+ /** 局部高度(长轴) */
23
+ h: number;
24
+ /** 微笑的弧度,正值 = 嘴角上扬;缺省 = 笔直的胶囊 */
25
+ smile?: number;
26
+ }
27
+ export interface Pose {
28
+ /** 身体的轮廓,以球体半径为单位 */
29
+ sil: Silhouette;
30
+ /** 身体和眼睛的整体偏移 */
31
+ offX: number;
32
+ offY: number;
33
+ gaze: HeadGaze;
34
+ /** 眼睛在球面上的半间距,单位为度 */
35
+ split: number;
36
+ /** [内侧眼,外侧眼] */
37
+ eyes: [EyeCfg, EyeCfg];
38
+ /** 嘴巴,默认不存在:只有带有嘴巴的表情才会有 */
39
+ mouth: MouthCfg | null;
40
+ /** 眼睛的不透明度:用于没有面部的状态 */
41
+ eyeAlpha: number;
42
+ bodyAlpha: number;
43
+ dots: DotRender[];
44
+ arcs: ArcSpec[];
45
+ notif: {
46
+ x: number;
47
+ y: number;
48
+ r: number;
49
+ notch: number;
50
+ } | null;
51
+ /** true = 装饰移到身体后面(爆裂粒子) */
52
+ dotsBehind: boolean;
53
+ }
54
+ export type StateId = 'idle' | 'thinking' | 'wink' | 'wide' | 'alert' | 'notify' | 'exclaim' | 'sleep' | 'egg' | 'hexagon' | 'play' | 'orbit' | 'burst' | 'comet'
55
+ /** 界面过渡,不是目录中的动画:不在 `SEQUENCE` 中 */
56
+ | 'swirl';
57
+ export interface StateDef {
58
+ id: StateId;
59
+ /** 播放完整序列时的保持时长 */
60
+ duration: number;
61
+ /**
62
+ * 动画会在此时长以下被截断而无法完成:「!」不会回到原位,身体保持爆裂状态。
63
+ * 这个值从下方 `pose` 的常量中读出,而不是人为选择。缺省 = 该状态忽略时间
64
+ * 或循环播放,任意时长都合适(见 `MIN_BLOCK`)。
65
+ */
66
+ minDuration?: number;
67
+ /** 入场过渡的时长 */
68
+ morph: number;
69
+ /** true = 入场被一次眨眼掩盖,和视频中一样 */
70
+ blinkIn: boolean;
71
+ /**
72
+ * true = 身体是「静止」轮廓,因此可以被个性化设置中选择的形状替换。自行绘制
73
+ * 形状的状态(「!」、圆点、蛋、三角形……)为 false:那个形状本身才是动画。
74
+ */
75
+ baseBody: boolean;
76
+ /**
77
+ * true = 该状态带有「静止」面部,因此可以被选中的表情替换。只有 `idle`:
78
+ * 其他有面部的状态在视频中都有专属表情,我们复现的正是这一点。
79
+ */
80
+ baseFace: boolean;
81
+ pose(local: number): Pose;
82
+ }
83
+ export declare const STATES: StateDef[];
84
+ export declare const STATE_BY_ID: Map<StateId, StateDef>;
85
+ /** 完整序列的播放顺序,参照参考视频。 */
86
+ /**
87
+ * 每个状态最清晰可读的本地时间点:这是缩略图和样张所展示的姿态。
88
+ * 渲染是确定性的,因此各次运行之间可比。类型强制覆盖所有新状态。
89
+ */
90
+ export declare const POSES: Record<StateId, number>;
91
+ export declare const SEQUENCE: StateId[];
@@ -0,0 +1,83 @@
1
+ import { type GazeScript } from '../../ui/gaze';
2
+ import { type Block } from '../../bot/cycles';
3
+ import { type StateId } from '../../bot/states';
4
+ type __VLS_Props = {
5
+ size?: number;
6
+ /** identifiant de forme du personnalisateur */
7
+ shape?: string;
8
+ /** identifiant de couleur du personnalisateur */
9
+ color?: string;
10
+ /** identifiant d'expression de repos du personnalisateur */
11
+ expression?: string;
12
+ /** couleur du fond, utilisee pour la brume de profondeur des particules */
13
+ paper?: string;
14
+ /**
15
+ * Fige le rendu a cette date (en secondes depuis le debut de l'etat).
16
+ * Le moteur etant une fonction pure du temps, on obtient une image
17
+ * reproductible au pixel pres, sans boucle d'animation : utile pour une
18
+ * planche d'etats, une vignette de personnalisateur ou un test.
19
+ */
20
+ frozenAt?: number;
21
+ /**
22
+ * Montage joue par le lecteur : une suite d'etats, chacun tenu la duree de
23
+ * son bloc. Par defaut, le cycle releve sur la video.
24
+ */
25
+ cycle?: Block[];
26
+ /**
27
+ * Le regard suit le pointeur. Hors de portee des vignettes figees, qui n'ont
28
+ * pas de boucle d'animation : les brancher reveillerait autant de boucles
29
+ * qu'il y a de cases.
30
+ */
31
+ follow?: boolean;
32
+ /**
33
+ * Regard scripte de l'arrivee : evalue a chaque image avec le temps ecoule
34
+ * depuis qu'il a ete pose. Independant de `follow`, qui vise le pointeur —
35
+ * ici c'est le script qui decide de tout, y compris de sa duree.
36
+ */
37
+ gaze?: GazeScript | null;
38
+ /** Nom accessible du svg, localise par l'application qui l'utilise. */
39
+ ariaLabel?: string;
40
+ };
41
+ declare function seek(index: number, offset?: number): void;
42
+ declare function rendAt(t: number): void;
43
+ type __VLS_ModelProps = {
44
+ /**
45
+ * Le curseur de lecture est un **index de bloc**, pas un etat : un montage peut
46
+ * jouer deux fois le meme etat, et il faut alors savoir dans lequel des deux on
47
+ * se trouve. `state` suit le bloc courant — c'est une sortie, l'exterieur pilote
48
+ * la lecture par `block`.
49
+ */
50
+ 'block'?: number;
51
+ 'state'?: StateId;
52
+ 'playing'?: boolean;
53
+ /** Temps ecoule dans le bloc courant, pour la tete de lecture de la timeline. */
54
+ 'elapsed'?: number;
55
+ };
56
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
57
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
58
+ seek: typeof seek;
59
+ rendAt: typeof rendAt;
60
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
61
+ "update:block": (value: number) => any;
62
+ "update:state": (value: StateId) => any;
63
+ "update:playing": (value: boolean) => any;
64
+ "update:elapsed": (value: number) => any;
65
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
66
+ "onUpdate:block"?: ((value: number) => any) | undefined;
67
+ "onUpdate:state"?: ((value: StateId) => any) | undefined;
68
+ "onUpdate:playing"?: ((value: boolean) => any) | undefined;
69
+ "onUpdate:elapsed"?: ((value: number) => any) | undefined;
70
+ }>, {
71
+ gaze: GazeScript | null;
72
+ size: number;
73
+ shape: string;
74
+ color: string;
75
+ expression: string;
76
+ paper: string;
77
+ frozenAt: number;
78
+ cycle: Block[];
79
+ follow: boolean;
80
+ ariaLabel: string;
81
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
82
+ declare const _default: typeof __VLS_export;
83
+ export default _default;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * 核心渲染引擎(bloub)的库入口:只导出 `BloubBot` 及其公开类型。
3
+ * 这个文件是 npm 库的打包入口,应用代码仍然直接 import `BloubBot.vue`。
4
+ */
5
+ export { default as BloubBot } from './BloubBot.vue';
6
+ export type { Block } from '../../bot/cycles';
7
+ export type { StateId } from '../../bot/states';
8
+ export type { GazeScript } from '../../ui/gaze';
@@ -0,0 +1,130 @@
1
+ import type { Look } from '../bot/engine';
2
+ import type { ExpressionId } from '../bot/expressions';
3
+ /**
4
+ * Ou le bot regarde quand il suit le curseur. Pur, comme `src/ui/timeline.ts` :
5
+ * la position du pointeur entre en coordonnees deja normalisees, donc la regle
6
+ * se teste sans DOM — et elle a besoin de l'etre, parce que deux signes s'y
7
+ * trompent facilement.
8
+ */
9
+ /**
10
+ * Angles en degres d'orientation de tete. CHOISIS, pas releves : la video de
11
+ * reference ne montre aucun suivi de curseur. Assez amples pour se distinguer de
12
+ * la derive au repos (±7deg de lacet, ±5,5 de tangage), assez retenus pour
13
+ * qu'aucun oeil ne parte derriere le limbe de la sphere.
14
+ */
15
+ export declare const YAW_MAX = 16;
16
+ export declare const PITCH_MAX = 13;
17
+ /**
18
+ * Hauteur a laquelle le regard se tient, curseur au centre. CHOISIE : legerement
19
+ * au-dessus de l'equateur, ce qui donne un bot attentif plutot qu'absent.
20
+ *
21
+ * C'est une valeur ABSOLUE, et c'est tout le point : en relatif, la hauteur des
22
+ * yeux suivait celle de chaque expression, et comme « neutre » regarde a
23
+ * +28,6deg quand les humeurs sont entre -9 et +9, les yeux tombaient d'un coup
24
+ * au premier changement d'humeur.
25
+ */
26
+ export declare const PITCH = 10;
27
+ /**
28
+ * Direction ou la tete se pose dans la vue des reglages : le bot cesse de regarder en haut a
29
+ * droite (sa pose de repos) pour regarder a GAUCHE, du cote du panneau.
30
+ *
31
+ * Ce n'est pas un miroir de l'image : les yeux font vraiment le tour de la
32
+ * sphere, donc ils gardent leur inclinaison en `\\` et leur compression de
33
+ * profondeur. Retourner l'image les aurait couches en `//`.
34
+ */
35
+ export declare const TURN = 26;
36
+ /**
37
+ * Tour complet parcouru EN CHEMIN : les yeux ne glissent pas en travers du
38
+ * visage, ils font le tour de la boule avant d'arriver.
39
+ *
40
+ * C'est gratuit parce que les yeux vivent sur une sphere : passe 90deg de lacet
41
+ * ils franchissent le limbe, le moteur les retire de l'image, puis ils
42
+ * reapparaissent de l'autre cote. Le tourbillon n'est donc pas un effet pose
43
+ * par-dessus, c'est la meme projection orthographique poussee d'un tour.
44
+ *
45
+ * Et surtout : il ATTERRIT JUSTE par construction, `-360deg` etant le meme angle
46
+ * que `0`. C'est ce qui le distingue d'une pose de regard ecrite dans un etat,
47
+ * qui laisse les yeux la ou sa courbe se termine.
48
+ */
49
+ export declare const SPIN = 360;
50
+ /**
51
+ * Duree du tour. Un peu plus courte que le bloc d'entree (`swirl`) : les yeux
52
+ * doivent etre poses a gauche avant que les anneaux ne s'effacent.
53
+ */
54
+ export declare const TURN_TIME = 1.1;
55
+ /**
56
+ * Humeurs que le bot traverse pendant qu'il suit le curseur.
57
+ *
58
+ * Toutes ont un ROULIS NUL, et c'est le critere de selection. Le lacet et le
59
+ * tangage sont neutralises par le suivi (ils sont absolus), mais pas le roulis —
60
+ * il fait pencher la tete, donc il deplace les yeux verticalement, et une humeur
61
+ * a -15deg suivie d'une a +8 les fait sauter. Ce qui reste pour distinguer les
62
+ * humeurs, c'est la FORME des yeux : ronds, plisses, ecarquilles, aplatis. C'est
63
+ * largement suffisant, et c'est ce qui se lit.
64
+ *
65
+ * Ce n'est donc pas une liste de gouts : y ajouter « curieux » (roulis -15deg)
66
+ * ramenerait le saut.
67
+ */
68
+ export declare const HUMEURS: readonly ExpressionId[];
69
+ /**
70
+ * Un regard SCRIPTE : evalue a chaque image avec le temps ecoule depuis le debut
71
+ * de l'arrivee, en secondes. Le script porte donc sa propre horloge et peut
72
+ * enchainer plusieurs mouvements — le composant ne fait que l'evaluer et n'a
73
+ * aucune duree a connaitre.
74
+ *
75
+ * REGLE, et c'est elle qui rend un script sans entretien : il doit FINIR a
76
+ * `mix: 0`, ou la pose de l'etat commande seule. Il n'y a alors jamais rien a
77
+ * relacher, et ce relachement — qui se verrait sous la forme d'un dernier
78
+ * glissement des yeux, juste quand tout devrait etre pose — n'existe pas.
79
+ *
80
+ * Le type reste general alors qu'il n'y a qu'un script aujourd'hui : quatre ont
81
+ * ete ecrits et compares cote a cote avant d'en garder un, et c'est cette forme
82
+ * qui a permis de les essayer sans toucher au moteur.
83
+ */
84
+ export type GazeScript = (t: number) => Look;
85
+ /**
86
+ * « Le tour » : la boule a l'air de tourner sur elle-meme.
87
+ *
88
+ * `mix` reste a ZERO du debut a la fin : aucune direction n'est imposee, seul
89
+ * `spin` fond, ce qui fait passer les yeux DERRIERE la boule avant de les
90
+ * ramener exactement la ou l'expression choisie les met.
91
+ *
92
+ * Ease-in-OUT et non l'ease-out exponentiel du reste du projet : ce n'est pas une
93
+ * valeur qui se pose, c'est un objet qui tourne. En ease-out, les deux tiers du
94
+ * tour etaient avales en 0,3 s — un a-coup, pas une rotation.
95
+ *
96
+ * Il est VIF au passage du limbe — 20 px entre deux images sur une boule de 100
97
+ * de rayon — et ce n'est pas un defaut de reglage : pres du bord, un petit angle
98
+ * devient un grand deplacement a l'ecran, et l'oeil disparait puis reparait de
99
+ * l'autre cote. Ralentir n'y change rien, c'est la trajectoire qui veut ca, et
100
+ * c'est justement ce qui fait l'effet. Ne pas chercher a l'adoucir.
101
+ *
102
+ * Corollaire indispensable : ce tour ne se joue que sur un CERCLE. Les yeux sont
103
+ * recolles au contour reel (`radiusAtAngle`), donc sur une forme non circulaire
104
+ * ils suivent le profil en tournant et sautillent. Voir `forme` dans `App.vue`.
105
+ */
106
+ export declare const TOUR_TIME = 1.5;
107
+ export declare const tourLook: GazeScript;
108
+ export interface Aim {
109
+ /** ecart horizontal du pointeur au centre du bot, -1 a 1 (droite positive) */
110
+ nx: number;
111
+ /** ecart vertical, -1 a 1, dans le sens de l'ecran (bas positif) */
112
+ ny: number;
113
+ /** avancement de l'arrivee, 0 a 1 */
114
+ tour: number;
115
+ /** false = aucun pointeur connu : la tete reste tournee, mais elle revit */
116
+ pointer: boolean;
117
+ }
118
+ /**
119
+ * Cible de regard.
120
+ *
121
+ * `tour` mene tout : il fait monter l'emprise sur la pose (`mix`) et fondre le
122
+ * tour parcouru (`spin`) en meme temps. A 0 la pose de l'etat commande seule ; a
123
+ * 1 la tete est posee a gauche et suit le curseur.
124
+ *
125
+ * Rien ici ne compense l'expression affichee : c'est le moteur qui melange,
126
+ * parce que lui seul connait la pose a l'instant t. Le faire ici obligerait a
127
+ * lire le lacet d'ARRIVEE de l'expression pendant que le moteur, lui, morphe
128
+ * encore — et les yeux sautaient a chaque changement d'humeur.
129
+ */
130
+ export declare function lookTarget({ nx, ny, tour, pointer }: Aim): Look;
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "bloub-to-vue-and-react",
3
+ "version": "0.1.2",
4
+ "type": "module",
5
+ "description": "SVG recreation of the x.ai bot avatar. One shape morphing through 14 states, measured off the reference video frame by frame.",
6
+ "license": "MIT",
7
+ "author": "Jérémy Perret",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/jeremy-prt/bloub.git"
11
+ },
12
+ "keywords": [
13
+ "svg",
14
+ "animation",
15
+ "avatar",
16
+ "vue",
17
+ "morphing"
18
+ ],
19
+ "packageManager": "pnpm@11.17.0",
20
+ "main": "./dist/bloub.js",
21
+ "module": "./dist/bloub.js",
22
+ "types": "./dist/types/components/Core/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/types/components/Core/index.d.ts",
26
+ "import": "./dist/bloub.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "sideEffects": false,
33
+ "scripts": {
34
+ "dev": "vite",
35
+ "build": "vue-tsc --noEmit && vite build",
36
+ "build:lib": "vite build --config vite.lib.config.ts && vue-tsc --project tsconfig.lib.json",
37
+ "preview": "vite preview",
38
+ "test": "vitest run",
39
+ "test:watch": "vitest",
40
+ "prepublishOnly": "pnpm build:lib"
41
+ },
42
+ "peerDependencies": {
43
+ "vue": "^3.5.41"
44
+ },
45
+ "dependencies": {
46
+ "mediabunny": "^1.54.0"
47
+ },
48
+ "devDependencies": {
49
+ "@tailwindcss/vite": "^4.3.3",
50
+ "@types/node": "^26.0.0",
51
+ "@vitejs/plugin-vue": "^6.0.8",
52
+ "happy-dom": "^20.11.2",
53
+ "tailwindcss": "^4.3.3",
54
+ "typescript": "^5.9.3",
55
+ "vite": "^8.2.1",
56
+ "vitest": "^4.1.10",
57
+ "vue": "^3.5.41",
58
+ "vue-tsc": "^3.3.9"
59
+ }
60
+ }