sa2kit 2.0.0 → 2.0.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 (264) hide show
  1. package/README.md +1 -1
  2. package/dist/CollisionBalls-BpHufX3H.d.mts +41 -0
  3. package/dist/CollisionBalls-BpHufX3H.d.ts +41 -0
  4. package/dist/ConfigService-BxK06xP6.d.mts +262 -0
  5. package/dist/ConfigService-BxK06xP6.d.ts +262 -0
  6. package/dist/UniversalFileService-BpvbZitV.d.mts +139 -0
  7. package/dist/UniversalFileService-GsP6D3Rc.d.ts +139 -0
  8. package/dist/audioDetection/index.d.mts +449 -0
  9. package/dist/audioDetection/index.d.ts +449 -0
  10. package/dist/audioDetection/index.js +1244 -0
  11. package/dist/audioDetection/index.js.map +1 -0
  12. package/dist/audioDetection/index.mjs +1227 -0
  13. package/dist/audioDetection/index.mjs.map +1 -0
  14. package/dist/auth/legacy/core/index.d.mts +42 -0
  15. package/dist/auth/legacy/core/index.d.ts +42 -0
  16. package/dist/auth/legacy/core/index.js +242 -0
  17. package/dist/auth/legacy/core/index.js.map +1 -0
  18. package/dist/auth/legacy/core/index.mjs +226 -0
  19. package/dist/auth/legacy/core/index.mjs.map +1 -0
  20. package/dist/auth/legacy/db/index.d.mts +5 -0
  21. package/dist/auth/legacy/db/index.d.ts +5 -0
  22. package/dist/auth/legacy/db/index.js +261 -0
  23. package/dist/auth/legacy/db/index.js.map +1 -0
  24. package/dist/auth/legacy/db/index.mjs +250 -0
  25. package/dist/auth/legacy/db/index.mjs.map +1 -0
  26. package/dist/auth/legacy/index.d.mts +5 -0
  27. package/dist/auth/legacy/index.d.ts +5 -0
  28. package/dist/auth/legacy/index.js +1107 -0
  29. package/dist/auth/legacy/index.js.map +1 -0
  30. package/dist/auth/legacy/index.mjs +1086 -0
  31. package/dist/auth/legacy/index.mjs.map +1 -0
  32. package/dist/auth/legacy/logic/index.d.mts +9 -0
  33. package/dist/auth/legacy/logic/index.d.ts +9 -0
  34. package/dist/auth/legacy/logic/index.js +194 -0
  35. package/dist/auth/legacy/logic/index.js.map +1 -0
  36. package/dist/auth/legacy/logic/index.mjs +187 -0
  37. package/dist/auth/legacy/logic/index.mjs.map +1 -0
  38. package/dist/auth/legacy/miniapp/index.d.mts +5 -0
  39. package/dist/auth/legacy/miniapp/index.d.ts +5 -0
  40. package/dist/auth/legacy/miniapp/index.js +506 -0
  41. package/dist/auth/legacy/miniapp/index.js.map +1 -0
  42. package/dist/auth/legacy/miniapp/index.mjs +487 -0
  43. package/dist/auth/legacy/miniapp/index.mjs.map +1 -0
  44. package/dist/auth/legacy/routes/index.d.mts +53 -0
  45. package/dist/auth/legacy/routes/index.d.ts +53 -0
  46. package/dist/auth/legacy/routes/index.js +278 -0
  47. package/dist/auth/legacy/routes/index.js.map +1 -0
  48. package/dist/auth/legacy/routes/index.mjs +271 -0
  49. package/dist/auth/legacy/routes/index.mjs.map +1 -0
  50. package/dist/auth/legacy/schema/index.d.mts +401 -0
  51. package/dist/auth/legacy/schema/index.d.ts +401 -0
  52. package/dist/auth/legacy/schema/index.js +50 -0
  53. package/dist/auth/legacy/schema/index.js.map +1 -0
  54. package/dist/auth/legacy/schema/index.mjs +44 -0
  55. package/dist/auth/legacy/schema/index.mjs.map +1 -0
  56. package/dist/auth/legacy/server/index.d.mts +13 -0
  57. package/dist/auth/legacy/server/index.d.ts +13 -0
  58. package/dist/auth/legacy/server/index.js +21 -0
  59. package/dist/auth/legacy/server/index.js.map +1 -0
  60. package/dist/auth/legacy/server/index.mjs +19 -0
  61. package/dist/auth/legacy/server/index.mjs.map +1 -0
  62. package/dist/auth/legacy/services/index.d.mts +40 -0
  63. package/dist/auth/legacy/services/index.d.ts +40 -0
  64. package/dist/auth/legacy/services/index.js +258 -0
  65. package/dist/auth/legacy/services/index.js.map +1 -0
  66. package/dist/auth/legacy/services/index.mjs +252 -0
  67. package/dist/auth/legacy/services/index.mjs.map +1 -0
  68. package/dist/auth/legacy/ui/miniapp/index.d.mts +10 -0
  69. package/dist/auth/legacy/ui/miniapp/index.d.ts +10 -0
  70. package/dist/auth/legacy/ui/miniapp/index.js +298 -0
  71. package/dist/auth/legacy/ui/miniapp/index.js.map +1 -0
  72. package/dist/auth/legacy/ui/miniapp/index.mjs +290 -0
  73. package/dist/auth/legacy/ui/miniapp/index.mjs.map +1 -0
  74. package/dist/auth/legacy/ui/web/index.d.mts +22 -0
  75. package/dist/auth/legacy/ui/web/index.d.ts +22 -0
  76. package/dist/auth/legacy/ui/web/index.js +899 -0
  77. package/dist/auth/legacy/ui/web/index.js.map +1 -0
  78. package/dist/auth/legacy/ui/web/index.mjs +889 -0
  79. package/dist/auth/legacy/ui/web/index.mjs.map +1 -0
  80. package/dist/auth/legacy/web/index.d.mts +5 -0
  81. package/dist/auth/legacy/web/index.d.ts +5 -0
  82. package/dist/auth/legacy/web/index.js +1107 -0
  83. package/dist/auth/legacy/web/index.js.map +1 -0
  84. package/dist/auth/legacy/web/index.mjs +1086 -0
  85. package/dist/auth/legacy/web/index.mjs.map +1 -0
  86. package/dist/auth/rn/index.d.mts +64 -0
  87. package/dist/auth/rn/index.d.ts +64 -0
  88. package/dist/auth/rn/index.js +765 -0
  89. package/dist/auth/rn/index.js.map +1 -0
  90. package/dist/auth/rn/index.mjs +754 -0
  91. package/dist/auth/rn/index.mjs.map +1 -0
  92. package/dist/base-api-client-ACKKt13v.d.mts +277 -0
  93. package/dist/base-api-client-ACKKt13v.d.ts +277 -0
  94. package/dist/boothVaultService-Cn4WPhjg.d.mts +83 -0
  95. package/dist/boothVaultService-Cn4WPhjg.d.ts +83 -0
  96. package/dist/business/index.d.mts +6 -0
  97. package/dist/business/index.d.ts +6 -0
  98. package/dist/business/index.js +1682 -0
  99. package/dist/business/index.js.map +1 -0
  100. package/dist/business/index.mjs +1675 -0
  101. package/dist/business/index.mjs.map +1 -0
  102. package/dist/calendar/index.d.mts +1325 -0
  103. package/dist/calendar/index.d.ts +1325 -0
  104. package/dist/calendar/index.js +5964 -0
  105. package/dist/calendar/index.js.map +1 -0
  106. package/dist/calendar/index.mjs +5878 -0
  107. package/dist/calendar/index.mjs.map +1 -0
  108. package/dist/components/index.d.mts +405 -0
  109. package/dist/components/index.d.ts +405 -0
  110. package/dist/components/index.js +2516 -0
  111. package/dist/components/index.js.map +1 -0
  112. package/dist/components/index.mjs +2396 -0
  113. package/dist/components/index.mjs.map +1 -0
  114. package/dist/drizzle-schema-BNhqj2AZ.d.mts +1114 -0
  115. package/dist/drizzle-schema-BNhqj2AZ.d.ts +1114 -0
  116. package/dist/festivalCard/index.d.mts +75 -0
  117. package/dist/festivalCard/index.d.ts +75 -0
  118. package/dist/festivalCard/index.js +1492 -0
  119. package/dist/festivalCard/index.js.map +1 -0
  120. package/dist/festivalCard/index.mjs +1475 -0
  121. package/dist/festivalCard/index.mjs.map +1 -0
  122. package/dist/festivalCard/server/index.d.mts +120 -0
  123. package/dist/festivalCard/server/index.d.ts +120 -0
  124. package/dist/festivalCard/server/index.js +272 -0
  125. package/dist/festivalCard/server/index.js.map +1 -0
  126. package/dist/festivalCard/server/index.mjs +265 -0
  127. package/dist/festivalCard/server/index.mjs.map +1 -0
  128. package/dist/festivalCardService-CZomuQ4E.d.mts +80 -0
  129. package/dist/festivalCardService-CZomuQ4E.d.ts +80 -0
  130. package/dist/index-1Ag7IBXN.d.ts +144 -0
  131. package/dist/index-DNKZ7-R_.d.mts +184 -0
  132. package/dist/index-DNKZ7-R_.d.ts +184 -0
  133. package/dist/index-DSel44Ke.d.mts +93 -0
  134. package/dist/index-DSel44Ke.d.ts +93 -0
  135. package/dist/index-DdeZSeTJ.d.mts +144 -0
  136. package/dist/index-DrPcMJPc.d.mts +250 -0
  137. package/dist/index-DrPcMJPc.d.ts +250 -0
  138. package/dist/index.d.mts +5333 -0
  139. package/dist/index.d.ts +5333 -0
  140. package/dist/index.js +18809 -0
  141. package/dist/index.js.map +1 -0
  142. package/dist/index.mjs +18533 -0
  143. package/dist/index.mjs.map +1 -0
  144. package/dist/mikuContest/ui/web/index.d.mts +2 -0
  145. package/dist/mikuContest/ui/web/index.d.ts +2 -0
  146. package/dist/mikuContest/ui/web/index.js +353 -0
  147. package/dist/mikuContest/ui/web/index.js.map +1 -0
  148. package/dist/mikuContest/ui/web/index.mjs +343 -0
  149. package/dist/mikuContest/ui/web/index.mjs.map +1 -0
  150. package/dist/mikuFireworks3D/index.d.mts +268 -0
  151. package/dist/mikuFireworks3D/index.d.ts +268 -0
  152. package/dist/mikuFireworks3D/index.js +1267 -0
  153. package/dist/mikuFireworks3D/index.js.map +1 -0
  154. package/dist/mikuFireworks3D/index.mjs +1228 -0
  155. package/dist/mikuFireworks3D/index.mjs.map +1 -0
  156. package/dist/mikuFusionGame/index.d.mts +117 -0
  157. package/dist/mikuFusionGame/index.d.ts +117 -0
  158. package/dist/mikuFusionGame/index.js +1208 -0
  159. package/dist/mikuFusionGame/index.js.map +1 -0
  160. package/dist/mikuFusionGame/index.mjs +1195 -0
  161. package/dist/mikuFusionGame/index.mjs.map +1 -0
  162. package/dist/mmd/admin/index.d.mts +487 -0
  163. package/dist/mmd/admin/index.d.ts +487 -0
  164. package/dist/mmd/admin/index.js +1058 -0
  165. package/dist/mmd/admin/index.js.map +1 -0
  166. package/dist/mmd/admin/index.mjs +1027 -0
  167. package/dist/mmd/admin/index.mjs.map +1 -0
  168. package/dist/mmd/index.d.mts +2467 -0
  169. package/dist/mmd/index.d.ts +2467 -0
  170. package/dist/mmd/index.js +10119 -0
  171. package/dist/mmd/index.js.map +1 -0
  172. package/dist/mmd/index.mjs +10028 -0
  173. package/dist/mmd/index.mjs.map +1 -0
  174. package/dist/mmd/server/index.d.mts +139 -0
  175. package/dist/mmd/server/index.d.ts +139 -0
  176. package/dist/mmd/server/index.js +424 -0
  177. package/dist/mmd/server/index.js.map +1 -0
  178. package/dist/mmd/server/index.mjs +404 -0
  179. package/dist/mmd/server/index.mjs.map +1 -0
  180. package/dist/music/index.d.mts +74 -0
  181. package/dist/music/index.d.ts +74 -0
  182. package/dist/music/index.js +830 -0
  183. package/dist/music/index.js.map +1 -0
  184. package/dist/music/index.mjs +809 -0
  185. package/dist/music/index.mjs.map +1 -0
  186. package/dist/music/server/index.d.mts +1 -0
  187. package/dist/music/server/index.d.ts +1 -0
  188. package/dist/music/server/index.js +194 -0
  189. package/dist/music/server/index.js.map +1 -0
  190. package/dist/music/server/index.mjs +182 -0
  191. package/dist/music/server/index.mjs.map +1 -0
  192. package/dist/navigation/index.d.mts +93 -0
  193. package/dist/navigation/index.d.ts +93 -0
  194. package/dist/navigation/index.js +453 -0
  195. package/dist/navigation/index.js.map +1 -0
  196. package/dist/navigation/index.mjs +443 -0
  197. package/dist/navigation/index.mjs.map +1 -0
  198. package/dist/portfolio/index.d.mts +66 -0
  199. package/dist/portfolio/index.d.ts +66 -0
  200. package/dist/portfolio/index.js +736 -0
  201. package/dist/portfolio/index.js.map +1 -0
  202. package/dist/portfolio/index.mjs +724 -0
  203. package/dist/portfolio/index.mjs.map +1 -0
  204. package/dist/qqbot/server/index.d.mts +216 -0
  205. package/dist/qqbot/server/index.d.ts +216 -0
  206. package/dist/qqbot/server/index.js +394 -0
  207. package/dist/qqbot/server/index.js.map +1 -0
  208. package/dist/qqbot/server/index.mjs +385 -0
  209. package/dist/qqbot/server/index.mjs.map +1 -0
  210. package/dist/qqbot/ui/web/index.d.mts +10 -0
  211. package/dist/qqbot/ui/web/index.d.ts +10 -0
  212. package/dist/qqbot/ui/web/index.js +105 -0
  213. package/dist/qqbot/ui/web/index.js.map +1 -0
  214. package/dist/qqbot/ui/web/index.mjs +99 -0
  215. package/dist/qqbot/ui/web/index.mjs.map +1 -0
  216. package/dist/screenReceiver/index.d.mts +86 -0
  217. package/dist/screenReceiver/index.d.ts +86 -0
  218. package/dist/screenReceiver/index.js +281 -0
  219. package/dist/screenReceiver/index.js.map +1 -0
  220. package/dist/screenReceiver/index.mjs +273 -0
  221. package/dist/screenReceiver/index.mjs.map +1 -0
  222. package/dist/testYourself/admin/index.d.mts +58 -0
  223. package/dist/testYourself/admin/index.d.ts +58 -0
  224. package/dist/testYourself/admin/index.js +1009 -0
  225. package/dist/testYourself/admin/index.js.map +1 -0
  226. package/dist/testYourself/admin/index.mjs +1002 -0
  227. package/dist/testYourself/admin/index.mjs.map +1 -0
  228. package/dist/testYourself/index.d.mts +53 -0
  229. package/dist/testYourself/index.d.ts +53 -0
  230. package/dist/testYourself/index.js +2551 -0
  231. package/dist/testYourself/index.js.map +1 -0
  232. package/dist/testYourself/index.mjs +2531 -0
  233. package/dist/testYourself/index.mjs.map +1 -0
  234. package/dist/testYourself/server/index.d.mts +1029 -0
  235. package/dist/testYourself/server/index.d.ts +1029 -0
  236. package/dist/testYourself/server/index.js +825 -0
  237. package/dist/testYourself/server/index.js.map +1 -0
  238. package/dist/testYourself/server/index.mjs +816 -0
  239. package/dist/testYourself/server/index.mjs.map +1 -0
  240. package/dist/types-BTiaMsBz.d.mts +292 -0
  241. package/dist/types-DyG3ZV9V.d.mts +270 -0
  242. package/dist/types-DyG3ZV9V.d.ts +270 -0
  243. package/dist/types-ERmJyjx8.d.ts +292 -0
  244. package/dist/types-HorDyIRv.d.mts +303 -0
  245. package/dist/types-HorDyIRv.d.ts +303 -0
  246. package/dist/vocaloidBooth/index.d.mts +64 -0
  247. package/dist/vocaloidBooth/index.d.ts +64 -0
  248. package/dist/vocaloidBooth/index.js +376 -0
  249. package/dist/vocaloidBooth/index.js.map +1 -0
  250. package/dist/vocaloidBooth/index.mjs +362 -0
  251. package/dist/vocaloidBooth/index.mjs.map +1 -0
  252. package/dist/vocaloidBooth/server/index.d.mts +111 -0
  253. package/dist/vocaloidBooth/server/index.d.ts +111 -0
  254. package/dist/vocaloidBooth/server/index.js +247 -0
  255. package/dist/vocaloidBooth/server/index.js.map +1 -0
  256. package/dist/vocaloidBooth/server/index.mjs +237 -0
  257. package/dist/vocaloidBooth/server/index.mjs.map +1 -0
  258. package/dist/vocaloidBooth/web/index.d.mts +3 -0
  259. package/dist/vocaloidBooth/web/index.d.ts +3 -0
  260. package/dist/vocaloidBooth/web/index.js +376 -0
  261. package/dist/vocaloidBooth/web/index.js.map +1 -0
  262. package/dist/vocaloidBooth/web/index.mjs +362 -0
  263. package/dist/vocaloidBooth/web/index.mjs.map +1 -0
  264. package/package.json +1 -1
