sa2kit 1.6.30 → 1.6.32

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 (251) hide show
  1. package/dist/AliyunOSSProvider-4W47OFEK.mjs +6 -0
  2. package/dist/{AliyunOSSProvider-KJYRIZES.mjs.map → AliyunOSSProvider-4W47OFEK.mjs.map} +1 -1
  3. package/dist/AliyunOSSProvider-HCNGDJL7.js +15 -0
  4. package/dist/{AliyunOSSProvider-FWAKUB2T.js.map → AliyunOSSProvider-HCNGDJL7.js.map} +1 -1
  5. package/dist/ConfigService-3DIC6C3Q.js +21 -0
  6. package/dist/{ConfigService-7MEZXKJ5.js.map → ConfigService-3DIC6C3Q.js.map} +1 -1
  7. package/dist/ConfigService-V6ZK273Z.mjs +4 -0
  8. package/dist/{ConfigService-BV57YYFW.mjs.map → ConfigService-V6ZK273Z.mjs.map} +1 -1
  9. package/dist/LocalStorageProvider-3RVPCQB3.mjs +6 -0
  10. package/dist/{LocalStorageProvider-RTPMUOZ2.mjs.map → LocalStorageProvider-3RVPCQB3.mjs.map} +1 -1
  11. package/dist/LocalStorageProvider-PP7MA5OT.js +15 -0
  12. package/dist/{LocalStorageProvider-XSRCUXOU.js.map → LocalStorageProvider-PP7MA5OT.js.map} +1 -1
  13. package/dist/PMXParser-2VTA737I.js +13 -0
  14. package/dist/{PMXParser-YBS3B6HM.js.map → PMXParser-2VTA737I.js.map} +1 -1
  15. package/dist/PMXParser-RNVQL76A.mjs +4 -0
  16. package/dist/{PMXParser-L6IWHL4I.mjs.map → PMXParser-RNVQL76A.mjs.map} +1 -1
  17. package/dist/analytics/index.js +46 -45
  18. package/dist/analytics/index.js.map +1 -1
  19. package/dist/analytics/index.mjs +45 -44
  20. package/dist/analytics/index.mjs.map +1 -1
  21. package/dist/analytics/server/index.js +4 -4
  22. package/dist/analytics/server/index.js.map +1 -1
  23. package/dist/analytics/server/index.mjs +4 -4
  24. package/dist/analytics/server/index.mjs.map +1 -1
  25. package/dist/api/index.js +5 -5
  26. package/dist/api/index.js.map +1 -1
  27. package/dist/api/index.mjs +5 -5
  28. package/dist/api/index.mjs.map +1 -1
  29. package/dist/audioDetection/index.js +17 -16
  30. package/dist/audioDetection/index.js.map +1 -1
  31. package/dist/audioDetection/index.mjs +17 -16
  32. package/dist/audioDetection/index.mjs.map +1 -1
  33. package/dist/auth/client/index.js +4 -4
  34. package/dist/auth/client/index.mjs +1 -1
  35. package/dist/auth/components/index.js +3 -3
  36. package/dist/auth/components/index.js.map +1 -1
  37. package/dist/auth/components/index.mjs +3 -3
  38. package/dist/auth/components/index.mjs.map +1 -1
  39. package/dist/auth/index.js +29 -29
  40. package/dist/auth/index.mjs +5 -5
  41. package/dist/auth/middleware/index.js +3 -3
  42. package/dist/auth/middleware/index.mjs +2 -2
  43. package/dist/auth/routes/index.js +14 -14
  44. package/dist/auth/routes/index.mjs +2 -2
  45. package/dist/auth/services/index.js +7 -7
  46. package/dist/auth/services/index.mjs +1 -1
  47. package/dist/calendar/index.js +146 -182
  48. package/dist/calendar/index.js.map +1 -1
  49. package/dist/calendar/index.mjs +139 -175
  50. package/dist/calendar/index.mjs.map +1 -1
  51. package/dist/calendar/routes/index.js +1 -1
  52. package/dist/calendar/routes/index.js.map +1 -1
  53. package/dist/calendar/routes/index.mjs +1 -1
  54. package/dist/calendar/routes/index.mjs.map +1 -1
  55. package/dist/{chunk-5YQ5B7IZ.js → chunk-24HGREE6.js} +5 -5
  56. package/dist/{chunk-5YQ5B7IZ.js.map → chunk-24HGREE6.js.map} +1 -1
  57. package/dist/{chunk-6PRFP5EG.js → chunk-25OFOKNF.js} +6 -6
  58. package/dist/chunk-25OFOKNF.js.map +1 -0
  59. package/dist/{chunk-KQGP6BTS.mjs → chunk-3DXPQ4YV.mjs} +6 -6
  60. package/dist/chunk-3DXPQ4YV.mjs.map +1 -0
  61. package/dist/{chunk-3BGPZN4X.mjs → chunk-3NHAT7D4.mjs} +12 -12
  62. package/dist/chunk-3NHAT7D4.mjs.map +1 -0
  63. package/dist/{chunk-MW4BCIZC.mjs → chunk-4HC6M7FK.mjs} +3 -3
  64. package/dist/chunk-4HC6M7FK.mjs.map +1 -0
  65. package/dist/{chunk-ESRCX5TQ.mjs → chunk-52TN2QSS.mjs} +3 -3
  66. package/dist/{chunk-ESRCX5TQ.mjs.map → chunk-52TN2QSS.mjs.map} +1 -1
  67. package/dist/{chunk-CNTILN5J.mjs → chunk-5YQ62BKX.mjs} +20 -19
  68. package/dist/chunk-5YQ62BKX.mjs.map +1 -0
  69. package/dist/{chunk-6W5BMXJG.js → chunk-6OWNMJKG.js} +4 -4
  70. package/dist/{chunk-6W5BMXJG.js.map → chunk-6OWNMJKG.js.map} +1 -1
  71. package/dist/{chunk-DUHZ7VZP.js → chunk-7VRT55ZD.js} +3 -3
  72. package/dist/chunk-7VRT55ZD.js.map +1 -0
  73. package/dist/{chunk-3WOAPLEG.mjs → chunk-EB4NR623.mjs} +27 -26
  74. package/dist/chunk-EB4NR623.mjs.map +1 -0
  75. package/dist/chunk-EI27JKND.mjs +1988 -0
  76. package/dist/chunk-EI27JKND.mjs.map +1 -0
  77. package/dist/{chunk-CD77U7LZ.js → chunk-GBPLX42J.js} +9 -9
  78. package/dist/chunk-GBPLX42J.js.map +1 -0
  79. package/dist/{chunk-TFQF2HDO.mjs → chunk-HDEOCX2L.mjs} +12 -12
  80. package/dist/chunk-HDEOCX2L.mjs.map +1 -0
  81. package/dist/{chunk-TV3VKRJK.mjs → chunk-HDMIOOZY.mjs} +38 -68
  82. package/dist/chunk-HDMIOOZY.mjs.map +1 -0
  83. package/dist/{chunk-OPPF3326.js → chunk-HJ6MH7J7.js} +39 -69
  84. package/dist/chunk-HJ6MH7J7.js.map +1 -0
  85. package/dist/chunk-KO73EBUT.js +80 -0
  86. package/dist/chunk-KO73EBUT.js.map +1 -0
  87. package/dist/{chunk-6YKMCPQI.mjs → chunk-KZKIH4AS.mjs} +4 -4
  88. package/dist/chunk-KZKIH4AS.mjs.map +1 -0
  89. package/dist/{chunk-LX4XX6W7.js → chunk-L47ZOYHL.js} +15 -89
  90. package/dist/chunk-L47ZOYHL.js.map +1 -0
  91. package/dist/{chunk-6MQUBPKB.mjs → chunk-LJ4CCSSY.mjs} +3 -3
  92. package/dist/{chunk-6MQUBPKB.mjs.map → chunk-LJ4CCSSY.mjs.map} +1 -1
  93. package/dist/{chunk-TOC5FSHP.js → chunk-NJ2SNXBJ.js} +12 -12
  94. package/dist/chunk-NJ2SNXBJ.js.map +1 -0
  95. package/dist/{chunk-OCR5DS4C.mjs → chunk-PE5EAHZK.mjs} +3 -3
  96. package/dist/chunk-PE5EAHZK.mjs.map +1 -0
  97. package/dist/{chunk-LZHMNOED.js → chunk-Q5EDCKQA.js} +26 -26
  98. package/dist/chunk-Q5EDCKQA.js.map +1 -0
  99. package/dist/{chunk-CLKKZSPZ.js → chunk-RBKGYWME.js} +20 -19
  100. package/dist/chunk-RBKGYWME.js.map +1 -0
  101. package/dist/{chunk-VRTRSEEH.mjs → chunk-RSJSZ7QH.mjs} +11 -11
  102. package/dist/chunk-RSJSZ7QH.mjs.map +1 -0
  103. package/dist/{chunk-E7RGBAYJ.js → chunk-TDCDEBGP.js} +30 -29
  104. package/dist/chunk-TDCDEBGP.js.map +1 -0
  105. package/dist/{chunk-JZXJQMVE.js → chunk-UIFFDRTE.js} +11 -11
  106. package/dist/chunk-UIFFDRTE.js.map +1 -0
  107. package/dist/{chunk-T5OZHYVM.mjs → chunk-UKT3PLON.mjs} +13 -85
  108. package/dist/chunk-UKT3PLON.mjs.map +1 -0
  109. package/dist/{chunk-UOFTHYIH.js → chunk-UL6XJGUZ.js} +4 -4
  110. package/dist/chunk-UL6XJGUZ.js.map +1 -0
  111. package/dist/chunk-VVWQTO4Y.mjs +77 -0
  112. package/dist/chunk-VVWQTO4Y.mjs.map +1 -0
  113. package/dist/{chunk-A3UP56MS.js → chunk-WA67GZSZ.js} +3 -3
  114. package/dist/chunk-WA67GZSZ.js.map +1 -0
  115. package/dist/{chunk-OLHGZXN3.mjs → chunk-WEEXCPSE.mjs} +5 -5
  116. package/dist/chunk-WEEXCPSE.mjs.map +1 -0
  117. package/dist/chunk-XGBE4SUV.js +2093 -0
  118. package/dist/chunk-XGBE4SUV.js.map +1 -0
  119. package/dist/{chunk-QU5OT4DF.js → chunk-XJ7ZAGC5.js} +5 -5
  120. package/dist/chunk-XJ7ZAGC5.js.map +1 -0
  121. package/dist/{chunk-ZI25QCHD.mjs → chunk-YOTQG4NP.mjs} +25 -25
  122. package/dist/chunk-YOTQG4NP.mjs.map +1 -0
  123. package/dist/{chunk-QAT2RWAO.mjs → chunk-Z36R3P62.mjs} +7 -7
  124. package/dist/chunk-Z36R3P62.mjs.map +1 -0
  125. package/dist/{chunk-7Z5LLJ3A.js → chunk-ZWQJSZEY.js} +13 -13
  126. package/dist/chunk-ZWQJSZEY.js.map +1 -0
  127. package/dist/config/index.js +6 -6
  128. package/dist/config/index.js.map +1 -1
  129. package/dist/config/index.mjs +6 -6
  130. package/dist/config/index.mjs.map +1 -1
  131. package/dist/config/server/index.js +37 -37
  132. package/dist/config/server/index.js.map +1 -1
  133. package/dist/config/server/index.mjs +37 -37
  134. package/dist/config/server/index.mjs.map +1 -1
  135. package/dist/i18n/index.d.mts +2 -2
  136. package/dist/i18n/index.d.ts +2 -2
  137. package/dist/i18n/index.js +16 -17
  138. package/dist/i18n/index.js.map +1 -1
  139. package/dist/i18n/index.mjs +16 -17
  140. package/dist/i18n/index.mjs.map +1 -1
  141. package/dist/imageCrop/index.js +11 -10
  142. package/dist/imageCrop/index.js.map +1 -1
  143. package/dist/imageCrop/index.mjs +11 -10
  144. package/dist/imageCrop/index.mjs.map +1 -1
  145. package/dist/index.d.mts +185 -100
  146. package/dist/index.d.ts +185 -100
  147. package/dist/index.js +225 -249
  148. package/dist/index.js.map +1 -1
  149. package/dist/index.mjs +80 -104
  150. package/dist/index.mjs.map +1 -1
  151. package/dist/logger/index.js +6 -6
  152. package/dist/logger/index.mjs +1 -1
  153. package/dist/mikuFusionGame/index.d.mts +112 -0
  154. package/dist/mikuFusionGame/index.d.ts +112 -0
  155. package/dist/mikuFusionGame/index.js +680 -0
  156. package/dist/mikuFusionGame/index.js.map +1 -0
  157. package/dist/mikuFusionGame/index.mjs +667 -0
  158. package/dist/mikuFusionGame/index.mjs.map +1 -0
  159. package/dist/mmd/admin/index.js +11 -10
  160. package/dist/mmd/admin/index.js.map +1 -1
  161. package/dist/mmd/admin/index.mjs +11 -10
  162. package/dist/mmd/admin/index.mjs.map +1 -1
  163. package/dist/mmd/index.js +223 -241
  164. package/dist/mmd/index.js.map +1 -1
  165. package/dist/mmd/index.mjs +220 -238
  166. package/dist/mmd/index.mjs.map +1 -1
  167. package/dist/mmd/server/index.js +6 -6
  168. package/dist/mmd/server/index.js.map +1 -1
  169. package/dist/mmd/server/index.mjs +6 -6
  170. package/dist/mmd/server/index.mjs.map +1 -1
  171. package/dist/music/index.js +16 -16
  172. package/dist/music/index.mjs +2 -2
  173. package/dist/music/server/index.js +8 -8
  174. package/dist/music/server/index.mjs +1 -1
  175. package/dist/request/index.js +2 -2
  176. package/dist/request/index.js.map +1 -1
  177. package/dist/request/index.mjs +2 -2
  178. package/dist/request/index.mjs.map +1 -1
  179. package/dist/storage/index.js +15 -14
  180. package/dist/storage/index.mjs +3 -2
  181. package/dist/testYourself/admin/index.js +3 -3
  182. package/dist/testYourself/admin/index.mjs +1 -1
  183. package/dist/testYourself/index.js +22 -22
  184. package/dist/testYourself/index.js.map +1 -1
  185. package/dist/testYourself/index.mjs +14 -14
  186. package/dist/testYourself/index.mjs.map +1 -1
  187. package/dist/testYourself/server/index.js +4 -4
  188. package/dist/testYourself/server/index.mjs +1 -1
  189. package/dist/universalExport/index.d.mts +3 -3
  190. package/dist/universalExport/index.d.ts +3 -3
  191. package/dist/universalExport/index.js +48 -47
  192. package/dist/universalExport/index.js.map +1 -1
  193. package/dist/universalExport/index.mjs +48 -47
  194. package/dist/universalExport/index.mjs.map +1 -1
  195. package/dist/universalExport/server/index.js +29 -29
  196. package/dist/universalExport/server/index.js.map +1 -1
  197. package/dist/universalExport/server/index.mjs +28 -28
  198. package/dist/universalExport/server/index.mjs.map +1 -1
  199. package/dist/universalFile/index.d.mts +3 -3
  200. package/dist/universalFile/index.d.ts +3 -3
  201. package/dist/universalFile/index.js +73 -72
  202. package/dist/universalFile/index.js.map +1 -1
  203. package/dist/universalFile/index.mjs +73 -72
  204. package/dist/universalFile/index.mjs.map +1 -1
  205. package/dist/universalFile/server/index.js +258 -260
  206. package/dist/universalFile/server/index.js.map +1 -1
  207. package/dist/universalFile/server/index.mjs +244 -246
  208. package/dist/universalFile/server/index.mjs.map +1 -1
  209. package/dist/utils/index.js +11 -11
  210. package/dist/utils/index.mjs +2 -2
  211. package/package.json +25 -31
  212. package/dist/AliyunOSSProvider-FWAKUB2T.js +0 -15
  213. package/dist/AliyunOSSProvider-KJYRIZES.mjs +0 -6
  214. package/dist/ConfigService-7MEZXKJ5.js +0 -21
  215. package/dist/ConfigService-BV57YYFW.mjs +0 -4
  216. package/dist/LocalStorageProvider-RTPMUOZ2.mjs +0 -6
  217. package/dist/LocalStorageProvider-XSRCUXOU.js +0 -15
  218. package/dist/PMXParser-L6IWHL4I.mjs +0 -4
  219. package/dist/PMXParser-YBS3B6HM.js +0 -13
  220. package/dist/chunk-3BGPZN4X.mjs.map +0 -1
  221. package/dist/chunk-3WOAPLEG.mjs.map +0 -1
  222. package/dist/chunk-6PRFP5EG.js.map +0 -1
  223. package/dist/chunk-6YKMCPQI.mjs.map +0 -1
  224. package/dist/chunk-7Z5LLJ3A.js.map +0 -1
  225. package/dist/chunk-A3UP56MS.js.map +0 -1
  226. package/dist/chunk-CD77U7LZ.js.map +0 -1
  227. package/dist/chunk-CLKKZSPZ.js.map +0 -1
  228. package/dist/chunk-CNTILN5J.mjs.map +0 -1
  229. package/dist/chunk-DUHZ7VZP.js.map +0 -1
  230. package/dist/chunk-DW2ZTOCV.js +0 -1727
  231. package/dist/chunk-DW2ZTOCV.js.map +0 -1
  232. package/dist/chunk-E7RGBAYJ.js.map +0 -1
  233. package/dist/chunk-JZXJQMVE.js.map +0 -1
  234. package/dist/chunk-KQGP6BTS.mjs.map +0 -1
  235. package/dist/chunk-LFG6FPM5.mjs +0 -1597
  236. package/dist/chunk-LFG6FPM5.mjs.map +0 -1
  237. package/dist/chunk-LX4XX6W7.js.map +0 -1
  238. package/dist/chunk-LZHMNOED.js.map +0 -1
  239. package/dist/chunk-MW4BCIZC.mjs.map +0 -1
  240. package/dist/chunk-OCR5DS4C.mjs.map +0 -1
  241. package/dist/chunk-OLHGZXN3.mjs.map +0 -1
  242. package/dist/chunk-OPPF3326.js.map +0 -1
  243. package/dist/chunk-QAT2RWAO.mjs.map +0 -1
  244. package/dist/chunk-QU5OT4DF.js.map +0 -1
  245. package/dist/chunk-T5OZHYVM.mjs.map +0 -1
  246. package/dist/chunk-TFQF2HDO.mjs.map +0 -1
  247. package/dist/chunk-TOC5FSHP.js.map +0 -1
  248. package/dist/chunk-TV3VKRJK.mjs.map +0 -1
  249. package/dist/chunk-UOFTHYIH.js.map +0 -1
  250. package/dist/chunk-VRTRSEEH.mjs.map +0 -1
  251. package/dist/chunk-ZI25QCHD.mjs.map +0 -1
