sa2kit 1.6.33 → 1.6.34

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 (37) hide show
  1. package/dist/calendar/index.js +11 -8
  2. package/dist/calendar/index.js.map +1 -1
  3. package/dist/calendar/index.mjs +4 -1
  4. package/dist/calendar/index.mjs.map +1 -1
  5. package/dist/chunk-B34YUZRL.js +809 -0
  6. package/dist/chunk-B34YUZRL.js.map +1 -0
  7. package/dist/chunk-EGJPS7OL.mjs +98 -0
  8. package/dist/chunk-EGJPS7OL.mjs.map +1 -0
  9. package/dist/{chunk-EI27JKND.mjs → chunk-GMIUSZXC.mjs} +2 -2
  10. package/dist/{chunk-EI27JKND.mjs.map → chunk-GMIUSZXC.mjs.map} +1 -1
  11. package/dist/chunk-HHVDOIPV.js +105 -0
  12. package/dist/chunk-HHVDOIPV.js.map +1 -0
  13. package/dist/chunk-HQ7VHIEK.mjs +772 -0
  14. package/dist/chunk-HQ7VHIEK.mjs.map +1 -0
  15. package/dist/{chunk-XGBE4SUV.js → chunk-SCDDMIF6.js} +2 -2
  16. package/dist/{chunk-XGBE4SUV.js.map → chunk-SCDDMIF6.js.map} +1 -1
  17. package/dist/index.d.mts +25 -1
  18. package/dist/index.d.ts +25 -1
  19. package/dist/index.js +171 -109
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +4 -2
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/mikuFireworks3D/index.d.mts +104 -0
  24. package/dist/mikuFireworks3D/index.d.ts +104 -0
  25. package/dist/mikuFireworks3D/index.js +61 -0
  26. package/dist/mikuFireworks3D/index.js.map +1 -0
  27. package/dist/mikuFireworks3D/index.mjs +4 -0
  28. package/dist/mikuFireworks3D/index.mjs.map +1 -0
  29. package/dist/mikuFusionGame/index.d.mts +7 -2
  30. package/dist/mikuFusionGame/index.d.ts +7 -2
  31. package/dist/mikuFusionGame/index.js +141 -10
  32. package/dist/mikuFusionGame/index.js.map +1 -1
  33. package/dist/mikuFusionGame/index.mjs +141 -10
  34. package/dist/mikuFusionGame/index.mjs.map +1 -1
  35. package/dist/mmd/index.js +1 -1
  36. package/dist/mmd/index.mjs +2 -2
  37. package/package.json +6 -1
