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