sa2kit 1.6.32 → 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 +142 -11
  32. package/dist/mikuFusionGame/index.js.map +1 -1
  33. package/dist/mikuFusionGame/index.mjs +142 -11
  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,809 @@
1
+ 'use strict';
2
+
3
+ var React3 = require('react');
4
+ var THREE2 = require('three');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ function _interopNamespace(e) {
9
+ if (e && e.__esModule) return e;
10
+ var n = Object.create(null);
11
+ if (e) {
12
+ Object.keys(e).forEach(function (k) {
13
+ if (k !== 'default') {
14
+ var d = Object.getOwnPropertyDescriptor(e, k);
15
+ Object.defineProperty(n, k, d.get ? d : {
16
+ enumerable: true,
17
+ get: function () { return e[k]; }
18
+ });
19
+ }
20
+ });
21
+ }
22
+ n.default = e;
23
+ return Object.freeze(n);
24
+ }
25
+
26
+ var React3__default = /*#__PURE__*/_interopDefault(React3);
27
+ var THREE2__namespace = /*#__PURE__*/_interopNamespace(THREE2);
28
+
29
+ // src/mikuFireworks3D/components/MikuFireworks3D.tsx
30
+ function DanmakuPanel({ onSend }) {
31
+ const [text, setText] = React3.useState("");
32
+ const emit = () => {
33
+ const value = text.trim();
34
+ if (!value) {
35
+ return;
36
+ }
37
+ onSend(value);
38
+ setText("");
39
+ };
40
+ return /* @__PURE__ */ React3__default.default.createElement("div", { className: "rounded-xl border border-slate-600/40 bg-slate-900/70 p-3 text-slate-100 backdrop-blur-sm" }, /* @__PURE__ */ React3__default.default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React3__default.default.createElement(
41
+ "input",
42
+ {
43
+ type: "text",
44
+ value: text,
45
+ onChange: (event) => setText(event.target.value),
46
+ onKeyDown: (event) => {
47
+ if (event.key === "Enter") {
48
+ emit();
49
+ }
50
+ },
51
+ placeholder: "\u53D1\u9001\u5F39\u5E55\uFF08\u652F\u6301 /miku /avatar /normal\uFF09",
52
+ className: "flex-1 rounded-md border border-slate-600 bg-slate-950 px-2.5 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400"
53
+ }
54
+ ), /* @__PURE__ */ React3__default.default.createElement(
55
+ "button",
56
+ {
57
+ type: "button",
58
+ onClick: emit,
59
+ className: "rounded-md bg-cyan-400 px-3 py-2 text-sm font-medium text-slate-950 hover:bg-cyan-300"
60
+ },
61
+ "\u53D1\u9001"
62
+ )));
63
+ }
64
+ function FireworksCanvas({ canvasRef }) {
65
+ return /* @__PURE__ */ React3__default.default.createElement(
66
+ "canvas",
67
+ {
68
+ ref: canvasRef,
69
+ className: "absolute inset-0 h-full w-full",
70
+ style: {
71
+ background: "radial-gradient(circle at 20% 20%, rgba(57,197,187,0.15) 0%, rgba(6,8,22,1) 45%, rgba(4,6,15,1) 100%)"
72
+ }
73
+ }
74
+ );
75
+ }
76
+
77
+ // src/mikuFireworks3D/constants.ts
78
+ var DEFAULT_MAX_PARTICLES = 5e3;
79
+ var DEFAULT_MAX_ACTIVE_FIREWORKS = 12;
80
+ var FIREWORK_KIND_LABELS = {
81
+ normal: "\u666E\u901A\u70DF\u82B1",
82
+ miku: "MIKU \u4E3B\u9898",
83
+ avatar: "\u5934\u50CF\u70DF\u82B1"
84
+ };
85
+ var MIKU_PALETTE = ["#39c5bb", "#66e3db", "#7ad8ff", "#b0fff8", "#8cf7e0"];
86
+ var NORMAL_PALETTE = ["#ffe066", "#ff6b6b", "#4dabf7", "#c77dff", "#69db7c"];
87
+ var DANMAKU_MAX_LENGTH = 32;
88
+ var DANMAKU_TRACK_COUNT = 8;
89
+
90
+ // src/mikuFireworks3D/components/FireworksControlPanel.tsx
91
+ function FireworksControlPanel({
92
+ selectedKind,
93
+ onKindChange,
94
+ autoLaunchOnDanmaku,
95
+ onAutoLaunchChange,
96
+ avatarUrl,
97
+ onAvatarUrlChange,
98
+ onLaunch,
99
+ fps
100
+ }) {
101
+ return /* @__PURE__ */ React3__default.default.createElement("div", { className: "rounded-xl border border-slate-600/40 bg-slate-900/70 p-3 text-slate-100 backdrop-blur-sm" }, /* @__PURE__ */ React3__default.default.createElement("div", { className: "mb-3 flex flex-wrap items-center gap-2" }, Object.keys(FIREWORK_KIND_LABELS).map((kind) => {
102
+ const active = kind === selectedKind;
103
+ return /* @__PURE__ */ React3__default.default.createElement(
104
+ "button",
105
+ {
106
+ key: kind,
107
+ type: "button",
108
+ onClick: () => onKindChange(kind),
109
+ className: `rounded-md px-3 py-1.5 text-sm transition ${active ? "bg-cyan-500 text-slate-950" : "bg-slate-700/70 hover:bg-slate-600/80"}`
110
+ },
111
+ FIREWORK_KIND_LABELS[kind]
112
+ );
113
+ }), /* @__PURE__ */ React3__default.default.createElement(
114
+ "button",
115
+ {
116
+ type: "button",
117
+ onClick: onLaunch,
118
+ className: "ml-auto rounded-md bg-emerald-400 px-3 py-1.5 text-sm font-medium text-slate-900 hover:bg-emerald-300"
119
+ },
120
+ "\u53D1\u5C04\u70DF\u82B1"
121
+ )), /* @__PURE__ */ React3__default.default.createElement("div", { className: "grid gap-2 md:grid-cols-2" }, /* @__PURE__ */ React3__default.default.createElement("label", { className: "flex items-center gap-2 text-sm" }, /* @__PURE__ */ React3__default.default.createElement(
122
+ "input",
123
+ {
124
+ type: "checkbox",
125
+ checked: autoLaunchOnDanmaku,
126
+ onChange: (event) => onAutoLaunchChange(event.target.checked)
127
+ }
128
+ ), "\u53D1\u9001\u5F39\u5E55\u540E\u81EA\u52A8\u653E\u70DF\u82B1"), /* @__PURE__ */ React3__default.default.createElement("div", { className: "text-sm text-slate-300" }, "FPS: ", fps)), selectedKind === "avatar" ? /* @__PURE__ */ React3__default.default.createElement("div", { className: "mt-2" }, /* @__PURE__ */ React3__default.default.createElement(
129
+ "input",
130
+ {
131
+ type: "url",
132
+ value: avatarUrl,
133
+ onChange: (event) => onAvatarUrlChange(event.target.value),
134
+ placeholder: "\u5934\u50CF\u56FE\u7247 URL\uFF08\u7528\u4E8E\u5934\u50CF\u70DF\u82B1\uFF09",
135
+ className: "w-full rounded-md border border-slate-600 bg-slate-950 px-2.5 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400"
136
+ }
137
+ )) : null);
138
+ }
139
+ function useDanmakuController(options) {
140
+ const [items, setItems] = React3.useState([]);
141
+ const [cursor, setCursor] = React3.useState(0);
142
+ const removeItem = React3.useCallback((id) => {
143
+ setItems((prev) => prev.filter((item) => item.id !== id));
144
+ }, []);
145
+ const send = React3.useCallback(
146
+ (text, color) => {
147
+ const trimmed = text.trim();
148
+ if (!trimmed) {
149
+ return null;
150
+ }
151
+ const { content, launchKind } = parseCommand(trimmed);
152
+ const safeText = content.slice(0, DANMAKU_MAX_LENGTH);
153
+ if (!safeText) {
154
+ return null;
155
+ }
156
+ const message = {
157
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
158
+ text: safeText,
159
+ color,
160
+ timestamp: Date.now()
161
+ };
162
+ setItems((prev) => {
163
+ const track = cursor % DANMAKU_TRACK_COUNT;
164
+ const item = {
165
+ ...message,
166
+ track,
167
+ durationMs: 8e3 + Math.floor(Math.random() * 2800)
168
+ };
169
+ return [...prev.slice(-40), item];
170
+ });
171
+ setCursor((prev) => prev + 1);
172
+ options?.onSend?.(message);
173
+ return {
174
+ message,
175
+ launchKind
176
+ };
177
+ },
178
+ [cursor, options]
179
+ );
180
+ return React3.useMemo(
181
+ () => ({
182
+ items,
183
+ send,
184
+ removeItem
185
+ }),
186
+ [items, removeItem, send]
187
+ );
188
+ }
189
+ function parseCommand(text) {
190
+ if (text.startsWith("/miku ")) {
191
+ return { launchKind: "miku", content: text.replace("/miku ", "").trim() };
192
+ }
193
+ if (text === "/miku") {
194
+ return { launchKind: "miku", content: "MIKU!" };
195
+ }
196
+ if (text.startsWith("/avatar ")) {
197
+ return { launchKind: "avatar", content: text.replace("/avatar ", "").trim() };
198
+ }
199
+ if (text === "/avatar") {
200
+ return { launchKind: "avatar", content: "Avatar Firework!" };
201
+ }
202
+ if (text.startsWith("/normal ")) {
203
+ return { launchKind: "normal", content: text.replace("/normal ", "").trim() };
204
+ }
205
+ if (text === "/normal") {
206
+ return { launchKind: "normal", content: "Fireworks!" };
207
+ }
208
+ return { content: text };
209
+ }
210
+ function createCircularSpriteTexture() {
211
+ const size = 64;
212
+ const canvas = document.createElement("canvas");
213
+ canvas.width = size;
214
+ canvas.height = size;
215
+ const ctx = canvas.getContext("2d");
216
+ if (!ctx) {
217
+ return new THREE2__namespace.CanvasTexture(canvas);
218
+ }
219
+ const center = size / 2;
220
+ const gradient = ctx.createRadialGradient(center, center, 2, center, center, center);
221
+ gradient.addColorStop(0, "rgba(255,255,255,1)");
222
+ gradient.addColorStop(0.4, "rgba(255,255,255,0.9)");
223
+ gradient.addColorStop(1, "rgba(255,255,255,0)");
224
+ ctx.fillStyle = gradient;
225
+ ctx.fillRect(0, 0, size, size);
226
+ const texture = new THREE2__namespace.CanvasTexture(canvas);
227
+ texture.needsUpdate = true;
228
+ return texture;
229
+ }
230
+
231
+ // src/mikuFireworks3D/utils/colorPalettes.ts
232
+ function pickPalette(kind) {
233
+ if (kind === "miku") {
234
+ return MIKU_PALETTE;
235
+ }
236
+ return NORMAL_PALETTE;
237
+ }
238
+ function pickRandomColor(colors) {
239
+ if (colors.length === 0) {
240
+ return "#ffffff";
241
+ }
242
+ const index = Math.floor(Math.random() * colors.length);
243
+ return colors[index] || "#ffffff";
244
+ }
245
+
246
+ // src/mikuFireworks3D/utils/avatarSprite.ts
247
+ async function sampleAvatarPoints(avatarUrl, sampleStep = 4, maxPoints = 500) {
248
+ const image = await loadImage(avatarUrl);
249
+ const size = 64;
250
+ const canvas = document.createElement("canvas");
251
+ canvas.width = size;
252
+ canvas.height = size;
253
+ const ctx = canvas.getContext("2d");
254
+ if (!ctx) {
255
+ return [];
256
+ }
257
+ ctx.clearRect(0, 0, size, size);
258
+ ctx.drawImage(image, 0, 0, size, size);
259
+ const { data } = ctx.getImageData(0, 0, size, size);
260
+ const points = [];
261
+ for (let y = 0; y < size; y += sampleStep) {
262
+ for (let x = 0; x < size; x += sampleStep) {
263
+ const index = (y * size + x) * 4;
264
+ const alpha = data[index + 3] ?? 0;
265
+ if (alpha < 24) {
266
+ continue;
267
+ }
268
+ const r = data[index] ?? 0;
269
+ const g = data[index + 1] ?? 0;
270
+ const b = data[index + 2] ?? 0;
271
+ const brightness = (r + g + b) / (3 * 255);
272
+ points.push({ x: x - size / 2, y: size / 2 - y, brightness });
273
+ if (points.length >= maxPoints) {
274
+ return points;
275
+ }
276
+ }
277
+ }
278
+ return points;
279
+ }
280
+ function loadImage(url) {
281
+ return new Promise((resolve, reject) => {
282
+ const image = new window.Image();
283
+ image.crossOrigin = "anonymous";
284
+ image.onload = () => resolve(image);
285
+ image.onerror = () => reject(new Error("Failed to load avatar image."));
286
+ image.src = url;
287
+ });
288
+ }
289
+
290
+ // src/mikuFireworks3D/engine/patterns/avatar.ts
291
+ async function createAvatarSeeds(avatarUrl, fallbackColor) {
292
+ const points = await sampleAvatarPoints(avatarUrl);
293
+ if (points.length === 0) {
294
+ return [];
295
+ }
296
+ return points.map((point) => {
297
+ const spread = 0.22;
298
+ return {
299
+ x: 0,
300
+ y: 0,
301
+ z: 0,
302
+ vx: point.x * spread + (Math.random() - 0.5) * 2.4,
303
+ vy: point.y * spread + Math.random() * 2.2,
304
+ vz: (Math.random() - 0.5) * 3.5,
305
+ life: 1.1 + point.brightness * 1.6,
306
+ color: fallbackColor
307
+ };
308
+ });
309
+ }
310
+
311
+ // src/mikuFireworks3D/engine/patterns/miku.ts
312
+ function createMikuSeeds(count) {
313
+ const seeds = [];
314
+ for (let i = 0; i < count; i += 1) {
315
+ const ratio = i / Math.max(count - 1, 1);
316
+ const angle = ratio * Math.PI * 2 * 2;
317
+ const radial = 7 + Math.random() * 9;
318
+ const spiralBoost = 3 + Math.random() * 4;
319
+ seeds.push({
320
+ x: 0,
321
+ y: 0,
322
+ z: 0,
323
+ vx: Math.cos(angle) * radial,
324
+ vy: Math.sin(angle * 0.5) * spiralBoost + 7,
325
+ vz: Math.sin(angle) * radial,
326
+ life: 1.2 + Math.random() * 1.5,
327
+ color: pickRandomColor(MIKU_PALETTE)
328
+ });
329
+ }
330
+ return seeds;
331
+ }
332
+
333
+ // src/mikuFireworks3D/engine/patterns/normal.ts
334
+ function createNormalSeeds(count, color) {
335
+ const seeds = [];
336
+ for (let i = 0; i < count; i += 1) {
337
+ const theta = Math.random() * Math.PI * 2;
338
+ const phi = Math.acos(2 * Math.random() - 1);
339
+ const speed = 8 + Math.random() * 12;
340
+ seeds.push({
341
+ x: 0,
342
+ y: 0,
343
+ z: 0,
344
+ vx: speed * Math.sin(phi) * Math.cos(theta),
345
+ vy: speed * Math.cos(phi),
346
+ vz: speed * Math.sin(phi) * Math.sin(theta),
347
+ life: 1 + Math.random() * 1.6,
348
+ color
349
+ });
350
+ }
351
+ return seeds;
352
+ }
353
+
354
+ // src/mikuFireworks3D/engine/emitters.ts
355
+ async function createSeedParticles(options) {
356
+ const palette = pickPalette(options.kind);
357
+ const color = options.color ?? pickRandomColor(palette);
358
+ if (options.kind === "miku") {
359
+ return createMikuSeeds(options.count);
360
+ }
361
+ if (options.kind === "avatar" && options.avatarUrl) {
362
+ const avatarSeeds = await createAvatarSeeds(options.avatarUrl, color);
363
+ if (avatarSeeds.length > 0) {
364
+ return avatarSeeds;
365
+ }
366
+ }
367
+ return createNormalSeeds(options.count, color);
368
+ }
369
+
370
+ // src/mikuFireworks3D/engine/particlePool.ts
371
+ var ParticlePool = class {
372
+ constructor() {
373
+ this.pool = [];
374
+ }
375
+ acquire() {
376
+ const reused = this.pool.pop();
377
+ if (reused) {
378
+ return reused;
379
+ }
380
+ return {
381
+ x: 0,
382
+ y: 0,
383
+ z: 0,
384
+ vx: 0,
385
+ vy: 0,
386
+ vz: 0,
387
+ life: 0,
388
+ maxLife: 1,
389
+ r: 1,
390
+ g: 1,
391
+ b: 1
392
+ };
393
+ }
394
+ release(particle) {
395
+ this.pool.push(particle);
396
+ }
397
+ };
398
+
399
+ // src/mikuFireworks3D/engine/postfx.ts
400
+ function evaluateDegradePolicy(fps) {
401
+ if (fps < 24) {
402
+ return { shouldDegrade: true, recommendedParticleScale: 0.65 };
403
+ }
404
+ if (fps < 34) {
405
+ return { shouldDegrade: true, recommendedParticleScale: 0.82 };
406
+ }
407
+ return { shouldDegrade: false, recommendedParticleScale: 1 };
408
+ }
409
+
410
+ // src/mikuFireworks3D/engine/FireworksEngine.ts
411
+ var FireworksEngine = class {
412
+ constructor(init) {
413
+ this.pool = new ParticlePool();
414
+ this.bursts = [];
415
+ this.animationFrameId = null;
416
+ this.lastTick = 0;
417
+ this.fpsWindow = { frames: 0, elapsed: 0, fps: 60, particleScale: 1 };
418
+ this.resizeObserver = null;
419
+ this.loop = () => {
420
+ const now = window.performance.now();
421
+ const dt = Math.min((now - this.lastTick) / 1e3, 0.05);
422
+ this.lastTick = now;
423
+ this.update(dt);
424
+ this.renderer.render(this.scene, this.camera);
425
+ this.animationFrameId = window.requestAnimationFrame(this.loop);
426
+ };
427
+ this.canvas = init.canvas;
428
+ this.container = init.container;
429
+ this.maxParticles = init.options?.maxParticles ?? DEFAULT_MAX_PARTICLES;
430
+ this.maxActiveFireworks = init.options?.maxActiveFireworks ?? DEFAULT_MAX_ACTIVE_FIREWORKS;
431
+ this.onError = init.options?.onError;
432
+ this.onFpsReport = init.options?.onFpsReport;
433
+ this.scene = new THREE2__namespace.Scene();
434
+ this.scene.background = new THREE2__namespace.Color("#060816");
435
+ this.camera = new THREE2__namespace.PerspectiveCamera(50, 1, 0.1, 1e3);
436
+ this.camera.position.set(0, 0, 45);
437
+ this.renderer = new THREE2__namespace.WebGLRenderer({
438
+ canvas: this.canvas,
439
+ alpha: true,
440
+ antialias: true,
441
+ powerPreference: "high-performance"
442
+ });
443
+ this.renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
444
+ this.spriteTexture = createCircularSpriteTexture();
445
+ this.attachResizeObserver();
446
+ this.resize();
447
+ }
448
+ start() {
449
+ if (this.animationFrameId != null) {
450
+ return;
451
+ }
452
+ this.lastTick = window.performance.now();
453
+ this.loop();
454
+ }
455
+ stop() {
456
+ if (this.animationFrameId != null) {
457
+ window.cancelAnimationFrame(this.animationFrameId);
458
+ this.animationFrameId = null;
459
+ }
460
+ }
461
+ async launch(payload) {
462
+ try {
463
+ this.enforceBurstCap();
464
+ const particleBudget = Math.max(80, Math.floor(280 * this.fpsWindow.particleScale));
465
+ const seeds = await createSeedParticles({
466
+ kind: payload.kind,
467
+ count: particleBudget,
468
+ color: payload.color,
469
+ avatarUrl: payload.avatarUrl
470
+ });
471
+ if (seeds.length === 0) {
472
+ return;
473
+ }
474
+ const launchPosition = payload.position ?? {
475
+ x: (Math.random() - 0.5) * 18,
476
+ y: -4 + Math.random() * 12,
477
+ z: (Math.random() - 0.5) * 4
478
+ };
479
+ const particles = [];
480
+ const positions = new Float32Array(seeds.length * 3);
481
+ const colors = new Float32Array(seeds.length * 3);
482
+ const colorHelper = new THREE2__namespace.Color();
483
+ for (let i = 0; i < seeds.length; i += 1) {
484
+ const seed = seeds[i];
485
+ if (!seed) {
486
+ continue;
487
+ }
488
+ const particle = this.pool.acquire();
489
+ particle.x = launchPosition.x + seed.x;
490
+ particle.y = launchPosition.y + seed.y;
491
+ particle.z = launchPosition.z + seed.z;
492
+ particle.vx = seed.vx;
493
+ particle.vy = seed.vy;
494
+ particle.vz = seed.vz;
495
+ particle.life = seed.life;
496
+ particle.maxLife = seed.life;
497
+ colorHelper.set(seed.color);
498
+ particle.r = colorHelper.r;
499
+ particle.g = colorHelper.g;
500
+ particle.b = colorHelper.b;
501
+ particles.push(particle);
502
+ const offset = i * 3;
503
+ positions[offset] = particle.x;
504
+ positions[offset + 1] = particle.y;
505
+ positions[offset + 2] = particle.z;
506
+ colors[offset] = particle.r;
507
+ colors[offset + 1] = particle.g;
508
+ colors[offset + 2] = particle.b;
509
+ }
510
+ if (this.totalParticleCount() + particles.length > this.maxParticles) {
511
+ this.releaseParticles(particles);
512
+ return;
513
+ }
514
+ const geometry = new THREE2__namespace.BufferGeometry();
515
+ geometry.setAttribute("position", new THREE2__namespace.BufferAttribute(positions, 3));
516
+ geometry.setAttribute("color", new THREE2__namespace.BufferAttribute(colors, 3));
517
+ const material = new THREE2__namespace.PointsMaterial({
518
+ size: payload.kind === "miku" ? 0.42 : 0.36,
519
+ vertexColors: true,
520
+ map: this.spriteTexture,
521
+ transparent: true,
522
+ opacity: 1,
523
+ depthWrite: false,
524
+ blending: THREE2__namespace.AdditiveBlending
525
+ });
526
+ const points = new THREE2__namespace.Points(geometry, material);
527
+ this.scene.add(points);
528
+ const burst = {
529
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
530
+ points,
531
+ geometry,
532
+ material,
533
+ positions,
534
+ colors,
535
+ particles
536
+ };
537
+ this.bursts.push(burst);
538
+ } catch (error) {
539
+ this.onError?.(error instanceof Error ? error : new Error("Failed to launch firework."));
540
+ }
541
+ }
542
+ dispose() {
543
+ this.stop();
544
+ if (this.resizeObserver) {
545
+ this.resizeObserver.disconnect();
546
+ this.resizeObserver = null;
547
+ }
548
+ for (const burst of this.bursts) {
549
+ this.destroyBurst(burst);
550
+ }
551
+ this.bursts.length = 0;
552
+ this.spriteTexture.dispose();
553
+ this.renderer.dispose();
554
+ }
555
+ update(dt) {
556
+ const gravity = -8.8;
557
+ for (let b = this.bursts.length - 1; b >= 0; b -= 1) {
558
+ const burst = this.bursts[b];
559
+ if (!burst) {
560
+ continue;
561
+ }
562
+ let alive = 0;
563
+ for (let i = 0; i < burst.particles.length; i += 1) {
564
+ const particle = burst.particles[i];
565
+ if (!particle) {
566
+ continue;
567
+ }
568
+ particle.life -= dt;
569
+ if (particle.life <= 0) {
570
+ continue;
571
+ }
572
+ particle.vx *= 0.992;
573
+ particle.vy = particle.vy * 0.992 + gravity * dt;
574
+ particle.vz *= 0.992;
575
+ particle.x += particle.vx * dt;
576
+ particle.y += particle.vy * dt;
577
+ particle.z += particle.vz * dt;
578
+ const idx = i * 3;
579
+ burst.positions[idx] = particle.x;
580
+ burst.positions[idx + 1] = particle.y;
581
+ burst.positions[idx + 2] = particle.z;
582
+ const alpha = Math.max(particle.life / particle.maxLife, 0);
583
+ burst.colors[idx] = particle.r * alpha;
584
+ burst.colors[idx + 1] = particle.g * alpha;
585
+ burst.colors[idx + 2] = particle.b * alpha;
586
+ alive += 1;
587
+ }
588
+ const positionAttr = burst.geometry.getAttribute("position");
589
+ const colorAttr = burst.geometry.getAttribute("color");
590
+ positionAttr.needsUpdate = true;
591
+ colorAttr.needsUpdate = true;
592
+ burst.material.opacity = Math.min(1, 0.22 + alive / Math.max(burst.particles.length, 1));
593
+ if (alive === 0) {
594
+ this.bursts.splice(b, 1);
595
+ this.destroyBurst(burst);
596
+ }
597
+ }
598
+ this.updateFpsStats(dt);
599
+ }
600
+ updateFpsStats(dt) {
601
+ this.fpsWindow.frames += 1;
602
+ this.fpsWindow.elapsed += dt;
603
+ if (this.fpsWindow.elapsed < 0.6) {
604
+ return;
605
+ }
606
+ const fps = this.fpsWindow.frames / this.fpsWindow.elapsed;
607
+ this.fpsWindow.fps = fps;
608
+ const policy = evaluateDegradePolicy(fps);
609
+ this.fpsWindow.particleScale = policy.recommendedParticleScale;
610
+ this.onFpsReport?.(Math.round(fps));
611
+ this.fpsWindow.frames = 0;
612
+ this.fpsWindow.elapsed = 0;
613
+ }
614
+ totalParticleCount() {
615
+ return this.bursts.reduce((sum, burst) => sum + burst.particles.length, 0);
616
+ }
617
+ enforceBurstCap() {
618
+ while (this.bursts.length >= this.maxActiveFireworks) {
619
+ const burst = this.bursts.shift();
620
+ if (!burst) {
621
+ break;
622
+ }
623
+ this.destroyBurst(burst);
624
+ }
625
+ }
626
+ destroyBurst(burst) {
627
+ this.scene.remove(burst.points);
628
+ burst.geometry.dispose();
629
+ burst.material.dispose();
630
+ this.releaseParticles(burst.particles);
631
+ }
632
+ releaseParticles(particles) {
633
+ for (const particle of particles) {
634
+ this.pool.release(particle);
635
+ }
636
+ }
637
+ attachResizeObserver() {
638
+ this.resizeObserver = new ResizeObserver(() => this.resize());
639
+ this.resizeObserver.observe(this.container);
640
+ }
641
+ resize() {
642
+ const width = Math.max(1, this.container.clientWidth);
643
+ const height = Math.max(1, this.container.clientHeight);
644
+ this.camera.aspect = width / height;
645
+ this.camera.updateProjectionMatrix();
646
+ this.renderer.setSize(width, height, false);
647
+ }
648
+ };
649
+
650
+ // src/mikuFireworks3D/hooks/useFireworksEngine.ts
651
+ function useFireworksEngine(options) {
652
+ const containerRef = React3.useRef(null);
653
+ const canvasRef = React3.useRef(null);
654
+ const engineRef = React3.useRef(null);
655
+ const [fps, setFps] = React3.useState(60);
656
+ const { maxParticles, maxActiveFireworks, onError, onFpsReport, onLaunch } = options || {};
657
+ React3.useEffect(() => {
658
+ if (!containerRef.current || !canvasRef.current) {
659
+ return;
660
+ }
661
+ const engine = new FireworksEngine({
662
+ container: containerRef.current,
663
+ canvas: canvasRef.current,
664
+ options: {
665
+ maxParticles,
666
+ maxActiveFireworks,
667
+ onError,
668
+ onFpsReport: (nextFps) => {
669
+ setFps(nextFps);
670
+ onFpsReport?.(nextFps);
671
+ }
672
+ }
673
+ });
674
+ engine.start();
675
+ engineRef.current = engine;
676
+ return () => {
677
+ engineRef.current?.dispose();
678
+ engineRef.current = null;
679
+ };
680
+ }, [maxParticles, maxActiveFireworks, onError, onFpsReport]);
681
+ const launch = React3.useCallback(
682
+ (payload) => {
683
+ void engineRef.current?.launch(payload);
684
+ onLaunch?.(payload);
685
+ },
686
+ [onLaunch]
687
+ );
688
+ const api = React3.useMemo(
689
+ () => ({
690
+ containerRef,
691
+ canvasRef,
692
+ launch,
693
+ fps
694
+ }),
695
+ [fps, launch]
696
+ );
697
+ return api;
698
+ }
699
+
700
+ // src/mikuFireworks3D/components/MikuFireworks3D.tsx
701
+ function MikuFireworks3D({
702
+ width = "100%",
703
+ height = 520,
704
+ className,
705
+ defaultKind = "normal",
706
+ autoLaunchOnDanmaku = true,
707
+ maxParticles,
708
+ maxActiveFireworks,
709
+ defaultAvatarUrl = "",
710
+ onLaunch,
711
+ onDanmakuSend,
712
+ onError,
713
+ onFpsReport
714
+ }) {
715
+ const [selectedKind, setSelectedKind] = React3.useState(defaultKind);
716
+ const [avatarUrl, setAvatarUrl] = React3.useState(defaultAvatarUrl);
717
+ const [autoLaunch, setAutoLaunch] = React3.useState(autoLaunchOnDanmaku);
718
+ const { containerRef, canvasRef, launch, fps } = useFireworksEngine({
719
+ maxParticles,
720
+ maxActiveFireworks,
721
+ onLaunch,
722
+ onError,
723
+ onFpsReport
724
+ });
725
+ const { items, send, removeItem } = useDanmakuController({
726
+ onSend: onDanmakuSend
727
+ });
728
+ const handleLaunch = (kind) => {
729
+ launch({
730
+ kind,
731
+ avatarUrl: kind === "avatar" ? avatarUrl || void 0 : void 0
732
+ });
733
+ };
734
+ const handleSendDanmaku = (text) => {
735
+ const result = send(text);
736
+ if (!result) {
737
+ return;
738
+ }
739
+ const launchKind = result.launchKind ?? selectedKind;
740
+ if (autoLaunch) {
741
+ launch({
742
+ kind: launchKind,
743
+ avatarUrl: launchKind === "avatar" ? avatarUrl || void 0 : void 0,
744
+ message: result.message
745
+ });
746
+ }
747
+ };
748
+ const containerStyle = React3.useMemo(
749
+ () => ({
750
+ width,
751
+ height,
752
+ minHeight: 360
753
+ }),
754
+ [height, width]
755
+ );
756
+ return /* @__PURE__ */ React3__default.default.createElement("div", { className: `mx-auto flex w-full max-w-5xl flex-col gap-3 ${className || ""}` }, /* @__PURE__ */ React3__default.default.createElement("div", { ref: containerRef, className: "relative overflow-hidden rounded-2xl border border-slate-700", style: containerStyle }, /* @__PURE__ */ React3__default.default.createElement(FireworksCanvas, { canvasRef }), /* @__PURE__ */ React3__default.default.createElement("div", { className: "pointer-events-none absolute inset-0 overflow-hidden" }, items.map((item) => /* @__PURE__ */ React3__default.default.createElement(
757
+ "div",
758
+ {
759
+ key: item.id,
760
+ onAnimationEnd: () => removeItem(item.id),
761
+ className: "absolute right-[-30%] whitespace-nowrap text-sm font-semibold text-white drop-shadow",
762
+ style: {
763
+ top: `${8 + item.track * 11}%`,
764
+ color: item.color || "#ffffff",
765
+ animation: `sa2kit-danmaku-move ${item.durationMs}ms linear forwards`
766
+ }
767
+ },
768
+ item.text
769
+ )))), /* @__PURE__ */ React3__default.default.createElement(
770
+ FireworksControlPanel,
771
+ {
772
+ selectedKind,
773
+ onKindChange: setSelectedKind,
774
+ autoLaunchOnDanmaku: autoLaunch,
775
+ onAutoLaunchChange: setAutoLaunch,
776
+ avatarUrl,
777
+ onAvatarUrlChange: setAvatarUrl,
778
+ onLaunch: () => handleLaunch(selectedKind),
779
+ fps
780
+ }
781
+ ), /* @__PURE__ */ React3__default.default.createElement(DanmakuPanel, { onSend: handleSendDanmaku }), /* @__PURE__ */ React3__default.default.createElement("style", null, `
782
+ @keyframes sa2kit-danmaku-move {
783
+ 0% {
784
+ transform: translateX(0);
785
+ opacity: 1;
786
+ }
787
+ 100% {
788
+ transform: translateX(-160vw);
789
+ opacity: 0.92;
790
+ }
791
+ }
792
+ `));
793
+ }
794
+
795
+ exports.DANMAKU_MAX_LENGTH = DANMAKU_MAX_LENGTH;
796
+ exports.DANMAKU_TRACK_COUNT = DANMAKU_TRACK_COUNT;
797
+ exports.DEFAULT_MAX_ACTIVE_FIREWORKS = DEFAULT_MAX_ACTIVE_FIREWORKS;
798
+ exports.DEFAULT_MAX_PARTICLES = DEFAULT_MAX_PARTICLES;
799
+ exports.DanmakuPanel = DanmakuPanel;
800
+ exports.FIREWORK_KIND_LABELS = FIREWORK_KIND_LABELS;
801
+ exports.FireworksCanvas = FireworksCanvas;
802
+ exports.FireworksControlPanel = FireworksControlPanel;
803
+ exports.MIKU_PALETTE = MIKU_PALETTE;
804
+ exports.MikuFireworks3D = MikuFireworks3D;
805
+ exports.NORMAL_PALETTE = NORMAL_PALETTE;
806
+ exports.useDanmakuController = useDanmakuController;
807
+ exports.useFireworksEngine = useFireworksEngine;
808
+ //# sourceMappingURL=chunk-B34YUZRL.js.map
809
+ //# sourceMappingURL=chunk-B34YUZRL.js.map