@lemoncat7/dsh-theme-xiaohei 0.3.0-alpha.23 → 0.3.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.
Files changed (222) hide show
  1. package/README.md +39 -17
  2. package/THIRD_PARTY_NOTICES.md +41 -0
  3. package/lib/appearance.js +26 -10
  4. package/lib/blink.js +3 -3
  5. package/lib/bloub-heixiu/ambient.d.ts +3 -0
  6. package/lib/bloub-heixiu/ambient.js +12 -0
  7. package/lib/bloub-heixiu/character.d.ts +33 -0
  8. package/lib/bloub-heixiu/character.js +124 -0
  9. package/lib/bloub-heixiu/effects.d.ts +4 -0
  10. package/lib/bloub-heixiu/effects.js +91 -0
  11. package/lib/bloub-heixiu/flight.d.ts +15 -0
  12. package/lib/bloub-heixiu/flight.js +66 -0
  13. package/lib/bloub-heixiu/follow.d.ts +13 -0
  14. package/lib/bloub-heixiu/follow.js +23 -0
  15. package/lib/bloub-heixiu/layout.d.ts +18 -0
  16. package/lib/bloub-heixiu/layout.js +12 -0
  17. package/lib/bloub-heixiu/renderer.d.ts +16 -0
  18. package/lib/bloub-heixiu/renderer.js +94 -0
  19. package/lib/bloub-heixiu/runtime.d.ts +6 -0
  20. package/lib/bloub-heixiu/runtime.js +395 -0
  21. package/lib/bloub-heixiu/signals.d.ts +17 -0
  22. package/lib/bloub-heixiu/signals.js +53 -0
  23. package/lib/bloub-heixiu/styles.d.ts +2 -0
  24. package/lib/bloub-heixiu/styles.js +98 -0
  25. package/lib/chrome/accessibility.d.ts +1 -1
  26. package/lib/chrome/accessibility.js +5 -13
  27. package/lib/chrome/composer-avatar.d.ts +10 -0
  28. package/lib/chrome/composer-avatar.js +599 -0
  29. package/lib/chrome/composer-send-heixiu.d.ts +3 -0
  30. package/lib/chrome/composer-send-heixiu.js +143 -0
  31. package/lib/chrome/composer.d.ts +2 -2
  32. package/lib/chrome/composer.js +84 -95
  33. package/lib/chrome/controls.d.ts +8 -0
  34. package/lib/chrome/controls.js +348 -0
  35. package/lib/chrome/conversation-messages.d.ts +1 -1
  36. package/lib/chrome/conversation-messages.js +207 -9
  37. package/lib/chrome/conversation.d.ts +1 -1
  38. package/lib/chrome/conversation.js +6 -0
  39. package/lib/chrome/identity.js +4 -18
  40. package/lib/chrome/portal.d.ts +1 -1
  41. package/lib/chrome/portal.js +1 -1
  42. package/lib/chrome/sidebar.d.ts +1 -1
  43. package/lib/chrome/sidebar.js +134 -153
  44. package/lib/chrome/tokens.d.ts +1 -1
  45. package/lib/chrome/tokens.js +121 -68
  46. package/lib/chrome/workspace.d.ts +2 -2
  47. package/lib/chrome/workspace.js +35 -254
  48. package/lib/chrome.js +4 -0
  49. package/lib/client.d.ts +5 -12
  50. package/lib/client.js +6262 -4455
  51. package/lib/composer-avatar.d.ts +18 -0
  52. package/lib/composer-avatar.js +441 -0
  53. package/lib/composer-send-heixiu.d.ts +8 -0
  54. package/lib/composer-send-heixiu.js +133 -0
  55. package/lib/gaze.js +3 -3
  56. package/lib/generated-backgrounds.d.ts +14 -0
  57. package/lib/generated-backgrounds.js +14 -0
  58. package/lib/generated-character-motion.d.ts +4 -0
  59. package/lib/generated-character-motion.js +2 -0
  60. package/lib/generated-identity.d.ts +1 -0
  61. package/lib/generated-identity.js +1 -0
  62. package/lib/generated-keyart.d.ts +18 -3
  63. package/lib/generated-keyart.js +18 -3
  64. package/lib/host-contract.d.ts +9 -0
  65. package/lib/host-contract.js +13 -0
  66. package/lib/host-dom.d.ts +11 -0
  67. package/lib/host-dom.js +60 -0
  68. package/lib/loading-heixiu.js +3 -3
  69. package/lib/metallic-brand-mark.d.ts +10 -0
  70. package/lib/metallic-brand-mark.js +323 -0
  71. package/lib/plugin.d.ts +1 -1
  72. package/lib/plugin.js +31 -16
  73. package/lib/portal.js +11 -10
  74. package/lib/reactions.js +3 -3
  75. package/lib/scene/avatar-model.d.ts +6 -0
  76. package/lib/scene/avatar-model.js +646 -0
  77. package/lib/scene/avatar-motion.d.ts +3 -0
  78. package/lib/scene/avatar-motion.js +799 -0
  79. package/lib/scene/character-idle.d.ts +17 -0
  80. package/lib/scene/character-idle.js +65 -0
  81. package/lib/scene/character-layout.d.ts +24 -0
  82. package/lib/scene/character-layout.js +51 -0
  83. package/lib/scene/character-motion-clock.d.ts +18 -0
  84. package/lib/scene/character-motion-clock.js +57 -0
  85. package/lib/scene/character-motion-types.d.ts +12 -0
  86. package/lib/scene/character-motion-types.js +2 -0
  87. package/lib/scene/character-motion.d.ts +6 -0
  88. package/lib/scene/character-motion.js +126 -0
  89. package/lib/scene/character-poses.d.ts +26 -0
  90. package/lib/scene/character-poses.js +11 -0
  91. package/lib/scene/character-rig-renderer.d.ts +4 -0
  92. package/lib/scene/character-rig-renderer.js +85 -0
  93. package/lib/scene/character-rig.d.ts +20 -0
  94. package/lib/scene/character-rig.js +53 -0
  95. package/lib/scene/heixiu-feedback.d.ts +1 -1
  96. package/lib/scene/heixiu-feedback.js +0 -17
  97. package/lib/scene/pointer-bridge.d.ts +21 -0
  98. package/lib/scene/pointer-bridge.js +155 -0
  99. package/lib/scene/runtime.d.ts +12 -0
  100. package/lib/scene/runtime.js +87 -0
  101. package/lib/scene/styles.d.ts +7 -0
  102. package/lib/scene/styles.js +75 -0
  103. package/lib/scene/sylva-performance.d.ts +11 -0
  104. package/lib/scene/sylva-performance.js +58 -0
  105. package/lib/scene/wallpaper-character-styles.d.ts +3 -0
  106. package/lib/scene/wallpaper-character-styles.js +53 -0
  107. package/lib/scene/wallpaper-character.d.ts +4 -0
  108. package/lib/scene/wallpaper-character.js +213 -0
  109. package/lib/scene.d.ts +7 -13
  110. package/lib/scene.js +7 -1023
  111. package/lib/sidebar-glass.js +99 -36
  112. package/lib/sidebar-heixiu-roaming.js +5 -5
  113. package/lib/sidebar-reveal.d.ts +8 -0
  114. package/lib/sidebar-reveal.js +51 -0
  115. package/lib/state.d.ts +14 -2
  116. package/lib/state.js +12 -4
  117. package/lib/theme.js +2 -2
  118. package/lib/vendor/bloub/decor.d.ts +114 -0
  119. package/lib/vendor/bloub/decor.js +198 -0
  120. package/lib/vendor/bloub/engine.d.ts +213 -0
  121. package/lib/vendor/bloub/engine.js +452 -0
  122. package/lib/vendor/bloub/expressions.d.ts +35 -0
  123. package/lib/vendor/bloub/expressions.js +143 -0
  124. package/lib/vendor/bloub/eyefit.d.ts +75 -0
  125. package/lib/vendor/bloub/eyefit.js +393 -0
  126. package/lib/vendor/bloub/face.d.ts +63 -0
  127. package/lib/vendor/bloub/face.js +106 -0
  128. package/lib/vendor/bloub/math.d.ts +22 -0
  129. package/lib/vendor/bloub/math.js +36 -0
  130. package/lib/vendor/bloub/profiles.d.ts +8 -0
  131. package/lib/vendor/bloub/profiles.js +20 -0
  132. package/lib/vendor/bloub/repere.d.ts +30 -0
  133. package/lib/vendor/bloub/repere.js +32 -0
  134. package/lib/vendor/bloub/shape.d.ts +80 -0
  135. package/lib/vendor/bloub/shape.js +268 -0
  136. package/lib/vendor/bloub/skins.d.ts +36 -0
  137. package/lib/vendor/bloub/skins.js +74 -0
  138. package/lib/vendor/bloub/states.d.ts +87 -0
  139. package/lib/vendor/bloub/states.js +468 -0
  140. package/package.json +23 -6
  141. package/lib/appearance.d.ts.map +0 -1
  142. package/lib/appearance.js.map +0 -1
  143. package/lib/blink.d.ts.map +0 -1
  144. package/lib/blink.js.map +0 -1
  145. package/lib/boot-appearance.d.ts.map +0 -1
  146. package/lib/boot-appearance.js.map +0 -1
  147. package/lib/boot-loader.d.ts.map +0 -1
  148. package/lib/boot-loader.js.map +0 -1
  149. package/lib/chrome/accessibility.d.ts.map +0 -1
  150. package/lib/chrome/accessibility.js.map +0 -1
  151. package/lib/chrome/blink.d.ts.map +0 -1
  152. package/lib/chrome/blink.js.map +0 -1
  153. package/lib/chrome/composer.d.ts.map +0 -1
  154. package/lib/chrome/composer.js.map +0 -1
  155. package/lib/chrome/conversation-messages.d.ts.map +0 -1
  156. package/lib/chrome/conversation-messages.js.map +0 -1
  157. package/lib/chrome/conversation.d.ts.map +0 -1
  158. package/lib/chrome/conversation.js.map +0 -1
  159. package/lib/chrome/frames.d.ts.map +0 -1
  160. package/lib/chrome/frames.js.map +0 -1
  161. package/lib/chrome/gaze.d.ts.map +0 -1
  162. package/lib/chrome/gaze.js.map +0 -1
  163. package/lib/chrome/identity.d.ts.map +0 -1
  164. package/lib/chrome/identity.js.map +0 -1
  165. package/lib/chrome/overlays.d.ts.map +0 -1
  166. package/lib/chrome/overlays.js.map +0 -1
  167. package/lib/chrome/portal.d.ts.map +0 -1
  168. package/lib/chrome/portal.js.map +0 -1
  169. package/lib/chrome/reactions.d.ts.map +0 -1
  170. package/lib/chrome/reactions.js.map +0 -1
  171. package/lib/chrome/sidebar.d.ts.map +0 -1
  172. package/lib/chrome/sidebar.js.map +0 -1
  173. package/lib/chrome/tokens.d.ts.map +0 -1
  174. package/lib/chrome/tokens.js.map +0 -1
  175. package/lib/chrome/workspace.d.ts.map +0 -1
  176. package/lib/chrome/workspace.js.map +0 -1
  177. package/lib/chrome.d.ts.map +0 -1
  178. package/lib/chrome.js.map +0 -1
  179. package/lib/client.d.ts.map +0 -1
  180. package/lib/client.js.map +0 -7
  181. package/lib/gaze.d.ts.map +0 -1
  182. package/lib/gaze.js.map +0 -1
  183. package/lib/generated-identity.d.ts.map +0 -1
  184. package/lib/generated-identity.js.map +0 -1
  185. package/lib/generated-keyart.d.ts.map +0 -1
  186. package/lib/generated-keyart.js.map +0 -1
  187. package/lib/generated-loading-assets.d.ts.map +0 -1
  188. package/lib/generated-loading-assets.js.map +0 -1
  189. package/lib/generated-sidebar-assets.d.ts +0 -4
  190. package/lib/generated-sidebar-assets.d.ts.map +0 -1
  191. package/lib/generated-sidebar-assets.js +0 -4
  192. package/lib/generated-sidebar-assets.js.map +0 -1
  193. package/lib/heixiu-interactions.d.ts.map +0 -1
  194. package/lib/heixiu-interactions.js.map +0 -1
  195. package/lib/index.d.ts.map +0 -1
  196. package/lib/index.js.map +0 -1
  197. package/lib/loading-heixiu.d.ts.map +0 -1
  198. package/lib/loading-heixiu.js.map +0 -1
  199. package/lib/plugin.d.ts.map +0 -1
  200. package/lib/plugin.js.map +0 -1
  201. package/lib/portal.d.ts.map +0 -1
  202. package/lib/portal.js.map +0 -1
  203. package/lib/reactions.d.ts.map +0 -1
  204. package/lib/reactions.js.map +0 -1
  205. package/lib/scene/heixiu-feedback.d.ts.map +0 -1
  206. package/lib/scene/heixiu-feedback.js.map +0 -1
  207. package/lib/scene/heixiu-interactions.d.ts.map +0 -1
  208. package/lib/scene/heixiu-interactions.js.map +0 -1
  209. package/lib/scene/state-transitions.d.ts.map +0 -1
  210. package/lib/scene/state-transitions.js.map +0 -1
  211. package/lib/scene.d.ts.map +0 -1
  212. package/lib/scene.js.map +0 -1
  213. package/lib/sidebar-glass.d.ts.map +0 -1
  214. package/lib/sidebar-glass.js.map +0 -1
  215. package/lib/sidebar-heixiu-escape.d.ts.map +0 -1
  216. package/lib/sidebar-heixiu-escape.js.map +0 -1
  217. package/lib/sidebar-heixiu-roaming.d.ts.map +0 -1
  218. package/lib/sidebar-heixiu-roaming.js.map +0 -1
  219. package/lib/state.d.ts.map +0 -1
  220. package/lib/state.js.map +0 -1
  221. package/lib/theme.d.ts.map +0 -1
  222. package/lib/theme.js.map +0 -1
