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,1208 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var clsx = require('clsx');
5
+ var tailwindMerge = require('tailwind-merge');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var React__default = /*#__PURE__*/_interopDefault(React);
10
+
11
+ // src/mikuFusionGame/components/MikuFusionGame.tsx
12
+ function useStorage(storage, key, defaultValue) {
13
+ const [value, setValue] = React.useState(defaultValue);
14
+ const [loading, setLoading] = React.useState(true);
15
+ React.useEffect(() => {
16
+ const loadValue = async () => {
17
+ try {
18
+ const stored = await storage.getItem(key);
19
+ if (stored !== null) {
20
+ setValue(JSON.parse(stored));
21
+ }
22
+ } catch (error) {
23
+ console.error('Error reading storage key "' + key + '":', error);
24
+ } finally {
25
+ setLoading(false);
26
+ }
27
+ };
28
+ void loadValue();
29
+ }, [storage, key]);
30
+ const updateValue = React.useCallback(
31
+ async (newValue) => {
32
+ try {
33
+ setValue(newValue);
34
+ await storage.setItem(key, JSON.stringify(newValue));
35
+ if ("dispatchChange" in storage && typeof storage.dispatchChange === "function") {
36
+ storage.dispatchChange(key, JSON.stringify(newValue));
37
+ }
38
+ } catch (error) {
39
+ console.error('Error setting storage key "' + key + '":', error);
40
+ }
41
+ },
42
+ [storage, key]
43
+ );
44
+ const removeValue = React.useCallback(async () => {
45
+ try {
46
+ setValue(defaultValue);
47
+ await storage.removeItem(key);
48
+ if ("dispatchChange" in storage && typeof storage.dispatchChange === "function") {
49
+ storage.dispatchChange(key, null);
50
+ }
51
+ } catch (error) {
52
+ console.error('Error removing storage key "' + key + '":', error);
53
+ }
54
+ }, [storage, key, defaultValue]);
55
+ React.useEffect(() => {
56
+ if (!storage.addChangeListener) {
57
+ return;
58
+ }
59
+ const cleanup = storage.addChangeListener((changedKey, newValue) => {
60
+ if (changedKey === key) {
61
+ try {
62
+ if (newValue === null) {
63
+ setValue(defaultValue);
64
+ } else {
65
+ setValue(JSON.parse(newValue));
66
+ }
67
+ } catch (error) {
68
+ console.error("Error parsing storage change event:", error);
69
+ }
70
+ }
71
+ });
72
+ return cleanup;
73
+ }, [storage, key, defaultValue]);
74
+ return [value, updateValue, removeValue, loading];
75
+ }
76
+
77
+ // src/storage/adapters/web-adapter.ts
78
+ var isBrowser = typeof window !== "undefined" && typeof window.localStorage !== "undefined";
79
+ var WebStorageAdapter = class {
80
+ async getItem(key) {
81
+ if (!isBrowser) {
82
+ return null;
83
+ }
84
+ try {
85
+ return localStorage.getItem(key);
86
+ } catch (error) {
87
+ console.error('[WebStorage] Error getting item "' + key + '":', error);
88
+ return null;
89
+ }
90
+ }
91
+ async setItem(key, value) {
92
+ if (!isBrowser) {
93
+ return;
94
+ }
95
+ try {
96
+ localStorage.setItem(key, value);
97
+ } catch (error) {
98
+ console.error('[WebStorage] Error setting item "' + key + '":', error);
99
+ throw error;
100
+ }
101
+ }
102
+ async removeItem(key) {
103
+ if (!isBrowser) {
104
+ return;
105
+ }
106
+ try {
107
+ localStorage.removeItem(key);
108
+ } catch (error) {
109
+ console.error('[WebStorage] Error removing item "' + key + '":', error);
110
+ throw error;
111
+ }
112
+ }
113
+ async clear() {
114
+ if (!isBrowser) {
115
+ return;
116
+ }
117
+ try {
118
+ localStorage.clear();
119
+ } catch (error) {
120
+ console.error("[WebStorage] Error clearing storage:", error);
121
+ throw error;
122
+ }
123
+ }
124
+ addChangeListener(callback) {
125
+ if (!isBrowser) {
126
+ return () => {
127
+ };
128
+ }
129
+ const handleStorageEvent = (e) => {
130
+ if (e.key) {
131
+ callback(e.key, e.newValue);
132
+ }
133
+ };
134
+ const handleCustomEvent = (e) => {
135
+ const customEvent = e;
136
+ callback(customEvent.detail.key, customEvent.detail.value);
137
+ };
138
+ window.addEventListener("storage", handleStorageEvent);
139
+ window.addEventListener("local-storage-change", handleCustomEvent);
140
+ return () => {
141
+ window.removeEventListener("storage", handleStorageEvent);
142
+ window.removeEventListener("local-storage-change", handleCustomEvent);
143
+ };
144
+ }
145
+ /**
146
+ * 触发自定义事件,通知同标签页的其他组件
147
+ */
148
+ dispatchChange(key, value) {
149
+ if (!isBrowser) {
150
+ return;
151
+ }
152
+ window.dispatchEvent(
153
+ new CustomEvent("local-storage-change", {
154
+ detail: { key, value }
155
+ })
156
+ );
157
+ }
158
+ };
159
+
160
+ // src/storage/hooks/useLocalStorage.ts
161
+ var webStorage = new WebStorageAdapter();
162
+ function useLocalStorage(key, defaultValue) {
163
+ return useStorage(webStorage, key, defaultValue);
164
+ }
165
+
166
+ // src/logger/console-adapter.ts
167
+ var ConsoleLoggerAdapter = class {
168
+ constructor() {
169
+ this.colors = {
170
+ DEBUG: "\x1B[36m",
171
+ // Cyan
172
+ INFO: "\x1B[32m",
173
+ // Green
174
+ WARN: "\x1B[33m",
175
+ // Yellow
176
+ ERROR: "\x1B[31m",
177
+ // Red
178
+ RESET: "\x1B[0m"
179
+ };
180
+ }
181
+ log(entry) {
182
+ const { level, message, timestamp, data, context, error } = entry;
183
+ let logMessage = "";
184
+ if (timestamp) {
185
+ logMessage += "[" + this.formatTimestamp(timestamp) + "] ";
186
+ }
187
+ const levelName = this.getLevelName(level);
188
+ logMessage += levelName + ": ";
189
+ if (context) {
190
+ logMessage += "[" + context + "] ";
191
+ }
192
+ logMessage += message;
193
+ switch (level) {
194
+ case 0:
195
+ console.debug(this.colorize(logMessage, "DEBUG"), data || "");
196
+ break;
197
+ case 1:
198
+ console.info(this.colorize(logMessage, "INFO"), data || "");
199
+ break;
200
+ case 2:
201
+ console.warn(this.colorize(logMessage, "WARN"), data || "");
202
+ break;
203
+ case 3:
204
+ console.error(this.colorize(logMessage, "ERROR"), data || "");
205
+ if (error) {
206
+ console.error(error);
207
+ }
208
+ break;
209
+ }
210
+ }
211
+ formatTimestamp(date) {
212
+ return date.toISOString();
213
+ }
214
+ getLevelName(level) {
215
+ const names = ["DEBUG", "INFO", "WARN", "ERROR", "NONE"];
216
+ return names[level] || "UNKNOWN";
217
+ }
218
+ colorize(message, level) {
219
+ if (typeof process !== "undefined" && process.stdout?.isTTY) {
220
+ return this.colors[level] + message + this.colors.RESET;
221
+ }
222
+ return message;
223
+ }
224
+ };
225
+
226
+ // src/logger/Logger.ts
227
+ var Logger = class _Logger {
228
+ constructor(config, context) {
229
+ const isProduction = typeof process !== "undefined" ? process.env.NODE_ENV === "production" : false;
230
+ this.config = {
231
+ minLevel: config?.minLevel ?? (isProduction ? 1 : 0),
232
+ // INFO in prod, DEBUG in dev
233
+ enableTimestamp: config?.enableTimestamp ?? true,
234
+ enableContext: config?.enableContext ?? true,
235
+ environment: config?.environment ?? (isProduction ? "production" : "development"),
236
+ adapter: config?.adapter ?? new ConsoleLoggerAdapter()
237
+ };
238
+ this.adapter = this.config.adapter;
239
+ this.context = context;
240
+ }
241
+ /**
242
+ * 创建带上下文的子 Logger
243
+ */
244
+ createChild(context) {
245
+ return new _Logger(this.config, context);
246
+ }
247
+ /**
248
+ * 调试日志
249
+ */
250
+ debug(message, data) {
251
+ this.log(0, message, data);
252
+ }
253
+ /**
254
+ * 信息日志
255
+ */
256
+ info(message, data) {
257
+ this.log(1, message, data);
258
+ }
259
+ /**
260
+ * 警告日志
261
+ */
262
+ warn(message, data) {
263
+ this.log(2, message, data);
264
+ }
265
+ /**
266
+ * 错误日志
267
+ */
268
+ error(message, error) {
269
+ this.log(
270
+ 3,
271
+ // LogLevel.ERROR
272
+ message,
273
+ error instanceof Error ? void 0 : error,
274
+ error instanceof Error ? error : void 0
275
+ );
276
+ }
277
+ /**
278
+ * 核心日志方法
279
+ */
280
+ log(level, message, data, error) {
281
+ if (level < this.config.minLevel) {
282
+ return;
283
+ }
284
+ if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
285
+ const loggerDebug = localStorage.getItem("logger-debug");
286
+ if (loggerDebug === "false" && level < 3) {
287
+ return;
288
+ }
289
+ }
290
+ const entry = {
291
+ level,
292
+ message,
293
+ timestamp: this.config.enableTimestamp ? /* @__PURE__ */ new Date() : void 0,
294
+ data,
295
+ context: this.config.enableContext ? this.context : void 0,
296
+ error
297
+ };
298
+ this.adapter.log(entry);
299
+ }
300
+ /**
301
+ * 设置日志级别
302
+ */
303
+ setLevel(level) {
304
+ this.config.minLevel = level;
305
+ }
306
+ /**
307
+ * 获取当前日志级别
308
+ */
309
+ getLevel() {
310
+ return this.config.minLevel;
311
+ }
312
+ };
313
+ new Logger();
314
+ function cn(...inputs) {
315
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
316
+ }
317
+
318
+ // src/components/ImageMappingPanel.tsx
319
+ function ImageMappingPanel({
320
+ title,
321
+ items,
322
+ value,
323
+ onChange,
324
+ className,
325
+ uploadLabel = "\u4E0A\u4F20\u56FE\u7247",
326
+ clearLabel = "\u6E05\u9664"
327
+ }) {
328
+ const handleFileChange = (id, file) => {
329
+ if (!file) {
330
+ return;
331
+ }
332
+ const reader = new FileReader();
333
+ reader.onload = () => {
334
+ const base64 = typeof reader.result === "string" ? reader.result : "";
335
+ if (!base64) {
336
+ return;
337
+ }
338
+ onChange({
339
+ ...value,
340
+ [id]: base64
341
+ });
342
+ };
343
+ reader.readAsDataURL(file);
344
+ };
345
+ const handleRemove = (id) => {
346
+ const next = { ...value };
347
+ delete next[id];
348
+ onChange(next);
349
+ };
350
+ return /* @__PURE__ */ React__default.default.createElement("details", { className: cn("rounded-lg border border-slate-200 bg-white/80 p-3", className) }, /* @__PURE__ */ React__default.default.createElement("summary", { className: "cursor-pointer text-sm font-semibold text-slate-700" }, title), /* @__PURE__ */ React__default.default.createElement("div", { className: "mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2" }, items.map((item) => {
351
+ const image = value[item.id];
352
+ return /* @__PURE__ */ React__default.default.createElement(
353
+ "div",
354
+ {
355
+ key: item.id,
356
+ className: "flex items-center gap-2 rounded-md border border-slate-200 bg-white p-2"
357
+ },
358
+ /* @__PURE__ */ React__default.default.createElement("div", { className: "w-16 text-xs font-semibold text-slate-600" }, item.label),
359
+ /* @__PURE__ */ React__default.default.createElement("label", { className: "flex-1 cursor-pointer rounded-md bg-cyan-600 px-2 py-1 text-center text-xs font-medium text-white" }, uploadLabel, /* @__PURE__ */ React__default.default.createElement(
360
+ "input",
361
+ {
362
+ type: "file",
363
+ accept: "image/*",
364
+ className: "hidden",
365
+ onChange: (event) => {
366
+ handleFileChange(item.id, event.target.files?.[0]);
367
+ event.currentTarget.value = "";
368
+ }
369
+ }
370
+ )),
371
+ image ? /* @__PURE__ */ React__default.default.createElement(React__default.default.Fragment, null, /* @__PURE__ */ React__default.default.createElement(
372
+ "img",
373
+ {
374
+ src: image,
375
+ alt: item.label,
376
+ className: "h-8 w-8 rounded-full border border-slate-200 object-cover"
377
+ }
378
+ ), /* @__PURE__ */ React__default.default.createElement(
379
+ "button",
380
+ {
381
+ type: "button",
382
+ className: "rounded-md border border-red-200 px-2 py-1 text-xs text-red-600",
383
+ onClick: () => handleRemove(item.id)
384
+ },
385
+ clearLabel
386
+ )) : null
387
+ );
388
+ })));
389
+ }
390
+
391
+ // src/components/LocalImageMappingPanel.tsx
392
+ function LocalImageMappingPanel({
393
+ storageKey,
394
+ defaultValue = {},
395
+ onValueChange,
396
+ ...panelProps
397
+ }) {
398
+ const [value, setValue] = useLocalStorage(storageKey, defaultValue);
399
+ return /* @__PURE__ */ React__default.default.createElement(
400
+ ImageMappingPanel,
401
+ {
402
+ ...panelProps,
403
+ value,
404
+ onChange: (next) => {
405
+ setValue(next);
406
+ onValueChange?.(next);
407
+ }
408
+ }
409
+ );
410
+ }
411
+
412
+ // src/mikuFusionGame/constants.ts
413
+ var BASE_RADIUS = 22;
414
+ var RADIUS_STEP = 4;
415
+ var LEVEL_LABEL_PREFIX = "M";
416
+ var DEFAULT_MIKU_FUSION_CONFIG = {
417
+ width: 390,
418
+ height: 700,
419
+ gravity: 1450,
420
+ damping: 0.995,
421
+ collisionDamping: 0.92,
422
+ spawnY: 82,
423
+ lossLineY: 132,
424
+ maxLevel: 10,
425
+ maxOrbs: 90,
426
+ maxMergesPerTick: 6,
427
+ gameOverAgeThreshold: 1.1,
428
+ spawnWeights: [0.62, 0.28, 0.1],
429
+ theme: {
430
+ backgroundTop: "#dafaff",
431
+ backgroundBottom: "#86e5ef",
432
+ aimLine: "#14b8a6",
433
+ lossLine: "#ef4444",
434
+ orbColors: [
435
+ "#67e8f9",
436
+ "#22d3ee",
437
+ "#2dd4bf",
438
+ "#5eead4",
439
+ "#34d399",
440
+ "#a7f3d0",
441
+ "#99f6e4",
442
+ "#0ea5e9",
443
+ "#06b6d4",
444
+ "#14b8a6"
445
+ ]
446
+ }
447
+ };
448
+
449
+ // src/mikuFusionGame/engine/collision.ts
450
+ function resolveCircleCollisions(orbs, config) {
451
+ const next = orbs.map((orb) => ({ ...orb }));
452
+ for (let i = 0; i < next.length; i += 1) {
453
+ for (let j = i + 1; j < next.length; j += 1) {
454
+ const a = next[i];
455
+ const b = next[j];
456
+ const dx = b.x - a.x;
457
+ const dy = b.y - a.y;
458
+ const distance = Math.sqrt(dx * dx + dy * dy) || 1e-4;
459
+ const minDistance = a.radius + b.radius;
460
+ if (distance >= minDistance) {
461
+ continue;
462
+ }
463
+ const nx = dx / distance;
464
+ const ny = dy / distance;
465
+ const overlap = minDistance - distance;
466
+ a.x -= nx * overlap * 0.5;
467
+ a.y -= ny * overlap * 0.5;
468
+ b.x += nx * overlap * 0.5;
469
+ b.y += ny * overlap * 0.5;
470
+ const rvx = b.vx - a.vx;
471
+ const rvy = b.vy - a.vy;
472
+ const velocityAlongNormal = rvx * nx + rvy * ny;
473
+ if (velocityAlongNormal > 0) {
474
+ continue;
475
+ }
476
+ const restitution = config.collisionDamping;
477
+ const impulse = -(1 + restitution) * velocityAlongNormal / 2;
478
+ const impulseX = impulse * nx;
479
+ const impulseY = impulse * ny;
480
+ a.vx -= impulseX;
481
+ a.vy -= impulseY;
482
+ b.vx += impulseX;
483
+ b.vy += impulseY;
484
+ }
485
+ }
486
+ return next;
487
+ }
488
+
489
+ // src/mikuFusionGame/engine/scoring.ts
490
+ function getMergeScore(newLevel, chainIndex) {
491
+ const base = 10 * Math.max(1, newLevel) * Math.max(1, newLevel);
492
+ const chainMultiplier = 1 + Math.max(0, chainIndex - 1) * 0.15;
493
+ return Math.round(base * chainMultiplier);
494
+ }
495
+
496
+ // src/mikuFusionGame/engine/physics.ts
497
+ function getRadiusByLevel(level) {
498
+ return BASE_RADIUS + (Math.max(1, level) - 1) * RADIUS_STEP;
499
+ }
500
+ function stepPhysics(orbs, config, dt) {
501
+ return orbs.map((orb) => {
502
+ const next = { ...orb };
503
+ next.vy += config.gravity * dt;
504
+ next.vx *= config.damping;
505
+ next.vy *= config.damping;
506
+ next.x += next.vx * dt;
507
+ next.y += next.vy * dt;
508
+ next.age += dt;
509
+ if (next.x - next.radius < 0) {
510
+ next.x = next.radius;
511
+ next.vx = Math.abs(next.vx) * config.collisionDamping;
512
+ } else if (next.x + next.radius > config.width) {
513
+ next.x = config.width - next.radius;
514
+ next.vx = -Math.abs(next.vx) * config.collisionDamping;
515
+ }
516
+ if (next.y + next.radius > config.height) {
517
+ next.y = config.height - next.radius;
518
+ next.vy = -Math.abs(next.vy) * config.collisionDamping;
519
+ }
520
+ return next;
521
+ });
522
+ }
523
+
524
+ // src/mikuFusionGame/engine/merge.ts
525
+ function canMerge(a, b, config) {
526
+ if (a.level !== b.level) {
527
+ return false;
528
+ }
529
+ if (a.level >= config.maxLevel) {
530
+ return false;
531
+ }
532
+ const dx = b.x - a.x;
533
+ const dy = b.y - a.y;
534
+ const distance = Math.sqrt(dx * dx + dy * dy);
535
+ const threshold = a.radius + b.radius + 0.75;
536
+ return distance <= threshold;
537
+ }
538
+ function mergeSameLevelOrbs(orbs, config) {
539
+ const mergedIds = /* @__PURE__ */ new Set();
540
+ const spawned = [];
541
+ let scoreGain = 0;
542
+ let chainIndex = 0;
543
+ for (let i = 0; i < orbs.length; i += 1) {
544
+ if (mergedIds.has(orbs[i].id)) {
545
+ continue;
546
+ }
547
+ for (let j = i + 1; j < orbs.length; j += 1) {
548
+ if (mergedIds.has(orbs[j].id)) {
549
+ continue;
550
+ }
551
+ const a = orbs[i];
552
+ const b = orbs[j];
553
+ if (!canMerge(a, b, config)) {
554
+ continue;
555
+ }
556
+ mergedIds.add(a.id);
557
+ mergedIds.add(b.id);
558
+ chainIndex += 1;
559
+ const nextLevel = Math.min(config.maxLevel, a.level + 1);
560
+ const mergedOrb = {
561
+ id: `orb-${a.id}-${b.id}-${chainIndex}`,
562
+ x: (a.x + b.x) / 2,
563
+ y: (a.y + b.y) / 2,
564
+ vx: (a.vx + b.vx) / 2 * 0.4,
565
+ vy: (a.vy + b.vy) / 2 * 0.4,
566
+ radius: getRadiusByLevel(nextLevel),
567
+ level: nextLevel,
568
+ age: Math.min(a.age, b.age)
569
+ };
570
+ spawned.push(mergedOrb);
571
+ scoreGain += getMergeScore(nextLevel, chainIndex);
572
+ if (chainIndex >= config.maxMergesPerTick) {
573
+ break;
574
+ }
575
+ }
576
+ if (chainIndex >= config.maxMergesPerTick) {
577
+ break;
578
+ }
579
+ }
580
+ if (mergedIds.size === 0) {
581
+ return {
582
+ orbs,
583
+ scoreGain: 0,
584
+ mergeCount: 0
585
+ };
586
+ }
587
+ const survivors = orbs.filter((orb) => !mergedIds.has(orb.id));
588
+ const mergedOrbs = survivors.concat(spawned);
589
+ return {
590
+ orbs: mergedOrbs,
591
+ scoreGain,
592
+ mergeCount: chainIndex
593
+ };
594
+ }
595
+
596
+ // src/mikuFusionGame/engine/stateMachine.ts
597
+ var ALLOWED_TRANSITIONS = {
598
+ ready: ["playing"],
599
+ playing: ["paused", "gameOver", "ready"],
600
+ paused: ["playing", "ready"],
601
+ gameOver: ["ready", "playing"]
602
+ };
603
+ function canTransition(from, to) {
604
+ return ALLOWED_TRANSITIONS[from].includes(to);
605
+ }
606
+
607
+ // src/mikuFusionGame/hooks/useMikuFusionGame.ts
608
+ function clamp(value, min, max) {
609
+ return Math.max(min, Math.min(max, value));
610
+ }
611
+ function nextOrbLevel(weights) {
612
+ const random = Math.random();
613
+ let cumulative = 0;
614
+ for (let i = 0; i < weights.length; i += 1) {
615
+ cumulative += weights[i] ?? 0;
616
+ if (random <= cumulative) {
617
+ return i + 1;
618
+ }
619
+ }
620
+ return Math.max(1, weights.length);
621
+ }
622
+ function getOrbColor(level, config) {
623
+ const index = Math.max(0, Math.min(config.theme.orbColors.length - 1, level - 1));
624
+ return config.theme.orbColors[index] || "#22d3ee";
625
+ }
626
+ function drawScene(context, width, height, config, orbs, aimX, nextLevel, status, imageCache) {
627
+ const gradient = context.createLinearGradient(0, 0, 0, height);
628
+ gradient.addColorStop(0, config.theme.backgroundTop);
629
+ gradient.addColorStop(1, config.theme.backgroundBottom);
630
+ context.fillStyle = gradient;
631
+ context.fillRect(0, 0, width, height);
632
+ context.lineWidth = 2;
633
+ context.strokeStyle = config.theme.lossLine;
634
+ context.setLineDash([6, 6]);
635
+ context.beginPath();
636
+ context.moveTo(0, config.lossLineY);
637
+ context.lineTo(width, config.lossLineY);
638
+ context.stroke();
639
+ context.setLineDash([]);
640
+ context.strokeStyle = config.theme.aimLine;
641
+ context.lineWidth = 1.5;
642
+ context.beginPath();
643
+ context.moveTo(aimX, 0);
644
+ context.lineTo(aimX, config.spawnY);
645
+ context.stroke();
646
+ const previewRadius = getRadiusByLevel(nextLevel);
647
+ const previewY = config.spawnY - previewRadius - 4;
648
+ const previewImage = imageCache[nextLevel];
649
+ context.beginPath();
650
+ context.arc(aimX, previewY, previewRadius, 0, Math.PI * 2);
651
+ context.closePath();
652
+ if (previewImage && previewImage.complete && previewImage.naturalWidth > 0) {
653
+ context.save();
654
+ context.clip();
655
+ context.drawImage(
656
+ previewImage,
657
+ aimX - previewRadius,
658
+ previewY - previewRadius,
659
+ previewRadius * 2,
660
+ previewRadius * 2
661
+ );
662
+ context.restore();
663
+ } else {
664
+ context.fillStyle = getOrbColor(nextLevel, config);
665
+ context.fill();
666
+ }
667
+ orbs.forEach((orb) => {
668
+ context.beginPath();
669
+ context.arc(orb.x, orb.y, orb.radius, 0, Math.PI * 2);
670
+ context.closePath();
671
+ const orbImage = imageCache[orb.level];
672
+ if (orbImage && orbImage.complete && orbImage.naturalWidth > 0) {
673
+ context.save();
674
+ context.clip();
675
+ context.drawImage(
676
+ orbImage,
677
+ orb.x - orb.radius,
678
+ orb.y - orb.radius,
679
+ orb.radius * 2,
680
+ orb.radius * 2
681
+ );
682
+ context.restore();
683
+ context.beginPath();
684
+ context.strokeStyle = "rgba(15, 23, 42, 0.15)";
685
+ context.lineWidth = 1;
686
+ context.arc(orb.x, orb.y, orb.radius, 0, Math.PI * 2);
687
+ context.stroke();
688
+ } else {
689
+ context.fillStyle = getOrbColor(orb.level, config);
690
+ context.fill();
691
+ }
692
+ context.fillStyle = "#0f172a";
693
+ context.font = "bold 12px system-ui";
694
+ context.textAlign = "center";
695
+ context.textBaseline = "middle";
696
+ context.fillText(`${LEVEL_LABEL_PREFIX}${orb.level}`, orb.x, orb.y);
697
+ });
698
+ if (status === "paused") {
699
+ context.fillStyle = "rgba(15, 23, 42, 0.5)";
700
+ context.fillRect(0, 0, width, height);
701
+ context.fillStyle = "#ffffff";
702
+ context.font = "bold 30px system-ui";
703
+ context.textAlign = "center";
704
+ context.fillText("Paused", width / 2, height / 2);
705
+ }
706
+ }
707
+ function normalizeConfig(config) {
708
+ const mergedTheme = {
709
+ ...DEFAULT_MIKU_FUSION_CONFIG.theme,
710
+ ...config?.theme ?? {}
711
+ };
712
+ const merged = {
713
+ ...DEFAULT_MIKU_FUSION_CONFIG,
714
+ ...config ?? {},
715
+ theme: mergedTheme
716
+ };
717
+ const totalWeight = merged.spawnWeights.reduce((sum, current) => sum + current, 0);
718
+ if (totalWeight <= 0) {
719
+ merged.spawnWeights = [...DEFAULT_MIKU_FUSION_CONFIG.spawnWeights];
720
+ } else if (Math.abs(totalWeight - 1) > 1e-3) {
721
+ merged.spawnWeights = merged.spawnWeights.map((weight) => weight / totalWeight);
722
+ }
723
+ return merged;
724
+ }
725
+ function useMikuFusionGame(options = {}) {
726
+ const configRef = React.useRef(normalizeConfig(options));
727
+ const config = configRef.current;
728
+ const onScoreChangeRef = React.useRef(options.onScoreChange);
729
+ const onGameOverRef = React.useRef(options.onGameOver);
730
+ const orbImageMappingRef = React.useRef(options.orbImageMapping ?? {});
731
+ const imageCacheRef = React.useRef({});
732
+ React.useEffect(() => {
733
+ onScoreChangeRef.current = options.onScoreChange;
734
+ onGameOverRef.current = options.onGameOver;
735
+ }, [options.onGameOver, options.onScoreChange]);
736
+ React.useEffect(() => {
737
+ orbImageMappingRef.current = options.orbImageMapping ?? {};
738
+ const nextCache = {};
739
+ Object.entries(orbImageMappingRef.current).forEach(([levelText, src]) => {
740
+ const level = Number(levelText);
741
+ if (!src || Number.isNaN(level)) {
742
+ return;
743
+ }
744
+ const image = new window.Image();
745
+ image.src = src;
746
+ nextCache[level] = image;
747
+ });
748
+ imageCacheRef.current = nextCache;
749
+ }, [options.orbImageMapping]);
750
+ const [bestScore, setBestScore] = useLocalStorage(
751
+ options.storageKey || "sa2kit:mikuFusionGame:bestScore",
752
+ 0
753
+ );
754
+ const canvasRef = React.useRef(null);
755
+ const rafRef = React.useRef(null);
756
+ const lastTsRef = React.useRef(0);
757
+ const idRef = React.useRef(0);
758
+ const orbsRef = React.useRef([]);
759
+ const aimXRef = React.useRef(config.width / 2);
760
+ const scoreRef = React.useRef(0);
761
+ const [status, setStatus] = React.useState("ready");
762
+ const [score, setScore] = React.useState(0);
763
+ const [nextLevel, setNextLevel] = React.useState(() => nextOrbLevel(config.spawnWeights));
764
+ const draw = React.useCallback(
765
+ (nextStatus = status) => {
766
+ const canvas = canvasRef.current;
767
+ if (!canvas) {
768
+ return;
769
+ }
770
+ const context = canvas.getContext("2d");
771
+ if (!context) {
772
+ return;
773
+ }
774
+ drawScene(
775
+ context,
776
+ config.width,
777
+ config.height,
778
+ config,
779
+ orbsRef.current,
780
+ aimXRef.current,
781
+ nextLevel,
782
+ nextStatus,
783
+ imageCacheRef.current
784
+ );
785
+ },
786
+ [config, nextLevel, status]
787
+ );
788
+ const transitionStatus = React.useCallback((nextStatus) => {
789
+ setStatus((previous) => canTransition(previous, nextStatus) ? nextStatus : previous);
790
+ }, []);
791
+ const spawnOrb = React.useCallback(() => {
792
+ if (status === "paused" || status === "gameOver") {
793
+ return;
794
+ }
795
+ if (status === "ready") {
796
+ transitionStatus("playing");
797
+ }
798
+ idRef.current += 1;
799
+ const radius = getRadiusByLevel(nextLevel);
800
+ const x = clamp(aimXRef.current, radius, config.width - radius);
801
+ const created = {
802
+ id: `${idRef.current}`,
803
+ x,
804
+ y: config.spawnY,
805
+ vx: 0,
806
+ vy: 0,
807
+ radius,
808
+ level: nextLevel,
809
+ age: 0
810
+ };
811
+ const nextOrbs = orbsRef.current.concat(created);
812
+ orbsRef.current = nextOrbs.slice(-config.maxOrbs);
813
+ setNextLevel(nextOrbLevel(config.spawnWeights));
814
+ }, [config, nextLevel, status, transitionStatus]);
815
+ const restart = React.useCallback(() => {
816
+ orbsRef.current = [];
817
+ scoreRef.current = 0;
818
+ setScore(0);
819
+ setNextLevel(nextOrbLevel(config.spawnWeights));
820
+ transitionStatus("ready");
821
+ draw("ready");
822
+ }, [config.spawnWeights, draw, transitionStatus]);
823
+ const togglePause = React.useCallback(() => {
824
+ setStatus((previous) => {
825
+ if (previous === "playing" && canTransition(previous, "paused")) {
826
+ return "paused";
827
+ }
828
+ if (previous === "paused" && canTransition(previous, "playing")) {
829
+ return "playing";
830
+ }
831
+ return previous;
832
+ });
833
+ }, []);
834
+ const setAimFromClientX = React.useCallback((clientX, rect) => {
835
+ const ratio = config.width / rect.width;
836
+ const x = (clientX - rect.left) * ratio;
837
+ aimXRef.current = clamp(x, 0, config.width);
838
+ }, [config.width]);
839
+ const handleDrop = React.useCallback(
840
+ (clientX, rect) => {
841
+ setAimFromClientX(clientX, rect);
842
+ spawnOrb();
843
+ },
844
+ [setAimFromClientX, spawnOrb]
845
+ );
846
+ React.useEffect(() => {
847
+ draw(status);
848
+ }, [draw, status]);
849
+ React.useEffect(() => {
850
+ if (status !== "playing") {
851
+ if (rafRef.current) {
852
+ window.cancelAnimationFrame(rafRef.current);
853
+ }
854
+ lastTsRef.current = 0;
855
+ draw(status);
856
+ return;
857
+ }
858
+ const frame = (timestamp) => {
859
+ if (lastTsRef.current === 0) {
860
+ lastTsRef.current = timestamp;
861
+ }
862
+ const dt = Math.min((timestamp - lastTsRef.current) / 1e3, 0.033);
863
+ lastTsRef.current = timestamp;
864
+ let orbs = stepPhysics(orbsRef.current, config, dt);
865
+ orbs = resolveCircleCollisions(orbs, config);
866
+ const merged = mergeSameLevelOrbs(orbs, config);
867
+ orbs = merged.orbs;
868
+ orbsRef.current = orbs.slice(-config.maxOrbs);
869
+ if (merged.scoreGain > 0) {
870
+ scoreRef.current += merged.scoreGain;
871
+ setScore(scoreRef.current);
872
+ onScoreChangeRef.current?.(scoreRef.current);
873
+ }
874
+ const gameOver = orbs.some(
875
+ (orb) => orb.age > config.gameOverAgeThreshold && orb.y - orb.radius <= config.lossLineY && Math.abs(orb.vy) < 90
876
+ );
877
+ if (gameOver) {
878
+ transitionStatus("gameOver");
879
+ const latestBest = Math.max(bestScore, scoreRef.current);
880
+ if (latestBest !== bestScore) {
881
+ setBestScore(latestBest);
882
+ }
883
+ onGameOverRef.current?.(scoreRef.current, latestBest);
884
+ draw("gameOver");
885
+ return;
886
+ }
887
+ draw("playing");
888
+ rafRef.current = window.requestAnimationFrame(frame);
889
+ };
890
+ rafRef.current = window.requestAnimationFrame(frame);
891
+ return () => {
892
+ if (rafRef.current) {
893
+ window.cancelAnimationFrame(rafRef.current);
894
+ }
895
+ };
896
+ }, [
897
+ bestScore,
898
+ config,
899
+ draw,
900
+ setBestScore,
901
+ status,
902
+ transitionStatus
903
+ ]);
904
+ React.useEffect(() => {
905
+ if (score > bestScore) {
906
+ setBestScore(score);
907
+ }
908
+ }, [bestScore, score, setBestScore]);
909
+ return {
910
+ canvasRef,
911
+ status,
912
+ score,
913
+ bestScore: Math.max(bestScore, score),
914
+ nextLevel,
915
+ config,
916
+ setAimFromClientX,
917
+ handleDrop,
918
+ togglePause,
919
+ restart
920
+ };
921
+ }
922
+ function useResponsiveCanvas(logicalWidth, logicalHeight, containerRef) {
923
+ const [size, setSize] = React.useState({
924
+ displayWidth: logicalWidth,
925
+ displayHeight: logicalHeight,
926
+ scale: 1
927
+ });
928
+ React.useEffect(() => {
929
+ const updateSize = () => {
930
+ const container2 = containerRef.current;
931
+ if (!container2 || typeof window === "undefined") {
932
+ return;
933
+ }
934
+ const rect = container2.getBoundingClientRect();
935
+ const maxWidth = Math.max(280, rect.width);
936
+ const maxHeight = Math.max(420, Math.floor(window.innerHeight * 0.78));
937
+ const scale = Math.min(maxWidth / logicalWidth, maxHeight / logicalHeight, 1);
938
+ setSize({
939
+ displayWidth: Math.floor(logicalWidth * scale),
940
+ displayHeight: Math.floor(logicalHeight * scale),
941
+ scale
942
+ });
943
+ };
944
+ updateSize();
945
+ const container = containerRef.current;
946
+ const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(updateSize) : null;
947
+ if (container && observer) {
948
+ observer.observe(container);
949
+ }
950
+ window.addEventListener("resize", updateSize);
951
+ return () => {
952
+ observer?.disconnect();
953
+ window.removeEventListener("resize", updateSize);
954
+ };
955
+ }, [containerRef, logicalWidth, logicalHeight]);
956
+ return size;
957
+ }
958
+ function GameCanvas({
959
+ canvasRef,
960
+ width,
961
+ height,
962
+ displayWidth,
963
+ displayHeight,
964
+ onPointerMove,
965
+ onPointerDown,
966
+ onPointerUp
967
+ }) {
968
+ return /* @__PURE__ */ React__default.default.createElement(
969
+ "canvas",
970
+ {
971
+ ref: canvasRef,
972
+ width,
973
+ height,
974
+ className: "rounded-xl border border-cyan-200 bg-cyan-50 shadow-sm",
975
+ style: {
976
+ width: `${displayWidth}px`,
977
+ height: `${displayHeight}px`,
978
+ maxWidth: "100%",
979
+ touchAction: "none"
980
+ },
981
+ onPointerMove,
982
+ onPointerDown,
983
+ onPointerUp,
984
+ onContextMenu: (event) => event.preventDefault()
985
+ }
986
+ );
987
+ }
988
+ function GameControls({ isPaused, isPlaying, onTogglePause, onRestart }) {
989
+ return /* @__PURE__ */ React__default.default.createElement("div", { className: "grid grid-cols-2 gap-2" }, /* @__PURE__ */ React__default.default.createElement(
990
+ "button",
991
+ {
992
+ type: "button",
993
+ className: "rounded-lg bg-cyan-600 px-4 py-3 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:opacity-50",
994
+ onClick: onTogglePause,
995
+ disabled: !isPlaying
996
+ },
997
+ isPaused ? "\u7EE7\u7EED" : "\u6682\u505C"
998
+ ), /* @__PURE__ */ React__default.default.createElement(
999
+ "button",
1000
+ {
1001
+ type: "button",
1002
+ className: "rounded-lg bg-emerald-600 px-4 py-3 text-sm font-semibold text-white",
1003
+ onClick: onRestart
1004
+ },
1005
+ "\u91CD\u5F00"
1006
+ ));
1007
+ }
1008
+ function GameHUD({ score, bestScore, nextLevel, statusText }) {
1009
+ return /* @__PURE__ */ React__default.default.createElement("div", { className: "flex items-center justify-between gap-2 rounded-lg border border-cyan-200 bg-white/80 p-3" }, /* @__PURE__ */ React__default.default.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React__default.default.createElement("div", { className: "text-xs text-slate-500" }, "\u5F53\u524D\u5206\u6570"), /* @__PURE__ */ React__default.default.createElement("div", { className: "text-xl font-bold text-cyan-700" }, score), /* @__PURE__ */ React__default.default.createElement("div", { className: "text-xs text-slate-500" }, "\u6700\u9AD8\u5206 ", bestScore)), /* @__PURE__ */ React__default.default.createElement("div", { className: "text-right" }, /* @__PURE__ */ React__default.default.createElement("div", { className: "text-xs text-slate-500" }, "\u4E0B\u4E00\u4E2A"), /* @__PURE__ */ React__default.default.createElement("div", { className: "text-base font-semibold text-emerald-700" }, "M", nextLevel), /* @__PURE__ */ React__default.default.createElement("div", { className: "text-xs font-medium text-slate-600" }, statusText)));
1010
+ }
1011
+ function GameResultModal({ open, score, bestScore, onRestart }) {
1012
+ if (!open) {
1013
+ return null;
1014
+ }
1015
+ return /* @__PURE__ */ React__default.default.createElement("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-slate-900/50 p-4" }, /* @__PURE__ */ React__default.default.createElement("div", { className: "pointer-events-auto w-full max-w-xs rounded-xl bg-white p-5 shadow-2xl" }, /* @__PURE__ */ React__default.default.createElement("h3", { className: "text-lg font-semibold text-slate-900" }, "\u6E38\u620F\u7ED3\u675F"), /* @__PURE__ */ React__default.default.createElement("p", { className: "mt-2 text-sm text-slate-600" }, "\u5F53\u524D\u5206\u6570\uFF1A", score), /* @__PURE__ */ React__default.default.createElement("p", { className: "text-sm text-slate-600" }, "\u6700\u9AD8\u5206\uFF1A", bestScore), /* @__PURE__ */ React__default.default.createElement(
1016
+ "button",
1017
+ {
1018
+ type: "button",
1019
+ className: "mt-4 w-full rounded-lg bg-cyan-600 px-4 py-2 text-sm font-semibold text-white",
1020
+ onClick: onRestart
1021
+ },
1022
+ "\u518D\u6765\u4E00\u5C40"
1023
+ )));
1024
+ }
1025
+
1026
+ // src/mikuFusionGame/components/MikuFusionGame.tsx
1027
+ function MikuFusionGame({
1028
+ className,
1029
+ storageKey,
1030
+ orbImageStorageKey = "sa2kit:mikuFusionGame:orbImages",
1031
+ enableImageConfigPanel = true,
1032
+ presetOrbImageMapping = {},
1033
+ ...options
1034
+ }) {
1035
+ const containerRef = React.useRef(null);
1036
+ const [orbImageMapping, setOrbImageMapping] = React.useState({});
1037
+ const [isUserDefineMode] = React.useState(() => {
1038
+ if (typeof window === "undefined") {
1039
+ return false;
1040
+ }
1041
+ const value = new URLSearchParams(window.location.search).get("userDefine");
1042
+ return value === "true";
1043
+ });
1044
+ const activeOrbImageMapping = isUserDefineMode ? orbImageMapping : presetOrbImageMapping;
1045
+ const {
1046
+ canvasRef,
1047
+ status,
1048
+ score,
1049
+ bestScore,
1050
+ nextLevel,
1051
+ config,
1052
+ setAimFromClientX,
1053
+ handleDrop,
1054
+ togglePause,
1055
+ restart
1056
+ } = useMikuFusionGame({ ...options, storageKey, orbImageMapping: activeOrbImageMapping });
1057
+ const panelItems = React.useMemo(
1058
+ () => Array.from({ length: config.maxLevel }, (_, index) => ({
1059
+ id: String(index + 1),
1060
+ label: `M${index + 1}`
1061
+ })),
1062
+ [config.maxLevel]
1063
+ );
1064
+ const panelValue = React.useMemo(
1065
+ () => Object.entries(orbImageMapping).reduce((acc, [key, value]) => {
1066
+ if (value) {
1067
+ acc[String(key)] = value;
1068
+ }
1069
+ return acc;
1070
+ }, {}),
1071
+ [orbImageMapping]
1072
+ );
1073
+ React.useEffect(() => {
1074
+ if (typeof window === "undefined") {
1075
+ return;
1076
+ }
1077
+ try {
1078
+ const raw = window.localStorage.getItem(orbImageStorageKey);
1079
+ if (!raw) {
1080
+ setOrbImageMapping({});
1081
+ return;
1082
+ }
1083
+ const parsed = JSON.parse(raw);
1084
+ const normalized = {};
1085
+ Object.entries(parsed).forEach(([key, value]) => {
1086
+ const level = Number(key);
1087
+ if (!Number.isNaN(level) && typeof value === "string" && value) {
1088
+ normalized[level] = value;
1089
+ }
1090
+ });
1091
+ setOrbImageMapping(normalized);
1092
+ } catch {
1093
+ setOrbImageMapping({});
1094
+ }
1095
+ }, [orbImageStorageKey]);
1096
+ const { displayWidth, displayHeight } = useResponsiveCanvas(
1097
+ config.width,
1098
+ config.height,
1099
+ containerRef
1100
+ );
1101
+ const statusText = React.useMemo(() => {
1102
+ if (status === "ready") {
1103
+ return "\u5F85\u5F00\u59CB";
1104
+ }
1105
+ if (status === "playing") {
1106
+ return "\u8FDB\u884C\u4E2D";
1107
+ }
1108
+ if (status === "paused") {
1109
+ return "\u5DF2\u6682\u505C";
1110
+ }
1111
+ return "\u5DF2\u7ED3\u675F";
1112
+ }, [status]);
1113
+ const handlePointerMove = (event) => {
1114
+ const rect = event.currentTarget.getBoundingClientRect();
1115
+ setAimFromClientX(event.clientX, rect);
1116
+ };
1117
+ const handlePointerDown = (event) => {
1118
+ event.currentTarget.setPointerCapture(event.pointerId);
1119
+ const rect = event.currentTarget.getBoundingClientRect();
1120
+ setAimFromClientX(event.clientX, rect);
1121
+ };
1122
+ const handlePointerUp = (event) => {
1123
+ const rect = event.currentTarget.getBoundingClientRect();
1124
+ handleDrop(event.clientX, rect);
1125
+ event.currentTarget.releasePointerCapture(event.pointerId);
1126
+ };
1127
+ const handleKeyDown = (event) => {
1128
+ if (event.key.toLowerCase() === "r") {
1129
+ restart();
1130
+ }
1131
+ if (event.key.toLowerCase() === "p") {
1132
+ togglePause();
1133
+ }
1134
+ };
1135
+ const handlePanelValueChange = (next) => {
1136
+ const normalized = {};
1137
+ Object.entries(next).forEach(([key, value]) => {
1138
+ const level = Number(key);
1139
+ if (!Number.isNaN(level) && value) {
1140
+ normalized[level] = value;
1141
+ }
1142
+ });
1143
+ setOrbImageMapping(normalized);
1144
+ };
1145
+ return /* @__PURE__ */ React__default.default.createElement(
1146
+ "div",
1147
+ {
1148
+ ref: containerRef,
1149
+ tabIndex: 0,
1150
+ onKeyDown: handleKeyDown,
1151
+ className: `relative mx-auto flex w-full max-w-xl flex-col gap-3 p-3 md:p-4 ${className || ""}`,
1152
+ style: {
1153
+ paddingBottom: "max(0.75rem, env(safe-area-inset-bottom))"
1154
+ }
1155
+ },
1156
+ /* @__PURE__ */ React__default.default.createElement(GameHUD, { score, bestScore, nextLevel, statusText }),
1157
+ /* @__PURE__ */ React__default.default.createElement("div", { className: "relative mx-auto" }, /* @__PURE__ */ React__default.default.createElement(
1158
+ GameCanvas,
1159
+ {
1160
+ canvasRef,
1161
+ width: config.width,
1162
+ height: config.height,
1163
+ displayWidth,
1164
+ displayHeight,
1165
+ onPointerMove: handlePointerMove,
1166
+ onPointerDown: handlePointerDown,
1167
+ onPointerUp: handlePointerUp
1168
+ }
1169
+ ), /* @__PURE__ */ React__default.default.createElement(GameResultModal, { open: status === "gameOver", score, bestScore, onRestart: restart })),
1170
+ /* @__PURE__ */ React__default.default.createElement(
1171
+ GameControls,
1172
+ {
1173
+ isPaused: status === "paused",
1174
+ isPlaying: status === "playing" || status === "paused",
1175
+ onTogglePause: togglePause,
1176
+ onRestart: restart
1177
+ }
1178
+ ),
1179
+ enableImageConfigPanel && isUserDefineMode ? /* @__PURE__ */ React__default.default.createElement(
1180
+ LocalImageMappingPanel,
1181
+ {
1182
+ storageKey: orbImageStorageKey,
1183
+ title: "\u7403\u4F53\u56FE\u7247\u914D\u7F6E\uFF08\u4FDD\u5B58\u5230\u672C\u5730\uFF09",
1184
+ items: panelItems,
1185
+ defaultValue: panelValue,
1186
+ onValueChange: handlePanelValueChange
1187
+ }
1188
+ ) : null
1189
+ );
1190
+ }
1191
+
1192
+ // src/mikuFusionGame/content/index.ts
1193
+ var defaultContentRegistry = {
1194
+ producers: [],
1195
+ songs: [],
1196
+ themes: []
1197
+ };
1198
+
1199
+ exports.BASE_RADIUS = BASE_RADIUS;
1200
+ exports.DEFAULT_MIKU_FUSION_CONFIG = DEFAULT_MIKU_FUSION_CONFIG;
1201
+ exports.LEVEL_LABEL_PREFIX = LEVEL_LABEL_PREFIX;
1202
+ exports.MikuFusionGame = MikuFusionGame;
1203
+ exports.RADIUS_STEP = RADIUS_STEP;
1204
+ exports.defaultContentRegistry = defaultContentRegistry;
1205
+ exports.useMikuFusionGame = useMikuFusionGame;
1206
+ exports.useResponsiveCanvas = useResponsiveCanvas;
1207
+ //# sourceMappingURL=index.js.map
1208
+ //# sourceMappingURL=index.js.map