@@ -0,0 +1,667 @@
1
+ import { useLocalStorage } from '../chunk-VVWQTO4Y.mjs';
2
+ import '../chunk-WEEXCPSE.mjs';
3
+ import '../chunk-BJTO5JO5.mjs';
4
+ import React3, { useRef, useEffect, useState, useCallback, useMemo } from 'react';
5
+
6
+ // src/mikuFusionGame/constants.ts
7
+ var BASE_RADIUS = 22;
8
+ var RADIUS_STEP = 4;
9
+ var LEVEL_LABEL_PREFIX = "M";
10
+ var DEFAULT_MIKU_FUSION_CONFIG = {
11
+ width: 390,
12
+ height: 700,
13
+ gravity: 1450,
14
+ damping: 0.995,
15
+ collisionDamping: 0.92,
16
+ spawnY: 82,
17
+ lossLineY: 132,
18
+ maxLevel: 10,
19
+ maxOrbs: 90,
20
+ maxMergesPerTick: 6,
21
+ gameOverAgeThreshold: 1.1,
22
+ spawnWeights: [0.62, 0.28, 0.1],
23
+ theme: {
24
+ backgroundTop: "#dafaff",
25
+ backgroundBottom: "#86e5ef",
26
+ aimLine: "#14b8a6",
27
+ lossLine: "#ef4444",
28
+ orbColors: [
29
+ "#67e8f9",
30
+ "#22d3ee",
31
+ "#2dd4bf",
32
+ "#5eead4",
33
+ "#34d399",
34
+ "#a7f3d0",
35
+ "#99f6e4",
36
+ "#0ea5e9",
37
+ "#06b6d4",
38
+ "#14b8a6"
39
+ ]
40
+ }
41
+ };
42
+
43
+ // src/mikuFusionGame/engine/collision.ts
44
+ function resolveCircleCollisions(orbs, config) {
45
+ const next = orbs.map((orb) => ({ ...orb }));
46
+ for (let i = 0; i < next.length; i += 1) {
47
+ for (let j = i + 1; j < next.length; j += 1) {
48
+ const a = next[i];
49
+ const b = next[j];
50
+ const dx = b.x - a.x;
51
+ const dy = b.y - a.y;
52
+ const distance = Math.sqrt(dx * dx + dy * dy) || 1e-4;
53
+ const minDistance = a.radius + b.radius;
54
+ if (distance >= minDistance) {
55
+ continue;
56
+ }
57
+ const nx = dx / distance;
58
+ const ny = dy / distance;
59
+ const overlap = minDistance - distance;
60
+ a.x -= nx * overlap * 0.5;
61
+ a.y -= ny * overlap * 0.5;
62
+ b.x += nx * overlap * 0.5;
63
+ b.y += ny * overlap * 0.5;
64
+ const rvx = b.vx - a.vx;
65
+ const rvy = b.vy - a.vy;
66
+ const velocityAlongNormal = rvx * nx + rvy * ny;
67
+ if (velocityAlongNormal > 0) {
68
+ continue;
69
+ }
70
+ const restitution = config.collisionDamping;
71
+ const impulse = -(1 + restitution) * velocityAlongNormal / 2;
72
+ const impulseX = impulse * nx;
73
+ const impulseY = impulse * ny;
74
+ a.vx -= impulseX;
75
+ a.vy -= impulseY;
76
+ b.vx += impulseX;
77
+ b.vy += impulseY;
78
+ }
79
+ }
80
+ return next;
81
+ }
82
+
83
+ // src/mikuFusionGame/engine/scoring.ts
84
+ function getMergeScore(newLevel, chainIndex) {
85
+ const base = 10 * Math.max(1, newLevel) * Math.max(1, newLevel);
86
+ const chainMultiplier = 1 + Math.max(0, chainIndex - 1) * 0.15;
87
+ return Math.round(base * chainMultiplier);
88
+ }
89
+
90
+ // src/mikuFusionGame/engine/physics.ts
91
+ function getRadiusByLevel(level) {
92
+ return BASE_RADIUS + (Math.max(1, level) - 1) * RADIUS_STEP;
93
+ }
94
+ function stepPhysics(orbs, config, dt) {
95
+ return orbs.map((orb) => {
96
+ const next = { ...orb };
97
+ next.vy += config.gravity * dt;
98
+ next.vx *= config.damping;
99
+ next.vy *= config.damping;
100
+ next.x += next.vx * dt;
101
+ next.y += next.vy * dt;
102
+ next.age += dt;
103
+ if (next.x - next.radius < 0) {
104
+ next.x = next.radius;
105
+ next.vx = Math.abs(next.vx) * config.collisionDamping;
106
+ } else if (next.x + next.radius > config.width) {
107
+ next.x = config.width - next.radius;
108
+ next.vx = -Math.abs(next.vx) * config.collisionDamping;
109
+ }
110
+ if (next.y + next.radius > config.height) {
111
+ next.y = config.height - next.radius;
112
+ next.vy = -Math.abs(next.vy) * config.collisionDamping;
113
+ }
114
+ return next;
115
+ });
116
+ }
117
+
118
+ // src/mikuFusionGame/engine/merge.ts
119
+ function canMerge(a, b, config) {
120
+ if (a.level !== b.level) {
121
+ return false;
122
+ }
123
+ if (a.level >= config.maxLevel) {
124
+ return false;
125
+ }
126
+ const dx = b.x - a.x;
127
+ const dy = b.y - a.y;
128
+ const distance = Math.sqrt(dx * dx + dy * dy);
129
+ const threshold = (a.radius + b.radius) * 0.92;
130
+ return distance <= threshold;
131
+ }
132
+ function mergeSameLevelOrbs(orbs, config) {
133
+ const mergedIds = /* @__PURE__ */ new Set();
134
+ const spawned = [];
135
+ let scoreGain = 0;
136
+ let chainIndex = 0;
137
+ for (let i = 0; i < orbs.length; i += 1) {
138
+ if (mergedIds.has(orbs[i].id)) {
139
+ continue;
140
+ }
141
+ for (let j = i + 1; j < orbs.length; j += 1) {
142
+ if (mergedIds.has(orbs[j].id)) {
143
+ continue;
144
+ }
145
+ const a = orbs[i];
146
+ const b = orbs[j];
147
+ if (!canMerge(a, b, config)) {
148
+ continue;
149
+ }
150
+ mergedIds.add(a.id);
151
+ mergedIds.add(b.id);
152
+ chainIndex += 1;
153
+ const nextLevel = Math.min(config.maxLevel, a.level + 1);
154
+ const mergedOrb = {
155
+ id: `orb-${a.id}-${b.id}-${chainIndex}`,
156
+ x: (a.x + b.x) / 2,
157
+ y: (a.y + b.y) / 2,
158
+ vx: (a.vx + b.vx) / 2 * 0.4,
159
+ vy: (a.vy + b.vy) / 2 * 0.4,
160
+ radius: getRadiusByLevel(nextLevel),
161
+ level: nextLevel,
162
+ age: Math.min(a.age, b.age)
163
+ };
164
+ spawned.push(mergedOrb);
165
+ scoreGain += getMergeScore(nextLevel, chainIndex);
166
+ if (chainIndex >= config.maxMergesPerTick) {
167
+ break;
168
+ }
169
+ }
170
+ if (chainIndex >= config.maxMergesPerTick) {
171
+ break;
172
+ }
173
+ }
174
+ if (mergedIds.size === 0) {
175
+ return {
176
+ orbs,
177
+ scoreGain: 0,
178
+ mergeCount: 0
179
+ };
180
+ }
181
+ const survivors = orbs.filter((orb) => !mergedIds.has(orb.id));
182
+ const mergedOrbs = survivors.concat(spawned);
183
+ return {
184
+ orbs: mergedOrbs,
185
+ scoreGain,
186
+ mergeCount: chainIndex
187
+ };
188
+ }
189
+
190
+ // src/mikuFusionGame/engine/stateMachine.ts
191
+ var ALLOWED_TRANSITIONS = {
192
+ ready: ["playing"],
193
+ playing: ["paused", "gameOver", "ready"],
194
+ paused: ["playing", "ready"],
195
+ gameOver: ["ready", "playing"]
196
+ };
197
+ function canTransition(from, to) {
198
+ return ALLOWED_TRANSITIONS[from].includes(to);
199
+ }
200
+
201
+ // src/mikuFusionGame/hooks/useMikuFusionGame.ts
202
+ function clamp(value, min, max) {
203
+ return Math.max(min, Math.min(max, value));
204
+ }
205
+ function nextOrbLevel(weights) {
206
+ const random = Math.random();
207
+ let cumulative = 0;
208
+ for (let i = 0; i < weights.length; i += 1) {
209
+ cumulative += weights[i] ?? 0;
210
+ if (random <= cumulative) {
211
+ return i + 1;
212
+ }
213
+ }
214
+ return Math.max(1, weights.length);
215
+ }
216
+ function getOrbColor(level, config) {
217
+ const index = Math.max(0, Math.min(config.theme.orbColors.length - 1, level - 1));
218
+ return config.theme.orbColors[index] || "#22d3ee";
219
+ }
220
+ function drawScene(context, width, height, config, orbs, aimX, nextLevel, status) {
221
+ const gradient = context.createLinearGradient(0, 0, 0, height);
222
+ gradient.addColorStop(0, config.theme.backgroundTop);
223
+ gradient.addColorStop(1, config.theme.backgroundBottom);
224
+ context.fillStyle = gradient;
225
+ context.fillRect(0, 0, width, height);
226
+ context.lineWidth = 2;
227
+ context.strokeStyle = config.theme.lossLine;
228
+ context.setLineDash([6, 6]);
229
+ context.beginPath();
230
+ context.moveTo(0, config.lossLineY);
231
+ context.lineTo(width, config.lossLineY);
232
+ context.stroke();
233
+ context.setLineDash([]);
234
+ context.strokeStyle = config.theme.aimLine;
235
+ context.lineWidth = 1.5;
236
+ context.beginPath();
237
+ context.moveTo(aimX, 0);
238
+ context.lineTo(aimX, config.spawnY);
239
+ context.stroke();
240
+ const previewRadius = getRadiusByLevel(nextLevel);
241
+ context.fillStyle = getOrbColor(nextLevel, config);
242
+ context.beginPath();
243
+ context.arc(aimX, config.spawnY - previewRadius - 4, previewRadius, 0, Math.PI * 2);
244
+ context.fill();
245
+ orbs.forEach((orb) => {
246
+ context.beginPath();
247
+ context.fillStyle = getOrbColor(orb.level, config);
248
+ context.arc(orb.x, orb.y, orb.radius, 0, Math.PI * 2);
249
+ context.fill();
250
+ context.fillStyle = "#0f172a";
251
+ context.font = "bold 12px system-ui";
252
+ context.textAlign = "center";
253
+ context.textBaseline = "middle";
254
+ context.fillText(`${LEVEL_LABEL_PREFIX}${orb.level}`, orb.x, orb.y);
255
+ });
256
+ if (status === "paused") {
257
+ context.fillStyle = "rgba(15, 23, 42, 0.5)";
258
+ context.fillRect(0, 0, width, height);
259
+ context.fillStyle = "#ffffff";
260
+ context.font = "bold 30px system-ui";
261
+ context.textAlign = "center";
262
+ context.fillText("Paused", width / 2, height / 2);
263
+ }
264
+ }
265
+ function normalizeConfig(config) {
266
+ const mergedTheme = {
267
+ ...DEFAULT_MIKU_FUSION_CONFIG.theme,
268
+ ...config?.theme ?? {}
269
+ };
270
+ const merged = {
271
+ ...DEFAULT_MIKU_FUSION_CONFIG,
272
+ ...config ?? {},
273
+ theme: mergedTheme
274
+ };
275
+ const totalWeight = merged.spawnWeights.reduce((sum, current) => sum + current, 0);
276
+ if (totalWeight <= 0) {
277
+ merged.spawnWeights = [...DEFAULT_MIKU_FUSION_CONFIG.spawnWeights];
278
+ } else if (Math.abs(totalWeight - 1) > 1e-3) {
279
+ merged.spawnWeights = merged.spawnWeights.map((weight) => weight / totalWeight);
280
+ }
281
+ return merged;
282
+ }
283
+ function useMikuFusionGame(options = {}) {
284
+ const configRef = useRef(normalizeConfig(options));
285
+ const config = configRef.current;
286
+ const onScoreChangeRef = useRef(options.onScoreChange);
287
+ const onGameOverRef = useRef(options.onGameOver);
288
+ useEffect(() => {
289
+ onScoreChangeRef.current = options.onScoreChange;
290
+ onGameOverRef.current = options.onGameOver;
291
+ }, [options.onGameOver, options.onScoreChange]);
292
+ const [bestScore, setBestScore] = useLocalStorage(
293
+ options.storageKey || "sa2kit:mikuFusionGame:bestScore",
294
+ 0
295
+ );
296
+ const canvasRef = useRef(null);
297
+ const rafRef = useRef(null);
298
+ const lastTsRef = useRef(0);
299
+ const idRef = useRef(0);
300
+ const orbsRef = useRef([]);
301
+ const aimXRef = useRef(config.width / 2);
302
+ const scoreRef = useRef(0);
303
+ const [status, setStatus] = useState("ready");
304
+ const [score, setScore] = useState(0);
305
+ const [nextLevel, setNextLevel] = useState(() => nextOrbLevel(config.spawnWeights));
306
+ const draw = useCallback(
307
+ (nextStatus = status) => {
308
+ const canvas = canvasRef.current;
309
+ if (!canvas) {
310
+ return;
311
+ }
312
+ const context = canvas.getContext("2d");
313
+ if (!context) {
314
+ return;
315
+ }
316
+ drawScene(
317
+ context,
318
+ config.width,
319
+ config.height,
320
+ config,
321
+ orbsRef.current,
322
+ aimXRef.current,
323
+ nextLevel,
324
+ nextStatus
325
+ );
326
+ },
327
+ [config, nextLevel, status]
328
+ );
329
+ const transitionStatus = useCallback((nextStatus) => {
330
+ setStatus((previous) => canTransition(previous, nextStatus) ? nextStatus : previous);
331
+ }, []);
332
+ const spawnOrb = useCallback(() => {
333
+ if (status === "paused" || status === "gameOver") {
334
+ return;
335
+ }
336
+ if (status === "ready") {
337
+ transitionStatus("playing");
338
+ }
339
+ idRef.current += 1;
340
+ const radius = getRadiusByLevel(nextLevel);
341
+ const x = clamp(aimXRef.current, radius, config.width - radius);
342
+ const created = {
343
+ id: `${idRef.current}`,
344
+ x,
345
+ y: config.spawnY,
346
+ vx: 0,
347
+ vy: 0,
348
+ radius,
349
+ level: nextLevel,
350
+ age: 0
351
+ };
352
+ const nextOrbs = orbsRef.current.concat(created);
353
+ orbsRef.current = nextOrbs.slice(-config.maxOrbs);
354
+ setNextLevel(nextOrbLevel(config.spawnWeights));
355
+ }, [config, nextLevel, status, transitionStatus]);
356
+ const restart = useCallback(() => {
357
+ orbsRef.current = [];
358
+ scoreRef.current = 0;
359
+ setScore(0);
360
+ setNextLevel(nextOrbLevel(config.spawnWeights));
361
+ transitionStatus("ready");
362
+ draw("ready");
363
+ }, [config.spawnWeights, draw, transitionStatus]);
364
+ const togglePause = useCallback(() => {
365
+ setStatus((previous) => {
366
+ if (previous === "playing" && canTransition(previous, "paused")) {
367
+ return "paused";
368
+ }
369
+ if (previous === "paused" && canTransition(previous, "playing")) {
370
+ return "playing";
371
+ }
372
+ return previous;
373
+ });
374
+ }, []);
375
+ const setAimFromClientX = useCallback((clientX, rect) => {
376
+ const ratio = config.width / rect.width;
377
+ const x = (clientX - rect.left) * ratio;
378
+ aimXRef.current = clamp(x, 0, config.width);
379
+ }, [config.width]);
380
+ const handleDrop = useCallback(
381
+ (clientX, rect) => {
382
+ setAimFromClientX(clientX, rect);
383
+ spawnOrb();
384
+ },
385
+ [setAimFromClientX, spawnOrb]
386
+ );
387
+ useEffect(() => {
388
+ draw(status);
389
+ }, [draw, status]);
390
+ useEffect(() => {
391
+ if (status !== "playing") {
392
+ if (rafRef.current) {
393
+ window.cancelAnimationFrame(rafRef.current);
394
+ }
395
+ lastTsRef.current = 0;
396
+ draw(status);
397
+ return;
398
+ }
399
+ const frame = (timestamp) => {
400
+ if (lastTsRef.current === 0) {
401
+ lastTsRef.current = timestamp;
402
+ }
403
+ const dt = Math.min((timestamp - lastTsRef.current) / 1e3, 0.033);
404
+ lastTsRef.current = timestamp;
405
+ let orbs = stepPhysics(orbsRef.current, config, dt);
406
+ orbs = resolveCircleCollisions(orbs, config);
407
+ const merged = mergeSameLevelOrbs(orbs, config);
408
+ orbs = merged.orbs;
409
+ orbsRef.current = orbs.slice(-config.maxOrbs);
410
+ if (merged.scoreGain > 0) {
411
+ scoreRef.current += merged.scoreGain;
412
+ setScore(scoreRef.current);
413
+ onScoreChangeRef.current?.(scoreRef.current);
414
+ }
415
+ const gameOver = orbs.some(
416
+ (orb) => orb.age > config.gameOverAgeThreshold && orb.y - orb.radius <= config.lossLineY && Math.abs(orb.vy) < 90
417
+ );
418
+ if (gameOver) {
419
+ transitionStatus("gameOver");
420
+ const latestBest = Math.max(bestScore, scoreRef.current);
421
+ if (latestBest !== bestScore) {
422
+ setBestScore(latestBest);
423
+ }
424
+ onGameOverRef.current?.(scoreRef.current, latestBest);
425
+ draw("gameOver");
426
+ return;
427
+ }
428
+ draw("playing");
429
+ rafRef.current = window.requestAnimationFrame(frame);
430
+ };
431
+ rafRef.current = window.requestAnimationFrame(frame);
432
+ return () => {
433
+ if (rafRef.current) {
434
+ window.cancelAnimationFrame(rafRef.current);
435
+ }
436
+ };
437
+ }, [
438
+ bestScore,
439
+ config,
440
+ draw,
441
+ setBestScore,
442
+ status,
443
+ transitionStatus
444
+ ]);
445
+ useEffect(() => {
446
+ if (score > bestScore) {
447
+ setBestScore(score);
448
+ }
449
+ }, [bestScore, score, setBestScore]);
450
+ return {
451
+ canvasRef,
452
+ status,
453
+ score,
454
+ bestScore: Math.max(bestScore, score),
455
+ nextLevel,
456
+ config,
457
+ setAimFromClientX,
458
+ handleDrop,
459
+ togglePause,
460
+ restart
461
+ };
462
+ }
463
+ function useResponsiveCanvas(logicalWidth, logicalHeight, containerRef) {
464
+ const [size, setSize] = useState({
465
+ displayWidth: logicalWidth,
466
+ displayHeight: logicalHeight,
467
+ scale: 1
468
+ });
469
+ useEffect(() => {
470
+ const updateSize = () => {
471
+ const container2 = containerRef.current;
472
+ if (!container2 || typeof window === "undefined") {
473
+ return;
474
+ }
475
+ const rect = container2.getBoundingClientRect();
476
+ const maxWidth = Math.max(280, rect.width);
477
+ const maxHeight = Math.max(420, Math.floor(window.innerHeight * 0.78));
478
+ const scale = Math.min(maxWidth / logicalWidth, maxHeight / logicalHeight, 1);
479
+ setSize({
480
+ displayWidth: Math.floor(logicalWidth * scale),
481
+ displayHeight: Math.floor(logicalHeight * scale),
482
+ scale
483
+ });
484
+ };
485
+ updateSize();
486
+ const container = containerRef.current;
487
+ const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(updateSize) : null;
488
+ if (container && observer) {
489
+ observer.observe(container);
490
+ }
491
+ window.addEventListener("resize", updateSize);
492
+ return () => {
493
+ observer?.disconnect();
494
+ window.removeEventListener("resize", updateSize);
495
+ };
496
+ }, [containerRef, logicalWidth, logicalHeight]);
497
+ return size;
498
+ }
499
+ function GameCanvas({
500
+ canvasRef,
501
+ width,
502
+ height,
503
+ displayWidth,
504
+ displayHeight,
505
+ onPointerMove,
506
+ onPointerDown,
507
+ onPointerUp
508
+ }) {
509
+ return /* @__PURE__ */ React3.createElement(
510
+ "canvas",
511
+ {
512
+ ref: canvasRef,
513
+ width,
514
+ height,
515
+ className: "rounded-xl border border-cyan-200 bg-cyan-50 shadow-sm",
516
+ style: {
517
+ width: `${displayWidth}px`,
518
+ height: `${displayHeight}px`,
519
+ maxWidth: "100%",
520
+ touchAction: "none"
521
+ },
522
+ onPointerMove,
523
+ onPointerDown,
524
+ onPointerUp,
525
+ onContextMenu: (event) => event.preventDefault()
526
+ }
527
+ );
528
+ }
529
+ function GameControls({ isPaused, isPlaying, onTogglePause, onRestart }) {
530
+ return /* @__PURE__ */ React3.createElement("div", { className: "grid grid-cols-2 gap-2" }, /* @__PURE__ */ React3.createElement(
531
+ "button",
532
+ {
533
+ type: "button",
534
+ className: "rounded-lg bg-cyan-600 px-4 py-3 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:opacity-50",
535
+ onClick: onTogglePause,
536
+ disabled: !isPlaying
537
+ },
538
+ isPaused ? "\u7EE7\u7EED" : "\u6682\u505C"
539
+ ), /* @__PURE__ */ React3.createElement(
540
+ "button",
541
+ {
542
+ type: "button",
543
+ className: "rounded-lg bg-emerald-600 px-4 py-3 text-sm font-semibold text-white",
544
+ onClick: onRestart
545
+ },
546
+ "\u91CD\u5F00"
547
+ ));
548
+ }
549
+ function GameHUD({ score, bestScore, nextLevel, statusText }) {
550
+ return /* @__PURE__ */ React3.createElement("div", { className: "flex items-center justify-between gap-2 rounded-lg border border-cyan-200 bg-white/80 p-3" }, /* @__PURE__ */ React3.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React3.createElement("div", { className: "text-xs text-slate-500" }, "\u5F53\u524D\u5206\u6570"), /* @__PURE__ */ React3.createElement("div", { className: "text-xl font-bold text-cyan-700" }, score), /* @__PURE__ */ React3.createElement("div", { className: "text-xs text-slate-500" }, "\u6700\u9AD8\u5206 ", bestScore)), /* @__PURE__ */ React3.createElement("div", { className: "text-right" }, /* @__PURE__ */ React3.createElement("div", { className: "text-xs text-slate-500" }, "\u4E0B\u4E00\u4E2A"), /* @__PURE__ */ React3.createElement("div", { className: "text-base font-semibold text-emerald-700" }, "M", nextLevel), /* @__PURE__ */ React3.createElement("div", { className: "text-xs font-medium text-slate-600" }, statusText)));
551
+ }
552
+ function GameResultModal({ open, score, bestScore, onRestart }) {
553
+ if (!open) {
554
+ return null;
555
+ }
556
+ return /* @__PURE__ */ React3.createElement("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-slate-900/50 p-4" }, /* @__PURE__ */ React3.createElement("div", { className: "pointer-events-auto w-full max-w-xs rounded-xl bg-white p-5 shadow-2xl" }, /* @__PURE__ */ React3.createElement("h3", { className: "text-lg font-semibold text-slate-900" }, "\u6E38\u620F\u7ED3\u675F"), /* @__PURE__ */ React3.createElement("p", { className: "mt-2 text-sm text-slate-600" }, "\u5F53\u524D\u5206\u6570\uFF1A", score), /* @__PURE__ */ React3.createElement("p", { className: "text-sm text-slate-600" }, "\u6700\u9AD8\u5206\uFF1A", bestScore), /* @__PURE__ */ React3.createElement(
557
+ "button",
558
+ {
559
+ type: "button",
560
+ className: "mt-4 w-full rounded-lg bg-cyan-600 px-4 py-2 text-sm font-semibold text-white",
561
+ onClick: onRestart
562
+ },
563
+ "\u518D\u6765\u4E00\u5C40"
564
+ )));
565
+ }
566
+
567
+ // src/mikuFusionGame/components/MikuFusionGame.tsx
568
+ function MikuFusionGame({ className, storageKey, ...options }) {
569
+ const containerRef = useRef(null);
570
+ const {
571
+ canvasRef,
572
+ status,
573
+ score,
574
+ bestScore,
575
+ nextLevel,
576
+ config,
577
+ setAimFromClientX,
578
+ handleDrop,
579
+ togglePause,
580
+ restart
581
+ } = useMikuFusionGame({ ...options, storageKey });
582
+ const { displayWidth, displayHeight } = useResponsiveCanvas(
583
+ config.width,
584
+ config.height,
585
+ containerRef
586
+ );
587
+ const statusText = useMemo(() => {
588
+ if (status === "ready") {
589
+ return "\u5F85\u5F00\u59CB";
590
+ }
591
+ if (status === "playing") {
592
+ return "\u8FDB\u884C\u4E2D";
593
+ }
594
+ if (status === "paused") {
595
+ return "\u5DF2\u6682\u505C";
596
+ }
597
+ return "\u5DF2\u7ED3\u675F";
598
+ }, [status]);
599
+ const handlePointerMove = (event) => {
600
+ const rect = event.currentTarget.getBoundingClientRect();
601
+ setAimFromClientX(event.clientX, rect);
602
+ };
603
+ const handlePointerDown = (event) => {
604
+ event.currentTarget.setPointerCapture(event.pointerId);
605
+ const rect = event.currentTarget.getBoundingClientRect();
606
+ setAimFromClientX(event.clientX, rect);
607
+ };
608
+ const handlePointerUp = (event) => {
609
+ const rect = event.currentTarget.getBoundingClientRect();
610
+ handleDrop(event.clientX, rect);
611
+ event.currentTarget.releasePointerCapture(event.pointerId);
612
+ };
613
+ const handleKeyDown = (event) => {
614
+ if (event.key.toLowerCase() === "r") {
615
+ restart();
616
+ }
617
+ if (event.key.toLowerCase() === "p") {
618
+ togglePause();
619
+ }
620
+ };
621
+ return /* @__PURE__ */ React3.createElement(
622
+ "div",
623
+ {
624
+ ref: containerRef,
625
+ tabIndex: 0,
626
+ onKeyDown: handleKeyDown,
627
+ className: `relative mx-auto flex w-full max-w-xl flex-col gap-3 p-3 md:p-4 ${className || ""}`,
628
+ style: {
629
+ paddingBottom: "max(0.75rem, env(safe-area-inset-bottom))"
630
+ }
631
+ },
632
+ /* @__PURE__ */ React3.createElement(GameHUD, { score, bestScore, nextLevel, statusText }),
633
+ /* @__PURE__ */ React3.createElement("div", { className: "relative mx-auto" }, /* @__PURE__ */ React3.createElement(
634
+ GameCanvas,
635
+ {
636
+ canvasRef,
637
+ width: config.width,
638
+ height: config.height,
639
+ displayWidth,
640
+ displayHeight,
641
+ onPointerMove: handlePointerMove,
642
+ onPointerDown: handlePointerDown,
643
+ onPointerUp: handlePointerUp
644
+ }
645
+ ), /* @__PURE__ */ React3.createElement(GameResultModal, { open: status === "gameOver", score, bestScore, onRestart: restart })),
646
+ /* @__PURE__ */ React3.createElement(
647
+ GameControls,
648
+ {
649
+ isPaused: status === "paused",
650
+ isPlaying: status === "playing" || status === "paused",
651
+ onTogglePause: togglePause,
652
+ onRestart: restart
653
+ }
654
+ )
655
+ );
656
+ }
657
+
658
+ // src/mikuFusionGame/content/index.ts
659
+ var defaultContentRegistry = {
660
+ producers: [],
661
+ songs: [],
662
+ themes: []
663
+ };
664
+
665
+ export { BASE_RADIUS, DEFAULT_MIKU_FUSION_CONFIG, LEVEL_LABEL_PREFIX, MikuFusionGame, RADIUS_STEP, defaultContentRegistry, useMikuFusionGame, useResponsiveCanvas };
666
+ //# sourceMappingURL=index.mjs.map
667
+ //# sourceMappingURL=index.mjs.map