@@ -0,0 +1,104 @@
1
+ import * as React from 'react';
2
+ import React__default from 'react';
3
+
4
+ type FireworkKind = 'normal' | 'miku' | 'avatar';
5
+ interface DanmakuMessage {
6
+ id: string;
7
+ userId?: string;
8
+ text: string;
9
+ color?: string;
10
+ timestamp: number;
11
+ }
12
+ interface FireworkPosition {
13
+ x: number;
14
+ y: number;
15
+ z: number;
16
+ }
17
+ interface FireworkLaunchPayload {
18
+ kind: FireworkKind;
19
+ position?: FireworkPosition;
20
+ color?: string;
21
+ avatarUrl?: string;
22
+ message?: DanmakuMessage;
23
+ }
24
+ interface FireworkEngineOptions {
25
+ maxParticles?: number;
26
+ maxActiveFireworks?: number;
27
+ onError?: (error: Error) => void;
28
+ onFpsReport?: (fps: number) => void;
29
+ }
30
+ interface MikuFireworks3DProps {
31
+ width?: number | string;
32
+ height?: number | string;
33
+ className?: string;
34
+ defaultKind?: FireworkKind;
35
+ autoLaunchOnDanmaku?: boolean;
36
+ maxParticles?: number;
37
+ maxActiveFireworks?: number;
38
+ defaultAvatarUrl?: string;
39
+ onLaunch?: (payload: FireworkLaunchPayload) => void;
40
+ onDanmakuSend?: (message: DanmakuMessage) => void;
41
+ onError?: (error: Error) => void;
42
+ onFpsReport?: (fps: number) => void;
43
+ }
44
+ interface DanmakuSendResult {
45
+ message: DanmakuMessage;
46
+ launchKind?: FireworkKind;
47
+ }
48
+ interface DanmakuControllerOptions {
49
+ onSend?: (message: DanmakuMessage) => void;
50
+ }
51
+
52
+ declare function MikuFireworks3D({ width, height, className, defaultKind, autoLaunchOnDanmaku, maxParticles, maxActiveFireworks, defaultAvatarUrl, onLaunch, onDanmakuSend, onError, onFpsReport, }: MikuFireworks3DProps): React__default.JSX.Element;
53
+
54
+ interface FireworksCanvasProps {
55
+ canvasRef: React__default.RefObject<HTMLCanvasElement>;
56
+ }
57
+ declare function FireworksCanvas({ canvasRef }: FireworksCanvasProps): React__default.JSX.Element;
58
+
59
+ interface FireworksControlPanelProps {
60
+ selectedKind: FireworkKind;
61
+ onKindChange: (kind: FireworkKind) => void;
62
+ autoLaunchOnDanmaku: boolean;
63
+ onAutoLaunchChange: (value: boolean) => void;
64
+ avatarUrl: string;
65
+ onAvatarUrlChange: (value: string) => void;
66
+ onLaunch: () => void;
67
+ fps: number;
68
+ }
69
+ declare function FireworksControlPanel({ selectedKind, onKindChange, autoLaunchOnDanmaku, onAutoLaunchChange, avatarUrl, onAvatarUrlChange, onLaunch, fps, }: FireworksControlPanelProps): React__default.JSX.Element;
70
+
71
+ interface DanmakuPanelProps {
72
+ onSend: (text: string) => void;
73
+ }
74
+ declare function DanmakuPanel({ onSend }: DanmakuPanelProps): React__default.JSX.Element;
75
+
76
+ interface UseFireworksEngineOptions extends FireworkEngineOptions {
77
+ onLaunch?: (payload: FireworkLaunchPayload) => void;
78
+ }
79
+ declare function useFireworksEngine(options?: UseFireworksEngineOptions): {
80
+ containerRef: React.RefObject<HTMLDivElement>;
81
+ canvasRef: React.RefObject<HTMLCanvasElement>;
82
+ launch: (payload: FireworkLaunchPayload) => void;
83
+ fps: number;
84
+ };
85
+
86
+ interface DanmakuOverlayItem extends DanmakuMessage {
87
+ track: number;
88
+ durationMs: number;
89
+ }
90
+ declare function useDanmakuController(options?: DanmakuControllerOptions): {
91
+ items: DanmakuOverlayItem[];
92
+ send: (text: string, color?: string) => DanmakuSendResult | null;
93
+ removeItem: (id: string) => void;
94
+ };
95
+
96
+ declare const DEFAULT_MAX_PARTICLES = 5000;
97
+ declare const DEFAULT_MAX_ACTIVE_FIREWORKS = 12;
98
+ declare const FIREWORK_KIND_LABELS: Record<FireworkKind, string>;
99
+ declare const MIKU_PALETTE: string[];
100
+ declare const NORMAL_PALETTE: string[];
101
+ declare const DANMAKU_MAX_LENGTH = 32;
102
+ declare const DANMAKU_TRACK_COUNT = 8;
103
+
104
+ export { DANMAKU_MAX_LENGTH, DANMAKU_TRACK_COUNT, DEFAULT_MAX_ACTIVE_FIREWORKS, DEFAULT_MAX_PARTICLES, type DanmakuControllerOptions, type DanmakuMessage, type DanmakuOverlayItem, DanmakuPanel, type DanmakuSendResult, FIREWORK_KIND_LABELS, type FireworkEngineOptions, type FireworkKind, type FireworkLaunchPayload, type FireworkPosition, FireworksCanvas, FireworksControlPanel, MIKU_PALETTE, MikuFireworks3D, type MikuFireworks3DProps, NORMAL_PALETTE, type UseFireworksEngineOptions, useDanmakuController, useFireworksEngine };
@@ -0,0 +1,104 @@
1
+ import * as React from 'react';
2
+ import React__default from 'react';
3
+
4
+ type FireworkKind = 'normal' | 'miku' | 'avatar';
5
+ interface DanmakuMessage {
6
+ id: string;
7
+ userId?: string;
8
+ text: string;
9
+ color?: string;
10
+ timestamp: number;
11
+ }
12
+ interface FireworkPosition {
13
+ x: number;
14
+ y: number;
15
+ z: number;
16
+ }
17
+ interface FireworkLaunchPayload {
18
+ kind: FireworkKind;
19
+ position?: FireworkPosition;
20
+ color?: string;
21
+ avatarUrl?: string;
22
+ message?: DanmakuMessage;
23
+ }
24
+ interface FireworkEngineOptions {
25
+ maxParticles?: number;
26
+ maxActiveFireworks?: number;
27
+ onError?: (error: Error) => void;
28
+ onFpsReport?: (fps: number) => void;
29
+ }
30
+ interface MikuFireworks3DProps {
31
+ width?: number | string;
32
+ height?: number | string;
33
+ className?: string;
34
+ defaultKind?: FireworkKind;
35
+ autoLaunchOnDanmaku?: boolean;
36
+ maxParticles?: number;
37
+ maxActiveFireworks?: number;
38
+ defaultAvatarUrl?: string;
39
+ onLaunch?: (payload: FireworkLaunchPayload) => void;
40
+ onDanmakuSend?: (message: DanmakuMessage) => void;
41
+ onError?: (error: Error) => void;
42
+ onFpsReport?: (fps: number) => void;
43
+ }
44
+ interface DanmakuSendResult {
45
+ message: DanmakuMessage;
46
+ launchKind?: FireworkKind;
47
+ }
48
+ interface DanmakuControllerOptions {
49
+ onSend?: (message: DanmakuMessage) => void;
50
+ }
51
+
52
+ declare function MikuFireworks3D({ width, height, className, defaultKind, autoLaunchOnDanmaku, maxParticles, maxActiveFireworks, defaultAvatarUrl, onLaunch, onDanmakuSend, onError, onFpsReport, }: MikuFireworks3DProps): React__default.JSX.Element;
53
+
54
+ interface FireworksCanvasProps {
55
+ canvasRef: React__default.RefObject<HTMLCanvasElement>;
56
+ }
57
+ declare function FireworksCanvas({ canvasRef }: FireworksCanvasProps): React__default.JSX.Element;
58
+
59
+ interface FireworksControlPanelProps {
60
+ selectedKind: FireworkKind;
61
+ onKindChange: (kind: FireworkKind) => void;
62
+ autoLaunchOnDanmaku: boolean;
63
+ onAutoLaunchChange: (value: boolean) => void;
64
+ avatarUrl: string;
65
+ onAvatarUrlChange: (value: string) => void;
66
+ onLaunch: () => void;
67
+ fps: number;
68
+ }
69
+ declare function FireworksControlPanel({ selectedKind, onKindChange, autoLaunchOnDanmaku, onAutoLaunchChange, avatarUrl, onAvatarUrlChange, onLaunch, fps, }: FireworksControlPanelProps): React__default.JSX.Element;
70
+
71
+ interface DanmakuPanelProps {
72
+ onSend: (text: string) => void;
73
+ }
74
+ declare function DanmakuPanel({ onSend }: DanmakuPanelProps): React__default.JSX.Element;
75
+
76
+ interface UseFireworksEngineOptions extends FireworkEngineOptions {
77
+ onLaunch?: (payload: FireworkLaunchPayload) => void;
78
+ }
79
+ declare function useFireworksEngine(options?: UseFireworksEngineOptions): {
80
+ containerRef: React.RefObject<HTMLDivElement>;
81
+ canvasRef: React.RefObject<HTMLCanvasElement>;
82
+ launch: (payload: FireworkLaunchPayload) => void;
83
+ fps: number;
84
+ };
85
+
86
+ interface DanmakuOverlayItem extends DanmakuMessage {
87
+ track: number;
88
+ durationMs: number;
89
+ }
90
+ declare function useDanmakuController(options?: DanmakuControllerOptions): {
91
+ items: DanmakuOverlayItem[];
92
+ send: (text: string, color?: string) => DanmakuSendResult | null;
93
+ removeItem: (id: string) => void;
94
+ };
95
+
96
+ declare const DEFAULT_MAX_PARTICLES = 5000;
97
+ declare const DEFAULT_MAX_ACTIVE_FIREWORKS = 12;
98
+ declare const FIREWORK_KIND_LABELS: Record<FireworkKind, string>;
99
+ declare const MIKU_PALETTE: string[];
100
+ declare const NORMAL_PALETTE: string[];
101
+ declare const DANMAKU_MAX_LENGTH = 32;
102
+ declare const DANMAKU_TRACK_COUNT = 8;
103
+
104
+ export { DANMAKU_MAX_LENGTH, DANMAKU_TRACK_COUNT, DEFAULT_MAX_ACTIVE_FIREWORKS, DEFAULT_MAX_PARTICLES, type DanmakuControllerOptions, type DanmakuMessage, type DanmakuOverlayItem, DanmakuPanel, type DanmakuSendResult, FIREWORK_KIND_LABELS, type FireworkEngineOptions, type FireworkKind, type FireworkLaunchPayload, type FireworkPosition, FireworksCanvas, FireworksControlPanel, MIKU_PALETTE, MikuFireworks3D, type MikuFireworks3DProps, NORMAL_PALETTE, type UseFireworksEngineOptions, useDanmakuController, useFireworksEngine };
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ var chunkB34YUZRL_js = require('../chunk-B34YUZRL.js');
4
+ require('../chunk-DGUM43GV.js');
5
+
6
+
7
+
8
+ Object.defineProperty(exports, "DANMAKU_MAX_LENGTH", {
9
+ enumerable: true,
10
+ get: function () { return chunkB34YUZRL_js.DANMAKU_MAX_LENGTH; }
11
+ });
12
+ Object.defineProperty(exports, "DANMAKU_TRACK_COUNT", {
13
+ enumerable: true,
14
+ get: function () { return chunkB34YUZRL_js.DANMAKU_TRACK_COUNT; }
15
+ });
16
+ Object.defineProperty(exports, "DEFAULT_MAX_ACTIVE_FIREWORKS", {
17
+ enumerable: true,
18
+ get: function () { return chunkB34YUZRL_js.DEFAULT_MAX_ACTIVE_FIREWORKS; }
19
+ });
20
+ Object.defineProperty(exports, "DEFAULT_MAX_PARTICLES", {
21
+ enumerable: true,
22
+ get: function () { return chunkB34YUZRL_js.DEFAULT_MAX_PARTICLES; }
23
+ });
24
+ Object.defineProperty(exports, "DanmakuPanel", {
25
+ enumerable: true,
26
+ get: function () { return chunkB34YUZRL_js.DanmakuPanel; }
27
+ });
28
+ Object.defineProperty(exports, "FIREWORK_KIND_LABELS", {
29
+ enumerable: true,
30
+ get: function () { return chunkB34YUZRL_js.FIREWORK_KIND_LABELS; }
31
+ });
32
+ Object.defineProperty(exports, "FireworksCanvas", {
33
+ enumerable: true,
34
+ get: function () { return chunkB34YUZRL_js.FireworksCanvas; }
35
+ });
36
+ Object.defineProperty(exports, "FireworksControlPanel", {
37
+ enumerable: true,
38
+ get: function () { return chunkB34YUZRL_js.FireworksControlPanel; }
39
+ });
40
+ Object.defineProperty(exports, "MIKU_PALETTE", {
41
+ enumerable: true,
42
+ get: function () { return chunkB34YUZRL_js.MIKU_PALETTE; }
43
+ });
44
+ Object.defineProperty(exports, "MikuFireworks3D", {
45
+ enumerable: true,
46
+ get: function () { return chunkB34YUZRL_js.MikuFireworks3D; }
47
+ });
48
+ Object.defineProperty(exports, "NORMAL_PALETTE", {
49
+ enumerable: true,
50
+ get: function () { return chunkB34YUZRL_js.NORMAL_PALETTE; }
51
+ });
52
+ Object.defineProperty(exports, "useDanmakuController", {
53
+ enumerable: true,
54
+ get: function () { return chunkB34YUZRL_js.useDanmakuController; }
55
+ });
56
+ Object.defineProperty(exports, "useFireworksEngine", {
57
+ enumerable: true,
58
+ get: function () { return chunkB34YUZRL_js.useFireworksEngine; }
59
+ });
60
+ //# sourceMappingURL=index.js.map
61
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,4 @@
1
+ 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, useDanmakuController, useFireworksEngine } from '../chunk-HQ7VHIEK.mjs';
2
+ import '../chunk-BJTO5JO5.mjs';
3
+ //# sourceMappingURL=index.mjs.map
4
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs"}
@@ -1,6 +1,7 @@
1
1
  import React__default, { RefObject } from 'react';
