@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,16 @@
1
+ import type { BotFrame } from '../vendor/bloub/engine.js';
2
+ import { type HeixiuReaction } from './character.js';
3
+ /** A hollow eye, not a hole in the body: wallpaper never bleeds through it. */
4
+ export declare function heixiuEyeRadii(width: number, height: number): {
5
+ rx: number;
6
+ ry: number;
7
+ };
8
+ /** Filled upstream eyes may touch; hollow rings need a small readable gap. */
9
+ export declare function heixiuEyeSeparation(eyes: BotFrame['eyes']): number;
10
+ /** Persistent SVG nodes: frame updates never replace HTML or retrigger host observers. */
11
+ export declare function createHeixiuRenderer(doc: Document): {
12
+ svg: SVGSVGElement;
13
+ render: (frame: BotFrame, reaction?: HeixiuReaction) => void;
14
+ prepareEffects: () => void;
15
+ };
16
+ //# sourceMappingURL=renderer.d.ts.map
@@ -0,0 +1,94 @@
1
+ import { DEMI_VIEWBOX } from '../vendor/bloub/repere.js';
2
+ import { HEIXIU_FOOT, HEIXIU_REST } from './character.js';
3
+ import { createHeixiuEffects } from './effects.js';
4
+ const NS = 'http://www.w3.org/2000/svg';
5
+ let serial = 0;
6
+ /** A hollow eye, not a hole in the body: wallpaper never bleeds through it. */
7
+ export function heixiuEyeRadii(width, height) {
8
+ // Upstream special poses bring their eyes close together. Keep the large
9
+ // resting rings, but taper their width in small poses to avoid fused eyes.
10
+ const roomy = Math.max(0, Math.min(1, (width - 20) / 12));
11
+ return { rx: Math.max(0.01, Math.min(width, 38) * (1 + .15 * roomy)), ry: Math.max(0.01, Math.min(height, 62) * 0.96) };
12
+ }
13
+ /** Filled upstream eyes may touch; hollow rings need a small readable gap. */
14
+ export function heixiuEyeSeparation(eyes) {
15
+ if (eyes.length !== 2)
16
+ return 1;
17
+ const matrices = eyes.map(eye => eye.openMatrix.slice(7, -1).split(/[ ,]+/).map(Number));
18
+ const [a, b] = matrices;
19
+ if (!a || !b || a.length !== 6 || b.length !== 6 || [...a, ...b].some(n => !Number.isFinite(n)))
20
+ return 1;
21
+ const dx = b[4] - a[4], dy = b[5] - a[5], distance = Math.hypot(dx, dy);
22
+ if (distance < 1)
23
+ return .1;
24
+ const nx = dx / distance, ny = dy / distance;
25
+ const support = eyes.reduce((sum, eye, i) => {
26
+ const m = matrices[i], { rx, ry } = heixiuEyeRadii(eye.width, eye.height);
27
+ return sum + Math.hypot(rx * (m[0] * nx + m[1] * ny), ry * (m[2] * nx + m[3] * ny));
28
+ }, 0);
29
+ return Math.min(1, Math.max(.1, (distance - 10) / Math.max(1, support)));
30
+ }
31
+ /** Persistent SVG nodes: frame updates never replace HTML or retrigger host observers. */
32
+ export function createHeixiuRenderer(doc) {
33
+ const el = (name) => doc.createElementNS(NS, name);
34
+ const attrs = (node, values) => {
35
+ for (const [key, value] of Object.entries(values)) {
36
+ const text = String(value);
37
+ if (node.getAttribute(key) !== text)
38
+ node.setAttribute(key, text);
39
+ }
40
+ };
41
+ const svg = el('svg'), defs = el('defs'), clip = el('clipPath'), clipBody = el('path');
42
+ const id = `xiaohei-bloub-${++serial}`;
43
+ attrs(svg, { viewBox: `${-DEMI_VIEWBOX} ${-DEMI_VIEWBOX} ${DEMI_VIEWBOX * 2} ${DEMI_VIEWBOX * 2}`, 'aria-hidden': 'true', focusable: 'false' });
44
+ attrs(clip, { id: `${id}-body`, clipPathUnits: 'userSpaceOnUse' });
45
+ clip.append(clipBody);
46
+ defs.append(clip);
47
+ const bodyGroup = el('g'), body = el('path'), eyesGroup = el('g');
48
+ attrs(body, { fill: 'var(--heixiu-ink)', stroke: 'var(--heixiu-outline)', 'stroke-width': 1.2, 'data-heixiu-body': '' });
49
+ attrs(eyesGroup, { 'clip-path': `url(#${id}-body)`, fill: 'none', stroke: '#f5f3db', 'stroke-width': 10.5 });
50
+ const eyes = [el('path'), el('path')];
51
+ for (const eye of eyes)
52
+ attrs(eye, { 'data-heixiu-eye': '', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' });
53
+ eyesGroup.append(...eyes);
54
+ bodyGroup.append(body, eyesGroup);
55
+ const character = el('g');
56
+ attrs(character, { 'data-heixiu-character': '' });
57
+ character.append(bodyGroup);
58
+ svg.append(defs, character);
59
+ let effects;
60
+ function prepareEffects() {
61
+ if (effects)
62
+ return;
63
+ const behind = el('g'), front = el('g');
64
+ character.insertBefore(behind, bodyGroup);
65
+ character.append(front);
66
+ effects = createHeixiuEffects(doc, id, defs, behind, front, bodyGroup);
67
+ }
68
+ function render(frame, reaction = HEIXIU_REST) {
69
+ attrs(character, { transform: `translate(${reaction.x} ${reaction.y + HEIXIU_FOOT}) rotate(${reaction.roll}) scale(${reaction.sx} ${reaction.sy}) translate(0 ${-HEIXIU_FOOT})` });
70
+ attrs(body, { d: frame.bodyPath });
71
+ attrs(clipBody, { d: frame.bodyPath });
72
+ attrs(bodyGroup, { opacity: frame.bodyAlpha });
73
+ const eyeScale = heixiuEyeSeparation(frame.eyes);
74
+ for (const [i, node] of eyes.entries()) {
75
+ const eye = frame.eyes[i];
76
+ if (!eye) {
77
+ attrs(node, { opacity: 0 });
78
+ continue;
79
+ }
80
+ const radii = heixiuEyeRadii(eye.width, eye.height);
81
+ const rx = radii.rx * eyeScale, ry = radii.ry * eyeScale;
82
+ const lid = Math.max(0, Math.min(eye.lid, reaction.lid));
83
+ // Both halves meet in a curved, constant-weight lid. No flattened matrix,
84
+ // double-outline flash, vanished stroke, or full-body opacity swapping.
85
+ const curve = -rx * .26 * reaction.cheer * (1 - lid);
86
+ const top = curve - ry * lid * 1.3333, bottom = curve + ry * lid * 1.3333;
87
+ const d = `M${-rx} 0C${-rx} ${top} ${rx} ${top} ${rx} 0C${rx} ${bottom} ${-rx} ${bottom} ${-rx} 0Z`;
88
+ attrs(node, { d, transform: eye.openMatrix, opacity: eye.alpha, 'stroke-width': Math.min(10.5, rx * .28) });
89
+ }
90
+ effects?.(frame);
91
+ }
92
+ return { svg, render, prepareEffects };
93
+ }
94
+ //# sourceMappingURL=renderer.js.map
@@ -0,0 +1,6 @@
1
+ import type { HeixiuSignals } from './signals.js';
2
+ export declare const HEIXIU_BLOUB_ACTIONS: readonly ["burst", "egg", "orbit", "hexagon", "wink", "wide", "exclaim", "alert", "sleep", "thinking", "notify"];
3
+ export declare const HEIXIU_AMBIENT_ACTIONS: readonly ["burst", "egg", "orbit", "hexagon", "wink", "wide", "sleep"];
4
+ /** One native-composer-owned button; optional read-only state, never send handlers. */
5
+ export declare function installComposerBloub(doc?: Document | undefined, signals?: HeixiuSignals): () => void;
6
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1,395 @@
1
+ import { BotEngine } from '../vendor/bloub/engine.js';
2
+ import { liveliness } from '../vendor/bloub/face.js';
3
+ import { STATE_BY_ID } from '../vendor/bloub/states.js';
4
+ import { subscribeXiaoheiHostDom } from '../host-dom.js';
5
+ import { XIAOHEI_HOST_SELECTORS } from '../host-contract.js';
6
+ import { createHeixiuRenderer } from './renderer.js';
7
+ import { resolveHeixiuDock, resolveHeixiuWelcome } from './layout.js';
8
+ import { resolveHeixiuFollow, resolveHeixiuGaze } from './follow.js';
9
+ import { createHeixiuFlight } from './flight.js';
10
+ import { heixiuAmbientDelay, heixiuAmbientIndex } from './ambient.js';
11
+ import { HEIXIU_BLOUB_CSS } from './styles.js';
12
+ import { HEIXIU_FACE, HEIXIU_PROFILE, HEIXIU_REACTIONS, HeixiuReactions } from './character.js';
13
+ export const HEIXIU_BLOUB_ACTIONS = ['burst', 'egg', 'orbit', 'hexagon', 'wink', 'wide', 'exclaim', 'alert', 'sleep', 'thinking', 'notify'];
14
+ export const HEIXIU_AMBIENT_ACTIONS = ['burst', 'egg', 'orbit', 'hexagon', 'wink', 'wide', 'sleep'];
15
+ /** One native-composer-owned button; optional read-only state, never send handlers. */
16
+ export function installComposerBloub(doc = typeof document === 'undefined' ? undefined : document, signals) {
17
+ const win = doc?.defaultView;
18
+ if (!doc || !win)
19
+ return () => { };
20
+ const style = doc.createElement('style');
21
+ style.textContent = HEIXIU_BLOUB_CSS;
22
+ doc.head.append(style);
23
+ const button = doc.createElement('button');
24
+ button.className = 'xiaohei-bloub';
25
+ button.type = 'button';
26
+ button.setAttribute('aria-label', '逗逗嘿咻');
27
+ button.title = '单击逗逗嘿咻 · 双击轮换变形(键盘 Shift+Enter)';
28
+ const renderer = createHeixiuRenderer(doc);
29
+ const drift = doc.createElement('span');
30
+ drift.className = 'xiaohei-bloub-drift';
31
+ drift.append(renderer.svg);
32
+ button.append(drift);
33
+ const flight = createHeixiuFlight(button);
34
+ const engine = new BotEngine(100, 'idle', HEIXIU_PROFILE, HEIXIU_FACE);
35
+ const reactions = new HeixiuReactions();
36
+ const neutral = { yaw: 0, pitch: -7, mix: 1, spin: 0, wander: .35 };
37
+ let gaze = neutral;
38
+ engine.setLook(neutral, 0);
39
+ renderer.render(engine.sample(0));
40
+ let disposed = false, visible = false, dirty = true;
41
+ let composer = null, sidebar = null;
42
+ let hero = null, heroRow = null;
43
+ let bounds;
44
+ let layoutFrame, animationFrame, wake;
45
+ let time = 0, lastStamp, activeUntil = 2.5, actionUntil = 0, reactionIndex = 0;
46
+ let pointerSide = 1;
47
+ let special = false, specialIndex = 0;
48
+ let followingX = '', followingY = '';
49
+ let ambientTimer, previousSpecial = -1, lastInput = -Infinity;
50
+ let signal = signals?.getSnapshot();
51
+ let dockMode, viewportWidth = win.innerWidth, viewportHeight = win.innerHeight;
52
+ const motion = win.matchMedia('(prefers-reduced-motion: reduce)');
53
+ const ro = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(() => { dirty = true; scheduleLayout(); }) : undefined;
54
+ const io = typeof win.IntersectionObserver === 'function' ? new win.IntersectionObserver(entries => {
55
+ const entry = entries.find(e => e.target === button);
56
+ if (entry) {
57
+ visible = entry.isIntersecting;
58
+ syncPlayback();
59
+ }
60
+ }) : undefined;
61
+ function stop() {
62
+ button.dataset.floating = 'false';
63
+ follow(0, 0);
64
+ if (ambientTimer !== undefined)
65
+ win.clearTimeout(ambientTimer);
66
+ ambientTimer = undefined;
67
+ flight.cancel();
68
+ if (animationFrame !== undefined)
69
+ win.cancelAnimationFrame(animationFrame);
70
+ if (wake !== undefined)
71
+ win.clearTimeout(wake);
72
+ animationFrame = undefined;
73
+ wake = undefined;
74
+ lastStamp = undefined;
75
+ }
76
+ function follow(dx, dy) {
77
+ const offset = resolveHeixiuFollow(dx, dy, win.innerWidth <= 760 || composer?.dataset.heixiuDock === 'compact');
78
+ const x = `${offset.x.toFixed(3)}px`, y = `${offset.y.toFixed(3)}px`;
79
+ if (x !== followingX) {
80
+ button.style.setProperty('--heixiu-follow-x', x);
81
+ followingX = x;
82
+ }
83
+ if (y !== followingY) {
84
+ button.style.setProperty('--heixiu-follow-y', y);
85
+ followingY = y;
86
+ }
87
+ }
88
+ function allowed() { return !disposed && visible && !!composer?.isConnected && !doc.hidden && !motion.matches; }
89
+ function scheduleAmbient(delay = heixiuAmbientDelay()) {
90
+ if (ambientTimer !== undefined)
91
+ win.clearTimeout(ambientTimer);
92
+ ambientTimer = undefined;
93
+ if (!allowed())
94
+ return;
95
+ ambientTimer = win.setTimeout(() => {
96
+ ambientTimer = undefined;
97
+ if (!allowed())
98
+ return;
99
+ if (actionUntil || signal?.running || flight.active || Date.now() - lastInput < 5000) {
100
+ scheduleAmbient(5000);
101
+ return;
102
+ }
103
+ playState(HEIXIU_AMBIENT_ACTIONS[heixiuAmbientIndex(HEIXIU_AMBIENT_ACTIONS.length, previousSpecial)], 'ambient');
104
+ }, delay);
105
+ }
106
+ function animateFor(seconds) {
107
+ activeUntil = Math.max(activeUntil, time + seconds);
108
+ if (!allowed() || animationFrame !== undefined)
109
+ return;
110
+ if (wake !== undefined)
111
+ win.clearTimeout(wake);
112
+ wake = undefined;
113
+ lastStamp = undefined;
114
+ animationFrame = win.requestAnimationFrame(tick);
115
+ }
116
+ function tick(stamp) {
117
+ animationFrame = undefined;
118
+ if (!allowed()) {
119
+ stop();
120
+ return;
121
+ }
122
+ if (lastStamp !== undefined)
123
+ time += Math.min(.05, Math.max(0, (stamp - lastStamp) / 1000));
124
+ lastStamp = stamp;
125
+ if (actionUntil && time >= actionUntil) {
126
+ special = false;
127
+ engine.setLook(gaze, time);
128
+ actionUntil = 0;
129
+ engine.setState('idle', time);
130
+ button.dataset.action = 'idle';
131
+ activeUntil = Math.max(activeUntil, time + .65);
132
+ if (signal?.running)
133
+ playState('thinking', 'session', true);
134
+ }
135
+ renderer.render(engine.sample(time), reactions.sample(time));
136
+ // Stop only with open eyes. The next short life window resumes the same
137
+ // clock, so neither gaze nor a partly completed blink can jump on waking.
138
+ if (time < activeUntil || actionUntil || liveliness(time).lid < .99) {
139
+ animationFrame = win.requestAnimationFrame(tick);
140
+ }
141
+ else {
142
+ lastStamp = undefined;
143
+ wake = win.setTimeout(() => { wake = undefined; animateFor(2); }, 3200);
144
+ }
145
+ }
146
+ function syncPlayback() {
147
+ if (!allowed()) {
148
+ stop();
149
+ if (motion.matches) {
150
+ engine.reset('idle', time);
151
+ engine.setLook(neutral, time, .001);
152
+ actionUntil = 0;
153
+ special = false;
154
+ reactions.reset();
155
+ time += 1;
156
+ renderer.render(engine.sample(time));
157
+ button.dataset.action = 'idle';
158
+ }
159
+ }
160
+ else {
161
+ button.dataset.floating = 'true';
162
+ animateFor(1);
163
+ if (signal?.running && !actionUntil)
164
+ playState('thinking', 'session', true);
165
+ if (ambientTimer === undefined)
166
+ scheduleAmbient();
167
+ }
168
+ }
169
+ function reconcile() {
170
+ layoutFrame = undefined;
171
+ if (disposed)
172
+ return;
173
+ const next = doc.querySelector(XIAOHEI_HOST_SELECTORS.composerCard);
174
+ const nextSidebar = doc.querySelector(XIAOHEI_HOST_SELECTORS.sidebarShell);
175
+ const nextHero = doc.querySelector(XIAOHEI_HOST_SELECTORS.composerHeroMark);
176
+ if (composer !== next || sidebar !== nextSidebar || hero !== nextHero) {
177
+ composer?.removeAttribute('data-heixiu-dock');
178
+ ro?.disconnect();
179
+ heroRow?.removeAttribute('data-heixiu-hero-row');
180
+ composer = next;
181
+ sidebar = nextSidebar;
182
+ hero = nextHero;
183
+ dirty = true;
184
+ heroRow = hero?.closest(XIAOHEI_HOST_SELECTORS.composerHeroRow) ?? null;
185
+ heroRow?.setAttribute('data-heixiu-hero-row', '');
186
+ if (composer)
187
+ ro?.observe(composer);
188
+ if (sidebar)
189
+ ro?.observe(sidebar);
190
+ if (hero)
191
+ ro?.observe(hero);
192
+ if (heroRow)
193
+ ro?.observe(heroRow);
194
+ }
195
+ if (!composer) {
196
+ button.remove();
197
+ visible = false;
198
+ stop();
199
+ return;
200
+ }
201
+ if (button.parentElement !== composer || composer.lastElementChild === button) {
202
+ // The native trailing toolbar must stay last: DSH and the existing send
203
+ // skin use its structural position. The overlay must not replace it.
204
+ const toolbar = [...composer.children].reverse().find(child => child !== button);
205
+ composer.insertBefore(button, toolbar ?? null);
206
+ io?.observe(button);
207
+ dirty = true;
208
+ }
209
+ if (!dirty)
210
+ return;
211
+ dirty = false;
212
+ const wasFlying = flight.active;
213
+ const viewportChanged = viewportWidth !== win.innerWidth || viewportHeight !== win.innerHeight;
214
+ const origin = flight.capture(bounds);
215
+ follow(0, 0);
216
+ const rect = composer.getBoundingClientRect();
217
+ const sidebarRight = sidebar?.getBoundingClientRect().right ?? 0;
218
+ const dock = resolveHeixiuDock(rect.left, sidebarRight, win.innerWidth);
219
+ const heroRect = hero?.getBoundingClientRect();
220
+ const welcome = heroRect && resolveHeixiuWelcome(heroRect, rect, sidebarRight, win.innerWidth);
221
+ if (welcome) {
222
+ button.style.setProperty('--heixiu-welcome-left', `${welcome.left}px`);
223
+ button.style.setProperty('--heixiu-welcome-top', `${welcome.top}px`);
224
+ button.style.setProperty('--heixiu-welcome-size', `${welcome.size}px`);
225
+ }
226
+ const mode = welcome ? 'welcome' : dock.mode;
227
+ const fly = !viewportChanged && (wasFlying || !!dockMode && dockMode !== mode && (dockMode === 'welcome' || mode === 'welcome'));
228
+ dockMode = mode;
229
+ viewportWidth = win.innerWidth;
230
+ viewportHeight = win.innerHeight;
231
+ if (composer.dataset.heixiuDock !== mode)
232
+ composer.dataset.heixiuDock = mode;
233
+ // One read after layout configuration; never inside the animation loop.
234
+ bounds = flight.target(button.getBoundingClientRect());
235
+ visible = rect.width > 0 && rect.height > 0 && bounds.top + bounds.height > 0 && bounds.top < win.innerHeight;
236
+ syncPlayback();
237
+ if (allowed() && fly)
238
+ flight.move(origin, bounds, motion.matches);
239
+ else
240
+ flight.cancel();
241
+ }
242
+ function scheduleLayout() {
243
+ if (disposed || layoutFrame !== undefined)
244
+ return;
245
+ layoutFrame = win.requestAnimationFrame(reconcile);
246
+ }
247
+ const move = (event) => {
248
+ if (!allowed() || flight.active || !bounds?.width)
249
+ return;
250
+ if (event.pointerType === 'touch') {
251
+ leave();
252
+ return;
253
+ }
254
+ const dx = event.clientX - (bounds.left + bounds.width / 2);
255
+ const dy = event.clientY - (bounds.top + bounds.height / 2);
256
+ follow(dx, dy);
257
+ gaze = resolveHeixiuGaze(dx, dy);
258
+ if (special)
259
+ return;
260
+ pointerSide = dx < 0 ? -1 : 1;
261
+ reactions.aim(Math.abs(dx) < 90 && Math.abs(dy) < 90 ? dx / 60 : 0, time);
262
+ engine.setLook(gaze, time);
263
+ animateFor(.8);
264
+ };
265
+ const leave = () => {
266
+ follow(0, 0);
267
+ gaze = neutral;
268
+ if (allowed() && !special) {
269
+ reactions.aim(0, time);
270
+ engine.setLook(neutral, time);
271
+ animateFor(.8);
272
+ }
273
+ };
274
+ const click = (event) => {
275
+ event.stopPropagation();
276
+ if (!allowed() || event.detail > 1)
277
+ return;
278
+ // A follow-up double click must not flash idle between special actions.
279
+ // A single click during one lets it finish; the next double click can
280
+ // interrupt it directly, without a delayed single-click timer.
281
+ if (special && event.detail > 0)
282
+ return;
283
+ const index = reactionIndex++ % (HEIXIU_REACTIONS.length + 1);
284
+ if (index === HEIXIU_REACTIONS.length) {
285
+ playState('exclaim', 'manual');
286
+ return;
287
+ }
288
+ const kind = HEIXIU_REACTIONS[index];
289
+ special = false;
290
+ engine.setState('idle', time);
291
+ engine.setLook(gaze, time);
292
+ reactions.play(kind, time, pointerSide);
293
+ button.dataset.action = kind;
294
+ button.dataset.actionSource = 'manual';
295
+ scheduleAmbient();
296
+ actionUntil = reactions.end;
297
+ animateFor(actionUntil - time + .65);
298
+ };
299
+ function playState(state, source, held = false) {
300
+ const def = STATE_BY_ID.get(state);
301
+ renderer.prepareEffects();
302
+ reactions.settle(time);
303
+ special = true;
304
+ engine.setLook(null, time);
305
+ engine.setState(state, time);
306
+ button.dataset.action = state;
307
+ previousSpecial = HEIXIU_AMBIENT_ACTIONS.findIndex(action => action === state);
308
+ button.dataset.actionSource = source;
309
+ actionUntil = held ? Infinity : time + Math.max(def.duration, def.minDuration ?? 0);
310
+ // Do not poison activeUntil with Infinity: normal idle must sleep again.
311
+ animateFor(Math.max(def.duration, def.minDuration ?? 0) + .65);
312
+ scheduleAmbient();
313
+ }
314
+ const doubleClick = (event) => {
315
+ event.stopPropagation();
316
+ if (!allowed())
317
+ return;
318
+ playState(HEIXIU_BLOUB_ACTIONS[specialIndex++ % HEIXIU_BLOUB_ACTIONS.length], 'manual');
319
+ };
320
+ const keydown = (event) => {
321
+ if (event.key === 'Enter' && event.shiftKey) {
322
+ event.preventDefault();
323
+ if (!event.repeat)
324
+ doubleClick(event);
325
+ }
326
+ };
327
+ const resized = () => { dirty = true; scheduleLayout(); };
328
+ const input = (event) => { if (composer?.contains(event.target))
329
+ lastInput = Date.now(); };
330
+ const unsubscribeSignals = signals?.subscribe(() => {
331
+ const next = signals.getSnapshot(), changedSession = next.sessionId !== signal?.sessionId;
332
+ signal = next;
333
+ button.dataset.sessionState = next.running ? 'running' : 'idle';
334
+ // Never replay missed notifications on visibility/reduced-motion changes.
335
+ if (!allowed()) {
336
+ if (actionUntil === Infinity || button.dataset.actionSource === 'session' || changedSession) {
337
+ actionUntil = 0;
338
+ special = false;
339
+ engine.setState('idle', time);
340
+ engine.setLook(gaze, time);
341
+ button.dataset.action = 'idle';
342
+ }
343
+ return;
344
+ }
345
+ if (changedSession || actionUntil === Infinity && !next.running) {
346
+ actionUntil = 0;
347
+ special = false;
348
+ engine.setState('idle', time);
349
+ engine.setLook(gaze, time);
350
+ button.dataset.action = 'idle';
351
+ animateFor(.65);
352
+ }
353
+ if (!changedSession && next.notice)
354
+ playState(next.notice, 'session');
355
+ else if (next.running && (!actionUntil || button.dataset.actionSource !== 'session'))
356
+ playState('thinking', 'session', true);
357
+ }) ?? (() => { });
358
+ const unsubscribe = subscribeXiaoheiHostDom(doc, scheduleLayout);
359
+ doc.addEventListener('pointermove', move, { passive: true });
360
+ doc.addEventListener('pointerleave', leave);
361
+ doc.addEventListener('visibilitychange', syncPlayback);
362
+ doc.addEventListener('input', input, { passive: true });
363
+ button.addEventListener('click', click);
364
+ button.addEventListener('dblclick', doubleClick);
365
+ button.addEventListener('keydown', keydown);
366
+ win.addEventListener('resize', resized, { passive: true });
367
+ win.addEventListener('scroll', resized, { passive: true, capture: true });
368
+ motion.addEventListener('change', syncPlayback);
369
+ scheduleLayout();
370
+ return () => {
371
+ disposed = true;
372
+ stop();
373
+ unsubscribe();
374
+ unsubscribeSignals();
375
+ ro?.disconnect();
376
+ io?.disconnect();
377
+ if (layoutFrame !== undefined)
378
+ win.cancelAnimationFrame(layoutFrame);
379
+ doc.removeEventListener('pointermove', move);
380
+ doc.removeEventListener('pointerleave', leave);
381
+ doc.removeEventListener('visibilitychange', syncPlayback);
382
+ button.removeEventListener('click', click);
383
+ doc.removeEventListener('input', input);
384
+ button.removeEventListener('dblclick', doubleClick);
385
+ button.removeEventListener('keydown', keydown);
386
+ win.removeEventListener('resize', resized);
387
+ win.removeEventListener('scroll', resized, true);
388
+ motion.removeEventListener('change', syncPlayback);
389
+ heroRow?.removeAttribute('data-heixiu-hero-row');
390
+ composer?.removeAttribute('data-heixiu-dock');
391
+ button.remove();
392
+ style.remove();
393
+ };
394
+ }
395
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1,17 @@
1
+ import type { ISessions } from '@deepseek-ai/dsh-api-session-controller/client';
2
+ export interface HeixiuSignal {
3
+ sessionId?: string;
4
+ running: boolean;
5
+ notice: 'notify' | 'alert' | null;
6
+ revision: number;
7
+ }
8
+ /** Small local read model. No messages, RPCs, history scans or persisted state. */
9
+ export declare function createHeixiuSignals(): {
10
+ getSnapshot: () => HeixiuSignal;
11
+ subscribe(fn: () => void): () => void;
12
+ publish(next: Omit<HeixiuSignal, "revision">): void;
13
+ };
14
+ export type HeixiuSignals = ReturnType<typeof createHeixiuSignals>;
15
+ /** Optional official service adapter, subscribed only to the selected session. */
16
+ export declare function bindHeixiuSessions(sessions: ISessions, signals: HeixiuSignals): () => void;
17
+ //# sourceMappingURL=signals.d.ts.map
@@ -0,0 +1,53 @@
1
+ /** Small local read model. No messages, RPCs, history scans or persisted state. */
2
+ export function createHeixiuSignals() {
3
+ let value = { running: false, notice: null, revision: 0 };
4
+ const listeners = new Set();
5
+ return {
6
+ getSnapshot: () => value,
7
+ subscribe(fn) { listeners.add(fn); return () => { listeners.delete(fn); }; },
8
+ publish(next) {
9
+ if (next.sessionId === value.sessionId && next.running === value.running && next.notice === value.notice)
10
+ return;
11
+ value = { ...next, revision: value.revision + 1 };
12
+ for (const fn of listeners)
13
+ fn();
14
+ },
15
+ };
16
+ }
17
+ /** Optional official service adapter, subscribed only to the selected session. */
18
+ export function bindHeixiuSessions(sessions, signals) {
19
+ let face;
20
+ let removeSession = () => { };
21
+ let previous;
22
+ const read = () => {
23
+ const next = face?.getSnapshot();
24
+ const live = next?.openState === 'open' && !next.removed;
25
+ const wasLive = previous?.openState === 'open' && !previous.removed;
26
+ const same = previous?.sessionId === next?.sessionId;
27
+ const failed = !!next && !!previous && (next.lastAgentError && next.lastAgentError !== previous.lastAgentError
28
+ || next.promptError && next.promptError !== previous.promptError);
29
+ const ended = wasLive && same && previous?.running && !next?.running;
30
+ const notice = live && wasLive && same ? failed ? 'alert' : ended ? 'notify' : null : null;
31
+ signals.publish({ sessionId: next?.sessionId, running: !!live && !!next?.running, notice });
32
+ previous = next;
33
+ };
34
+ const select = () => {
35
+ const id = sessions.list.getSnapshot().current;
36
+ const next = id ? sessions.binding(id)?.session : undefined;
37
+ if (next === face)
38
+ return;
39
+ removeSession();
40
+ previous = undefined;
41
+ face = next;
42
+ removeSession = face?.subscribe(read) ?? (() => { });
43
+ read();
44
+ };
45
+ const removeList = sessions.list.subscribe(select);
46
+ select();
47
+ return () => {
48
+ removeList();
49
+ removeSession();
50
+ signals.publish({ running: false, notice: null });
51
+ };
52
+ }
53
+ //# sourceMappingURL=signals.js.map
@@ -0,0 +1,2 @@
1
+ export declare const HEIXIU_BLOUB_CSS = "\n#root [data-composer-card] > button.xiaohei-bloub {\n all: unset;\n --heixiu-ink: #111214;\n --heixiu-outline: rgba(245,243,215,.16);\n position: absolute;\n box-sizing: border-box;\n display: block;\n width: 96px;\n height: 96px;\n min-width: 44px;\n min-height: 44px;\n right: calc(100% + 18px);\n top: calc(50% - 48px);\n margin: 0 !important;\n padding: 0 !important;\n border: 0 !important;\n border-radius: 50%;\n background: none !important;\n box-shadow: none !important;\n color: inherit;\n cursor: pointer;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n /* Flight owns this transform. A CSS transition would replay cancelled\n WAAPI transforms and corrupt the next target measurement. */\n transition: none;\n z-index: 3;\n}\n#root [data-composer-card] > button.xiaohei-bloub:focus-visible {\n outline: 2px solid var(--dsw-alias-label-secondary);\n outline-offset: -7px;\n}\n#root [data-composer-card] > button.xiaohei-bloub > .xiaohei-bloub-drift {\n display: block;\n width: 100%; height: 100%;\n pointer-events: none;\n --heixiu-drift-x: 7px;\n --heixiu-drift-y: 10px;\n animation: xiaohei-bloub-float 14s cubic-bezier(.45,0,.55,1) infinite;\n animation-play-state: paused;\n}\n#root [data-composer-card] > button.xiaohei-bloub[data-floating='true'] > .xiaohei-bloub-drift {\n animation-play-state: running;\n}\n@keyframes xiaohei-bloub-float {\n 0%, 100% { transform: translate(calc(-1 * var(--heixiu-drift-x)), var(--heixiu-drift-y)); }\n 50% { transform: translate(var(--heixiu-drift-x), calc(-1 * var(--heixiu-drift-y))); }\n}\n#root [data-composer-card] > button.xiaohei-bloub > .xiaohei-bloub-drift > svg {\n display: block;\n width: 100%; height: 100%;\n overflow: hidden;\n pointer-events: none;\n transform: translate(var(--heixiu-follow-x, 0px), var(--heixiu-follow-y, 0px));\n transition: transform 480ms cubic-bezier(.2,.75,.25,1);\n}\n#root [data-composer-card] > button.xiaohei-bloub:active > .xiaohei-bloub-drift > svg {\n transform: translate(var(--heixiu-follow-x, 0px), var(--heixiu-follow-y, 0px)) scale(.98);\n}\n#root [data-composer-card][data-heixiu-dock='compact'] {\n padding-inline-start: 64px !important;\n}\n#root [data-composer-card][data-heixiu-dock='compact'] > button.xiaohei-bloub {\n width: 52px; height: 52px;\n right: auto; left: 4px; top: 6px;\n}\n#root [data-composer-card][data-heixiu-dock='compact'] .xiaohei-bloub-drift {\n --heixiu-drift-x: 2px;\n --heixiu-drift-y: 3px;\n}\n#root [data-composer-card][data-heixiu-dock='welcome'] > button.xiaohei-bloub {\n right: auto;\n left: var(--heixiu-welcome-left);\n top: var(--heixiu-welcome-top);\n width: var(--heixiu-welcome-size);\n height: var(--heixiu-welcome-size);\n}\n@media (max-width: 760px) {\n #root [data-heixiu-hero-row] { box-sizing: border-box; padding-inline-start: 64px; max-width: 100%; }\n #root [data-composer-card][data-heixiu-dock='welcome'] .xiaohei-bloub-drift {\n --heixiu-drift-x: 2px;\n --heixiu-drift-y: 4px;\n }\n}\n#root [data-composer-card] > button.xiaohei-bloub[hidden] { display: none; }\n@media (prefers-reduced-motion: reduce) {\n #root [data-composer-card] > button.xiaohei-bloub { transition: none; transform: none; }\n #root [data-composer-card] > button.xiaohei-bloub > .xiaohei-bloub-drift { animation: none; transform: none; }\n #root [data-composer-card] > button.xiaohei-bloub > .xiaohei-bloub-drift > svg { transition: none; transform: none; }\n}\n@media print {\n #root [data-composer-card] > button.xiaohei-bloub { display: none; }\n #root [data-composer-card][data-heixiu-dock='compact'] { padding-inline-start: 12px !important; }\n}\n";
2
+ //# sourceMappingURL=styles.d.ts.map