@@ -0,0 +1,18 @@
1
+ export declare const XIAOHEI_COMPOSER_AVATAR_CLASS = "xiaohei-composer-avatar";
2
+ export declare const XIAOHEI_COMPOSER_IDLE_MOTIONS: readonly ["glance-left", "glance-right", "nod", "perk", "peek", "double-blink", "ponder"];
3
+ export type XiaoheiComposerIdleMotion = typeof XIAOHEI_COMPOSER_IDLE_MOTIONS[number];
4
+ /** Pick a varied idle gesture without visibly repeating the previous one. */
5
+ export declare function resolveXiaoheiComposerIdleMotion(random?: () => number, previous?: XiaoheiComposerIdleMotion): XiaoheiComposerIdleMotion;
6
+ type ComposerPlacement = 'hero' | 'docked' | 'mobile';
7
+ interface ComposerAvatarPosition {
8
+ placement: ComposerPlacement;
9
+ scale: number;
10
+ x: number;
11
+ y: number;
12
+ }
13
+ /** Resolve the avatar target without coupling to generated Host class names. */
14
+ export declare function resolveXiaoheiComposerAvatarPosition(composer: Pick<DOMRect, 'bottom' | 'height' | 'left' | 'top' | 'width'>, viewportWidth: number, viewportHeight: number): ComposerAvatarPosition;
15
+ /** Mount one persistent avatar and let the native composer remain fully owned by DSH. */
16
+ export declare function installXiaoheiComposerAvatar(doc?: Document | undefined): () => void;
17
+ export {};
18
+ //# sourceMappingURL=composer-avatar.d.ts.map
@@ -0,0 +1,441 @@
1
+ import { XIAOHEI_BREAKFRAME_BLINK, XIAOHEI_BREAKFRAME_BRACE, XIAOHEI_BREAKFRAME_COMPLETE, XIAOHEI_BREAKFRAME_ERROR, XIAOHEI_BREAKFRAME_IDLE, XIAOHEI_BREAKFRAME_REACH, XIAOHEI_BREAKFRAME_THINKING, XIAOHEI_BREAKFRAME_WORKING, } from './generated-keyart.js';
2
+ import { XIAOHEI_HOST_SELECTORS } from './host-contract.js';
3
+ import { subscribeXiaoheiHostDom } from './host-dom.js';
4
+ export const XIAOHEI_COMPOSER_AVATAR_CLASS = 'xiaohei-composer-avatar';
5
+ export const XIAOHEI_COMPOSER_IDLE_MOTIONS = [
6
+ 'glance-left',
7
+ 'glance-right',
8
+ 'nod',
9
+ 'perk',
10
+ 'peek',
11
+ 'double-blink',
12
+ 'ponder',
13
+ ];
14
+ /** Pick a varied idle gesture without visibly repeating the previous one. */
15
+ export function resolveXiaoheiComposerIdleMotion(random = Math.random, previous) {
16
+ const candidates = previous === undefined
17
+ ? XIAOHEI_COMPOSER_IDLE_MOTIONS
18
+ : XIAOHEI_COMPOSER_IDLE_MOTIONS.filter(motion => motion !== previous);
19
+ const index = Math.min(candidates.length - 1, Math.max(0, Math.floor(random() * candidates.length)));
20
+ return candidates[index] ?? XIAOHEI_COMPOSER_IDLE_MOTIONS[0];
21
+ }
22
+ const XIAOHEI_IDLE_MOTION_DURATION = {
23
+ 'glance-left': 1_760,
24
+ 'glance-right': 1_760,
25
+ 'nod': 1_520,
26
+ 'perk': 1_640,
27
+ 'peek': 1_860,
28
+ 'double-blink': 760,
29
+ 'ponder': 2_260,
30
+ };
31
+ /** Resolve the avatar target without coupling to generated Host class names. */
32
+ export function resolveXiaoheiComposerAvatarPosition(composer, viewportWidth, viewportHeight) {
33
+ const avatarWidth = 172;
34
+ const avatarHeight = 184;
35
+ const mobile = viewportWidth <= 760;
36
+ const hero = composer.top < viewportHeight * 0.58;
37
+ let placement;
38
+ let scale;
39
+ let x;
40
+ let y;
41
+ if (mobile) {
42
+ placement = 'mobile';
43
+ scale = 0.72;
44
+ x = composer.left + 8;
45
+ y = composer.top - avatarHeight * scale - 8;
46
+ }
47
+ else if (hero) {
48
+ placement = 'hero';
49
+ scale = 0.92;
50
+ x = composer.left + composer.width / 2 - avatarWidth / 2;
51
+ y = composer.top - avatarHeight * scale - 12;
52
+ }
53
+ else {
54
+ placement = 'docked';
55
+ scale = 0.9;
56
+ x = composer.left - avatarWidth * scale - 16;
57
+ y = composer.bottom - avatarHeight * scale + 8;
58
+ }
59
+ return {
60
+ placement,
61
+ scale,
62
+ x: Math.round(Math.max(8, Math.min(viewportWidth - avatarWidth * scale - 8, x))),
63
+ y: Math.round(Math.max(8, Math.min(viewportHeight - avatarHeight * scale - 8, y))),
64
+ };
65
+ }
66
+ /** Mount one persistent avatar and let the native composer remain fully owned by DSH. */
67
+ export function installXiaoheiComposerAvatar(doc = typeof document === 'undefined' ? undefined : document) {
68
+ if (doc === undefined)
69
+ return () => { };
70
+ const win = doc.defaultView;
71
+ if (win === null)
72
+ return () => { };
73
+ doc.querySelector(`.${XIAOHEI_COMPOSER_AVATAR_CLASS}`)?.remove();
74
+ const avatar = doc.createElement('span');
75
+ avatar.className = XIAOHEI_COMPOSER_AVATAR_CLASS;
76
+ avatar.setAttribute('aria-hidden', 'true');
77
+ avatar.dataset.xiaoheiPortrait = 'idle';
78
+ const effectsBack = createAvatarLayer(doc, 'effects-back');
79
+ const ringBack = createAvatarLayer(doc, 'ring-back');
80
+ const circle = createAvatarLayer(doc, 'circle');
81
+ circle.append(...createPortraitImages(doc, 'inside'));
82
+ const overflow = createAvatarLayer(doc, 'overflow');
83
+ overflow.append(...createPortraitImages(doc, 'overflow'));
84
+ const ringFront = createAvatarLayer(doc, 'ring-front');
85
+ const breakoutRight = createAvatarLayer(doc, 'breakout-right');
86
+ breakoutRight.append(...createBreakoutRightImages(doc));
87
+ const effectsFront = createAvatarLayer(doc, 'effects-front');
88
+ avatar.append(effectsBack, ringBack, circle, overflow, ringFront, breakoutRight, effectsFront);
89
+ doc.body.append(avatar);
90
+ let disposed = false;
91
+ let composer;
92
+ let resizeObserver;
93
+ let frameRequest;
94
+ let pointerRequest;
95
+ let reactionTimer;
96
+ let reactionStageTimers = [];
97
+ let stateTimer;
98
+ let blinkTimer;
99
+ let blinkResetTimer;
100
+ let idleTimer;
101
+ let idleResetTimer;
102
+ let portraitRequest = 0;
103
+ let reactionRequest = 0;
104
+ let previousIdleMotion;
105
+ let desiredHostPortrait = resolveDisplayPortrait(doc.documentElement.dataset.xiaoheiState);
106
+ let layoutReady = false;
107
+ let idlePortraitReady = false;
108
+ let pointerX = 0;
109
+ let pointerY = 0;
110
+ const revealAvatar = () => {
111
+ if (layoutReady && idlePortraitReady)
112
+ avatar.dataset.xiaoheiAvatarReady = 'true';
113
+ };
114
+ const portraitImages = Array.from(avatar.querySelectorAll('[data-xiaohei-portrait-frame]'));
115
+ const imagesByState = new Map();
116
+ for (const image of portraitImages) {
117
+ const state = image.dataset.xiaoheiPortraitFrame;
118
+ const images = imagesByState.get(state) ?? [];
119
+ images.push(image);
120
+ imagesByState.set(state, images);
121
+ }
122
+ const decodedStates = new Set();
123
+ const decodeTasks = new Map();
124
+ const ensurePortraitDecoded = (state) => {
125
+ if (decodedStates.has(state))
126
+ return Promise.resolve(true);
127
+ const existing = decodeTasks.get(state);
128
+ if (existing !== undefined)
129
+ return existing;
130
+ const task = Promise.all((imagesByState.get(state) ?? []).map(decodePortraitImage))
131
+ .then((results) => {
132
+ const decoded = results.length > 0 && results.every(Boolean);
133
+ if (decoded)
134
+ decodedStates.add(state);
135
+ return decoded;
136
+ });
137
+ decodeTasks.set(state, task);
138
+ return task;
139
+ };
140
+ const showPortrait = async (state) => {
141
+ const request = ++portraitRequest;
142
+ const decoded = await ensurePortraitDecoded(state);
143
+ if (disposed || request !== portraitRequest || !decoded)
144
+ return;
145
+ avatar.dataset.xiaoheiPortrait = state;
146
+ };
147
+ const applyHostPortrait = () => {
148
+ if (avatar.dataset.xiaoheiAvatarReaction !== undefined)
149
+ return;
150
+ void showPortrait(desiredHostPortrait);
151
+ };
152
+ const scheduleHostPortrait = () => {
153
+ desiredHostPortrait = resolveDisplayPortrait(doc.documentElement.dataset.xiaoheiState);
154
+ if (stateTimer !== undefined)
155
+ clearTimeout(stateTimer);
156
+ stateTimer = setTimeout(() => {
157
+ stateTimer = undefined;
158
+ applyHostPortrait();
159
+ }, 150);
160
+ };
161
+ const measure = () => {
162
+ frameRequest = undefined;
163
+ if (disposed || composer === undefined || !composer.isConnected) {
164
+ avatar.removeAttribute('data-xiaohei-avatar-ready');
165
+ layoutReady = false;
166
+ return;
167
+ }
168
+ const bounds = composer.getBoundingClientRect();
169
+ const viewport = win.visualViewport;
170
+ const viewportWidth = viewport?.width ?? win.innerWidth;
171
+ const viewportHeight = viewport?.height ?? win.innerHeight;
172
+ const position = resolveXiaoheiComposerAvatarPosition(bounds, viewportWidth, viewportHeight);
173
+ avatar.style.setProperty('--xiaohei-composer-avatar-x', `${position.x}px`);
174
+ avatar.style.setProperty('--xiaohei-composer-avatar-y', `${position.y}px`);
175
+ avatar.style.setProperty('--xiaohei-composer-avatar-scale', String(position.scale));
176
+ avatar.dataset.xiaoheiAvatarPlacement = position.placement;
177
+ if (!layoutReady) {
178
+ avatar.getBoundingClientRect();
179
+ layoutReady = true;
180
+ revealAvatar();
181
+ }
182
+ };
183
+ const scheduleMeasure = () => {
184
+ if (disposed || frameRequest !== undefined)
185
+ return;
186
+ frameRequest = win.requestAnimationFrame(measure);
187
+ };
188
+ const bindComposer = () => {
189
+ const next = doc.querySelector(XIAOHEI_HOST_SELECTORS.composerCard) ?? undefined;
190
+ if (next !== composer) {
191
+ resizeObserver?.disconnect();
192
+ resizeObserver = undefined;
193
+ composer = next;
194
+ if (composer !== undefined && typeof win.ResizeObserver === 'function') {
195
+ resizeObserver = new win.ResizeObserver(scheduleMeasure);
196
+ resizeObserver.observe(composer);
197
+ }
198
+ }
199
+ scheduleMeasure();
200
+ };
201
+ const handleViewportChange = () => scheduleMeasure();
202
+ const renderPointerResponse = () => {
203
+ pointerRequest = undefined;
204
+ const bounds = avatar.getBoundingClientRect();
205
+ const centerX = bounds.left + bounds.width / 2;
206
+ const centerY = bounds.top + bounds.height / 2;
207
+ const deltaX = pointerX - centerX;
208
+ const deltaY = pointerY - centerY;
209
+ const distance = Math.hypot(deltaX, deltaY);
210
+ const proximity = Math.max(0, 1 - distance / 220);
211
+ avatar.style.setProperty('--xiaohei-avatar-look-x', `${Math.max(-2.5, Math.min(2.5, deltaX / 62)) * proximity}px`);
212
+ avatar.style.setProperty('--xiaohei-avatar-look-y', `${Math.max(-1.6, Math.min(1.6, deltaY / 88)) * proximity}px`);
213
+ avatar.style.setProperty('--xiaohei-avatar-look-rotation', `${Math.max(-1.2, Math.min(1.2, deltaX / 170)) * proximity}deg`);
214
+ avatar.toggleAttribute('data-xiaohei-avatar-aware', proximity > 0);
215
+ avatar.toggleAttribute('data-xiaohei-avatar-near', distance < 104);
216
+ };
217
+ const handlePointerMove = (event) => {
218
+ pointerX = event.clientX;
219
+ pointerY = event.clientY;
220
+ if (pointerRequest === undefined)
221
+ pointerRequest = win.requestAnimationFrame(renderPointerResponse);
222
+ };
223
+ const handleAvatarClick = () => {
224
+ if (avatar.dataset.xiaoheiAvatarReaction !== undefined)
225
+ return;
226
+ if (reactionTimer !== undefined)
227
+ clearTimeout(reactionTimer);
228
+ for (const timer of reactionStageTimers)
229
+ clearTimeout(timer);
230
+ reactionStageTimers = [];
231
+ const request = ++reactionRequest;
232
+ if (idleResetTimer !== undefined)
233
+ clearTimeout(idleResetTimer);
234
+ idleResetTimer = undefined;
235
+ delete avatar.dataset.xiaoheiAvatarIdle;
236
+ avatar.dataset.xiaoheiAvatarReaction = 'touch';
237
+ avatar.dataset.xiaoheiAvatarReactionStage = 'anticipate';
238
+ reactionStageTimers.push(setTimeout(() => {
239
+ if (request !== reactionRequest)
240
+ return;
241
+ avatar.dataset.xiaoheiAvatarReactionStage = 'brace';
242
+ void showPortrait('brace');
243
+ }, 170), setTimeout(() => {
244
+ if (request !== reactionRequest)
245
+ return;
246
+ avatar.dataset.xiaoheiAvatarReactionStage = 'break';
247
+ void showPortrait('reach');
248
+ }, 430), setTimeout(() => {
249
+ if (request !== reactionRequest)
250
+ return;
251
+ avatar.dataset.xiaoheiAvatarReactionStage = 'recover';
252
+ void showPortrait('brace');
253
+ }, 820), setTimeout(() => {
254
+ if (request !== reactionRequest)
255
+ return;
256
+ avatar.dataset.xiaoheiAvatarReactionStage = 'settle';
257
+ void showPortrait(desiredHostPortrait);
258
+ }, 1_090));
259
+ reactionTimer = setTimeout(() => {
260
+ if (request !== reactionRequest)
261
+ return;
262
+ delete avatar.dataset.xiaoheiAvatarReaction;
263
+ delete avatar.dataset.xiaoheiAvatarReactionStage;
264
+ reactionTimer = undefined;
265
+ reactionStageTimers = [];
266
+ applyHostPortrait();
267
+ }, 1_340);
268
+ };
269
+ const scheduleBlink = () => {
270
+ if (disposed || blinkTimer !== undefined)
271
+ return;
272
+ blinkTimer = setTimeout(() => {
273
+ blinkTimer = undefined;
274
+ if (!doc.hidden
275
+ && avatar.dataset.xiaoheiPortrait === 'idle'
276
+ && avatar.dataset.xiaoheiAvatarReaction === undefined
277
+ && avatar.dataset.xiaoheiAvatarIdle === undefined) {
278
+ void ensurePortraitDecoded('blink').then((decoded) => {
279
+ if (!decoded || disposed || avatar.dataset.xiaoheiPortrait !== 'idle')
280
+ return;
281
+ avatar.dataset.xiaoheiAvatarBlink = 'true';
282
+ if (blinkResetTimer !== undefined)
283
+ clearTimeout(blinkResetTimer);
284
+ blinkResetTimer = setTimeout(() => {
285
+ delete avatar.dataset.xiaoheiAvatarBlink;
286
+ blinkResetTimer = undefined;
287
+ }, 130);
288
+ });
289
+ }
290
+ scheduleBlink();
291
+ }, 4200 + Math.random() * 3600);
292
+ };
293
+ const scheduleIdleMotion = () => {
294
+ if (disposed || idleTimer !== undefined)
295
+ return;
296
+ idleTimer = setTimeout(() => {
297
+ idleTimer = undefined;
298
+ if (!doc.hidden
299
+ && desiredHostPortrait === 'idle'
300
+ && avatar.dataset.xiaoheiAvatarReaction === undefined) {
301
+ const motion = resolveXiaoheiComposerIdleMotion(Math.random, previousIdleMotion);
302
+ previousIdleMotion = motion;
303
+ avatar.dataset.xiaoheiAvatarIdle = motion;
304
+ if (motion === 'ponder')
305
+ void showPortrait('thinking');
306
+ idleResetTimer = setTimeout(() => {
307
+ delete avatar.dataset.xiaoheiAvatarIdle;
308
+ idleResetTimer = undefined;
309
+ if (motion === 'ponder')
310
+ applyHostPortrait();
311
+ }, XIAOHEI_IDLE_MOTION_DURATION[motion]);
312
+ }
313
+ scheduleIdleMotion();
314
+ }, 4_800 + Math.random() * 5_600);
315
+ };
316
+ const handleTransitionEnd = (event) => {
317
+ if (composer === undefined)
318
+ return;
319
+ const target = event.target;
320
+ if (target instanceof win.Node && (target === composer || composer.contains(target)))
321
+ scheduleMeasure();
322
+ };
323
+ const unsubscribeHostDom = subscribeXiaoheiHostDom(doc, bindComposer);
324
+ const stateObserver = new win.MutationObserver(scheduleHostPortrait);
325
+ stateObserver.observe(doc.documentElement, {
326
+ attributes: true,
327
+ attributeFilter: ['data-xiaohei-state'],
328
+ });
329
+ win.addEventListener('resize', handleViewportChange, { passive: true });
330
+ win.addEventListener('pointermove', handlePointerMove, { passive: true });
331
+ win.visualViewport?.addEventListener('resize', handleViewportChange, { passive: true });
332
+ win.visualViewport?.addEventListener('scroll', handleViewportChange, { passive: true });
333
+ doc.addEventListener('transitionend', handleTransitionEnd, true);
334
+ avatar.addEventListener('click', handleAvatarClick);
335
+ bindComposer();
336
+ void ensurePortraitDecoded('idle').then((decoded) => {
337
+ if (!decoded || disposed)
338
+ return;
339
+ idlePortraitReady = true;
340
+ revealAvatar();
341
+ });
342
+ for (const [state] of PORTRAIT_SOURCES) {
343
+ if (state !== 'idle')
344
+ void ensurePortraitDecoded(state);
345
+ }
346
+ scheduleHostPortrait();
347
+ scheduleBlink();
348
+ scheduleIdleMotion();
349
+ return () => {
350
+ disposed = true;
351
+ unsubscribeHostDom();
352
+ stateObserver.disconnect();
353
+ resizeObserver?.disconnect();
354
+ if (frameRequest !== undefined)
355
+ win.cancelAnimationFrame(frameRequest);
356
+ if (pointerRequest !== undefined)
357
+ win.cancelAnimationFrame(pointerRequest);
358
+ if (reactionTimer !== undefined)
359
+ clearTimeout(reactionTimer);
360
+ for (const timer of reactionStageTimers)
361
+ clearTimeout(timer);
362
+ if (stateTimer !== undefined)
363
+ clearTimeout(stateTimer);
364
+ if (blinkTimer !== undefined)
365
+ clearTimeout(blinkTimer);
366
+ if (blinkResetTimer !== undefined)
367
+ clearTimeout(blinkResetTimer);
368
+ if (idleTimer !== undefined)
369
+ clearTimeout(idleTimer);
370
+ if (idleResetTimer !== undefined)
371
+ clearTimeout(idleResetTimer);
372
+ win.removeEventListener('resize', handleViewportChange);
373
+ win.removeEventListener('pointermove', handlePointerMove);
374
+ win.visualViewport?.removeEventListener('resize', handleViewportChange);
375
+ win.visualViewport?.removeEventListener('scroll', handleViewportChange);
376
+ doc.removeEventListener('transitionend', handleTransitionEnd, true);
377
+ avatar.removeEventListener('click', handleAvatarClick);
378
+ avatar.remove();
379
+ };
380
+ }
381
+ function createAvatarImage(doc, source, state) {
382
+ const image = doc.createElement('img');
383
+ image.className = `${XIAOHEI_COMPOSER_AVATAR_CLASS}__image ${XIAOHEI_COMPOSER_AVATAR_CLASS}__image--${state}`;
384
+ image.alt = '';
385
+ image.decoding = 'async';
386
+ const portraitState = state.slice(state.lastIndexOf('-') + 1);
387
+ image.dataset.xiaoheiPortraitFrame = portraitState;
388
+ image.fetchPriority = portraitState === 'idle' ? 'high' : 'low';
389
+ image.src = source;
390
+ return image;
391
+ }
392
+ function resolveDisplayPortrait(state) {
393
+ switch (state) {
394
+ case 'thinking':
395
+ case 'waiting':
396
+ return 'thinking';
397
+ case 'tool':
398
+ case 'streaming':
399
+ return 'working';
400
+ case 'complete':
401
+ return 'complete';
402
+ case 'error':
403
+ return 'error';
404
+ default:
405
+ return 'idle';
406
+ }
407
+ }
408
+ async function decodePortraitImage(image) {
409
+ try {
410
+ if (typeof image.decode === 'function')
411
+ await image.decode();
412
+ return image.naturalWidth > 0 || image.complete;
413
+ }
414
+ catch {
415
+ return image.complete && image.naturalWidth > 0;
416
+ }
417
+ }
418
+ const PORTRAIT_SOURCES = [
419
+ ['idle', XIAOHEI_BREAKFRAME_IDLE],
420
+ ['blink', XIAOHEI_BREAKFRAME_BLINK],
421
+ ['brace', XIAOHEI_BREAKFRAME_BRACE],
422
+ ['reach', XIAOHEI_BREAKFRAME_REACH],
423
+ ['thinking', XIAOHEI_BREAKFRAME_THINKING],
424
+ ['working', XIAOHEI_BREAKFRAME_WORKING],
425
+ ['complete', XIAOHEI_BREAKFRAME_COMPLETE],
426
+ ['error', XIAOHEI_BREAKFRAME_ERROR],
427
+ ];
428
+ function createPortraitImages(doc, layer) {
429
+ return PORTRAIT_SOURCES.map(([state, source]) => createAvatarImage(doc, source, `${layer}-${state}`));
430
+ }
431
+ function createBreakoutRightImages(doc) {
432
+ return PORTRAIT_SOURCES
433
+ .filter(([state]) => state === 'brace' || state === 'reach')
434
+ .map(([state, source]) => createAvatarImage(doc, source, `breakout-right-${state}`));
435
+ }
436
+ function createAvatarLayer(doc, name) {
437
+ const layer = doc.createElement('span');
438
+ layer.className = `${XIAOHEI_COMPOSER_AVATAR_CLASS}__${name}`;
439
+ return layer;
440
+ }
441
+ //# sourceMappingURL=composer-avatar.js.map
@@ -0,0 +1,8 @@
1
+ /** Keep idle blinks sparse without making their rhythm mechanical. */
2
+ export declare function resolveXiaoheiSendBlinkDelay(random?: () => number): number;
3
+ /**
4
+ * Turn the Host's real primary button into Heixiu without replacing its click,
5
+ * keyboard, disabled, tooltip, or accessible-name behaviour.
6
+ */
7
+ export declare function installXiaoheiComposerSendHeixiu(doc?: Document | undefined): () => void;
8
+ //# sourceMappingURL=composer-send-heixiu.d.ts.map
@@ -0,0 +1,133 @@
1
+ import { XIAOHEI_HEIXIU_BLINK, XIAOHEI_HEIXIU_OPEN } from './generated-keyart.js';
2
+ import { XIAOHEI_HOST_SELECTORS } from './host-contract.js';
3
+ import { subscribeXiaoheiHostDom } from './host-dom.js';
4
+ const SEND_BUTTON_SELECTOR = ':scope > :last-child > :last-child > button:last-child';
5
+ const SEND_ATTRIBUTE = 'data-xiaohei-send-heixiu';
6
+ const BLINK_ATTRIBUTE = 'data-xiaohei-blink';
7
+ const HEIXIU_CLASS = 'xiaohei-composer-send-heixiu';
8
+ const BLINK_DELAY_MIN_MS = 4600;
9
+ const BLINK_DELAY_MAX_MS = 8200;
10
+ const BLINK_CLOSED_MS = 128;
11
+ /** Keep idle blinks sparse without making their rhythm mechanical. */
12
+ export function resolveXiaoheiSendBlinkDelay(random = Math.random) {
13
+ const sample = Math.min(1, Math.max(0, random()));
14
+ return Math.round(BLINK_DELAY_MIN_MS + sample * (BLINK_DELAY_MAX_MS - BLINK_DELAY_MIN_MS));
15
+ }
16
+ function createFrame(doc, source, className) {
17
+ const image = doc.createElement('img');
18
+ image.className = className;
19
+ image.src = source;
20
+ image.alt = '';
21
+ image.draggable = false;
22
+ return image;
23
+ }
24
+ function createHeixiu(doc) {
25
+ const element = doc.createElement('span');
26
+ element.className = HEIXIU_CLASS;
27
+ element.setAttribute('aria-hidden', 'true');
28
+ element.append(createFrame(doc, XIAOHEI_HEIXIU_OPEN, `${HEIXIU_CLASS}__open`), createFrame(doc, XIAOHEI_HEIXIU_BLINK, `${HEIXIU_CLASS}__blink`));
29
+ return element;
30
+ }
31
+ /** The Host renders a rect only when the primary action has become Stop. */
32
+ function isSendAction(button) {
33
+ const hostGlyph = [...button.children].find(child => child.localName === 'svg');
34
+ return hostGlyph !== undefined && hostGlyph.querySelector('rect') === null;
35
+ }
36
+ /**
37
+ * Turn the Host's real primary button into Heixiu without replacing its click,
38
+ * keyboard, disabled, tooltip, or accessible-name behaviour.
39
+ */
40
+ export function installXiaoheiComposerSendHeixiu(doc = typeof document === 'undefined' ? undefined : document) {
41
+ if (doc?.body === undefined || doc.defaultView === null)
42
+ return () => { };
43
+ const win = doc.defaultView;
44
+ const reducedMotion = win.matchMedia('(prefers-reduced-motion: reduce)');
45
+ const decorations = new Map();
46
+ let blinkTimer = 0;
47
+ let reopenTimer = 0;
48
+ const canBlink = () => (decorations.size > 0
49
+ && doc.visibilityState !== 'hidden'
50
+ && !reducedMotion.matches);
51
+ const setClosed = (closed) => {
52
+ for (const decoration of decorations.values()) {
53
+ if (closed)
54
+ decoration.setAttribute(BLINK_ATTRIBUTE, 'closed');
55
+ else
56
+ decoration.removeAttribute(BLINK_ATTRIBUTE);
57
+ }
58
+ };
59
+ const clearTimers = () => {
60
+ if (blinkTimer !== 0)
61
+ win.clearTimeout(blinkTimer);
62
+ if (reopenTimer !== 0)
63
+ win.clearTimeout(reopenTimer);
64
+ blinkTimer = 0;
65
+ reopenTimer = 0;
66
+ };
67
+ const scheduleBlink = () => {
68
+ if (!canBlink() || blinkTimer !== 0 || reopenTimer !== 0)
69
+ return;
70
+ blinkTimer = win.setTimeout(() => {
71
+ blinkTimer = 0;
72
+ if (!canBlink())
73
+ return;
74
+ setClosed(true);
75
+ reopenTimer = win.setTimeout(() => {
76
+ reopenTimer = 0;
77
+ setClosed(false);
78
+ scheduleBlink();
79
+ }, BLINK_CLOSED_MS);
80
+ }, resolveXiaoheiSendBlinkDelay());
81
+ };
82
+ const restartBlink = () => {
83
+ clearTimers();
84
+ setClosed(false);
85
+ scheduleBlink();
86
+ };
87
+ const detach = (button, decoration) => {
88
+ decoration.remove();
89
+ button.removeAttribute(SEND_ATTRIBUTE);
90
+ decorations.delete(button);
91
+ };
92
+ const reconcile = () => {
93
+ const candidates = new Set();
94
+ for (const card of doc.querySelectorAll(XIAOHEI_HOST_SELECTORS.composerCard)) {
95
+ const button = card.querySelector(SEND_BUTTON_SELECTOR);
96
+ if (button !== null && isSendAction(button))
97
+ candidates.add(button);
98
+ }
99
+ const hadDecorations = decorations.size > 0;
100
+ for (const [button, decoration] of decorations) {
101
+ if (candidates.has(button) && decoration.parentElement === button)
102
+ continue;
103
+ detach(button, decoration);
104
+ }
105
+ for (const button of candidates) {
106
+ if (decorations.has(button))
107
+ continue;
108
+ const decoration = createHeixiu(doc);
109
+ button.setAttribute(SEND_ATTRIBUTE, 'true');
110
+ button.insertBefore(decoration, button.firstChild);
111
+ decorations.set(button, decoration);
112
+ }
113
+ if (!hadDecorations && decorations.size > 0)
114
+ scheduleBlink();
115
+ else if (hadDecorations && decorations.size === 0)
116
+ restartBlink();
117
+ };
118
+ const onEnvironmentChange = () => restartBlink();
119
+ reconcile();
120
+ const unsubscribeHostDom = subscribeXiaoheiHostDom(doc, reconcile);
121
+ doc.addEventListener('visibilitychange', onEnvironmentChange);
122
+ reducedMotion.addEventListener('change', onEnvironmentChange);
123
+ return () => {
124
+ clearTimers();
125
+ unsubscribeHostDom();
126
+ doc.removeEventListener('visibilitychange', onEnvironmentChange);
127
+ reducedMotion.removeEventListener('change', onEnvironmentChange);
128
+ for (const [button, decoration] of decorations)
129
+ detach(button, decoration);
130
+ decorations.clear();
131
+ };
132
+ }
133
+ //# sourceMappingURL=composer-send-heixiu.js.map
package/lib/gaze.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { XIAOHEI_IDLE_EYE_BASE } from './generated-keyart.js';
2
2
  import { XIAOHEI_GAZE_CSS } from './chrome/gaze.js';