2
2
 
3
3
  type MikuFusionGameStatus = 'ready' | 'playing' | 'paused' | 'gameOver';
4
+ type OrbImageMapping = Partial<Record<number, string>>;
4
5
  interface FusionOrb {
5
6
  id: string;
6
7
  x: number;
@@ -46,11 +47,15 @@ interface MergeResult {
46
47
  interface MikuFusionGameProps extends Partial<MikuFusionGameConfig>, MikuFusionGameCallbacks {
47
48
  className?: string;
48
49
  storageKey?: string;
50
+ orbImageStorageKey?: string;
51
+ enableImageConfigPanel?: boolean;
52
+ presetOrbImageMapping?: OrbImageMapping;
49
53
  }
50
- declare function MikuFusionGame({ className, storageKey, ...options }: MikuFusionGameProps): React__default.JSX.Element;
54
+ declare function MikuFusionGame({ className, storageKey, orbImageStorageKey, enableImageConfigPanel, presetOrbImageMapping, ...options }: MikuFusionGameProps): React__default.JSX.Element;
51
55
 
52
56
  interface UseMikuFusionGameOptions extends Partial<MikuFusionGameConfig>, MikuFusionGameCallbacks {
53
57
  storageKey?: string;
58
+ orbImageMapping?: OrbImageMapping;
54
59
  }
55
60
  interface UseMikuFusionGameResult {
56
61
  canvasRef: RefObject<HTMLCanvasElement>;
@@ -109,4 +114,4 @@ declare const RADIUS_STEP = 4;
109
114
  declare const LEVEL_LABEL_PREFIX = "M";
110
115
  declare const DEFAULT_MIKU_FUSION_CONFIG: MikuFusionGameConfig;
111
116
 
112
- export { BASE_RADIUS, DEFAULT_MIKU_FUSION_CONFIG, type FusionOrb, LEVEL_LABEL_PREFIX, type MergeResult, type MikuFusionContentRegistry, MikuFusionGame, type MikuFusionGameCallbacks, type MikuFusionGameConfig, type MikuFusionGameProps, type MikuFusionGameStatus, type MikuFusionTheme, type ProducerPack, RADIUS_STEP, type SongPack, type ThemePack, type UseMikuFusionGameOptions, type UseMikuFusionGameResult, defaultContentRegistry, useMikuFusionGame, useResponsiveCanvas };
117
+ export { BASE_RADIUS, DEFAULT_MIKU_FUSION_CONFIG, type FusionOrb, LEVEL_LABEL_PREFIX, type MergeResult, type MikuFusionContentRegistry, MikuFusionGame, type MikuFusionGameCallbacks, type MikuFusionGameConfig, type MikuFusionGameProps, type MikuFusionGameStatus, type MikuFusionTheme, type OrbImageMapping, type ProducerPack, RADIUS_STEP, type SongPack, type ThemePack, type UseMikuFusionGameOptions, type UseMikuFusionGameResult, defaultContentRegistry, useMikuFusionGame, useResponsiveCanvas };
@@ -1,6 +1,7 @@
1
1
  import React__default, { RefObject } from 'react';
2
2
 
3
3
  type MikuFusionGameStatus = 'ready' | 'playing' | 'paused' | 'gameOver';
4
+ type OrbImageMapping = Partial<Record<number, string>>;
4
5
  interface FusionOrb {
5
6
  id: string;
6
7
  x: number;
@@ -46,11 +47,15 @@ interface MergeResult {
46
47
  interface MikuFusionGameProps extends Partial<MikuFusionGameConfig>, MikuFusionGameCallbacks {
47
48
  className?: string;
48
49
  storageKey?: string;
50
+ orbImageStorageKey?: string;
51
+ enableImageConfigPanel?: boolean;
52
+ presetOrbImageMapping?: OrbImageMapping;
49
53
  }
50
- declare function MikuFusionGame({ className, storageKey, ...options }: MikuFusionGameProps): React__default.JSX.Element;
54
+ declare function MikuFusionGame({ className, storageKey, orbImageStorageKey, enableImageConfigPanel, presetOrbImageMapping, ...options }: MikuFusionGameProps): React__default.JSX.Element;
51
55
 
52
56
  interface UseMikuFusionGameOptions extends Partial<MikuFusionGameConfig>, MikuFusionGameCallbacks {
53
57
  storageKey?: string;
58
+ orbImageMapping?: OrbImageMapping;
54
59
  }
55
60
  interface UseMikuFusionGameResult {
56
61
  canvasRef: RefObject<HTMLCanvasElement>;
@@ -109,4 +114,4 @@ declare const RADIUS_STEP = 4;
109
114
  declare const LEVEL_LABEL_PREFIX = "M";
110
115
  declare const DEFAULT_MIKU_FUSION_CONFIG: MikuFusionGameConfig;
111
116
 
112
- export { BASE_RADIUS, DEFAULT_MIKU_FUSION_CONFIG, type FusionOrb, LEVEL_LABEL_PREFIX, type MergeResult, type MikuFusionContentRegistry, MikuFusionGame, type MikuFusionGameCallbacks, type MikuFusionGameConfig, type MikuFusionGameProps, type MikuFusionGameStatus, type MikuFusionTheme, type ProducerPack, RADIUS_STEP, type SongPack, type ThemePack, type UseMikuFusionGameOptions, type UseMikuFusionGameResult, defaultContentRegistry, useMikuFusionGame, useResponsiveCanvas };
117
+ export { BASE_RADIUS, DEFAULT_MIKU_FUSION_CONFIG, type FusionOrb, LEVEL_LABEL_PREFIX, type MergeResult, type MikuFusionContentRegistry, MikuFusionGame, type MikuFusionGameCallbacks, type MikuFusionGameConfig, type MikuFusionGameProps, type MikuFusionGameStatus, type MikuFusionTheme, type OrbImageMapping, type ProducerPack, RADIUS_STEP, type SongPack, type ThemePack, type UseMikuFusionGameOptions, type UseMikuFusionGameResult, defaultContentRegistry, useMikuFusionGame, useResponsiveCanvas };
@@ -1,7 +1,10 @@
1
1
  'use strict';
2
2
 
3
+ var chunkHHVDOIPV_js = require('../chunk-HHVDOIPV.js');
4
+ require('../chunk-ZWQJSZEY.js');
3
5
  var chunkKO73EBUT_js = require('../chunk-KO73EBUT.js');
4
6
  require('../chunk-XJ7ZAGC5.js');
7
+ require('../chunk-25OFOKNF.js');
5
8
  require('../chunk-DGUM43GV.js');
6
9
  var React3 = require('react');
7
10
 
@@ -223,7 +226,7 @@ function getOrbColor(level, config) {
223
226
  const index = Math.max(0, Math.min(config.theme.orbColors.length - 1, level - 1));
224
227
  return config.theme.orbColors[index] || "#22d3ee";
225
228
  }
226
- function drawScene(context, width, height, config, orbs, aimX, nextLevel, status) {
229
+ function drawScene(context, width, height, config, orbs, aimX, nextLevel, status, imageCache) {
227
230
  const gradient = context.createLinearGradient(0, 0, 0, height);
228
231
  gradient.addColorStop(0, config.theme.backgroundTop);
229
232
  gradient.addColorStop(1, config.theme.backgroundBottom);
@@ -244,15 +247,51 @@ function drawScene(context, width, height, config, orbs, aimX, nextLevel, status
244
247
  context.lineTo(aimX, config.spawnY);
245
248
  context.stroke();
246
249
  const previewRadius = getRadiusByLevel(nextLevel);
247
- context.fillStyle = getOrbColor(nextLevel, config);
250
+ const previewY = config.spawnY - previewRadius - 4;
251
+ const previewImage = imageCache[nextLevel];
248
252
  context.beginPath();
249
- context.arc(aimX, config.spawnY - previewRadius - 4, previewRadius, 0, Math.PI * 2);
250
- context.fill();
253
+ context.arc(aimX, previewY, previewRadius, 0, Math.PI * 2);
254
+ context.closePath();
255
+ if (previewImage && previewImage.complete && previewImage.naturalWidth > 0) {
256
+ context.save();
257
+ context.clip();
258
+ context.drawImage(
259
+ previewImage,
260
+ aimX - previewRadius,
261
+ previewY - previewRadius,
262
+ previewRadius * 2,
263
+ previewRadius * 2
264
+ );
265
+ context.restore();
266
+ } else {
267
+ context.fillStyle = getOrbColor(nextLevel, config);
268
+ context.fill();
269
+ }
251
270
  orbs.forEach((orb) => {
252
271
  context.beginPath();
253
- context.fillStyle = getOrbColor(orb.level, config);
254
272
  context.arc(orb.x, orb.y, orb.radius, 0, Math.PI * 2);
255
- context.fill();
273
+ context.closePath();
274
+ const orbImage = imageCache[orb.level];
275
+ if (orbImage && orbImage.complete && orbImage.naturalWidth > 0) {
276
+ context.save();
277
+ context.clip();
278
+ context.drawImage(
279
+ orbImage,
280
+ orb.x - orb.radius,
281
+ orb.y - orb.radius,
282
+ orb.radius * 2,
283
+ orb.radius * 2
284
+ );
285
+ context.restore();
286
+ context.beginPath();
287
+ context.strokeStyle = "rgba(15, 23, 42, 0.15)";
288
+ context.lineWidth = 1;
289
+ context.arc(orb.x, orb.y, orb.radius, 0, Math.PI * 2);
290
+ context.stroke();
291
+ } else {
292
+ context.fillStyle = getOrbColor(orb.level, config);
293
+ context.fill();
294
+ }
256
295
  context.fillStyle = "#0f172a";
257
296
  context.font = "bold 12px system-ui";
258
297
  context.textAlign = "center";
@@ -291,10 +330,26 @@ function useMikuFusionGame(options = {}) {
291
330
  const config = configRef.current;
292
331
  const onScoreChangeRef = React3.useRef(options.onScoreChange);
293
332
  const onGameOverRef = React3.useRef(options.onGameOver);
333
+ const orbImageMappingRef = React3.useRef(options.orbImageMapping ?? {});
334
+ const imageCacheRef = React3.useRef({});
294
335
  React3.useEffect(() => {
295
336
  onScoreChangeRef.current = options.onScoreChange;
296
337
  onGameOverRef.current = options.onGameOver;
297
338
  }, [options.onGameOver, options.onScoreChange]);
339
+ React3.useEffect(() => {
340
+ orbImageMappingRef.current = options.orbImageMapping ?? {};
341
+ const nextCache = {};
342
+ Object.entries(orbImageMappingRef.current).forEach(([levelText, src]) => {
343
+ const level = Number(levelText);
344
+ if (!src || Number.isNaN(level)) {
345
+ return;
346
+ }
347
+ const image = new window.Image();
348
+ image.src = src;
349
+ nextCache[level] = image;
350
+ });
351
+ imageCacheRef.current = nextCache;
352
+ }, [options.orbImageMapping]);
298
353
  const [bestScore, setBestScore] = chunkKO73EBUT_js.useLocalStorage(
299
354
  options.storageKey || "sa2kit:mikuFusionGame:bestScore",
300
355
  0
@@ -327,7 +382,8 @@ function useMikuFusionGame(options = {}) {
327
382
  orbsRef.current,
328
383
  aimXRef.current,
329
384
  nextLevel,
330
- nextStatus
385
+ nextStatus,
386
+ imageCacheRef.current
331
387
  );
332
388
  },
333
389
  [config, nextLevel, status]
@@ -571,8 +627,24 @@ function GameResultModal({ open, score, bestScore, onRestart }) {
571
627
  }
572
628
 
573
629
  // src/mikuFusionGame/components/MikuFusionGame.tsx
574
- function MikuFusionGame({ className, storageKey, ...options }) {
630
+ function MikuFusionGame({
631
+ className,
632
+ storageKey,
633
+ orbImageStorageKey = "sa2kit:mikuFusionGame:orbImages",
634
+ enableImageConfigPanel = true,
635
+ presetOrbImageMapping = {},
636
+ ...options
637
+ }) {
575
638
  const containerRef = React3.useRef(null);
639
+ const [orbImageMapping, setOrbImageMapping] = React3.useState({});
640
+ const [isUserDefineMode] = React3.useState(() => {
641
+ if (typeof window === "undefined") {
642
+ return false;
643
+ }
644
+ const value = new URLSearchParams(window.location.search).get("userDefine");
645
+ return value === "true";
646
+ });
647
+ const activeOrbImageMapping = isUserDefineMode ? orbImageMapping : presetOrbImageMapping;
576
648
  const {
577
649
  canvasRef,
578
650
  status,
@@ -584,7 +656,46 @@ function MikuFusionGame({ className, storageKey, ...options }) {
584
656
  handleDrop,
585
657
  togglePause,
586
658
  restart
587
- } = useMikuFusionGame({ ...options, storageKey });
659
+ } = useMikuFusionGame({ ...options, storageKey, orbImageMapping: activeOrbImageMapping });
660
+ const panelItems = React3.useMemo(
661
+ () => Array.from({ length: config.maxLevel }, (_, index) => ({
662
+ id: String(index + 1),
663
+ label: `M${index + 1}`
664
+ })),
665
+ [config.maxLevel]
666
+ );
667
+ const panelValue = React3.useMemo(
668
+ () => Object.entries(orbImageMapping).reduce((acc, [key, value]) => {
669
+ if (value) {
670
+ acc[String(key)] = value;
671
+ }
672
+ return acc;
673
+ }, {}),
674
+ [orbImageMapping]
675
+ );
676
+ React3.useEffect(() => {
677
+ if (typeof window === "undefined") {
678
+ return;
679
+ }
680
+ try {
681
+ const raw = window.localStorage.getItem(orbImageStorageKey);
682
+ if (!raw) {
683
+ setOrbImageMapping({});
684
+ return;
685
+ }
686
+ const parsed = JSON.parse(raw);
687
+ const normalized = {};
688
+ Object.entries(parsed).forEach(([key, value]) => {
689
+ const level = Number(key);
690
+ if (!Number.isNaN(level) && typeof value === "string" && value) {
691
+ normalized[level] = value;
692
+ }
693
+ });
694
+ setOrbImageMapping(normalized);
695
+ } catch {
696
+ setOrbImageMapping({});
697
+ }
698
+ }, [orbImageStorageKey]);
588
699
  const { displayWidth, displayHeight } = useResponsiveCanvas(
589
700
  config.width,
590
701
  config.height,
@@ -624,6 +735,16 @@ function MikuFusionGame({ className, storageKey, ...options }) {
624
735
  togglePause();
625
736
  }
626
737
  };
738
+ const handlePanelValueChange = (next) => {
739
+ const normalized = {};
740
+ Object.entries(next).forEach(([key, value]) => {
741
+ const level = Number(key);
742
+ if (!Number.isNaN(level) && value) {
743
+ normalized[level] = value;
744
+ }
745
+ });
746
+ setOrbImageMapping(normalized);
747
+ };
627
748
  return /* @__PURE__ */ React3__default.default.createElement(
628
749
  "div",
629
750
  {
@@ -657,7 +778,17 @@ function MikuFusionGame({ className, storageKey, ...options }) {
657
778
  onTogglePause: togglePause,
658
779
  onRestart: restart
659
780
  }
660
- )
781
+ ),
782
+ enableImageConfigPanel && isUserDefineMode ? /* @__PURE__ */ React3__default.default.createElement(
783
+ chunkHHVDOIPV_js.LocalImageMappingPanel,
784
+ {
785
+ storageKey: orbImageStorageKey,
786
+ title: "\u7403\u4F53\u56FE\u7247\u914D\u7F6E\uFF08\u4FDD\u5B58\u5230\u672C\u5730\uFF09",
787
+ items: panelItems,
788
+ defaultValue: panelValue,
789
+ onValueChange: handlePanelValueChange
790
+ }
791
+ ) : null
661
792
  );
662
793
  }
663
794