@@ -0,0 +1,1228 @@
1
+ import React3, { useState, useRef, useCallback, useMemo, useEffect } from 'react';
2
+ import * as THREE2 from 'three';
3
+
4
+ // src/mikuFireworks3D/components/MikuFireworks3D.tsx
5
+ function DanmakuPanel({ onSend }) {
6
+ const [text, setText] = useState("");
7
+ const emit = () => {
8
+ const value = text.trim();
9
+ if (!value) {
10
+ return;
11
+ }
12
+ onSend(value);
13
+ setText("");
14
+ };
15
+ return /* @__PURE__ */ React3.createElement("div", { className: "rounded-xl border border-slate-600/40 bg-slate-900/70 p-3 text-slate-100 backdrop-blur-sm" }, /* @__PURE__ */ React3.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React3.createElement(
16
+ "input",
17
+ {
18
+ type: "text",
19
+ value: text,
20
+ onChange: (event) => setText(event.target.value),
21
+ onKeyDown: (event) => {
22
+ if (event.key === "Enter") {
23
+ emit();
24
+ }
25
+ },
26
+ placeholder: "\u53D1\u9001\u5F39\u5E55\uFF08\u652F\u6301 /miku /avatar /normal\uFF09",
27
+ className: "flex-1 rounded-md border border-slate-600 bg-slate-950 px-2.5 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400"
28
+ }
29
+ ), /* @__PURE__ */ React3.createElement(
30
+ "button",
31
+ {
32
+ type: "button",
33
+ onClick: emit,
34
+ className: "rounded-md bg-cyan-400 px-3 py-2 text-sm font-medium text-slate-950 hover:bg-cyan-300"
35
+ },
36
+ "\u53D1\u9001"
37
+ )));
38
+ }
39
+ function FireworksCanvas({ canvasRef }) {
40
+ return /* @__PURE__ */ React3.createElement(
41
+ "canvas",
42
+ {
43
+ ref: canvasRef,
44
+ className: "absolute inset-0 h-full w-full",
45
+ style: {
46
+ background: "radial-gradient(circle at 20% 20%, rgba(57,197,187,0.15) 0%, rgba(6,8,22,1) 45%, rgba(4,6,15,1) 100%)"
47
+ }
48
+ }
49
+ );
50
+ }
51
+
52
+ // src/mikuFireworks3D/constants.ts
53
+ var DEFAULT_MAX_PARTICLES = 5e3;
54
+ var DEFAULT_MAX_ACTIVE_FIREWORKS = 12;
55
+ var FIREWORK_KIND_LABELS = {
56
+ normal: "\u666E\u901A\u70DF\u82B1",
57
+ miku: "MIKU \u4E3B\u9898",
58
+ avatar: "\u5934\u50CF\u70DF\u82B1"
59
+ };
60
+ var MIKU_PALETTE = ["#39c5bb", "#66e3db", "#7ad8ff", "#b0fff8", "#8cf7e0"];
61
+ var NORMAL_PALETTE = ["#ffe066", "#ff6b6b", "#4dabf7", "#c77dff", "#69db7c"];
62
+ var DANMAKU_MAX_LENGTH = 32;
63
+ var DANMAKU_TRACK_COUNT = 8;
64
+
65
+ // src/mikuFireworks3D/components/FireworksControlPanel.tsx
66
+ function FireworksControlPanel({
67
+ selectedKind,
68
+ onKindChange,
69
+ autoLaunchOnDanmaku,
70
+ onAutoLaunchChange,
71
+ avatarUrl,
72
+ onAvatarUrlChange,
73
+ onLaunch,
74
+ fps,
75
+ realtimeConnected,
76
+ onlineCount
77
+ }) {
78
+ return /* @__PURE__ */ React3.createElement("div", { className: "rounded-xl border border-slate-600/40 bg-slate-900/70 p-3 text-slate-100 backdrop-blur-sm" }, /* @__PURE__ */ React3.createElement("div", { className: "mb-3 flex flex-wrap items-center gap-2" }, Object.keys(FIREWORK_KIND_LABELS).map((kind) => {
79
+ const active = kind === selectedKind;
80
+ return /* @__PURE__ */ React3.createElement(
81
+ "button",
82
+ {
83
+ key: kind,
84
+ type: "button",
85
+ onClick: () => onKindChange(kind),
86
+ className: `rounded-md px-3 py-1.5 text-sm transition ${active ? "bg-cyan-500 text-slate-950" : "bg-slate-700/70 hover:bg-slate-600/80"}`
87
+ },
88
+ FIREWORK_KIND_LABELS[kind]
89
+ );
90
+ }), /* @__PURE__ */ React3.createElement(
91
+ "button",
92
+ {
93
+ type: "button",
94
+ onClick: onLaunch,
95
+ className: "ml-auto rounded-md bg-emerald-400 px-3 py-1.5 text-sm font-medium text-slate-900 hover:bg-emerald-300"
96
+ },
97
+ "\u53D1\u5C04\u70DF\u82B1"
98
+ )), /* @__PURE__ */ React3.createElement("div", { className: "grid gap-2 md:grid-cols-2" }, /* @__PURE__ */ React3.createElement("label", { className: "flex items-center gap-2 text-sm" }, /* @__PURE__ */ React3.createElement(
99
+ "input",
100
+ {
101
+ type: "checkbox",
102
+ checked: autoLaunchOnDanmaku,
103
+ onChange: (event) => onAutoLaunchChange(event.target.checked)
104
+ }
105
+ ), "\u53D1\u9001\u5F39\u5E55\u540E\u81EA\u52A8\u653E\u70DF\u82B1"), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-slate-300" }, "FPS: ", fps), typeof realtimeConnected === "boolean" ? /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-slate-300" }, "\u5B9E\u65F6\u72B6\u6001: ", realtimeConnected ? "\u5DF2\u8FDE\u63A5" : "\u672A\u8FDE\u63A5", typeof onlineCount === "number" ? ` \xB7 \u5728\u7EBF ${onlineCount}` : "") : null), selectedKind === "avatar" ? /* @__PURE__ */ React3.createElement("div", { className: "mt-2" }, /* @__PURE__ */ React3.createElement(
106
+ "input",
107
+ {
108
+ type: "url",
109
+ value: avatarUrl,
110
+ onChange: (event) => onAvatarUrlChange(event.target.value),
111
+ placeholder: "\u5934\u50CF\u56FE\u7247 URL\uFF08\u7528\u4E8E\u5934\u50CF\u70DF\u82B1\uFF09",
112
+ className: "w-full rounded-md border border-slate-600 bg-slate-950 px-2.5 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400"
113
+ }
114
+ )) : null);
115
+ }
116
+ function useDanmakuController(options) {
117
+ const [items, setItems] = useState([]);
118
+ const cursorRef = useRef(0);
119
+ const removeItem = useCallback((id) => {
120
+ setItems((prev) => prev.filter((item) => item.id !== id));
121
+ }, []);
122
+ const addIncoming = useCallback((message) => {
123
+ setItems((prev) => {
124
+ const track = cursorRef.current % DANMAKU_TRACK_COUNT;
125
+ const item = {
126
+ ...message,
127
+ track,
128
+ durationMs: 8e3 + Math.floor(Math.random() * 2800)
129
+ };
130
+ return [...prev.slice(-40), item];
131
+ });
132
+ cursorRef.current += 1;
133
+ }, []);
134
+ const send = useCallback(
135
+ (text, color, sendOptions) => {
136
+ const trimmed = text.trim();
137
+ if (!trimmed) {
138
+ return null;
139
+ }
140
+ const { content, launchKind } = parseCommand(trimmed);
141
+ const safeText = content.slice(0, DANMAKU_MAX_LENGTH);
142
+ if (!safeText) {
143
+ return null;
144
+ }
145
+ const message = {
146
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
147
+ text: safeText,
148
+ color,
149
+ timestamp: Date.now()
150
+ };
151
+ const optimistic = sendOptions?.optimistic ?? true;
152
+ if (optimistic) {
153
+ addIncoming(message);
154
+ }
155
+ options?.onSend?.(message);
156
+ return {
157
+ message,
158
+ launchKind
159
+ };
160
+ },
161
+ [addIncoming, options]
162
+ );
163
+ return useMemo(
164
+ () => ({
165
+ items,
166
+ send,
167
+ addIncoming,
168
+ removeItem
169
+ }),
170
+ [addIncoming, items, removeItem, send]
171
+ );
172
+ }
173
+ function parseCommand(text) {
174
+ if (text.startsWith("/miku ")) {
175
+ return { launchKind: "miku", content: text.replace("/miku ", "").trim() };
176
+ }
177
+ if (text === "/miku") {
178
+ return { launchKind: "miku", content: "MIKU!" };
179
+ }
180
+ if (text.startsWith("/avatar ")) {
181
+ return { launchKind: "avatar", content: text.replace("/avatar ", "").trim() };
182
+ }
183
+ if (text === "/avatar") {
184
+ return { launchKind: "avatar", content: "Avatar Firework!" };
185
+ }
186
+ if (text.startsWith("/normal ")) {
187
+ return { launchKind: "normal", content: text.replace("/normal ", "").trim() };
188
+ }
189
+ if (text === "/normal") {
190
+ return { launchKind: "normal", content: "Fireworks!" };
191
+ }
192
+ return { content: text };
193
+ }
194
+ function createCircularSpriteTexture() {
195
+ const size = 64;
196
+ const canvas = document.createElement("canvas");
197
+ canvas.width = size;
198
+ canvas.height = size;
199
+ const ctx = canvas.getContext("2d");
200
+ if (!ctx) {
201
+ return new THREE2.CanvasTexture(canvas);
202
+ }
203
+ const center = size / 2;
204
+ const gradient = ctx.createRadialGradient(center, center, 2, center, center, center);
205
+ gradient.addColorStop(0, "rgba(255,255,255,1)");
206
+ gradient.addColorStop(0.4, "rgba(255,255,255,0.9)");
207
+ gradient.addColorStop(1, "rgba(255,255,255,0)");
208
+ ctx.fillStyle = gradient;
209
+ ctx.fillRect(0, 0, size, size);
210
+ const texture = new THREE2.CanvasTexture(canvas);
211
+ texture.needsUpdate = true;
212
+ return texture;
213
+ }
214
+
215
+ // src/mikuFireworks3D/utils/colorPalettes.ts
216
+ function pickPalette(kind) {
217
+ if (kind === "miku") {
218
+ return MIKU_PALETTE;
219
+ }
220
+ return NORMAL_PALETTE;
221
+ }
222
+ function pickRandomColor(colors) {
223
+ if (colors.length === 0) {
224
+ return "#ffffff";
225
+ }
226
+ const index = Math.floor(Math.random() * colors.length);
227
+ return colors[index] || "#ffffff";
228
+ }
229
+
230
+ // src/mikuFireworks3D/utils/avatarSprite.ts
231
+ async function sampleAvatarPoints(avatarUrl, sampleStep = 4, maxPoints = 500) {
232
+ const image = await loadImage(avatarUrl);
233
+ const size = 64;
234
+ const canvas = document.createElement("canvas");
235
+ canvas.width = size;
236
+ canvas.height = size;
237
+ const ctx = canvas.getContext("2d");
238
+ if (!ctx) {
239
+ return [];
240
+ }
241
+ ctx.clearRect(0, 0, size, size);
242
+ ctx.drawImage(image, 0, 0, size, size);
243
+ const { data } = ctx.getImageData(0, 0, size, size);
244
+ const points = [];
245
+ for (let y = 0; y < size; y += sampleStep) {
246
+ for (let x = 0; x < size; x += sampleStep) {
247
+ const index = (y * size + x) * 4;
248
+ const alpha = data[index + 3] ?? 0;
249
+ if (alpha < 24) {
250
+ continue;
251
+ }
252
+ const r = data[index] ?? 0;
253
+ const g = data[index + 1] ?? 0;
254
+ const b = data[index + 2] ?? 0;
255
+ const brightness = (r + g + b) / (3 * 255);
256
+ points.push({ x: x - size / 2, y: size / 2 - y, brightness });
257
+ if (points.length >= maxPoints) {
258
+ return points;
259
+ }
260
+ }
261
+ }
262
+ return points;
263
+ }
264
+ function loadImage(url) {
265
+ return new Promise((resolve, reject) => {
266
+ const image = new window.Image();
267
+ image.crossOrigin = "anonymous";
268
+ image.onload = () => resolve(image);
269
+ image.onerror = () => reject(new Error("Failed to load avatar image."));
270
+ image.src = url;
271
+ });
272
+ }
273
+
274
+ // src/mikuFireworks3D/engine/patterns/avatar.ts
275
+ async function createAvatarSeeds(avatarUrl, fallbackColor) {
276
+ const points = await sampleAvatarPoints(avatarUrl);
277
+ if (points.length === 0) {
278
+ return [];
279
+ }
280
+ return points.map((point) => {
281
+ const spread = 0.22;
282
+ return {
283
+ x: 0,
284
+ y: 0,
285
+ z: 0,
286
+ vx: point.x * spread + (Math.random() - 0.5) * 2.4,
287
+ vy: point.y * spread + Math.random() * 2.2,
288
+ vz: (Math.random() - 0.5) * 3.5,
289
+ life: 1.1 + point.brightness * 1.6,
290
+ color: fallbackColor
291
+ };
292
+ });
293
+ }
294
+
295
+ // src/mikuFireworks3D/engine/patterns/miku.ts
296
+ function createMikuSeeds(count) {
297
+ const seeds = [];
298
+ for (let i = 0; i < count; i += 1) {
299
+ const ratio = i / Math.max(count - 1, 1);
300
+ const angle = ratio * Math.PI * 2 * 2;
301
+ const radial = 7 + Math.random() * 9;
302
+ const spiralBoost = 3 + Math.random() * 4;
303
+ seeds.push({
304
+ x: 0,
305
+ y: 0,
306
+ z: 0,
307
+ vx: Math.cos(angle) * radial,
308
+ vy: Math.sin(angle * 0.5) * spiralBoost + 7,
309
+ vz: Math.sin(angle) * radial,
310
+ life: 1.2 + Math.random() * 1.5,
311
+ color: pickRandomColor(MIKU_PALETTE)
312
+ });
313
+ }
314
+ return seeds;
315
+ }
316
+
317
+ // src/mikuFireworks3D/engine/patterns/normal.ts
318
+ function createNormalSeeds(count, color) {
319
+ const seeds = [];
320
+ for (let i = 0; i < count; i += 1) {
321
+ const theta = Math.random() * Math.PI * 2;
322
+ const phi = Math.acos(2 * Math.random() - 1);
323
+ const speed = 8 + Math.random() * 12;
324
+ seeds.push({
325
+ x: 0,
326
+ y: 0,
327
+ z: 0,
328
+ vx: speed * Math.sin(phi) * Math.cos(theta),
329
+ vy: speed * Math.cos(phi),
330
+ vz: speed * Math.sin(phi) * Math.sin(theta),
331
+ life: 1 + Math.random() * 1.6,
332
+ color
333
+ });
334
+ }
335
+ return seeds;
336
+ }
337
+
338
+ // src/mikuFireworks3D/engine/emitters.ts
339
+ async function createSeedParticles(options) {
340
+ const palette = pickPalette(options.kind);
341
+ const color = options.color ?? pickRandomColor(palette);
342
+ if (options.kind === "miku") {
343
+ return createMikuSeeds(options.count);
344
+ }
345
+ if (options.kind === "avatar" && options.avatarUrl) {
346
+ const avatarSeeds = await createAvatarSeeds(options.avatarUrl, color);
347
+ if (avatarSeeds.length > 0) {
348
+ return avatarSeeds;
349
+ }
350
+ }
351
+ return createNormalSeeds(options.count, color);
352
+ }
353
+
354
+ // src/mikuFireworks3D/engine/particlePool.ts
355
+ var ParticlePool = class {
356
+ constructor() {
357
+ this.pool = [];
358
+ }
359
+ acquire() {
360
+ const reused = this.pool.pop();
361
+ if (reused) {
362
+ return reused;
363
+ }
364
+ return {
365
+ x: 0,
366
+ y: 0,
367
+ z: 0,
368
+ vx: 0,
369
+ vy: 0,
370
+ vz: 0,
371
+ life: 0,
372
+ maxLife: 1,
373
+ r: 1,
374
+ g: 1,
375
+ b: 1
376
+ };
377
+ }
378
+ release(particle) {
379
+ this.pool.push(particle);
380
+ }
381
+ };
382
+
383
+ // src/mikuFireworks3D/engine/postfx.ts
384
+ function evaluateDegradePolicy(fps) {
385
+ if (fps < 24) {
386
+ return { shouldDegrade: true, recommendedParticleScale: 0.65 };
387
+ }
388
+ if (fps < 34) {
389
+ return { shouldDegrade: true, recommendedParticleScale: 0.82 };
390
+ }
391
+ return { shouldDegrade: false, recommendedParticleScale: 1 };
392
+ }
393
+
394
+ // src/mikuFireworks3D/engine/FireworksEngine.ts
395
+ var FireworksEngine = class {
396
+ constructor(init) {
397
+ this.pool = new ParticlePool();
398
+ this.bursts = [];
399
+ this.animationFrameId = null;
400
+ this.lastTick = 0;
401
+ this.fpsWindow = { frames: 0, elapsed: 0, fps: 60, particleScale: 1 };
402
+ this.resizeObserver = null;
403
+ this.loop = () => {
404
+ const now = window.performance.now();
405
+ const dt = Math.min((now - this.lastTick) / 1e3, 0.05);
406
+ this.lastTick = now;
407
+ this.update(dt);
408
+ this.renderer.render(this.scene, this.camera);
409
+ this.animationFrameId = window.requestAnimationFrame(this.loop);
410
+ };
411
+ this.canvas = init.canvas;
412
+ this.container = init.container;
413
+ this.maxParticles = init.options?.maxParticles ?? DEFAULT_MAX_PARTICLES;
414
+ this.maxActiveFireworks = init.options?.maxActiveFireworks ?? DEFAULT_MAX_ACTIVE_FIREWORKS;
415
+ this.onError = init.options?.onError;
416
+ this.onFpsReport = init.options?.onFpsReport;
417
+ this.scene = new THREE2.Scene();
418
+ this.scene.background = new THREE2.Color("#060816");
419
+ this.camera = new THREE2.PerspectiveCamera(50, 1, 0.1, 1e3);
420
+ this.camera.position.set(0, 0, 45);
421
+ this.renderer = new THREE2.WebGLRenderer({
422
+ canvas: this.canvas,
423
+ alpha: true,
424
+ antialias: true,
425
+ powerPreference: "high-performance"
426
+ });
427
+ this.renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
428
+ this.spriteTexture = createCircularSpriteTexture();
429
+ this.attachResizeObserver();
430
+ this.resize();
431
+ }
432
+ start() {
433
+ if (this.animationFrameId != null) {
434
+ return;
435
+ }
436
+ this.lastTick = window.performance.now();
437
+ this.loop();
438
+ }
439
+ stop() {
440
+ if (this.animationFrameId != null) {
441
+ window.cancelAnimationFrame(this.animationFrameId);
442
+ this.animationFrameId = null;
443
+ }
444
+ }
445
+ async launch(payload) {
446
+ try {
447
+ this.enforceBurstCap();
448
+ const particleBudget = Math.max(80, Math.floor(280 * this.fpsWindow.particleScale));
449
+ const seeds = await createSeedParticles({
450
+ kind: payload.kind,
451
+ count: particleBudget,
452
+ color: payload.color,
453
+ avatarUrl: payload.avatarUrl
454
+ });
455
+ if (seeds.length === 0) {
456
+ return;
457
+ }
458
+ const launchPosition = payload.position ?? {
459
+ x: (Math.random() - 0.5) * 18,
460
+ y: -4 + Math.random() * 12,
461
+ z: (Math.random() - 0.5) * 4
462
+ };
463
+ const particles = [];
464
+ const positions = new Float32Array(seeds.length * 3);
465
+ const colors = new Float32Array(seeds.length * 3);
466
+ const colorHelper = new THREE2.Color();
467
+ for (let i = 0; i < seeds.length; i += 1) {
468
+ const seed = seeds[i];
469
+ if (!seed) {
470
+ continue;
471
+ }
472
+ const particle = this.pool.acquire();
473
+ particle.x = launchPosition.x + seed.x;
474
+ particle.y = launchPosition.y + seed.y;
475
+ particle.z = launchPosition.z + seed.z;
476
+ particle.vx = seed.vx;
477
+ particle.vy = seed.vy;
478
+ particle.vz = seed.vz;
479
+ particle.life = seed.life;
480
+ particle.maxLife = seed.life;
481
+ colorHelper.set(seed.color);
482
+ particle.r = colorHelper.r;
483
+ particle.g = colorHelper.g;
484
+ particle.b = colorHelper.b;
485
+ particles.push(particle);
486
+ const offset = i * 3;
487
+ positions[offset] = particle.x;
488
+ positions[offset + 1] = particle.y;
489
+ positions[offset + 2] = particle.z;
490
+ colors[offset] = particle.r;
491
+ colors[offset + 1] = particle.g;
492
+ colors[offset + 2] = particle.b;
493
+ }
494
+ if (this.totalParticleCount() + particles.length > this.maxParticles) {
495
+ this.releaseParticles(particles);
496
+ return;
497
+ }
498
+ const geometry = new THREE2.BufferGeometry();
499
+ geometry.setAttribute("position", new THREE2.BufferAttribute(positions, 3));
500
+ geometry.setAttribute("color", new THREE2.BufferAttribute(colors, 3));
501
+ const material = new THREE2.PointsMaterial({
502
+ size: payload.kind === "miku" ? 0.42 : 0.36,
503
+ vertexColors: true,
504
+ map: this.spriteTexture,
505
+ transparent: true,
506
+ opacity: 1,
507
+ depthWrite: false,
508
+ blending: THREE2.AdditiveBlending
509
+ });
510
+ const points = new THREE2.Points(geometry, material);
511
+ this.scene.add(points);
512
+ const burst = {
513
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
514
+ points,
515
+ geometry,
516
+ material,
517
+ positions,
518
+ colors,
519
+ particles
520
+ };
521
+ this.bursts.push(burst);
522
+ } catch (error) {
523
+ this.onError?.(error instanceof Error ? error : new Error("Failed to launch firework."));
524
+ }
525
+ }
526
+ dispose() {
527
+ this.stop();
528
+ if (this.resizeObserver) {
529
+ this.resizeObserver.disconnect();
530
+ this.resizeObserver = null;
531
+ }
532
+ for (const burst of this.bursts) {
533
+ this.destroyBurst(burst);
534
+ }
535
+ this.bursts.length = 0;
536
+ this.spriteTexture.dispose();
537
+ this.renderer.dispose();
538
+ }
539
+ update(dt) {
540
+ const gravity = -8.8;
541
+ for (let b = this.bursts.length - 1; b >= 0; b -= 1) {
542
+ const burst = this.bursts[b];
543
+ if (!burst) {
544
+ continue;
545
+ }
546
+ let alive = 0;
547
+ for (let i = 0; i < burst.particles.length; i += 1) {
548
+ const particle = burst.particles[i];
549
+ if (!particle) {
550
+ continue;
551
+ }
552
+ particle.life -= dt;
553
+ if (particle.life <= 0) {
554
+ continue;
555
+ }
556
+ particle.vx *= 0.992;
557
+ particle.vy = particle.vy * 0.992 + gravity * dt;
558
+ particle.vz *= 0.992;
559
+ particle.x += particle.vx * dt;
560
+ particle.y += particle.vy * dt;
561
+ particle.z += particle.vz * dt;
562
+ const idx = i * 3;
563
+ burst.positions[idx] = particle.x;
564
+ burst.positions[idx + 1] = particle.y;
565
+ burst.positions[idx + 2] = particle.z;
566
+ const alpha = Math.max(particle.life / particle.maxLife, 0);
567
+ burst.colors[idx] = particle.r * alpha;
568
+ burst.colors[idx + 1] = particle.g * alpha;
569
+ burst.colors[idx + 2] = particle.b * alpha;
570
+ alive += 1;
571
+ }
572
+ const positionAttr = burst.geometry.getAttribute("position");
573
+ const colorAttr = burst.geometry.getAttribute("color");
574
+ positionAttr.needsUpdate = true;
575
+ colorAttr.needsUpdate = true;
576
+ burst.material.opacity = Math.min(1, 0.22 + alive / Math.max(burst.particles.length, 1));
577
+ if (alive === 0) {
578
+ this.bursts.splice(b, 1);
579
+ this.destroyBurst(burst);
580
+ }
581
+ }
582
+ this.updateFpsStats(dt);
583
+ }
584
+ updateFpsStats(dt) {
585
+ this.fpsWindow.frames += 1;
586
+ this.fpsWindow.elapsed += dt;
587
+ if (this.fpsWindow.elapsed < 0.6) {
588
+ return;
589
+ }
590
+ const fps = this.fpsWindow.frames / this.fpsWindow.elapsed;
591
+ this.fpsWindow.fps = fps;
592
+ const policy = evaluateDegradePolicy(fps);
593
+ this.fpsWindow.particleScale = policy.recommendedParticleScale;
594
+ this.onFpsReport?.(Math.round(fps));
595
+ this.fpsWindow.frames = 0;
596
+ this.fpsWindow.elapsed = 0;
597
+ }
598
+ totalParticleCount() {
599
+ return this.bursts.reduce((sum, burst) => sum + burst.particles.length, 0);
600
+ }
601
+ enforceBurstCap() {
602
+ while (this.bursts.length >= this.maxActiveFireworks) {
603
+ const burst = this.bursts.shift();
604
+ if (!burst) {
605
+ break;
606
+ }
607
+ this.destroyBurst(burst);
608
+ }
609
+ }
610
+ destroyBurst(burst) {
611
+ this.scene.remove(burst.points);
612
+ burst.geometry.dispose();
613
+ burst.material.dispose();
614
+ this.releaseParticles(burst.particles);
615
+ }
616
+ releaseParticles(particles) {
617
+ for (const particle of particles) {
618
+ this.pool.release(particle);
619
+ }
620
+ }
621
+ attachResizeObserver() {
622
+ this.resizeObserver = new ResizeObserver(() => this.resize());
623
+ this.resizeObserver.observe(this.container);
624
+ }
625
+ resize() {
626
+ const width = Math.max(1, this.container.clientWidth);
627
+ const height = Math.max(1, this.container.clientHeight);
628
+ this.camera.aspect = width / height;
629
+ this.camera.updateProjectionMatrix();
630
+ this.renderer.setSize(width, height, false);
631
+ }
632
+ };
633
+
634
+ // src/mikuFireworks3D/hooks/useFireworksEngine.ts
635
+ function useFireworksEngine(options) {
636
+ const containerRef = useRef(null);
637
+ const canvasRef = useRef(null);
638
+ const engineRef = useRef(null);
639
+ const pendingLaunchesRef = useRef([]);
640
+ const [fps, setFps] = useState(60);
641
+ const [engineReady, setEngineReady] = useState(false);
642
+ const [pendingLaunchCount, setPendingLaunchCount] = useState(0);
643
+ const { maxParticles, maxActiveFireworks, onError, onFpsReport, onLaunch } = options || {};
644
+ useEffect(() => {
645
+ if (!containerRef.current || !canvasRef.current) {
646
+ return;
647
+ }
648
+ const engine = new FireworksEngine({
649
+ container: containerRef.current,
650
+ canvas: canvasRef.current,
651
+ options: {
652
+ maxParticles,
653
+ maxActiveFireworks,
654
+ onError,
655
+ onFpsReport: (nextFps) => {
656
+ setFps(nextFps);
657
+ onFpsReport?.(nextFps);
658
+ }
659
+ }
660
+ });
661
+ engine.start();
662
+ engineRef.current = engine;
663
+ setEngineReady(true);
664
+ if (pendingLaunchesRef.current.length > 0) {
665
+ const pending = [...pendingLaunchesRef.current];
666
+ pendingLaunchesRef.current = [];
667
+ setPendingLaunchCount(0);
668
+ pending.forEach((payload) => {
669
+ void engine.launch(payload);
670
+ });
671
+ }
672
+ return () => {
673
+ engineRef.current?.dispose();
674
+ engineRef.current = null;
675
+ pendingLaunchesRef.current = [];
676
+ setPendingLaunchCount(0);
677
+ setEngineReady(false);
678
+ };
679
+ }, [maxParticles, maxActiveFireworks, onError, onFpsReport]);
680
+ const launch = useCallback(
681
+ (payload) => {
682
+ if (!engineRef.current) {
683
+ pendingLaunchesRef.current.push(payload);
684
+ if (pendingLaunchesRef.current.length > 120) {
685
+ pendingLaunchesRef.current.splice(0, pendingLaunchesRef.current.length - 120);
686
+ }
687
+ setPendingLaunchCount(pendingLaunchesRef.current.length);
688
+ } else {
689
+ void engineRef.current.launch(payload);
690
+ }
691
+ onLaunch?.(payload);
692
+ },
693
+ [onLaunch]
694
+ );
695
+ const api = useMemo(
696
+ () => ({
697
+ containerRef,
698
+ canvasRef,
699
+ launch,
700
+ fps,
701
+ engineReady,
702
+ pendingLaunchCount
703
+ }),
704
+ [engineReady, fps, launch, pendingLaunchCount]
705
+ );
706
+ return api;
707
+ }
708
+
709
+ // src/mikuFireworks3D/client/WebSocketTransport.ts
710
+ var WebSocketTransport = class {
711
+ constructor(config, callbacks) {
712
+ this.socket = null;
713
+ this.reconnectTimer = null;
714
+ this.isManualClose = false;
715
+ this.pendingQueue = [];
716
+ this.config = config;
717
+ this.callbacks = callbacks || {};
718
+ this.state = {
719
+ connected: false,
720
+ joined: false,
721
+ onlineCount: 0,
722
+ roomId: config.roomId
723
+ };
724
+ }
725
+ connect() {
726
+ if (this.socket && (this.socket.readyState === window.WebSocket.OPEN || this.socket.readyState === window.WebSocket.CONNECTING)) {
727
+ return;
728
+ }
729
+ this.isManualClose = false;
730
+ try {
731
+ this.socket = this.config.protocols ? new window.WebSocket(this.config.serverUrl, this.config.protocols) : new window.WebSocket(this.config.serverUrl);
732
+ this.socket.binaryType = "arraybuffer";
733
+ } catch {
734
+ this.callbacks.onError?.(new Error("Failed to create WebSocket connection."));
735
+ return;
736
+ }
737
+ this.socket.onopen = () => {
738
+ this.updateState({ connected: true, joined: false });
739
+ this.send({
740
+ type: "join",
741
+ roomId: this.config.roomId,
742
+ user: this.config.user
743
+ });
744
+ };
745
+ this.socket.onmessage = (event) => {
746
+ const parsed = parseServerMessage(event.data);
747
+ if (!parsed) {
748
+ return;
749
+ }
750
+ this.handleServerMessage(parsed);
751
+ };
752
+ this.socket.onerror = () => {
753
+ this.callbacks.onError?.(new Error("WebSocket transport error."));
754
+ };
755
+ this.socket.onclose = () => {
756
+ this.updateState({ connected: false, joined: false });
757
+ this.scheduleReconnect();
758
+ };
759
+ }
760
+ disconnect() {
761
+ this.isManualClose = true;
762
+ this.pendingQueue.length = 0;
763
+ if (this.reconnectTimer != null) {
764
+ window.clearTimeout(this.reconnectTimer);
765
+ this.reconnectTimer = null;
766
+ }
767
+ if (!this.socket) {
768
+ return;
769
+ }
770
+ this.send({ type: "leave" });
771
+ this.socket.close();
772
+ this.socket = null;
773
+ }
774
+ sendDanmaku(payload) {
775
+ this.send({
776
+ type: "danmaku.send",
777
+ payload
778
+ });
779
+ }
780
+ sendFirework(payload) {
781
+ this.send({
782
+ type: "firework.launch",
783
+ payload
784
+ });
785
+ }
786
+ getState() {
787
+ return this.state;
788
+ }
789
+ send(message) {
790
+ if (!this.socket || this.socket.readyState !== window.WebSocket.OPEN) {
791
+ if (message.type === "danmaku.send" || message.type === "firework.launch") {
792
+ this.pendingQueue.push(message);
793
+ }
794
+ return;
795
+ }
796
+ if ((message.type === "danmaku.send" || message.type === "firework.launch") && !this.state.joined) {
797
+ this.pendingQueue.push(message);
798
+ return;
799
+ }
800
+ this.socket.send(JSON.stringify(message));
801
+ }
802
+ updateState(partial) {
803
+ this.state = {
804
+ ...this.state,
805
+ ...partial
806
+ };
807
+ this.callbacks.onStateChange?.(this.state);
808
+ }
809
+ handleServerMessage(message) {
810
+ if (message.type === "joined") {
811
+ this.updateState({ roomId: message.roomId, onlineCount: message.onlineCount, joined: true });
812
+ this.flushPendingQueue();
813
+ return;
814
+ }
815
+ if (message.type === "room.user_joined" || message.type === "room.user_left") {
816
+ this.updateState({ onlineCount: message.onlineCount, roomId: message.roomId, joined: true });
817
+ this.flushPendingQueue();
818
+ return;
819
+ }
820
+ if (message.type === "room.snapshot") {
821
+ this.updateState({ roomId: message.roomId, onlineCount: message.users.length, joined: true });
822
+ this.flushPendingQueue();
823
+ this.callbacks.onSnapshot?.(message);
824
+ return;
825
+ }
826
+ if (message.type === "danmaku.broadcast") {
827
+ if (!this.state.joined) {
828
+ this.updateState({ joined: true, roomId: message.roomId });
829
+ this.flushPendingQueue();
830
+ }
831
+ this.callbacks.onDanmakuBroadcast?.(message.event);
832
+ return;
833
+ }
834
+ if (message.type === "firework.broadcast") {
835
+ if (!this.state.joined) {
836
+ this.updateState({ joined: true, roomId: message.roomId });
837
+ this.flushPendingQueue();
838
+ }
839
+ this.callbacks.onFireworkBroadcast?.(message.event);
840
+ return;
841
+ }
842
+ if (message.type === "error") {
843
+ this.callbacks.onError?.(new Error(`${message.code}: ${message.message}`));
844
+ }
845
+ }
846
+ scheduleReconnect() {
847
+ const reconnect = this.config.reconnect ?? true;
848
+ if (this.isManualClose || !reconnect) {
849
+ return;
850
+ }
851
+ if (this.reconnectTimer != null) {
852
+ return;
853
+ }
854
+ const delay = this.config.reconnectIntervalMs ?? 1500;
855
+ this.reconnectTimer = window.setTimeout(() => {
856
+ this.reconnectTimer = null;
857
+ this.connect();
858
+ }, delay);
859
+ }
860
+ flushPendingQueue() {
861
+ if (!this.socket || this.socket.readyState !== window.WebSocket.OPEN || !this.state.joined) {
862
+ return;
863
+ }
864
+ while (this.pendingQueue.length > 0) {
865
+ const message = this.pendingQueue.shift();
866
+ if (!message) {
867
+ break;
868
+ }
869
+ this.socket.send(JSON.stringify(message));
870
+ }
871
+ }
872
+ };
873
+ function parseServerMessage(raw) {
874
+ const text = decodeMessage(raw);
875
+ if (!text) {
876
+ return null;
877
+ }
878
+ try {
879
+ return JSON.parse(text);
880
+ } catch {
881
+ return null;
882
+ }
883
+ }
884
+ function decodeMessage(raw) {
885
+ if (typeof raw === "string") {
886
+ return raw;
887
+ }
888
+ if (raw instanceof ArrayBuffer) {
889
+ return new TextDecoder().decode(new Uint8Array(raw));
890
+ }
891
+ if (ArrayBuffer.isView(raw)) {
892
+ return new TextDecoder().decode(raw);
893
+ }
894
+ return null;
895
+ }
896
+
897
+ // src/mikuFireworks3D/hooks/useFireworksRealtime.ts
898
+ function useFireworksRealtime(options) {
899
+ const { config, enabled, onDanmakuBroadcast, onFireworkBroadcast, onSnapshot, onError, onStateChange } = options;
900
+ const transportRef = useRef(null);
901
+ const callbackRef = useRef({
902
+ onDanmakuBroadcast,
903
+ onFireworkBroadcast,
904
+ onSnapshot,
905
+ onError,
906
+ onStateChange
907
+ });
908
+ const serverUrl = config?.serverUrl ?? "";
909
+ const roomId = config?.roomId ?? "";
910
+ const userId = config?.user.userId ?? "";
911
+ const nickname = config?.user.nickname ?? "";
912
+ const avatarUrl = config?.user.avatarUrl ?? "";
913
+ const reconnect = config?.reconnect ?? true;
914
+ const reconnectIntervalMs = config?.reconnectIntervalMs ?? 1500;
915
+ const [state, setState] = useState({
916
+ connected: false,
917
+ joined: false,
918
+ onlineCount: 0,
919
+ roomId
920
+ });
921
+ useEffect(() => {
922
+ callbackRef.current = {
923
+ onDanmakuBroadcast,
924
+ onFireworkBroadcast,
925
+ onSnapshot,
926
+ onError,
927
+ onStateChange
928
+ };
929
+ }, [onDanmakuBroadcast, onError, onFireworkBroadcast, onSnapshot, onStateChange]);
930
+ const normalizedConfig = useMemo(() => {
931
+ if (!serverUrl || !roomId || !userId) {
932
+ return void 0;
933
+ }
934
+ const protocols = Array.isArray(config?.protocols) ? [...config.protocols] : config?.protocols;
935
+ return {
936
+ serverUrl,
937
+ roomId,
938
+ user: {
939
+ userId,
940
+ nickname: nickname || void 0,
941
+ avatarUrl: avatarUrl || void 0
942
+ },
943
+ protocols,
944
+ reconnect,
945
+ reconnectIntervalMs
946
+ };
947
+ }, [avatarUrl, config?.protocols, nickname, reconnect, reconnectIntervalMs, roomId, serverUrl, userId]);
948
+ useEffect(() => {
949
+ if (!enabled || !normalizedConfig) {
950
+ transportRef.current?.disconnect();
951
+ transportRef.current = null;
952
+ setState({
953
+ connected: false,
954
+ joined: false,
955
+ onlineCount: 0,
956
+ roomId: normalizedConfig?.roomId
957
+ });
958
+ return;
959
+ }
960
+ const transport = new WebSocketTransport(normalizedConfig, {
961
+ onStateChange: (nextState) => {
962
+ setState(nextState);
963
+ callbackRef.current.onStateChange?.(nextState);
964
+ },
965
+ onDanmakuBroadcast: (event) => {
966
+ callbackRef.current.onDanmakuBroadcast?.({
967
+ id: event.id,
968
+ text: event.text,
969
+ color: event.color,
970
+ kind: event.kind,
971
+ userId: event.user.userId,
972
+ timestamp: event.timestamp
973
+ });
974
+ },
975
+ onFireworkBroadcast: (event) => {
976
+ callbackRef.current.onFireworkBroadcast?.({
977
+ id: event.id,
978
+ payload: event.payload,
979
+ userId: event.user.userId,
980
+ timestamp: event.timestamp
981
+ });
982
+ },
983
+ onSnapshot: (snapshot) => {
984
+ callbackRef.current.onSnapshot?.({
985
+ roomId: snapshot.roomId,
986
+ danmakuHistory: snapshot.danmakuHistory.map((item) => ({
987
+ id: item.id,
988
+ text: item.text,
989
+ color: item.color,
990
+ kind: item.kind,
991
+ userId: item.user.userId,
992
+ timestamp: item.timestamp
993
+ })),
994
+ fireworkHistory: snapshot.fireworkHistory.map((item) => ({
995
+ id: item.id,
996
+ payload: item.payload,
997
+ userId: item.user.userId,
998
+ timestamp: item.timestamp
999
+ }))
1000
+ });
1001
+ },
1002
+ onError: (error) => {
1003
+ callbackRef.current.onError?.(error);
1004
+ }
1005
+ });
1006
+ transport.connect();
1007
+ transportRef.current = transport;
1008
+ return () => {
1009
+ transport.disconnect();
1010
+ if (transportRef.current === transport) {
1011
+ transportRef.current = null;
1012
+ }
1013
+ };
1014
+ }, [enabled, normalizedConfig]);
1015
+ return useMemo(
1016
+ () => ({
1017
+ state,
1018
+ sendDanmaku: (payload) => {
1019
+ transportRef.current?.sendDanmaku(payload);
1020
+ },
1021
+ sendFirework: (payload) => {
1022
+ transportRef.current?.sendFirework(payload);
1023
+ }
1024
+ }),
1025
+ [state]
1026
+ );
1027
+ }
1028
+
1029
+ // src/mikuFireworks3D/components/MikuFireworks3D.tsx
1030
+ function MikuFireworks3D({
1031
+ width = "100%",
1032
+ height = 520,
1033
+ className,
1034
+ defaultKind = "normal",
1035
+ autoLaunchOnDanmaku = true,
1036
+ maxParticles,
1037
+ maxActiveFireworks,
1038
+ defaultAvatarUrl = "",
1039
+ onLaunch,
1040
+ onDanmakuSend,
1041
+ onError,
1042
+ onFpsReport,
1043
+ onRealtimeStateChange,
1044
+ realtime
1045
+ }) {
1046
+ const [selectedKind, setSelectedKind] = useState(defaultKind);
1047
+ const [avatarUrl, setAvatarUrl] = useState(defaultAvatarUrl);
1048
+ const [autoLaunch, setAutoLaunch] = useState(autoLaunchOnDanmaku);
1049
+ const seenDanmakuIdsRef = useRef(/* @__PURE__ */ new Set());
1050
+ const seenFireworkIdsRef = useRef(/* @__PURE__ */ new Set());
1051
+ const {
1052
+ containerRef,
1053
+ canvasRef,
1054
+ launch,
1055
+ fps,
1056
+ engineReady,
1057
+ pendingLaunchCount
1058
+ } = useFireworksEngine({
1059
+ maxParticles,
1060
+ maxActiveFireworks,
1061
+ onLaunch,
1062
+ onError,
1063
+ onFpsReport
1064
+ });
1065
+ const { items, send, addIncoming, removeItem } = useDanmakuController({
1066
+ onSend: onDanmakuSend
1067
+ });
1068
+ const realtimeEnabled = Boolean(realtime && (realtime.enabled ?? true));
1069
+ const logSync = (phase, eventId, extra) => {
1070
+ const payload = {
1071
+ phase,
1072
+ eventId,
1073
+ engineReady,
1074
+ pendingLaunchCount,
1075
+ connected: realtimeApi.state.connected,
1076
+ joined: realtimeApi.state.joined,
1077
+ ...extra
1078
+ };
1079
+ globalThis.console?.log("[MikuFireworks3D][sync_event]", payload);
1080
+ };
1081
+ const realtimeApi = useFireworksRealtime({
1082
+ enabled: realtimeEnabled,
1083
+ config: realtime,
1084
+ onStateChange: onRealtimeStateChange,
1085
+ onError,
1086
+ onDanmakuBroadcast: (event) => {
1087
+ if (seenDanmakuIdsRef.current.has(event.id)) {
1088
+ return;
1089
+ }
1090
+ seenDanmakuIdsRef.current.add(event.id);
1091
+ addIncoming({
1092
+ id: event.id,
1093
+ userId: event.userId,
1094
+ text: event.text,
1095
+ color: event.color,
1096
+ timestamp: event.timestamp
1097
+ });
1098
+ },
1099
+ onFireworkBroadcast: (event) => {
1100
+ logSync("firework.broadcast.received", event.id, { kind: event.payload.kind });
1101
+ if (seenFireworkIdsRef.current.has(event.id)) {
1102
+ logSync("firework.broadcast.deduped", event.id);
1103
+ return;
1104
+ }
1105
+ seenFireworkIdsRef.current.add(event.id);
1106
+ launch(event.payload);
1107
+ logSync("firework.broadcast.dispatched", event.id, { kind: event.payload.kind });
1108
+ },
1109
+ onSnapshot: (snapshot) => {
1110
+ for (const danmaku of snapshot.danmakuHistory) {
1111
+ if (seenDanmakuIdsRef.current.has(danmaku.id)) {
1112
+ continue;
1113
+ }
1114
+ seenDanmakuIdsRef.current.add(danmaku.id);
1115
+ addIncoming({
1116
+ id: danmaku.id,
1117
+ userId: danmaku.userId,
1118
+ text: danmaku.text,
1119
+ color: danmaku.color,
1120
+ timestamp: danmaku.timestamp
1121
+ });
1122
+ }
1123
+ for (const firework of snapshot.fireworkHistory) {
1124
+ logSync("firework.snapshot.received", firework.id, { kind: firework.payload.kind });
1125
+ if (seenFireworkIdsRef.current.has(firework.id)) {
1126
+ logSync("firework.snapshot.deduped", firework.id);
1127
+ continue;
1128
+ }
1129
+ seenFireworkIdsRef.current.add(firework.id);
1130
+ launch(firework.payload);
1131
+ logSync("firework.snapshot.dispatched", firework.id, { kind: firework.payload.kind });
1132
+ }
1133
+ }
1134
+ });
1135
+ const handleLaunch = (kind) => {
1136
+ const payload = {
1137
+ kind,
1138
+ avatarUrl: kind === "avatar" ? avatarUrl || void 0 : void 0
1139
+ };
1140
+ if (realtimeEnabled) {
1141
+ realtimeApi.sendFirework(payload);
1142
+ return;
1143
+ }
1144
+ launch(payload);
1145
+ };
1146
+ const handleSendDanmaku = (text) => {
1147
+ const result = send(text, void 0, {
1148
+ optimistic: !realtimeEnabled
1149
+ });
1150
+ if (!result) {
1151
+ return;
1152
+ }
1153
+ const launchKind = result.launchKind ?? selectedKind;
1154
+ if (realtimeEnabled) {
1155
+ realtimeApi.sendDanmaku({
1156
+ text: result.message.text,
1157
+ color: result.message.color,
1158
+ kind: result.launchKind
1159
+ });
1160
+ if (autoLaunch) {
1161
+ realtimeApi.sendFirework({
1162
+ kind: launchKind,
1163
+ avatarUrl: launchKind === "avatar" ? avatarUrl || void 0 : void 0,
1164
+ message: result.message
1165
+ });
1166
+ }
1167
+ return;
1168
+ }
1169
+ if (autoLaunch || result.launchKind) {
1170
+ launch({
1171
+ kind: launchKind,
1172
+ avatarUrl: launchKind === "avatar" ? avatarUrl || void 0 : void 0,
1173
+ message: result.message
1174
+ });
1175
+ }
1176
+ };
1177
+ const containerStyle = useMemo(
1178
+ () => ({
1179
+ width,
1180
+ height,
1181
+ minHeight: 360
1182
+ }),
1183
+ [height, width]
1184
+ );
1185
+ return /* @__PURE__ */ React3.createElement("div", { className: `mx-auto flex w-full max-w-5xl flex-col gap-3 ${className || ""}` }, /* @__PURE__ */ React3.createElement("div", { ref: containerRef, className: "relative overflow-hidden rounded-2xl border border-slate-700", style: containerStyle }, /* @__PURE__ */ React3.createElement(FireworksCanvas, { canvasRef }), /* @__PURE__ */ React3.createElement("div", { className: "pointer-events-none absolute inset-0 overflow-hidden" }, items.map((item) => /* @__PURE__ */ React3.createElement(
1186
+ "div",
1187
+ {
1188
+ key: item.id,
1189
+ onAnimationEnd: () => removeItem(item.id),
1190
+ className: "absolute right-[-30%] whitespace-nowrap text-sm font-semibold text-white drop-shadow",
1191
+ style: {
1192
+ top: `${8 + item.track * 11}%`,
1193
+ color: item.color || "#ffffff",
1194
+ animation: `sa2kit-danmaku-move ${item.durationMs}ms linear forwards`
1195
+ }
1196
+ },
1197
+ item.text
1198
+ )))), /* @__PURE__ */ React3.createElement(
1199
+ FireworksControlPanel,
1200
+ {
1201
+ selectedKind,
1202
+ onKindChange: setSelectedKind,
1203
+ autoLaunchOnDanmaku: autoLaunch,
1204
+ onAutoLaunchChange: setAutoLaunch,
1205
+ avatarUrl,
1206
+ onAvatarUrlChange: setAvatarUrl,
1207
+ onLaunch: () => handleLaunch(selectedKind),
1208
+ fps,
1209
+ realtimeConnected: realtimeEnabled ? realtimeApi.state.connected : void 0,
1210
+ onlineCount: realtimeEnabled ? realtimeApi.state.onlineCount : void 0
1211
+ }
1212
+ ), /* @__PURE__ */ React3.createElement(DanmakuPanel, { onSend: handleSendDanmaku }), /* @__PURE__ */ React3.createElement("style", null, `
1213
+ @keyframes sa2kit-danmaku-move {
1214
+ 0% {
1215
+ transform: translateX(0);
1216
+ opacity: 1;
1217
+ }
1218
+ 100% {
1219
+ transform: translateX(-160vw);
1220
+ opacity: 0.92;
1221
+ }
1222
+ }
1223
+ `));
1224
+ }
1225
+
1226
+ export { DANMAKU_MAX_LENGTH, DANMAKU_TRACK_COUNT, DEFAULT_MAX_ACTIVE_FIREWORKS, DEFAULT_MAX_PARTICLES, DanmakuPanel, FIREWORK_KIND_LABELS, FireworksCanvas, FireworksControlPanel, MIKU_PALETTE, MikuFireworks3D, NORMAL_PALETTE, WebSocketTransport, useDanmakuController, useFireworksEngine, useFireworksRealtime };
1227
+ //# sourceMappingURL=index.mjs.map
1228
+ //# sourceMappingURL=index.mjs.map