3
+ import { subscribeXiaoheiHostDom } from './host-dom.js';
3
4
  import { XIAOHEI_PORTAL_ACTIVITY_EVENT, XIAOHEI_PORTAL_LAYER_ID, } from './portal.js';
4
5
  export const XIAOHEI_GAZE_STYLE_ID = 'dsh-theme-xiaohei/gaze-style';
5
6
  const POINTER_FULL_GAZE_RADIUS_PX = 360;
@@ -106,8 +107,7 @@ export function installXiaoheiGaze(doc = typeof document === 'undefined' ? undef
106
107
  };
107
108
  const onPreferenceChange = () => wake();
108
109
  const onStateChange = () => wake();
109
- const mountObserver = new win.MutationObserver(attach);
110
- mountObserver.observe(doc.body, { childList: true, subtree: true });
110
+ const unsubscribeHostDom = subscribeXiaoheiHostDom(doc, attach);
111
111
  const stateObserver = new win.MutationObserver(onStateChange);
112
112
  stateObserver.observe(doc.documentElement, {
113
113
  attributes: true,
@@ -124,7 +124,7 @@ export function installXiaoheiGaze(doc = typeof document === 'undefined' ? undef
124
124
  disposed = true;
125
125
  if (frameId !== 0)
126
126
  win.cancelAnimationFrame(frameId);
127
- mountObserver.disconnect();
127
+ unsubscribeHostDom();
128
128
  stateObserver.disconnect();
129
129
  doc.removeEventListener('pointermove', onPointerMove);
130
130
  doc.removeEventListener('pointerleave', clearPointer);
@@ -0,0 +1,14 @@
1
+ /** Generated by scripts/embed-assets.mjs for atmosphere and independent lossless character poses. */
2
+ export declare const XIAOHEI_WALLPAPER_LIGHT: string;
3
+ export declare const XIAOHEI_WALLPAPER_DARK: string;
4
+ export declare const XIAOHEI_SEATED_LIGHT: string;
5
+ export declare const XIAOHEI_SEATED_DARK: string;
6
+ export declare const XIAOHEI_PEEK_LIGHT: string;
7
+ export declare const XIAOHEI_PEEK_DARK: string;
8
+ export declare const XIAOHEI_CHIN_LIGHT: string;
9
+ export declare const XIAOHEI_CHIN_DARK: string;
10
+ export declare const XIAOHEI_DOZE_LIGHT: string;
11
+ export declare const XIAOHEI_DOZE_DARK: string;
12
+ export declare const XIAOHEI_HALFPEEK_LIGHT: string;
13
+ export declare const XIAOHEI_HALFPEEK_DARK: string;
14
+ //# sourceMappingURL=generated-backgrounds.d.ts.map