From 9b156a32c9167c016c1564973d361c9320a709d1 Mon Sep 17 00:00:00 2001 From: tianmo Date: Thu, 27 Aug 2026 17:23:39 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E6=AC=A1=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- headless-empty.mjs | 58 + headless-new.mjs | 30 + headless-ns.mjs | 39 + index.html | 16 + package-lock.json | 2740 +++++++++++ package.json | 24 + public/particles/dot.png | Bin 0 -> 3127 bytes public/particles/spark.png | Bin 0 -> 527 bytes public/particles/star.png | Bin 0 -> 4411 bytes public/spine/sample.json | 8980 +++++++++++++++++++++++++++++++++++ src/App.vue | 66 + src/core/particleEmitter.ts | 465 ++ src/main.ts | 6 + src/store/particleStore.ts | 114 + src/style.css | 9 + src/views/CurveEditor.vue | 134 + src/views/NumSlider.vue | 63 + src/views/ParticlePanel.vue | 331 ++ src/views/Stage.vue | 664 +++ src/views/Timeline.vue | 192 + tsconfig.json | 22 + tsconfig.node.json | 11 + vite.config.d.ts | 2 + vite.config.js | 13 + vite.config.ts | 14 + 开发计划.md | 168 + 粒子系统开发阶段一.md | 74 + 网页粒子系统阶段二.md | 249 + 重构设计.md | 83 + 29 files changed, 14567 insertions(+) create mode 100644 headless-empty.mjs create mode 100644 headless-new.mjs create mode 100644 headless-ns.mjs create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/particles/dot.png create mode 100644 public/particles/spark.png create mode 100644 public/particles/star.png create mode 100644 public/spine/sample.json create mode 100644 src/App.vue create mode 100644 src/core/particleEmitter.ts create mode 100644 src/main.ts create mode 100644 src/store/particleStore.ts create mode 100644 src/style.css create mode 100644 src/views/CurveEditor.vue create mode 100644 src/views/NumSlider.vue create mode 100644 src/views/ParticlePanel.vue create mode 100644 src/views/Stage.vue create mode 100644 src/views/Timeline.vue create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.d.ts create mode 100644 vite.config.js create mode 100644 vite.config.ts create mode 100644 开发计划.md create mode 100644 粒子系统开发阶段一.md create mode 100644 网页粒子系统阶段二.md create mode 100644 重构设计.md diff --git a/headless-empty.mjs b/headless-empty.mjs new file mode 100644 index 0000000..d19534c --- /dev/null +++ b/headless-empty.mjs @@ -0,0 +1,58 @@ +import puppeteer from 'puppeteer-core' +const CHROME = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' +const URL = 'http://localhost:5173/' +const b = await puppeteer.launch({ + executablePath: CHROME, headless: 'new', + args: ['--no-sandbox','--use-gl=swiftshader','--enable-unsafe-swiftshader','--window-size=1400,900'], +}) +try { + const p = await b.newPage() + await p.setViewport({ width: 1400, height: 900 }) + const errs = [] + p.on('pageerror', (e) => errs.push('PAGEERROR: ' + e.message)) + + await p.goto(URL, { waitUntil: 'networkidle2', timeout: 30000 }) + await new Promise((r) => setTimeout(r, 2500)) + + // 1) 初期应为空态 + const init = await p.evaluate(() => { + const empty = document.querySelector('.empty') + const animSelect = document.querySelector('.topbar select') + const sysNames = [...document.querySelectorAll('.sys-name')].map(e => e.textContent) + return { + emptyShown: !!empty, + emptyTitle: empty?.querySelector('.empty-title')?.textContent || null, + hasAnimSelect: !!animSelect, + systemCount: sysNames.length, + loadBtn: [...document.querySelectorAll('.topbar button')].map(e=>e.textContent).join('|'), + } + }) + + // 2) 点击"加载示例骨骼" + await p.evaluate(() => { + const btn = [...document.querySelectorAll('.topbar button')].find((b) => b.textContent.includes('加载示例骨骼')) + if (btn) btn.click() + }) + await new Promise((r) => setTimeout(r, 3000)) + + const after = await p.evaluate(() => { + const animSelect = document.querySelector('.topbar select') + const opts = animSelect ? [...animSelect.querySelectorAll('option')].map(o => o.textContent) : [] + return { + emptyShown: !!document.querySelector('.empty'), + animOptionCount: opts.length, + firstOpts: opts.slice(0, 6), + sysCount: [...document.querySelectorAll('.sys-name')].length, + hud: document.querySelector('.hud')?.innerText?.replace(/\s+/g,' ').trim() || null, + boneSelect: document.querySelector('.group select')?.textContent?.split('\n').map(s=>s.trim()).filter(Boolean).slice(0,8) || [], + } + }) + + await p.screenshot({ path: '/tmp/empty-after-load.png' }) + console.log('=== 初始(应空态) ===') + console.log(JSON.stringify(init, null, 2)) + console.log('=== 加载骨骼后 ===') + console.log(JSON.stringify(after, null, 2)) + console.log('=== 错误 ===') + console.log(errs.join('\n') || '(无)') +} finally { await b.close() } diff --git a/headless-new.mjs b/headless-new.mjs new file mode 100644 index 0000000..ff2a02f --- /dev/null +++ b/headless-new.mjs @@ -0,0 +1,30 @@ +import puppeteer from 'puppeteer-core' +const CHROME='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' +const b=await puppeteer.launch({executablePath:CHROME,headless:'new',args:['--no-sandbox','--use-gl=swiftshader','--enable-unsafe-swiftshader','--window-size=1400,900']}) +try{ + const p=await b.newPage() + await p.setViewport({width:1400,height:900}) + const errs=[] + p.on('pageerror',e=>errs.push('PAGEERR: '+e.message)) + await p.goto('http://localhost:5173/',{waitUntil:'networkidle2',timeout:30000}) + await new Promise(r=>setTimeout(r,2500)) + const r=await p.evaluate(()=>({ + groupTitles: [...document.querySelectorAll('.group-head')].map(e=>e.textContent.trim()), + hasDirectionModule: [...document.querySelectorAll('.group-head')].some(e=>e.textContent.includes('方向')), + shapeHasAngle: (()=>{const g=[...document.querySelectorAll('.group')].find(x=>x.querySelector('.group-head')?.textContent.includes('发射器形状'));const t=g?.querySelector('.group-body')?.textContent||'';return t.includes('角度°')&&t.includes('扩散°')})(), + emitHasBur: (()=>{const g=[...document.querySelectorAll('.group')].find(x=>x.querySelector('.group-head')?.textContent.includes('发射模式'));const t=g?.querySelector('.group-body')?.textContent||'';return t.includes('爆发')&&t.includes('持续')})(), + })) + // 点一下"发射器形状"折叠 + await p.evaluate(()=>{const h=[...document.querySelectorAll('.group-head')].find(e=>e.textContent.includes('发射器形状'));h?.click()}) + await new Promise(r=>setTimeout(r,300)) + const collapsed=await p.evaluate(()=>{const g=[...document.querySelectorAll('.group')].find(x=>x.querySelector('.group-head')?.textContent.includes('发射器形状'));return {caret:g?.querySelector('.caret')?.textContent, visible: g? g.querySelector('.group-body')?.offsetParent ? true : false : '?'}}) + await p.screenshot({path:'/tmp/panel-new.png'}) + console.log('=== 分组标题 ===') + console.log(JSON.stringify(r.groupTitles,null,2)) + console.log('方向独立模块?', r.hasDirectionModule) + console.log('形状组含角度/扩散?', r.shapeHasAngle) + console.log('发射模式组含持续/爆发?', r.emitHasBur) + console.log('折叠后:', JSON.stringify(collapsed)) + console.log('=== 错误 ===') + console.log(errs.join('\n')||'(无)') +}finally{await b.close()} diff --git a/headless-ns.mjs b/headless-ns.mjs new file mode 100644 index 0000000..4b7231c --- /dev/null +++ b/headless-ns.mjs @@ -0,0 +1,39 @@ +import puppeteer from 'puppeteer-core' +const CHROME='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' +const b=await puppeteer.launch({executablePath:CHROME,headless:'new',args:['--no-sandbox','--use-gl=swiftshader','--enable-unsafe-swiftshader','--window-size=1400,900']}) +try{ + const p=await b.newPage() + await p.setViewport({width:1400,height:900}) + const errs=[] + p.on('pageerror',e=>errs.push('PAGEERR: '+e.message)) + await p.goto('http://localhost:5173/',{waitUntil:'networkidle2',timeout:30000}) + await new Promise(r=>setTimeout(r,2500)) + const r=await p.evaluate(()=>({ + groupTitles:[...document.querySelectorAll('.group-head')].map(e=>e.textContent.trim()), + nsCount:document.querySelectorAll('.ns').length, + nsRange:document.querySelectorAll('.ns-range').length, + nsNum:document.querySelectorAll('.ns-num').length, + // 第一个数值滑块标签+当前值 + firstNs:(()=>{const e=document.querySelector('.ns');return e?{label:e.querySelector('.ns-label')?.textContent,num:e.querySelector('.ns-num')?.value}:null})(), + })) + // 改一个数值框:把"速率/秒"的数字框改值 + const before=await p.evaluate(()=>document.querySelector('.hud')?.innerText) + const changed=await p.evaluate(()=>{ + // 找到 label=速率/秒 的 .ns-num + const ns=[...document.querySelectorAll('.ns')].find(n=>n.querySelector('.ns-label')?.textContent.includes('速率')) + const num=ns?.querySelector('.ns-num') + if(num){ const old=num.value; num.value='150'; num.dispatchEvent(new Event('input',{bubbles:true})); return {changed:true,old,new:num.value} } + return {changed:false} + }) + await new Promise(r=>setTimeout(r,2500)) + const after=await p.evaluate(()=>document.querySelector('.hud')?.innerText) + const rateVal=await p.evaluate(()=>{const ns=[...document.querySelectorAll('.ns')].find(n=>n.querySelector('.ns-label')?.textContent.includes('速率'));return ns?.querySelector('.ns-num')?.value}) + await p.screenshot({path:'/tmp/panel-numslider.png'}) + console.log('=== 分组 ==='); console.log(JSON.stringify(r.groupTitles)) + console.log('NumSlider: 控件=',r.nsCount,'滑块=',r.nsRange,'数字框=',r.nsNum) + console.log('首控件=', JSON.stringify(r.firstNs)) + console.log('改速率:', JSON.stringify(changed), '改后数字框值=', rateVal) + console.log('HUD before:', before?.replace(/\s+/g,' ').trim()) + console.log('HUD after :', after?.replace(/\s+/g,' ').trim()) + console.log('=== 错误 ==='); console.log(errs.join('\n')||'(无)') +}finally{await b.close()} diff --git a/index.html b/index.html new file mode 100644 index 0000000..0cb18fa --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + + Spine Particle · 阶段1 Spike + + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..bc24b1e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2740 @@ +{ + "name": "spine-particle", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "spine-particle", + "version": "0.1.0", + "dependencies": { + "pinia": "^4.0.3", + "pixi.js": "^7.4.3", + "vue": "^3.5.13" + }, + "devDependencies": { + "@types/node": "^20.19.43", + "@vitejs/plugin-vue": "^5.2.1", + "puppeteer-core": "^25.9.0", + "typescript": "^5.7.2", + "vite": "^6.0.5", + "vue-tsc": "^2.2.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@pixi/accessibility": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/accessibility/-/accessibility-7.4.3.tgz", + "integrity": "sha512-tCr0yeWpMe0yucFvEPidy5a7gVJGpTjqGrDpSEBYT/kbScfUwcoX49RrckCCCiXDlyO4WRh9lVVuHXTvqRLIMg==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/events": "7.4.3" + } + }, + "node_modules/@pixi/app": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/app/-/app-7.4.3.tgz", + "integrity": "sha512-opyWMuO0Ir8pf1DYUR++wAA6ZfNU+nIX2z95R2OD172HbcdhB4/HD7leLIIAny/LciEdMqlWEBhXK7N93YWbdg==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/assets": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/assets/-/assets-7.4.3.tgz", + "integrity": "sha512-StvjiJBSp/j9hHkGu8AFHNvwYUazXq64WhyhytztyDMRkg/l/cL7EcttY5T0qZNWlIpccdr60LUKrWDOuMpkiw==", + "license": "MIT", + "dependencies": { + "@types/css-font-loading-module": "^0.0.12" + }, + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/color": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.4.3.tgz", + "integrity": "sha512-a6R+bXKeXMDcRmjYQoBIK+v2EYqxSX49wcjAY579EYM/WrFKS98nSees6lqVUcLKrcQh2DT9srJHX7XMny3voQ==", + "license": "MIT", + "dependencies": { + "@pixi/colord": "^2.9.6" + } + }, + "node_modules/@pixi/colord": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz", + "integrity": "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==", + "license": "MIT" + }, + "node_modules/@pixi/compressed-textures": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/compressed-textures/-/compressed-textures-7.4.3.tgz", + "integrity": "sha512-uJ3CC+lNX4HIxs6IxEESO50/0A1KxSVm6CO9UlkXzTsNj9ynmdy5BkJ1dzii7LCdqGcHIXHO01yvKuUbJBBQtw==", + "license": "MIT", + "peerDependencies": { + "@pixi/assets": "7.4.3", + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/constants": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-7.4.3.tgz", + "integrity": "sha512-QGmwJUNQy/vVEHzL6VGQvnwawLZ1wceZMI8HwJAT4/I2uAzbBeFDdmCS8WsTpSWLZjF/DszDc1D8BFp4pVJ5UQ==", + "license": "MIT" + }, + "node_modules/@pixi/core": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/core/-/core-7.4.3.tgz", + "integrity": "sha512-5YDs11faWgVVTL8VZtLU05/Fl47vaP5Tnsbf+y/WRR0VSW3KhRRGTBU1J3Gdc2xEWbJhUK07KGP7eSZpvtPVgA==", + "license": "MIT", + "dependencies": { + "@pixi/color": "7.4.3", + "@pixi/constants": "7.4.3", + "@pixi/extensions": "7.4.3", + "@pixi/math": "7.4.3", + "@pixi/runner": "7.4.3", + "@pixi/settings": "7.4.3", + "@pixi/ticker": "7.4.3", + "@pixi/utils": "7.4.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/pixijs" + } + }, + "node_modules/@pixi/display": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/display/-/display-7.4.3.tgz", + "integrity": "sha512-b5m2dAaoNAVdxz1oDaxl3XZ059NEOcNtGkxTOZ4EYCw/jcp9sZXkgSROHRzsGn4k+NugH7+9MP4Id2Z0kkdUhw==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/events": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/events/-/events-7.4.3.tgz", + "integrity": "sha512-o3j/5Dxq6WDVS6eHfURB/cf/MP+NcsF/eC5PnbSHjXxJmDE7PoTVwLvxexm5uuvNRpFh/6/Fn0V8Vl4gV8sc8w==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/extensions": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.4.3.tgz", + "integrity": "sha512-FhoiYkHQEDYHUE7wXhqfsTRz6KxLXjuMbSiAwnLb9uG1vAgp6q6qd6HEsf4X30YaZbLFY8a4KY6hFZWjF+4Fdw==", + "license": "MIT" + }, + "node_modules/@pixi/extract": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/extract/-/extract-7.4.3.tgz", + "integrity": "sha512-HNvGNrEVaeVsbcnIO1MsHpjZbTwo9nIlaOEBzDGcL6JWwzuB1RnzUke7WUCndCUt91sGUdvPnvgCvy9/NNFg3w==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-alpha": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-7.4.3.tgz", + "integrity": "sha512-YFdUB1I53USQb+9TEhS849dV2KZhbnNGIoBbOSThUJfXQc4pDguIFWMagVToAQYgmZ4C4AtYfVjaSEELrMcCdA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-blur": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-blur/-/filter-blur-7.4.3.tgz", + "integrity": "sha512-ZFzS9L/whdRbs5A/EUgF3yQaBcxNarmbuwaMgrfnpQ84mRczkGByqDLGToadiufyals07ufTrXBGRle9lbtEDA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-color-matrix": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-matrix/-/filter-color-matrix-7.4.3.tgz", + "integrity": "sha512-TNu0h20SrzjUWIb5v19dAp1vPpqtG0w2XF9kIHN91bMNaf3R1jzhpWG6TtaVO9eo1IolWcEJLw38jIohyC+KNw==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-displacement": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-displacement/-/filter-displacement-7.4.3.tgz", + "integrity": "sha512-ax+cFA2mEnKgqf9F8qInpv09GNWzjwnASLETpwPXzWBtlAlNCeHV2tCv3+SlMdEKUkwG9sA7AmjjjC2JBUyt+Q==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-fxaa": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-fxaa/-/filter-fxaa-7.4.3.tgz", + "integrity": "sha512-y9jhho5cCflhEsPtNqqsd+XJHsb+/ysht4rG/VHQ8Z6pScHYpbgiEpowryGq8uSMQQwx6zKNS2DPiXdiOHPZsg==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-noise": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-noise/-/filter-noise-7.4.3.tgz", + "integrity": "sha512-rwgSO3BKe1jW/P5CaOcfLKjfpl674aBEo/igi/3QLxA3ORhILNqWRsKkOwP8xF/ejI5NE4rMEkdv0LScbdGFhA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/graphics": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/graphics/-/graphics-7.4.3.tgz", + "integrity": "sha512-wWLivD8/URb8A7X4TqCZGG39C91IE+aOuWY/z9NCz5Z6WvA/VWnsc5fLTlO+ggjGHgKF0cSucCXZfUe1wm0AOQ==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/math": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.4.3.tgz", + "integrity": "sha512-/uJOVhR2DOZ+zgdI6Bs/CwcXT4bNRKsS+TqX3ekRIxPCwaLra+Qdm7aDxT5cTToDzdxbKL5+rwiLu3Y1egILDw==", + "license": "MIT" + }, + "node_modules/@pixi/mesh": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/mesh/-/mesh-7.4.3.tgz", + "integrity": "sha512-CikqFPtKvU3Zj986/MSoC8X39CWv5CEpiEW/tYp47p4tgQNDSkNWYnDiNYgb+4VX6pNsBrgX4DALLdTR17SlSA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/mesh-extras": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/mesh-extras/-/mesh-extras-7.4.3.tgz", + "integrity": "sha512-EqpxpVZoTObyupxMSzuUsCGmWPQioW84n9EO9Ajawkk/HYA+qKFZ5viKiEThIUBYgv4Apn/7c0U3Feg7Ez4uQQ==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/mesh": "7.4.3" + } + }, + "node_modules/@pixi/mixin-cache-as-bitmap": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/mixin-cache-as-bitmap/-/mixin-cache-as-bitmap-7.4.3.tgz", + "integrity": "sha512-NgvDdgSgd2tfcTSc+SWF12JJjVVz5ZrkSlhX0idSp/LSako82AiFJlD2xqH9GUsEcA6sqBBlnu7nrGkPTHQdhA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/mixin-get-child-by-name": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/mixin-get-child-by-name/-/mixin-get-child-by-name-7.4.3.tgz", + "integrity": "sha512-HLhDxHwafQT+CxbqQx9w9ivJIyAOg9JJ/6m4fNymVuDWeuMGcxDxBD7DukdUYIieT+RD/RlxdPEmq8YoromlFA==", + "license": "MIT", + "peerDependencies": { + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/mixin-get-global-position": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/mixin-get-global-position/-/mixin-get-global-position-7.4.3.tgz", + "integrity": "sha512-k09kvkS379EypCIWgXMY7uiXtWk1BsaJyTYlV16Co0AsmNPdFd+wUozMx1xV6rxcGiWXsxr/1k9fbETuYkcXCQ==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/particle-container": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/particle-container/-/particle-container-7.4.3.tgz", + "integrity": "sha512-0DfJF5C0XTfuI2FsLYvMKCOtqWjXWGOWfA6m4l0W/Ke/qw5zKIOEhgjPLw4qNRtOhmEfkVKJUGp66Ap/ya2YzA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/prepare": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/prepare/-/prepare-7.4.3.tgz", + "integrity": "sha512-OjJHGKXPzwP5OLKxBnTBnKMOktHynLvO0TQPqTYgNtmGQzY109mypCqM4M+s/V+uYmBo/T+sXvBahj98q/f1tA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/graphics": "7.4.3", + "@pixi/text": "7.4.3" + } + }, + "node_modules/@pixi/runner": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-7.4.3.tgz", + "integrity": "sha512-TJyfp7y23u5vvRAyYhVSa7ytq0PdKSvPLXu4G3meoFh1oxTLHH6g/RIzLuxUAThPG2z7ftthuW3qWq6dRV+dhw==", + "license": "MIT" + }, + "node_modules/@pixi/settings": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-7.4.3.tgz", + "integrity": "sha512-SmGK8smc0PxRB9nr0UJioEtE9hl4gvj9OedCvZx3bxBwA3omA5BmP3CyhQfN8XJ29+o2OUL01r3zAPVol4l4lA==", + "license": "MIT", + "dependencies": { + "@pixi/constants": "7.4.3", + "@types/css-font-loading-module": "^0.0.12", + "ismobilejs": "^1.1.0" + } + }, + "node_modules/@pixi/sprite": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/sprite/-/sprite-7.4.3.tgz", + "integrity": "sha512-iNBrpOFF9nXDT6m2jcyYy6l/sRzklLDDck1eFHprHZwvNquY2nzRfh+RGBCecxhBcijiLJ3fsZN33fP0LDXkvw==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/sprite-animated": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/sprite-animated/-/sprite-animated-7.4.3.tgz", + "integrity": "sha512-mw5YIec8KfO1Jv9qrDNvGoD7Dlmcgww5YlMtd+ARi7Zzo+6ziNw899LXtoaKX1+3BXdZbYNyJAx3C5r30NtwXA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/sprite-tiling": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/sprite-tiling/-/sprite-tiling-7.4.3.tgz", + "integrity": "sha512-kUa9cEcMsGXSIZoXA7LhW4oo0eWa30w0KYd7mZ0bqalBMfOcvsGZMN701Lc5lpE8URw+8yu5bnyGLbrxhWBTuw==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/spritesheet": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/spritesheet/-/spritesheet-7.4.3.tgz", + "integrity": "sha512-Ce4xZzUxUSKfiROUjjVCBYNLuCcDEWKJ822bSV9rkgVHItu3q04VnEww0DXO+9K0hKv4Ukjjk8aP6Pz0LgPm7A==", + "license": "MIT", + "peerDependencies": { + "@pixi/assets": "7.4.3", + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/text": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/text/-/text-7.4.3.tgz", + "integrity": "sha512-IAF0iu04rPg3oiL0HZsEZI44fpJxq3UZ4xTmx8l1RyhhSXiElLvvSlSH57vt/BKMQZtCs+AqEit7yn8heK2+nQ==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/text-bitmap": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/text-bitmap/-/text-bitmap-7.4.3.tgz", + "integrity": "sha512-TnBocJm7f5nMAYwYcsojc62uCrOYauAGH26o3pNrlqmHDRDQ7FzPOGvkYZGYFREbUycloLSRlYpSy0FB9ZdV4Q==", + "license": "MIT", + "peerDependencies": { + "@pixi/assets": "7.4.3", + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/mesh": "7.4.3", + "@pixi/text": "7.4.3" + } + }, + "node_modules/@pixi/text-html": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/text-html/-/text-html-7.4.3.tgz", + "integrity": "sha512-nm9K9gjSZAU8ETwQZBE3kMGNdO1IzyghxoRTcJCWKhekiGDpUQhopfNhqieNZ7reVJpvhpFQWjbyaHDehndUaQ==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/sprite": "7.4.3", + "@pixi/text": "7.4.3" + } + }, + "node_modules/@pixi/ticker": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.4.3.tgz", + "integrity": "sha512-tHsAD0iOUb6QSGGw+c8cyRBvxsq/NlfzIFBZLEHhWZ+Bx4a0MmXup6I/yJDGmyPCYE+ctCcAfY13wKAzdiVFgQ==", + "license": "MIT", + "dependencies": { + "@pixi/extensions": "7.4.3", + "@pixi/settings": "7.4.3", + "@pixi/utils": "7.4.3" + } + }, + "node_modules/@pixi/utils": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-7.4.3.tgz", + "integrity": "sha512-NO3Y9HAn2UKS1YdxffqsPp+kDpVm8XWvkZcS/E+rBzY9VTLnNOI7cawSRm+dacdET3a8Jad3aDKEDZ0HmAqAFA==", + "license": "MIT", + "dependencies": { + "@pixi/color": "7.4.3", + "@pixi/constants": "7.4.3", + "@pixi/settings": "7.4.3", + "@types/earcut": "^2.1.0", + "earcut": "^2.2.4", + "eventemitter3": "^4.0.0", + "url": "^0.11.0" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-3.2.1.tgz", + "integrity": "sha512-KDz+3qDRdBAlRlMjmKyj6dEs33YHTk/xRHEENSXq6TNnhgoU15ruSHtEBeVF6OZ9tBDY55Se4P0nFMNsipzU9A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "modern-tar": "^0.8.0", + "yargs": "^18.0.0" + }, + "bin": { + "browsers": "lib/main-cli.js" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "proxy-agent": ">=8.0.1", + "yauzl": "^2.10.0 || ^3.4.0" + }, + "peerDependenciesMeta": { + "proxy-agent": { + "optional": true + }, + "yauzl": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.0.tgz", + "integrity": "sha512-70TeIFezKKy65LgAVyQh+w94/gjWhvPWaLaGGeMEgVrPkQhuj/M5bAYYZzIFUj9Y69oHyTm5Um/R6gcLh4A8JA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.0.tgz", + "integrity": "sha512-YC86tYIHK6M1IV+wbzO+Bxk8RCBr6ZyWYgWxUCzaZD8mc8rrFoIJDNzDrkHBYRc/wKdrsIXmm6/F7NzrAO+OrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.0.tgz", + "integrity": "sha512-oI+ECtUcli0y0fi4xpW82GdPIXdTkI8G8DSjG2LRuw09fPAGykaWYH/hXxiKuTxiAjiPSTIIuYUqof5Z2hShWw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.0.tgz", + "integrity": "sha512-NwV+1s7TiKrMe4owHyKB/dTLD7ZJD0YEBEhIz+hvav1Cu1GReJjF+rsdNwjzENQeIAbE/CoNiaAc5Vz2h5DPAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.0.tgz", + "integrity": "sha512-tWtHBTu5gOPK4u4Urtk4qAHW3zZ9rQAmbssO8gp7ELvGTGI3aCiq6NqyTQ0PCIg7KbHJF2UkGDDs77YZGxfjCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.0.tgz", + "integrity": "sha512-2qPoJiwTvtHQ27NnYvTnsgk8laXWYuVmNESG8WFZBcEPKLfZ3I27qBJarjVRQtwGeYyRfq5ZowHXih9lm2BItw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.0.tgz", + "integrity": "sha512-FQwsTRvLNuHoTdICABJQfbPUSEueISGmnpT06tXTMpfprf5NiKLSXKA0A+w45wJnCmZAnzgqBwbt6ARFuyOi5w==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.0.tgz", + "integrity": "sha512-BBVTXziw8mY1a4ZbWME9tZyfzqXCDPqaC7Z3heQ29p5dkvXzwL0NwelO8zLa8c3RBKvl3YTuSnBgsBhYBtwjIw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.0.tgz", + "integrity": "sha512-w2Iyy9+RqKwx3d9qWMKsJg0FfRBsY0/pXNv0mCQ3ueRvJI6+QAScfD4nrMlzFLs2HNVW6Ew+mtZfDl9b7Ew5/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.0.tgz", + "integrity": "sha512-YK++KtrFRHYE0P6/RtYEAy9t8F37znP+K03RrIuLPYOL6SVlObRumf/0OE4V/h63xL9DwkWbNssZfmA9hawuDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.0.tgz", + "integrity": "sha512-aBfOG6fP7YkkPmTqPwufRJeFyz7WPpECv9XNbnsk9+vg7rxdih0lbtEel7jcRng4LZrrmU3FfitCFyEj4BWDWg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.0.tgz", + "integrity": "sha512-LGaHEOeHNAag9VuS1Crs5DFg4RrU9MPi2nVnNJk9DTePx/B6RRYKVmrIXt2h7YOJlwjaFJ6lwtFDliZxScTLrQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.0.tgz", + "integrity": "sha512-jClvk+J0FC3b7Udvegiw5/4hErbHtmsNsQgENnKXDWtNCJXsJYZH5WURvu7imDOO38xYml24eeh5x3A04ppwCw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.0.tgz", + "integrity": "sha512-0OJlaGK+8+B777Ql5okIpD7ua5Ro9+VB9Ve0OKa28OQJZ1RbuUBVNHK/e3pr4BROqsyPl1JrPO1ZxJseCNffcA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.0.tgz", + "integrity": "sha512-Ygsx+HoNH7afwi1bTIXbnTvVnsO+zurPLSYxybV1hHFVU72OWOCl6v05ql/z0hkpAPx+DK7Kn9Bi7MayCcjLTA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.0.tgz", + "integrity": "sha512-pDQxtMGb+OvG3fLwR2OkZlSd47hW+kWg4BYMG/++sR6RqorQccwPTDsxda5hPwiIeIErAnCF9ma3SAU06bdQtQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.0.tgz", + "integrity": "sha512-0BnUG9mS8I4SSHr3XsxVhuCMEiu+rX61xxZF5vujso4LaiAGFZFxvDjg6Xn6tLPNTUAfuCvQYas4LMQMVsKRSQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.0.tgz", + "integrity": "sha512-Adu/VttB1dpPNW+FEacrZ+xVm9tFty84+RrFzsqlFaPxoJB+9XXyDGtp5dCOoBwGBIEVH0To7lExFXEx0BIF4A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.0.tgz", + "integrity": "sha512-NQ3bDvjUbFKmP23671xUlXtKmqVsUBd6M4PQCvbmNtOy06hnQIdKHy8oG/6S3R/S6He1JgPk6A5VT+prAJMYEw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.0.tgz", + "integrity": "sha512-u2eDAl4+0aFvA13GxlGBtTI3SS3sdgwgtV0HyjZ0QaQVCgNE+jqNGey+GtxWiq+wxr/UycAx/OnfJzApCFamvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.0.tgz", + "integrity": "sha512-XvRb5vfW3wAZQ+ZUG21AnHHDKtNcw99eigzEhjr//NZ3u7SoBaPP0seSc7FgP7p1epAEdAoZckMW9WY/+4w70w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.0.tgz", + "integrity": "sha512-iZPmniy4kNBf5yo2RezbkYNNK5HPbXE9+g+twnbqSng7dtLEJy1SKoxiE/ni4FDacjyuZpEeb9U054N4EoKHYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.0.tgz", + "integrity": "sha512-mFBBd+LF37fnE8JnYUOH+imj0aPFPK30vpar4ehJkgnLj9sZn8ZxiRENmLtgIwxK7TC8klF6N57fxdNBwQoqOA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.0.tgz", + "integrity": "sha512-ujeqEY3B+zbGn3Z4Q03cUBG/LGWnBJncVT36WER31LcOsQk9+1dmINKKtvmmfChUvRbK1G0R8OhMWFgHgaZtAw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.0.tgz", + "integrity": "sha512-hncn90N4sOky0L2LKE5oESKLbxCPeVo4eLA2LSMoDzM+879ml4WSr+Rr4DWknNIVVvS1Hirkc9hx02W6YxS8rQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/css-font-loading-module": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz", + "integrity": "sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==", + "license": "MIT" + }, + "node_modules/@types/earcut": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.4.tgz", + "integrity": "sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.15" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.41.tgz", + "integrity": "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.41", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.41.tgz", + "integrity": "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.41.tgz", + "integrity": "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.41", + "@vue/compiler-dom": "3.5.41", + "@vue/compiler-ssr": "3.5.41", + "@vue/shared": "3.5.41", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.41.tgz", + "integrity": "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.2.1.tgz", + "integrity": "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/devtools-kit": "^8.2.1" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.2.1.tgz", + "integrity": "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/devtools-shared": "^8.2.1", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.2.1.tgz", + "integrity": "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==", + "license": "MIT", + "peer": true + }, + "node_modules/@vue/language-core": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.41.tgz", + "integrity": "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.41.tgz", + "integrity": "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.41.tgz", + "integrity": "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.41", + "@vue/runtime-core": "3.5.41", + "@vue/shared": "3.5.41", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.41.tgz", + "integrity": "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.41", + "@vue/runtime-dom": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz", + "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", + "license": "MIT" + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chromium-bidi": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-17.0.2.tgz", + "integrity": "sha512-5v9GQFhTktFvotn/OFNJBmKLKRAb6n9r0bVCwf7sHgWc3/JryK0bj1nn93L3pHFrfgcsu6Be6EWsDi+1XHTGDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/devtools-protocol": { + "version": "0.0.1666840", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1666840.tgz", + "integrity": "sha512-gCcO42XCHKEs7Ag0S7aGYsnJ7hlgrO3qderYqeiY0Eqk+0GFfuvT13IA0hHreJTa2KCdDVyGMeOhdMNmrrTjVg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT", + "peer": true + }, + "node_modules/ismobilejs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz", + "integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/modern-tar": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.8.4.tgz", + "integrity": "sha512-gN54ddmyzEg10orwZ2u4OOv+bjpMWdIl5jIkodK97bMq8QBSL5c0D7YX0lT1Ooz+99S7+PvFbnxzdjgHo1r41g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nostics": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/nostics/-/nostics-1.2.0.tgz", + "integrity": "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==", + "license": "MIT" + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "license": "MIT", + "peer": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-4.0.3.tgz", + "integrity": "sha512-XMQqpvjgG7LMqVhhFUzKLT4KEbsYbfZZ0CZU9PgdFm1O2VKBmIkykL8+SfNhOaT7sR0wT6BEgKT0YNDYWgmVRA==", + "license": "MIT", + "dependencies": { + "nostics": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@vue/devtools-api": "^8.1.5", + "typescript": ">=5.6.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "@vue/devtools-api": { + "optional": false + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/pixi.js": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-7.4.3.tgz", + "integrity": "sha512-uIWdH0EI2dVgNoqN9aFaHCmR0V65OEhMkXs2sek3c/QP2ItV6UoM+ouX9esSv3ibo20F+J5D1XwnQhUZI6wqeQ==", + "license": "MIT", + "dependencies": { + "@pixi/accessibility": "7.4.3", + "@pixi/app": "7.4.3", + "@pixi/assets": "7.4.3", + "@pixi/compressed-textures": "7.4.3", + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/events": "7.4.3", + "@pixi/extensions": "7.4.3", + "@pixi/extract": "7.4.3", + "@pixi/filter-alpha": "7.4.3", + "@pixi/filter-blur": "7.4.3", + "@pixi/filter-color-matrix": "7.4.3", + "@pixi/filter-displacement": "7.4.3", + "@pixi/filter-fxaa": "7.4.3", + "@pixi/filter-noise": "7.4.3", + "@pixi/graphics": "7.4.3", + "@pixi/mesh": "7.4.3", + "@pixi/mesh-extras": "7.4.3", + "@pixi/mixin-cache-as-bitmap": "7.4.3", + "@pixi/mixin-get-child-by-name": "7.4.3", + "@pixi/mixin-get-global-position": "7.4.3", + "@pixi/particle-container": "7.4.3", + "@pixi/prepare": "7.4.3", + "@pixi/sprite": "7.4.3", + "@pixi/sprite-animated": "7.4.3", + "@pixi/sprite-tiling": "7.4.3", + "@pixi/spritesheet": "7.4.3", + "@pixi/text": "7.4.3", + "@pixi/text-bitmap": "7.4.3", + "@pixi/text-html": "7.4.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/pixijs" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT" + }, + "node_modules/puppeteer-core": { + "version": "25.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.9.0.tgz", + "integrity": "sha512-U61rCwSMha62CA/Opy6tCx2Fx+ck7ouiKnbpEApzSoLYMoEu9F71nuFpHL55vmIt33/GYm6eKZVhH2ev0nAIeg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "3.2.1", + "chromium-bidi": "17.0.2", + "devtools-protocol": "0.0.1666840", + "typed-query-selector": "^2.12.2", + "webdriver-bidi-protocol": "0.4.2", + "ws": "^8.21.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.0.tgz", + "integrity": "sha512-T5vnZ2y4QqC3/4P+w2+JO+Q/OVdnPsv4XcSYJYMEn0R9/jjl5AgLwO9LAZMzP2lN71O6pypn91rB7lDstUkfrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.0", + "@rollup/rollup-android-arm64": "4.63.0", + "@rollup/rollup-darwin-arm64": "4.63.0", + "@rollup/rollup-darwin-x64": "4.63.0", + "@rollup/rollup-freebsd-arm64": "4.63.0", + "@rollup/rollup-freebsd-x64": "4.63.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.0", + "@rollup/rollup-linux-arm-musleabihf": "4.63.0", + "@rollup/rollup-linux-arm64-gnu": "4.63.0", + "@rollup/rollup-linux-arm64-musl": "4.63.0", + "@rollup/rollup-linux-loong64-gnu": "4.63.0", + "@rollup/rollup-linux-loong64-musl": "4.63.0", + "@rollup/rollup-linux-ppc64-gnu": "4.63.0", + "@rollup/rollup-linux-ppc64-musl": "4.63.0", + "@rollup/rollup-linux-riscv64-gnu": "4.63.0", + "@rollup/rollup-linux-riscv64-musl": "4.63.0", + "@rollup/rollup-linux-s390x-gnu": "4.63.0", + "@rollup/rollup-linux-x64-gnu": "4.63.0", + "@rollup/rollup-linux-x64-musl": "4.63.0", + "@rollup/rollup-openbsd-x64": "4.63.0", + "@rollup/rollup-openharmony-arm64": "4.63.0", + "@rollup/rollup-win32-arm64-msvc": "4.63.0", + "@rollup/rollup-win32-ia32-msvc": "4.63.0", + "@rollup/rollup-win32-x64-gnu": "4.63.0", + "@rollup/rollup-win32-x64-msvc": "4.63.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typed-query-selector": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", + "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/url": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "license": "MIT", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.12.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.2.0.tgz", + "integrity": "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.41.tgz", + "integrity": "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.41", + "@vue/compiler-sfc": "3.5.41", + "@vue/runtime-dom": "3.5.41", + "@vue/server-renderer": "3.5.41", + "@vue/shared": "3.5.41" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-tsc": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.2.tgz", + "integrity": "sha512-VSV+fzfChirL3e7jay2yUC7B4HQCGtEWEg/MSSQbK+qWbqeGlRLlXTzPpYr3XGUvbpDHumWZBJxgesg4N7dbtA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", + "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..71d4125 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "spine-particle", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "pinia": "^4.0.3", + "pixi.js": "^7.4.3", + "vue": "^3.5.13" + }, + "devDependencies": { + "@types/node": "^20.19.43", + "@vitejs/plugin-vue": "^5.2.1", + "puppeteer-core": "^25.9.0", + "typescript": "^5.7.2", + "vite": "^6.0.5", + "vue-tsc": "^2.2.0" + } +} diff --git a/public/particles/dot.png b/public/particles/dot.png new file mode 100644 index 0000000000000000000000000000000000000000..3834067b2e85cb9604614f679a416eefcaa2dc20 GIT binary patch literal 3127 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 +
+ +
+ +
+
+ + +
+
+
+
+ +
+
+
+ + + + + diff --git a/src/core/particleEmitter.ts b/src/core/particleEmitter.ts new file mode 100644 index 0000000..714ee27 --- /dev/null +++ b/src/core/particleEmitter.ts @@ -0,0 +1,465 @@ +import { Container, Sprite, Texture, IPointData } from 'pixi.js' + +/** 发射模式 */ +export type EmitMode = 'stream' | 'burst' +/** 发射形状 */ +export type EmitShape = 'point' | 'circle' | 'rect' | 'cone' | 'orbit' +/** 混合模式 */ +export type Blend = 'normal' | 'add' | 'multiply' | 'screen' + +/** 粒子配置(引用保持,面板改字段即实时生效) */ +export interface EmitterConfig { + texture: Texture + name: string + // 发射 + mode: EmitMode + rate: number // 持续:每秒发射数 + burstCount: number // 爆发:单次数量 + burstLoop: boolean // 爆发是否每秒重复 + /** 延迟发射(秒)= 粒子条左端位置 */ + delay: number + /** 发射总时长(秒)= 粒子条宽度,超过后停止发射新粒子 */ + duration: number + shape: EmitShape // 发射形状 + radius: number + rectW: number + rectH: number + /** 发射器中心 X/Y */ + centerX: number + centerY: number + // 根节点(root)变换:作用于整个粒子系统(旋转/缩放),与场景对象参数联动 + rootRotation: number + rootScaleX: number + rootScaleY: number + // 方向 + direction: number + spread: number + /** 锥形开口角度(发电器形状=锥体时,粒子在锥形扇区内均匀散开) */ + coneAngle: number + // 属性 + lifeMin: number + lifeMax: number + speedMin: number + speedMax: number + scaleMin: number + scaleMax: number + scaleEndRatio: number + alpha: number + alphaEnd: number + rotationSpeedMin: number + rotationSpeedMax: number + // 物理 + gravity: number + /** 阻尼(速度衰减系数,0=无) */ + damping: number + /** 随机种子(确定性随机,0=每次不同) */ + seed: number + // 颜色 + colorStart: string + colorEnd: string + colorOverLife: boolean + // 混合 + blend: Blend + /** 透明度模式:fixed(固定)/ curve(曲线) */ + alphaMode: 'fixed' | 'curve' + /** 曲线模式(透明度插值方式) */ + curveMode: '跟随导出' | '线性' | '贝塞尔' + /** 透明度曲线关键帧:x=生命周期时刻(0~1),y=透明度(0~1,1=完全不透明) */ + alphaCurve: { x: number; y: number }[] + /** 图片名称(贴图资源名,默认 dot) */ + textureName: string + /** 子文件夹(图片资源路径,占位) */ + textureFolder: string + /** 占比(0~1) */ + alphaScale: number + /** 贴图锚点/轴心 x,y(0~1) */ + anchorX: number + anchorY: number + /** 独立透明度:off / fixed / curve */ + independentAlpha: 'off' | 'fixed' | 'curve' + // 发射点偏移 + offsetX: number + offsetY: number + // ===== 修改器 (Modifiers) ===== + /** 开启移动噪声 */ + noise: boolean + noiseAmt: number + /** 开启重力(由 gravity 控制) */ + useGravity: boolean + /** 开启风力 */ + wind: boolean + windX: number + windY: number + /** 开启力场(吸力/斥力) */ + forceField: boolean + forceStrength: number + forceRadius: number + /** 开启拖尾 */ + trail: boolean + /** 开启碰撞(预留) */ + collision: boolean + /** 开启路径跟随(预留) */ + pathFollow: boolean +} + +/** 粒子瞬间状态(供骨骼同步 / 导出) */ +export interface ParticleState { + /** 对应骨骼在 root 下的命名 p_ */ + boneName: string + x: number + y: number + rotation: number + scaleX: number + scaleY: number + alpha: number + colorHex: number + active: boolean +} + +export function defaultConfig(): EmitterConfig { + return { + texture: null as unknown as Texture, + name: 'ParticleSystem1', + mode: 'stream', + rate: 20, + burstCount: 50, + burstLoop: false, // 一次性爆发(持续爆发开关已移除) + delay: 0, + duration: 0, // 0 = 无限发射/爆发,不受时长限制(发射时长 UI 已移除) + shape: 'point', + radius: 30, + rectW: 60, + rectH: 40, + centerX: 0, + centerY: 0, + rootRotation: 0, + rootScaleX: 1, + rootScaleY: 1, + direction: 0, + spread: 360, + coneAngle: 60, + lifeMin: 0.6, + lifeMax: 1.8, + speedMin: 30, + speedMax: 140, + scaleMin: 0.3, + scaleMax: 0.8, + scaleEndRatio: 0.3, + alpha: 0.9, + alphaEnd: 0, + rotationSpeedMin: -180, + rotationSpeedMax: 180, + gravity: 0, + damping: 0, + seed: 0, + colorStart: '#ffffff', + colorEnd: '#ff4d4d', + colorOverLife: false, + blend: 'add', + alphaMode: 'curve', + curveMode: '线性', + alphaCurve: [{ x: 0, y: 0 }, { x: 0.2, y: 1 }, { x: 0.8, y: 1 }, { x: 1, y: 0 }], + textureName: 'star', + textureFolder: '', + alphaScale: 1, + anchorX: 0.5, + anchorY: 0.5, + independentAlpha: 'off', + offsetX: 0, + offsetY: 0, + noise: false, + noiseAmt: 0, + useGravity: true, + wind: false, + windX: 0, + windY: 0, + forceField: false, + forceStrength: 0, + forceRadius: 100, + trail: false, + collision: false, + pathFollow: false, + } +} + +interface Particle { + sprite: Sprite + active: boolean + boneName: string + life: number + maxLife: number + x: number; y: number + vx: number; vy: number + scaleStart: number; scaleEnd: number + alphaStart: number; alphaEnd: number + rotation: number; rotationSpeed: number +} + +const BLEND_MAP: Record = { normal: 0, add: 1, multiply: 2, screen: 3 } + +const MAX_PARTICLES = 400 + +export class ParticleEmitter extends Container { + private pool: Particle[] = [] + private cfg: EmitterConfig + private accumulator = 0 + private burstTimer = 0 + /** 发射器累计运行时间(秒) — 非响应式,避免污染 config */ + private _elapsed = 0 + private _emitterPos: (() => IPointData) | null = null // root 世界坐标(0,0) + /** 活动粒子状态快照(供骨架 / 导出) */ + states: ParticleState[] = [] + /** 确定性随机(种子>0 时可复现) */ + private rng: () => number = Math.random + private _lastSeed = -1 + + constructor(config: EmitterConfig, maxParticles = MAX_PARTICLES) { + super() + this.cfg = config + this._ensurePool(maxParticles) + } + + /** 仅在种子变化时重建随机源;种子不变时让序列持续推进,保证"确定性随机序列"语义 */ + private reseed() { + const s = this.cfg.seed + if (s === this._lastSeed) return + this._lastSeed = s + if (s > 0) { + this.rng = mulberry32(s >>> 0) + } else { + this.rng = Math.random + } + } + + private _ensurePool(n: number) { + while (this.pool.length < n) { + const sprite = new Sprite(this.cfg.texture) + sprite.blendMode = BLEND_MAP[this.cfg.blend] as any + sprite.anchor.set(0.5) + sprite.visible = false + this.addChild(sprite) + this.pool.push({ + sprite, active: false, boneName: 'p_' + this.pool.length, + life: 0, maxLife: 1, x: 0, y: 0, vx: 0, vy: 0, + scaleStart: 1, scaleEnd: 1, alphaStart: 1, alphaEnd: 1, rotation: 0, rotationSpeed: 0, + }) + } + } + + setEmitterPos(fn: () => IPointData) { this._emitterPos = fn } + + /** 每帧更新;返回活动粒子状态快照(与骨骼一一对应) */ + update(dt: number): ParticleState[] { + const cfg = this.cfg + this.reseed() // 种子变化即重建随机源 + // 累计发射时间 + this._elapsed += dt + const inWindow = cfg.delay <= 0 || this._elapsed >= cfg.delay + const withinDuration = cfg.duration <= 0 || this._elapsed < cfg.delay + cfg.duration + // 发射(仅在窗口内且未超时长) + if (inWindow && withinDuration) { + if (cfg.mode === 'stream') { + this.accumulator += dt * cfg.rate + while (this.accumulator >= 1) { this.accumulator -= 1; this.spawnOne() } + } else { + this.burstTimer += dt + if (cfg.burstLoop) { + if (this.burstTimer >= 1) { this.burstTimer = 0; this.emitBurst() } + } else if (this.burstTimer <= dt) { + this.emitBurst() + } + } + } + + const base = this._emitterPos ? this._emitterPos() : { x: this.cfg.offsetX, y: this.cfg.offsetY } + const states: ParticleState[] = [] + for (let i = 0; i < this.pool.length; i++) { + const p = this.pool[i] + if (!p.active) continue + p.life -= dt + if (p.life <= 0) { this.kill(i); continue } + if (cfg.useGravity) p.vy += cfg.gravity * dt + // 风力 + if (cfg.wind) { p.vx += cfg.windX * dt; p.vy += cfg.windY * dt } + // 力场(吸力/斥力,作用在发射点附近) + if (cfg.forceField && cfg.forceStrength !== 0) { + const dx = base.x - p.x, dy = base.y - p.y + const dist = Math.hypot(dx, dy) || 0.0001 + if (dist < cfg.forceRadius) { + const f = cfg.forceStrength * (1 - dist / cfg.forceRadius) + p.vx += (dx / dist) * f * dt * 10 + p.vy += (dy / dist) * f * dt * 10 + } + } + // 阻尼:速度按衰减系数逐渐减小 + if (cfg.damping > 0) { + const f = Math.max(0, 1 - cfg.damping * dt) + p.vx *= f; p.vy *= f + } + p.x += p.vx * dt + p.y += p.vy * dt + p.rotation += p.rotationSpeed * dt + const t = 1 - p.life / p.maxLife + const s = p.sprite + s.x = p.x; s.y = p.y; s.rotation = -p.rotation * Math.PI / 180 + s.scale.set(p.scaleStart + (p.scaleEnd - p.scaleStart) * t) + s.alpha = cfg.alphaMode === 'curve' ? curveAt(cfg.alphaCurve, t) * cfg.alphaScale : (p.alphaStart + (p.alphaEnd - p.alphaStart) * t) * cfg.alphaScale + const rgb = cfg.colorOverLife ? blendRgb(cfg.colorStart, cfg.colorEnd, t) : hexToNumber(cfg.colorStart) + s.tint = rgb + states.push({ boneName: p.boneName, x: p.x, y: p.y, rotation: p.rotation, scaleX: s.scale.x, scaleY: s.scale.y, alpha: s.alpha, colorHex: rgb, active: true }) + } + this.states = states + return states + } + + private emitBurst() { + for (let i = 0; i < this.cfg.burstCount; i++) this.spawnOne() + } + + private spawnOrigin(): { x: number; y: number } { + const cfg = this.cfg + // 局部原点 = 0(root 中心,centerX 由发射器容器 position 补回);粒子相对 root 中心 = 偏移X/Y + let ox = cfg.offsetX, oy = cfg.offsetY + // 采用数学坐标系(Y 向上为正):world 为屏幕坐标(向下为正),故 y 取负 + oy = -oy + const s = cfg.shape + if (s === 'circle') { + const a = this.rng() * Math.PI * 2, r = this.rng() * cfg.radius + return { x: ox + Math.cos(a) * r, y: oy + Math.sin(a) * r } + } else if (s === 'rect') { + return { x: ox + (this.rng() - 0.5) * cfg.rectW, y: oy + (this.rng() - 0.5) * cfg.rectH } + } else if (s === 'cone') { + // 锥体:粒子从中心(锥形顶点)发射,方向由 spawnOne 在 [direction ± coneAngle/2] 扇形内随机 + return { x: ox, y: oy } + } + return { x: ox, y: oy } + } + + private spawnOne() { + const cfg = this.cfg + let idx = this.pool.findIndex((p) => !p.active) + if (idx === -1) return // 池满 + const { x: ox, y: oy } = this.spawnOrigin() + const life = lerp(cfg.lifeMin, cfg.lifeMax, this.rng()) + const speed = lerp(cfg.speedMin, cfg.speedMax, this.rng()) + // 锥体:发射方向用锥形开口角度(coneAngle)散布,且 y 朝数学上正(与扇区绘制一致);其余形状沿用 spread + 屏幕坐标 + let dirAng: number + if (cfg.shape === 'cone') { + dirAng = (cfg.direction + lerp(-cfg.coneAngle / 2, cfg.coneAngle / 2, this.rng())) * Math.PI / 180 + } else { + dirAng = (cfg.direction + lerp(-cfg.spread / 2, cfg.spread / 2, this.rng())) * Math.PI / 180 + } + const p = this.pool[idx] + p.active = true; p.life = life; p.maxLife = life + p.x = ox; p.y = oy + p.vx = Math.cos(dirAng) * speed + p.vy = (cfg.shape === 'cone' ? -Math.sin(dirAng) : Math.sin(dirAng)) * speed + p.scaleStart = lerp(cfg.scaleMin, cfg.scaleMax, this.rng()) + p.scaleEnd = p.scaleStart * cfg.scaleEndRatio + p.alphaStart = cfg.alpha; p.alphaEnd = cfg.alphaEnd + p.rotation = this.rng() * 360; p.rotationSpeed = lerp(cfg.rotationSpeedMin, cfg.rotationSpeedMax, this.rng()) + p.sprite.tint = hexToNumber(cfg.colorStart) + p.sprite.visible = true + } + + private kill(i: number) { + const p = this.pool[i] + p.active = false + p.sprite.visible = false + } + + clear() { + for (const p of this.pool) { p.active = false; p.sprite.visible = false } + } + + get activeCount() { + let n = 0 + for (const p of this.pool) if (p.active) n++ + return n + } + + /** 录制:返回当前活动粒子的深拷贝快照 */ + capture(): ParticleState[] { + const out: ParticleState[] = [] + for (const p of this.pool) { + if (!p.active) continue + out.push({ + boneName: p.boneName, x: p.x, y: p.y, rotation: p.rotation, + scaleX: p.sprite.scale.x, scaleY: p.sprite.scale.y, + alpha: p.sprite.alpha, colorHex: p.sprite.tint as number, active: true, + }) + } + return out + } + + /** 回放:把快照还原到 sprite(非活动粒子隐藏) */ + apply(states: ParticleState[]) { + const byName = new Map() + for (const s of states) byName.set(s.boneName, s) + for (const p of this.pool) { + const st = byName.get(p.boneName) + if (!st) { p.sprite.visible = false; continue } + const s = p.sprite + s.visible = true + s.x = st.x; s.y = st.y + s.rotation = -st.rotation * Math.PI / 180 + s.scale.set(st.scaleX, st.scaleY) + s.alpha = st.alpha + s.tint = st.colorHex + } + } + + /** 清空所有粒子并重置发射计时 */ + reset() { + for (const p of this.pool) { p.active = false; p.sprite.visible = false } + this.accumulator = 0 + this.burstTimer = 0 + this._elapsed = 0 + // 从头重新模拟:若种子>0,重设随机源到序列头,保证同一种子每次从头录制结果一致 + if (this.cfg.seed > 0) { this._lastSeed = this.cfg.seed; this.rng = mulberry32(this.cfg.seed >>> 0) } + } +} + +function lerp(a: number, b: number, t: number) { return a + (b - a) * t } +/** 在透明度曲线关键帧上按 x(生命周期时刻 0~1)求 y(透明度 0~1)。保证有序、端点外取端点 */ +function curveAt(pts: { x: number; y: number }[], x: number): number { + if (!pts || pts.length === 0) return 1 + const p = [...pts].sort((a, b) => a.x - b.x) + if (x <= p[0].x) return p[0].y + if (x >= p[p.length - 1].x) return p[p.length - 1].y + for (let i = 0; i < p.length - 1; i++) { + if (x >= p[i].x && x <= p[i + 1].x) { + const t = (x - p[i].x) / (p[i + 1].x - p[i].x || 1) + return lerp(p[i].y, p[i + 1].y, t) + } + } + return p[p.length - 1].y +} +/** 确定性伪随机(mulberry32),种子相同则序列相同 */ +function mulberry32(seed: number): () => number { + let a = seed >>> 0 + return function () { + a |= 0; a = (a + 0x6D2B79F5) | 0 + let t = Math.imul(a ^ (a >>> 15), 1 | a) + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t + return ((t ^ (t >>> 14)) >>> 0) / 4294967296 + } +} +function hexToNumber(hex: string): number { + let h = hex.replace('#', '') + if (h.length === 3) h = h.split('').map((c) => c + c).join('') + return parseInt(h, 16) +} +function blendRgb(start: string, end: string, t: number): number { + const s = hexToRgb(start), e = hexToRgb(end) + const r = Math.round(s[0] + (e[0] - s[0]) * t) + const g = Math.round(s[1] + (e[1] - s[1]) * t) + const b = Math.round(s[2] + (e[2] - s[2]) * t) + return (r << 16) | (g << 8) | b +} +function hexToRgb(hex: string): [number, number, number] { + let h = hex.replace('#', '') + if (h.length === 3) h = h.split('').map((c) => c + c).join('') + const n = parseInt(h, 16) + return [(n >> 16) & 255, (n >> 8) & 255, n & 255] +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..7bf151f --- /dev/null +++ b/src/main.ts @@ -0,0 +1,6 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import App from './App.vue' +import './style.css' + +createApp(App).use(createPinia()).mount('#app') diff --git a/src/store/particleStore.ts b/src/store/particleStore.ts new file mode 100644 index 0000000..881f6c6 --- /dev/null +++ b/src/store/particleStore.ts @@ -0,0 +1,114 @@ +import { defineStore } from 'pinia' +import { EmitterConfig, defaultConfig, ParticleState } from '../core/particleEmitter' + +export interface ParticleSystem { + id: number + config: EmitterConfig + /** 录制到的帧序列(每帧 = 该帧活动粒子快照) */ + frames?: ParticleState[][] +} + +export interface EditorState { + viewScale: number + viewX: number + viewY: number +} + +/** 系统设置(可通过右上角设置面板调整) */ +export interface SettingsState { + /** 是否启用刻度(主开关;启用=显示坐标轴+刻度线+数值,可调整字号/粗细) */ + tickEnabled: boolean + /** 坐标轴刻度文字大小(px),默认 12 */ + axisFontSize: number + /** 刻度线粗细(px) */ + tickWidth: number + /** 是否显示坐标轴刻度数值 */ + showAxisLabels: boolean + /** 坐标轴数值颜色(#rrggbb) */ + axisColor: string +} + +export interface TimelineState { + /** 播放模式 */ + playing: boolean + /** 循环 */ + loop: boolean + /** 当前帧 */ + frame: number + /** 总帧数 */ + totalFrames: number + /** 帧率 */ + fps: number + /** 是否已录制 */ + recorded: boolean + /** 动画名(暂用于显示) */ + animation: string +} + +let seq = 1 + +export const useParticleStore = defineStore('particle', { + state: () => ({ + editor: { viewScale: 1.0, viewX: 0, viewY: 0 } as EditorState, + systems: [] as ParticleSystem[], + activeId: 0, + timeline: { playing: true, loop: true, frame: 0, totalFrames: 120, fps: 60, recorded: false, animation: 'animation' } as TimelineState, + settings: { tickEnabled: false, axisFontSize: 12, tickWidth: 1, showAxisLabels: true, axisColor: '#7a86ad' } as SettingsState, + }), + getters: { + activeSystemCount: (s) => s.systems.length, + }, + actions: { + addSystem(initial?: Partial<{ config: Partial }>) { + const cfg = defaultConfig() + if (initial?.config) Object.assign(cfg, initial.config) + // 自动生成不重复名字:若"ParticleSystemN"已存在,取已有数字最大值+1 + const existing = new Set(this.systems.map((s) => s.config.name)) + if (existing.has(cfg.name)) { + let maxN = 0 + for (const s of this.systems) { + const m = /^ParticleSystem(\d+)$/.exec(s.config.name) + if (m) maxN = Math.max(maxN, parseInt(m[1], 10)) + } + cfg.name = `ParticleSystem${maxN + 1}` + let i = 2 + while (existing.has(cfg.name)) { cfg.name = `ParticleSystem${maxN + i}`; i++ } + } + const sys: ParticleSystem = { id: seq++, config: cfg } + this.systems.push(sys) + this.activeId = sys.id + return sys + }, + removeSystem(id: number) { + const i = this.systems.findIndex((s) => s.id === id) + if (i >= 0) this.systems.splice(i, 1) + if (this.activeId === id) this.activeId = this.systems[0]?.id ?? 0 + }, + selectSystem(id: number) { this.activeId = id }, + play() { this.timeline.playing = true }, + pause() { this.timeline.playing = false }, + togglePlay() { this.timeline.playing = !this.timeline.playing }, + toStart() { this.timeline.frame = 0 }, + toggleLoop() { this.timeline.loop = !this.timeline.loop }, + setFrame(f: number) { + this.timeline.frame = Math.max(0, Math.min(f, this.timeline.totalFrames - 1)) + }, + /** 依据所有粒子系统的"最晚消失时间"重算总帧数。 + * 单系统总时长 = delay(延迟) + duration(发射时长) + lifeMax(粒子最大生命), + * 多系统取最大;转帧 = ceil(sec * fps)。 */ + recalcTotalFrames() { + const fps = this.timeline.fps || 60 + let maxSec = 0 + for (const sys of this.systems) { + const c = sys.config + const end = (c.delay || 0) + (c.duration || 0) + (c.lifeMax || 0) + if (end > maxSec) maxSec = end + } + const frames = Math.max(1, Math.ceil(maxSec * fps)) + this.timeline.totalFrames = frames + // clamp 当前帧 + if (this.timeline.frame >= frames) this.timeline.frame = frames - 1 + return frames + }, + }, +}) diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..e80c9d1 --- /dev/null +++ b/src/style.css @@ -0,0 +1,9 @@ +html, body { + margin: 0; + padding: 0; + background: #0b0b12; + color: #e8e8f0; + font-family: system-ui, -apple-system, 'Segoe UI', sans-serif; +} +* { box-sizing: border-box; } +#app { width: 100%; height: 100vh; } diff --git a/src/views/CurveEditor.vue b/src/views/CurveEditor.vue new file mode 100644 index 0000000..750c9a8 --- /dev/null +++ b/src/views/CurveEditor.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/src/views/NumSlider.vue b/src/views/NumSlider.vue new file mode 100644 index 0000000..a40ed75 --- /dev/null +++ b/src/views/NumSlider.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/views/ParticlePanel.vue b/src/views/ParticlePanel.vue new file mode 100644 index 0000000..8532874 --- /dev/null +++ b/src/views/ParticlePanel.vue @@ -0,0 +1,331 @@ + + + + + diff --git a/src/views/Stage.vue b/src/views/Stage.vue new file mode 100644 index 0000000..d7a3905 --- /dev/null +++ b/src/views/Stage.vue @@ -0,0 +1,664 @@ + + + + + diff --git a/src/views/Timeline.vue b/src/views/Timeline.vue new file mode 100644 index 0000000..25f8798 --- /dev/null +++ b/src/views/Timeline.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e60fbd6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + "types": ["vite/client"] + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..b940375 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "types": ["node"] + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.d.ts b/vite.config.d.ts new file mode 100644 index 0000000..340562a --- /dev/null +++ b/vite.config.d.ts @@ -0,0 +1,2 @@ +declare const _default: import("vite").UserConfig; +export default _default; diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..799b80f --- /dev/null +++ b/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +export default defineConfig({ + plugins: [vue()], + server: { + port: 5173, + host: true, + }, + // 让 node_modules 里的 ESM 依赖能直接 work + optimizeDeps: { + include: ['@esotericsoftware/spine-pixi-v7'], + }, +}); diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..30c2e76 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + server: { + port: 5173, + host: true, + }, + // 让 node_modules 里的 ESM 依赖能直接 work + optimizeDeps: { + include: ['@esotericsoftware/spine-pixi-v7'], + }, +}) diff --git a/开发计划.md b/开发计划.md new file mode 100644 index 0000000..2e01962 --- /dev/null +++ b/开发计划.md @@ -0,0 +1,168 @@ +# Spine 网页端粒子系统 · 开发计划(定稿 v1.0,已确认) + +> **已确认的决策** +> - 产品形态:**独立编辑器/演示站** +> - 技术栈:**Vue 3 + Vite + TypeScript + Pinia** +> - Spine 预览运行时:**@pixi/spine + PixiJS 8**(主打 4.2,兼容 3.8/4.0/4.1 预览) +> - 粒子系统:自研轻量粒子池(预览用,勿黑盒库) +> - 导出:**A 方案 —— 工程配置 JSON**(不产出 Spine 动画 JSON;先做 A,MVP 优先,架构为后续 B 预留) +> - 部署:**纯前端静态构建 → Netlify/Vercel/GitHub Pages**,零后端 + +--- + +## 一、目标 + +让没有粒子系统的 Spine 动画,能在网页端**可视化地叠加粒子特效**。用户在网页里: +1. 上传/加载 Spine 骨骼动画(`.json + .atlas + .png`)或内置示例; +2. 在骨骼上配置粒子发射器(可**跟随骨骼**、调各种参数、应用预设); +3. **实时预览**效果; +4. 保存为"工程配置 JSON",下次可再次加载编辑。 + +> 对标:喵喵 Spine Particle / Spine Particle Studio。**注意**:本次范围是"编辑器+预览+存配置",暂不做那两个站的"导出 Spine 动画 JSON"核心烘焙功能(那属 B 方案,列为后续扩展,见第八节)。 + +--- + +## 一·补充、参考站实测核对(已于浏览器读取确认) + +### 站1:喵喵 Spine Particle v0.14.1 +- 保存/加载配置;**场景对象=粒子系统**(名称/所属动画/系统X/Y/旋转/ScaleX/ScaleY/**参与碰撞 Collision Filter+碰撞标签**) +- 发射:爆发/持续、延迟(秒/帧)、数量、形状(点/圆/矩形/锥体/**轨道**)、方向(随机/固定角度世界/局部/跟随轨道) +- 属性:初始速度/生命周期/缩放(统一与XY分离)/旋转(各支持 固定/随机/**曲线**)、生命周期内变化 +- 外观:透明度(固定/曲线,跟随导出/强制线性/强制贝塞尔)、图片(单图/序列/子文件夹)、颜色(固定色/生命周期)、混合模式(正常/相加/相乘/滤色)、缩放/轴心/独立透明度 +- 修改器:**发射器跟随**/移动噪声/重力/风力/力场(吸斥)/拖尾 +- **导出 Spine JSON**(Spine 3.8/4.0/4.1/4.2、关键帧插值线性/贝塞尔/阶跃、导出FPS、整数帧对齐、**骨骼对象池**、导出骨架、排除无内容动画、图片路径) +- 操作:中键平移、Ctrl+滚轮缩放、Ctrl+Z 撤销、G/L 全局局部坐标、时间轴 +- 附:页面头部含**官方飞书文档** + +### 站2:Spine Particle Studio +- 系统列表/名称/粒子位置X/Y/发射器旋转 +- 贴图:模式(单图/序列帧/雪碧图)、**内置贴图**(发光星/柔光点/发光环/六角雪花/十字火花/闪耀菱形)+ 自定义上传 +- 发射:爆发/持续、方向/吸收(向外/向内)、数量、延迟、总时长、循环/无缝循环/**预热**、形状(点/圆/矩形/扇形)、发射角度/偏移 +- 粒子属性:速度/生命/透明度/缩放(固定/随机/曲线,且带**曲线绘制器** 基础值/重置/START/LIFE TIME/END)、初始旋转模式(固定/随机/方向)、旋转模式(速度/曲线)、颜色(固定/生命周期)、混合模式 +- 物理与随机:速度抑制/重力/抖动/移动噪声/吸引力场/**碰撞与阻碍物**/随机种子/路径跟随/死亡触发/**拖尾子粒子**(主粒子<20) +- 背景:参考图/上传背景/显示背景/透明度 +- **导出 Spine JSON**(Spine 3.8/4.0/4.1/4.2、导出FPS、图片路径)、UI 主题、撤回0/10 + +### 两站共性(确认) +> 都是一个**粒子特效编辑器**:可视化配置 → 实时预览 → **导出为 Spine 骨骼动画 JSON** 供并入 Spine 工程。站1 更强调"骨骼跟随+骨骼对象池+曲线插值";站2 更强调"内置贴图+物理+拖尾子粒子+曲线绘制器"。 + +### 对本次 A 方案的定位(重要) +> 本次(A 方案)做的是完整"编辑器+实时预览+工程配置 JSON 存取",**不含**上述"导出 Spine JSON 烘焙"。两者的功能清单(属性/发射/修改器/贴图)在**预览层**基本一致,只是导出层不同;因此做 A 时,除导出外其余功能均按上述清单实现,架构为 B(导出)预留,见第八节。 + +--- + +## 二、核心功能清单 + +| 模块 | 明细 | +|---|---| +| 资源加载 | 上传 `.json+.atlas+.png` / 内置示例(`sample.json` 4.2.43 可当测试数据) | +| 动画控制 | 播放/暂停/停止、循环、动画切换、时间缩放、皮肤切换、骨架缩放/位移 | +| **粒子系统** | 发射器(爆发/持续)、发射形状(点/圆/矩形/扇形/锥体)、发射方向/角度、粒子数量、延迟、时长 | +| 粒子属性 | 速度/生命/透明度/缩放/旋转(各支持 固定/随机/曲线)、颜色(固定/生命周期)、混合模式 | +| **骨骼跟随** | 发射器绑定到某根骨骼/插槽,原点随骨骼逐帧更新(核心特性) | +| 修改器(可选增强) | 重力、风力、移动噪声、拖尾、力场 | +| 贴图资源 | 内置(发光星/柔光点/发光环/雪花/火花/菱形)+ 用户上传;支持 单图/序列帧/雪碧图 | +| 编辑器 UI | 场景对象(粒子层)管理、参数面板(滑块/颜色选择/曲线编辑)、预设模板 | +| 保存/加载 | 导出/导入**工程配置 JSON**;支持撤销/重做、随机种子 | +| 操作习惯 | 中键平移、滚轮缩放 | + +--- + +## 三、技术选型 + +| 层 | 选型 | +|---|---| +| 前端 | Vue 3 + Vite + TypeScript + Pinia | +| Spine 渲染(预览) | @pixi/spine(runtime-4.2 主,兼容 3.8/4.0/4.1)+ PixiJS 8 | +| 粒子预览 | 自研粒子池(Pixi ParticleContainer / 自定义 WebGL 精灵) | +| 状态 | Pinia | +| 部署 | Vite 静态构建 → 静态托管 | + +--- + +## 四、核心设计(预览层) + +- 编辑器画布 = Spine 骨骼渲染 + 若干 `ParticleEmitter`。 +- `ParticleEmitter` 管理一个池化粒子容器;每帧:发射原点 = 所属骨骼世界坐标 + 偏移 → 发射粒子 → 更新(位置/速度/重力/寿命/颜色/旋转/alpha)→ 渲染。 +- 参数用**归一化数据模型**描述(固定 = 定值 / 随机 = [min,max] / 曲线 = 贝塞尔控制点),同一份模型驱动预览渲染与将来导出。 +- 贴图:内置一组 + 用户上传;支持单图/序列帧/雪碧图。 + +--- + +## 五、代码结构(建议) + +``` +SpineParticle/ +├─ index.html +├─ vite.config.ts +├─ package.json +├─ src/ +│ ├─ main.ts +│ ├─ App.vue +│ ├─ core/ +│ │ ├─ spineLoader.ts # json/atlas/png 加载解析、多版本 +│ │ ├─ particleEmitter.ts # 预览粒子发射器(池化) +│ │ ├─ particleSystem.ts # 多发射器/帧更新/骨骼跟随 +│ │ ├─ curves.ts # 固定/随机/贝塞尔 求值 +│ │ ├─ presets.ts # 内置贴图 + 预设模板 +│ │ └─ types.ts # 数据模型 + 工程配置结构 +│ ├─ views/ +│ │ ├─ Stage.vue # 场景/预览画布 +│ │ ├─ AnimationPanel.vue # 动画控制 +│ │ ├─ ParticlePanel.vue # 粒子层列表 + 参数面板 +│ │ └─ PresetPicker.vue # 预设模板 +│ └─ store/ +│ └─ particleStore.ts # spine 资源 + 粒子层状态 +└─ 开发计划.md +``` + +--- + +## 六、开发阶段(先 MVP 后增强) + +| 阶段 | 内容 | 产出 | +|---|---|---| +| **0 需求确认** | 完成(本计划定稿) | — | +| **1 技术验证 Spike** | pixi-spine 加载 `sample.json` 并播放;**一个粒子点跟随 `symbols` 骨骼** | 最小可演示 | +| **2 Spine 预览核心** | 资源加载(上传+内置)、动画/皮肤/缩放/控制、时间轴 | 可操作现有骨骼 | +| **3 粒子预览引擎** | 粒子池、发射器、基础参数、曲线求值、**骨骼跟随** | 骨骼上长出粒子 | +| **4 编辑器 UI** | 场景对象管理、参数面板(滑块/颜色/曲线)、内置贴图+预设 | 可视化编辑 | +| **5 保存/加载** | 导出/导入工程配置 JSON、撤销/重做、随机种子 | 可保存/分享 | +| **6 部署上线** | Vite 构建 → 静态托管,验证 URL | 线上可用 | + +> 每阶段都有可运行成果,可随时停下验收。 + +--- + +## 七、验收标准(阶段 6 完成时) + +- 打开网页 → 加载内置示例(或用你的 `sample.json`)→ 播放动画; +- 在任意骨骼上添加粒子层 → 粒子**跟随骨骼**运动; +- 通过面板调节速率/寿命/速度/颜色/缩放/alpha/重力等 → 实时可见; +- 切换预设 vs 自定义 → 生效; +- 保存工程配置 → 刷新后加载还原; +- 部署到静态托管,公网可访问。 + +--- + +## 八、后续扩展(本次不做,已明确后置) + +**用户决策:不做烘焙导出,本次只做预览。烘焙这一"大头功能"放后续做。** + +- **烘焙导出(后置大目标,即 B 方案)**:把粒子效果**烘焙导出为 Spine 骨骼动画 JSON**(对象池骨骼、逐帧采样、曲线插值线性/贝塞尔/阶跃、fps/整数帧对齐、多版本 3.8/4.0/4.1/4.2 schema)。本项目的"归一化数据模型 + 骨骼跟随"已为此预留,将来平滑接入,无需推翻现有架构。 +- 物理系统补充:碰撞体/阻碍物、路径跟随、死亡触发、拖尾子粒子、物理吸引力场。 +- 多版本**导出**支持(预览层面本项目已支持多版本)。 + +> 本次 A 方案与 B 方案只在"导出层"不同:预览层的功能清单(属性/发射/修改器/贴图)两者一致,因此当前实现的每一项都会成为将来 B 方案的直接输入。 + +--- + +## 附:交付物 + +- 一个可运行、可部署的 Vue 3 单页应用(代码 + `package.json`); +- 支持上传 Spine 资源、可视化配置粒子、骨骼跟随、实时预览、存/取配置; +- 开发计划本文件。 + +--- + +> ⚠️ 由于需在浏览器里实际调 spine + 粒子渲染与交互,**建议边开发边在浏览器里目视验收**,因此在阶段 1 用你的 `sample.json` 做技术验证(Spike)是关键第一步。 diff --git a/粒子系统开发阶段一.md b/粒子系统开发阶段一.md new file mode 100644 index 0000000..5f0ff43 --- /dev/null +++ b/粒子系统开发阶段一.md @@ -0,0 +1,74 @@ +# 粒子系统开发 · 阶段一总结 + +定位:一个网页端粒子发射编辑器(Vue3 + Vite + TS + Pinia + PixiJS),负责「粒子发射系统的参数化编辑 + 实时预览」。**不依赖 Spine 运行时**,Spine 只是未来导出的目标格式,未做烘焙导出(按决定后置)。 + +目录:`/Users/tianmokeji/Desktop/SpineParticle`(dev server: `http://localhost:5173`)。 + +--- + +## 一、产出 + +### 核心文件 +- `src/core/particleEmitter.ts` — 自研池化粒子发射器。排放模式(持续/爆发)、发射形状(点/圆/矩形/锥体)、速率、寿命、速度、缩放、旋转、颜色、混合、重力、风力、阻尼、随机种子、透明度曲线插值 `curveAt`。 +- `src/store/particleStore.ts` — Pinia 状态(多粒子系统、activeId 选中、时间轴、画布设置、`settings` 坐标轴配置)。 +- `src/views/ParticlePanel.vue` — 左侧参数面板,7 个可折叠分组:场景对象/发射模式/发射器形状/粒子属性/外观与资源/修改器/导出设置(预留)。 +- `src/views/Stage.vue` — Pixi 画布(网格背景、滚轮缩放、左键拖拽、无限坐标轴、系统设置面板、发射点标记)。 +- `src/views/Timeline.vue` — 底部时间轴(播放/暂停/回到起点/循环、帧计数、滚轮缩放、每系统轨道条、拖动调延迟/时长)。 +- `src/views/CurveEditor.vue` — 透明度生命周期曲线(横 0-1 时间、纵 0-1 透明度,节点拖动、双击新建/删除)。 +- `src/views/NumSlider.vue` — 滑块+数字框复用控件。 +- `public/particles/star.png` — 默认贴图。 + +### 已确认的关键行为 +- Y 轴采用数学坐标系:**向上为正、向下为负**。 +- 位置X/位置Y 是**锚点(黄点)**,偏移X/偏移Y 让**粒子相对黄点额外位移**,物理/力场源锚在锚点。 +- 一个粒子系统 = 一个坐标点;无系统时画布无边线;选中系统高亮黄色,其余淡灰蓝。 +- `totalFrames` 由 `delay + duration + lifeMax` 按生命周期自动算出,只在改这些属性时重算(签名监听,不卡)。 +- 默认:发射速率 10/s、透明度用曲线(默认平台曲线 `(0,0)(0.2,1)(0.8,1)(1,0)`)、贴图 star.png、英文命名 ParticleSystem1 递增、画布默认 100%(最小 1%、无上限)、默认不显示骨骼连线、进入页面空白、默认循环播放。 +- 设置面板「启用刻度」是坐标轴唯一主开关,默认关闭,内部含字号/线宽/颜色/显示刻度数值。 + +### 明确未做(预留) +导出 Spine JSON 烘焙、轨道路径/碰撞体/导出设置(界面占位)、真实 `.atlas+.png` 骨骼贴图。 + +--- + +## 二、踩过的坑 + +### 1. 方向性坑(代价最大) +- 一开始把 sample.json 当"要预览的素材",被纠正为"用来创建粒子系统",再被纠正为"用参数创建、json 是最终导出物",最后"不要强行匹配 spine,只负责粒子发射编辑器"。多轮方向错误导致大面积返工(一度引入 spine-pixi 依赖、做骨骼对象池架构)。 +- 做了"加载示例骨骼"按钮,被否定(粒子系统创建应由编辑器参数决定)。 +- 进页面自动建"系统1"、预置 item_11,被要求默认空白。 + +### 2. 技术坑 +- **Pixi7 Color API**:`copyFrom`、`setValue(r,g,b)` 不存在,渐变插值需手动拼 RGB 再 `.toNumber()`。 +- **坐标轴文字放大变糊**:Text 放在被缩放的 world 容器会位图模糊;移到独立 `axisLabelLayer`(不随 world scale),用世界→屏幕坐标定位。 +- **SVG 曲线编辑器点变椭圆/viewBox 拉伸**:`preserveAspectRatio="none"` 使 100×100 viewBox 拉成非正方形,圆变椭圆且命中区小;改为像素坐标系 + 加大命中圈。 +- **曲线编辑器 `dblclick` 不触发**:透明命中圆上的 dblclick 被 pointer 捕获抑制;改为在 pointerdown 里检测两次点击(300ms 内、位移<8px)。 +- **双击删除又新建**:删除节点后同一次双击的 dblclick 冒泡到 SVG 触发"双击空白新建";用删除抑制时间戳窗口解决。 +- **录制→回放使面板改动不实时**:进入回放读旧帧,改参数(速率/颜色/曲线)不更新;把触发重录的 watch 改为对整个 config 做 `JSON.stringify`(排除 texture)的签名监听。 +- **deep watch 引发每帧重算卡死**:之前 `watch(config, {deep:true})` 监听整个 config,emitter 每帧把 `_elapsed` 写进 config 导致深比较触发;修法:仅监听生命周期签名 + 把 `_elapsed` 移出 config 改为 emitter 私有字段。 +- **发射点与位置X/Y 错位**:`setEmitterPos` 返回 centerX/centerY,而 `spawnOrigin` 又加一次 centerX,等于 `2×centerX+offsetX`;把锚点来源统一为 `_emitterPos()`,再加 offset。 +- **Y 轴方向反了**:坐标轴标签是数学坐标(上正),world 用屏幕坐标(下正),位置Y 为正反而画向下;在发射点初始化处把锚点 Y 取负。 + +### 3. 协作/流程坑 +- 反复来回挪同一设置面板里的选项(坐标轴颜色、显示刻度数值),属"反复折腾同一功能"。 +- 有明确方向时仍多次用 ask_user_question 确认 A/B 方案,拖慢节奏。 + +--- + +## 三、经验 + +1. 先对齐"坐标系/数据模型"设计,再写编辑器代码。最贵的返工来自语义理解偏差,而非语法/API 问题。 +2. 区分"一次功能 vs 一次架构"。小参数改动要最小侵入;方向级错误要敢于回滚,不要继续堆。 +3. 性能敏感的循环里别 deep watch 可变的引用对象。粒子每帧改状态,任何 deep 监听都会拖垮帧率;用序列化签名 + 把每帧变化的场移到组件私有字段。 +4. 坐标系不统一是渲染 bug 高发地。屏幕坐标(下正)与数学坐标(上正)混用,导致"位置反了/文字糊了/缩放不对";最好在数据入口一次转换,渲染层保持单一约定。 +5. headless(puppeteer)自测有效。浏览器工具受限时,headless 截图 + `read_image` 能自主验证"位置/方向/颜色/数值是否生效"。 +6. 事件优先级要兜底。曲线编辑器这类点击/拖动/双击/删除并存的小组件,事件易互相干扰(删了又建、拖了误删),需时间戳/位移阈值/抑制窗口做防抖。 + +--- + +## 四、现状与风险 + +- 还没做**导出**,一切围绕"调参 + 预览";导出 JSON(含烘焙 Spine)是后续大工程,现有"锚点 + 偏移 + 曲线 + 生命周期"数据模型已为此留接口。 +- **发射方向(direction)/力场/移动噪声**这些修改器参数,物理方向在 Y 翻转后个别(发射方向角度、力场基准点 `base`)可能仍需按数学坐标复核;本次只改了黄点和粒子发射起点,`base`(力场)默认关闭未动,开启时需再验证。 +- **爆发模式下 totalFrames 算法**(一次全出、无持续窗口)可能与预期不同,值得确认。 +- 代码是反复迭代出来的,个别函数较长、命名一般;若有长期维护打算,建议做一轮小清理(非必须)。 diff --git a/网页粒子系统阶段二.md b/网页粒子系统阶段二.md new file mode 100644 index 0000000..6de0ec8 --- /dev/null +++ b/网页粒子系统阶段二.md @@ -0,0 +1,249 @@ +# 网页粒子系统 · 阶段二 + +> 一个纯前端的 **Spine 粒子发射编辑器**(参考 喵喵 Spine Particle / Spine Particle Studio)。 +> 本阶段聚焦「粒子发射编辑器的系统设计与显示」,不强行绑定 Spine 运行时;Spine 仅是未来导出格式的参考,`spine/sample.json` 只作素材,不做为编辑器模板。 +> 工程目录:`/Users/tianmokeji/Desktop/SpineParticle` + +--- + +## 一、项目定位与技术栈 + +**定位**:可视化配置粒子特效、实时预览,最终输出导出所需的数据模型(导出为后续阶段,现阶段为预览/编辑铺路)。 + +**技术栈** +- 前端框架:Vue 3 + TypeScript + Vite +- 状态管理:Pinia +- 渲染:PixiJS 7.4(`@esotericsoftware/spine-pixi-v7` **已卸载**,当前不依赖任何 Spine 运行时;`pixi.js` 用于画布) +- 粒子:自研粒子发射器 `ParticleEmitter`(池化,Sprite 池) + +**明确不做(预留)**:导出 Spine JSON 烘焙、骨骼对象池匹配 Spine 运行时。(本阶段核心 = 编辑器 + 预览。) + +--- + +## 二、已完成功能清单 + +### 2.1 面板(ParticlePanel)架构 +按参考站分组、全部可折叠: + +| 分组 | 内容 | +|---|---| +| **场景对象** | 树形层级(●粒子系统 / ◇轨道路径【预留】 / ◇碰撞体【预留】),名称、位置X、位置Y、偏移X、偏移Y、**旋转、缩放X、缩放Y** | +| **发射模式** | 持续(发射速率) / 爆发(单次数量) | +| **发射器形状** | 形状(点/圆/矩形/锥体/轨道),圆/锥(半径)、矩形(宽/高)、锥形开口角度、发射角度、扩散角度 | +| **粒子属性** | 速度下限/上限、生命下限/上限、缩放下限/上限、旋转速度、随机种子 | +| **外观与资源** | 透明度(曲线/固定)、曲线模式(跟随导出/线性/贝塞尔)、图片资源(子文件夹/名称)、颜色类型(固定色/渐变)、混合(正常/相加/相乘/滤色)、占比与缩放(占比/缩放/轴心/独立透明度) | +| **修改器 (Modifiers)** | 移动噪声、重力、风力、力场、拖尾、碰撞【预留】、路径跟随【预留】、阻尼 | +| **导出设置** | 预留(占位) | + +**数值调节统一为滑块 + 数字框并存**(`NumSlider` 组件)。 + +### 2.2 画布(Stage) +- 网格背景(可切 ▦),深色棋盘格。 +- 滚轮缩放画布:`默认 100%`,`最小 1%`,**无上限**;以鼠标为中心缩放(鼠标下的世界点不漂移)。 +- 左键长按拖动画布(平移)。 +- 顶部工具栏: + - **根变换工具**:✋ 拖动画布(默认)、✥ 位移、⟳ 旋转、⤢ 缩放(切换后用 gizmo 拖拽根节点)。 + - 视图工具:+/- 缩放、⤢ 适配画布、▦ 网格、⟳ 重置视图。 + - 开关:发射点、骨骼连线、⚙ 系统设置。 +- 坐标轴:`center+center` 无限长 x/y 轴 + 自适应整数刻度,数字标签放**独立不缩放图层**(避免放大变糊);由系统设置"启用刻度"主开关控制。 +- **发射点(黄点)**:每个系统一个,绑 位置X/位置Y(centerX/centerY);选中高亮黄,其余淡蓝灰;无系统不画。 +- **发射器形状范围**:选中对应形状时绘制绿圆(圆形)、蓝矩形(矩形)、绿锥形(锥体),随参数实时响应;点/轨道不绘制。 +- **根变换 gizmo**:选中工具后在 root 中心亮青色手柄(位移十字箭头 / 旋转圆环 / 缩放方框角点)。 + +### 2.3 时间轴(Timeline) +- 控制按钮:❚❚/▶ 播放暂停、⏮ 回到起点、⟳ 循环。 +- 帧显示 `当前帧 / 总帧数`,动画名下拉。 +- 标尺:整数帧刻度;**滚轮缩放只改变显示范围(视口帧数,左端恒为第 0 帧),不改变播放时长**;刻度为整数、最小 0。 +- 每系统一条轨道;播放头可拖动滑动。 +- **粒子条**:位置 = 延迟(delay)起点,宽度 = 发射时长(duration)+粒子生命(lifeMax) = 总时长;左端之前空白 = 延迟时长。可整体拖动(调 delay)、拖右缘(调 duration)。 + +### 2.4 系统设置(⚙ 面板) +- **启用刻度** 主开关(默认关闭):开启才显示坐标轴并展开其余配置项。 + - 刻度文字大小(默认 12px)、刻度线粗细、坐标轴颜色、显示刻度数值(均随"启用刻度"显隐)。 + +### 2.5 默认值(按用户给定的截图定版) +| 项 | 默认 | +|---|---| +| 发射模式 | 持续,发射速率 = 20 | +| 爆发 | 单次数量 = 50 | +| 延迟时间 | 0 | +| 发射时长 | 0(= 无限,已在 UI 移除该项) | +| 持续爆发开关 | 已移除 | +| 形状 | 点;发射角度 0、扩散角度 360 | +| 圆形 | 半径 100,方向 -90,扩散 0 | +| 矩形 | 宽 200,高 100,方向 -90,扩散 0 | +| 锥体 | 半径 100,锥形开口 90,朝向 90 | +| 系统命名 | ParticleSystem1(英文,同名自动 +1) | +| 贴图 | star.png | +| 透明度 | 曲线模式,默认点 (0,0)(0.2,1)(0.8,1)(1,0) | +| 随机种子 | 0(每次不同) | + +--- + +## 三、核心架构设计 + +### 3.1 数据流(单向) +``` +ParticlePanel(Template) ⇄ Pinia Store (systems[]) ⇄ Stage.loop (emitterMap) + ▲ │ + └────── 面板 v-model 改 config ──────┘ +``` +- **`sys.config` 按引用保持**:面板直接改 config 字段,Pixi 发射器读到的是同一对象,改动实时生效。 +- `emitterMap: Map` 由 Stage 维护,每帧 render。 + +### 3.2 坐标系(关键) +编辑器统一采用**数学坐标系:Y 向上为正、向下为负**。Pixi 内部是屏幕坐标系(Y 向下为正),因此: +- 发射点/黄点位置:`cy = -centerY`; +- 发射器容器 `em` position:`em.position.set(centerX, -centerY)`; +- 初始速度 y:`vy = -sin(ang)`(锥体); +- 形状范围、gizmo 绘制均遵循 `cy 取负`。 + +**统一手法**:显示层(黄点、形状、gizmo、容器 position)都做 Y 取负转换;而**粒子物理/运动方向**保持屏幕坐标,二者在"发射方向(锥体)"这一处需显式对齐。 + +### 3.3 发射器(ParticleEmitter extends Container) +粒子是发射器容器的子项(Sprite 池),坐标在**发射器局部坐标**。因此对容器设 `position/rotation/scale` 即整体变换整簇粒子 = **"root 变换"**。 + +角色划分: +- **root 位移** = `centerX/centerY` → 容器 `position`; +- **root 旋转** = `rootRotation` → 容器 `rotation`(数学上正,顺时针为负); +- **root 缩放** = `rootScaleX/rootScaleY` → 容器 `scale`。 + +**发射局部原点**:粒子发射位置 = `中心(centerX) + 偏移(offsetX/offsetY)`,其中 `spawnOrigin` 的局部原点只含 offset,中心由容器 position 补回。这样旋转/缩放绕 root 中心,且最终世界坐标不变。 + +### 3.4 发射模式与形状 +- **持续(stream)**:`accumulator += dt * rate`,累积满 1 发射一个。 +- **爆发(burst)**:一次性 `emitBurst()` 发 `burstCount` 个。 +- **发射形状**(`spawnOrigin`): + - point:中心; + - circle:半径内圆散布; + - rect:宽高矩形散布; + - cone:**从锥形顶点(中心)发射,方向在 `direction ± coneAngle/2` 扇形内随机**、半径 0~radius(与圆不同,只限锥体弧度范围)。 +- **速度方向**(`spawnOne`):锥体用 coneAngle 散布 + `vy=-sin`;其余用 spread + 屏幕坐标。 + +### 3.5 根变换 gizmo +- 3 种工具:位移 / 旋转 / 缩放,切换后画布按住拖动改 `config.centerX/centerY`、`rootRotation`、`rootScaleX/ScaleY`。 +- 与场景对象面板参数**双向联动**。 +- 计算基于**按下时的快照 + 相对按下点的总增量**(位移/旋转/缩放都不累积),保证"跟手"。 + +### 3.6 录制 → 回放 +- Stage.loop 每帧 `em.update(dt)`(固定 `dt = 1/fps` 保证帧对齐),产生的 `ParticleState[]` 深拷贝存到 `sys.frames[frame]`。 +- 录完后进入回放:`em.apply(frames[frame])`,到尾帧若 loop 则回 0,否则停末帧。 +- 每帧也收集活动粒子状态 `collected`(供 HUD 数字化/骨骼连线)。 + +### 3.7 时间轴总帧数(自动计算) +- `totalFrames = ceil( max(对所有系统 (delay + duration + lifeMax)) * fps )`。 +- 即"最后一个粒子消失的时刻";改 delay/duration/lifeMax 时重算。 +- 通过 **字段签名 watch** 触发重录(见踩坑点 #1)。 + +--- + +## 四、踩坑点(重要) + +1. **签名 watch 粒度** + - 起初 `watch(store.systems.map(config), {deep:true})` 监听整个 config;但发射器每帧改 `_elapsed` 等内部字段 → 每帧触发 → 每帧重算 totalFrames + 清空重录 → **卡死**。 + - 修复:`JSON.stringify(s.config, 排除 texture)` 生成**签名字符串**比较,只有"影响渲染的参数"变化才触发;并把 `_elapsed/_delayLeft` 从 config 移至 emitter **私有字段** `_elapsed`,避免污染响应式。 + +2. **坐标轴文字放大变糊** + - Text 放 world 容器随 viewScale 放大变为位图模糊。 + - 修复:文字放到**独立 `axisLabelLayer`(不随 world 缩放)**,用世界→屏幕坐标定位;字号由"系统设置"手动控制。 + +3. **曲线编辑器椭圆节点/选不中** + - SVG `viewBox + preserveAspectRatio="none"` 把圆拉伸成椭圆、命中区过小。 + - 修复:改按实际像素绘制(ResizeObserver 量尺寸),节点为正圆,叠加大命中圈,Pointer 捕获拖动。 + +4. **曲线节点双击删除又新建** + - dblclick 冒泡到 SVG 触发"双击空白新建"。 + - 修复:删除节点记录时间戳,`onDbl`(新建)在删除后 400ms 内忽略(抑制窗口)。 + +5. **Y 轴方向反** + - 坐标轴标签用数学坐标(上正),发射器/黄点用屏幕坐标(下正),方向相反。 + - 修复:黄点/发射器容器 position 的 y 取负,统一到数学坐标系。 + +6. **锥体朝向与粒子方向相反** + - 粒子发射用 `-sin`(数学上正),形状范围绘制用 `+sin`,二者符号不一致。 + - 修复:drawShapeRange 逐点做 `pt` 变换时统一 `Y = -ly - oyo`(与粒子一致)。 + +7. **锥体粒子散布在扇区内 vs 从中心发射** + - 初版把粒子位置散布在扇形区域(半径 × coneAngle),与圆的"中心全向"不同。 + - 按用户意图修正:粒子**从锥形顶点(中心)发射,方向限 `direction ± coneAngle/2` 扇形**,类似"圆心向各方向,但只限锥体弧度范围"。 + +8. **偏移不影响形状范围** + - 形状范围中心画在 root 中心,没用 offset;粒子发射源 = root 中心 + 偏移,导致偏移时粒子相对形状范围偏移。 + - 修复:drawShapeRange 的 `pt` 把偏移 `(oxo, oyo)` 并入局部原点;且 `offsetY` 转屏幕取负(`Y = -ly - oyo`)。 + +9. **发射时长 1 秒后停发** + - `duration` 默认 1.0,爆发/持续 1 秒后 `withinDuration=false` 停发,粒子随后消亡 → "没粒子"。 + - 修复:配合 UI 移除"发射时长/持续爆发",`duration` 默认改 0(=无限),`burstLoop` 默认 false(一次性爆发)。 + +10. **gizmo 拖拽不跟手** + - 位移用 `cfg.centerX + dx`(基于当前值累加总位移 dx),鼠标一动重复叠加 → 越拖越偏。 + - 修复:存按下时基准 `sCx/sCy`,位移 = 基准 + 总位移;旋转/缩放同样基于基准快照,并去掉 `Math.round` 取整抖动。 + +11. **默认贴图/命名等默认值** + - 曾把默认 textureName 设为 dot、并在用户改图后仍用旧缓存;后按用户要求默认 `star.png`,硬刷新即可。 + - 系统命名:默认英文 `ParticleSystem1`,同名自动 +1(正则 `^ParticleSystem(\d+)$` 取最大值 +1)。 + +12. **新增系统不显示贴图** + - 初期只给 onMounted 时存在的系统赋 texture,后续 `addSystem` 新增系统 texture 为 null → Sprite 画不出。 + - 修复:在 `syncEmitters()`(每帧)里,给 `dotTex 已加载但 sys.config.texture 为空` 的系统补上默认纹理。 + +--- + +## 五、根节点变换的坐标正确性(易错综述) + +- 发射器容器(position/rotation/scale)承载 root 变换。 +- **必须**把发射局部原点改为"只含 offset"(去 centerX),centerX 由容器 position 补回;否则 position 与粒子局部坐标双重叠加 → 位移重复。 +- 旋转/缩放绕 root 中心成立,前提是"root 中心 = 容器局部原点(0,0)"。 +- 数学坐标系下,一切显示层 y 取负;粒子运动方向(y 向)在锥体方向处需与形状绘制对齐。 + +--- + +## 六、响应式与性能要点 + +- **`sys.config` 保持引用**,面板 v-model 直接改属字段 → 实时生效,无深拷贝/重建。 +- **避免 deep watch** 整个 config;用签名字符串监听 + 内部计时字段移出 config。 +- **录制固定步长** `dt = 1/fps`,保证回放与实时帧对齐、可复现。 +- **确定性随机(种子)**:`mulberry32`;`seed>0` 时可复现;**仅在种子变化时重建 RNG**,`reset()` 时若种子>0 重置到序列头,保证同种子从头录制结果一致。 + +--- + +## 七、文件结构 + +``` +src/ + main.ts # 入口,注册 Pinia + App.vue # 布局:左面板 / 右上画布 / 右下时间轴(宽度/高度可拖拽) + style.css # 全局样式(深色主题) + core/ + particleEmitter.ts # 发射器:config + defaultConfig + 池化更新/捕获/回放/重置/确定性随机 + store/ + particleStore.ts # Pinia:systems[] + activeId + timeline + settings + recalcTotalFrames + views/ + Stage.vue # 画布:world/粒子/坐标轴/黄点/形状范围/gizmo + 录制回放 + 根变换 + ParticlePanel.vue # 参数面板(分组折叠 + NumSlider + 形状默认值切换) + Timeline.vue # 时间轴(控制/标尺缩放/轨道/粒子条拖拽) + CurveEditor.vue # 透明度曲线编辑器(节点可拖/双击删/双击空白新建) + NumSlider.vue # 滑块 + 数字框 双控组件 +public/ + particles/star.png # 默认粒子贴图(另有 dot.png / spark.png) + spine/sample.json # 仅作素材参考,不作编辑器模板 +``` + +--- + +## 八、当前遗留 / 预留(后续阶段) + +- **导出 Spine JSON 烘焙**(用户确认后置):现阶段编辑/预览,导出的数据模型(每粒子一根骨骼 `boneName=p_`、ParticleState)已为此铺路。 +- **轨道路径 / 碰撞体**:场景对象中为预留占位(`collision`/`pathFollow` 字段已存在)。 +- **导出设置**:分组占位。 +- **修改器实现程度**:重力/风力/力场/移动噪声/阻尼已接入 `emitter.update`;拖尾/碰撞/路径跟随为占位/预留。 + +--- + +## 九、常见调整指引 + +- **默认参数**集中在本文件 §2.5(来自 `particleEmitter.ts` 的 `defaultConfig()` 与面板 `SHAPE_DEFAULTS`)。 +- **形状范围跟随 root 旋转/缩放/偏移**:改 `Stage.vue` 的 `drawShapeRange`(逐点 `pt` 变换)。 +- **根变换 gizmo**:改 `Stage.vue` 的 `drawGizmo` / `onGizmoDown`。 +- **时间轴总帧**:改 `store.recalcTotalFrames`。 +- **坐标系**:凡涉及显示 y 方向,遵循 `Y 取负` 的数学坐标约定。 diff --git a/重构设计.md b/重构设计.md new file mode 100644 index 0000000..6347058 --- /dev/null +++ b/重构设计.md @@ -0,0 +1,83 @@ +# Spine 粒子编辑器 · 骨骼对象池架构(重构设计 v1) + +> 基于我们多轮确认的设计意图整理。**目标**:一个"用参数创建粒子系统"的编辑器, +> 粒子本质是**挂在动态骨骼下的图片**,最终可导出 Spine 骨骼 json。 +> +> **与旧实现的根本区别**:不再"加载外部 Spine 骨骼、让粒子跟随某根骨骼",而是反过来—— +> **编辑器自建骨架(仅 root 根骨骼),粒子生成骨骼并挂在 root 下**。 + +--- + +## 一、核心概念(最终确认) + +1. **自建最小骨架**:编辑器打开时,**自建一棵只含一根 `root` 根骨骼的骨架**。 + - 不加载任何外部 `.json/.atlas/.png`(禁止 sample.json 作模板、禁止"加载示例骨骼"入口)。 +2. **骨骼对象池**:按**粒子发射数量**,在 `root` 下**动态生成对应数量的骨骼**。 + - **一个粒子 = 一根骨骼**。 +3. **粒子图片挂到骨骼**:每个粒子的图片(贴图光斑)作为 **child 挂载到对应该粒子的骨骼下面**。 + - 骨骼决定粒子的 **位置 / 旋转 / 缩放**;粒子图片跟随骨骼运动(这正是 Spine 动画的结构)。 +4. **参数驱动一切**:粒子系统的创建与所有参数(发射/数量/形状/方向/速度/生命/缩放/透明度/旋转/颜色/混合/重力等)完全由**编辑器参数面板**决定。 +5. **最终产出**:导出的 json 描述这些**在 root 下动态生成、随动画变化的骨骼**(即 Spine 骨骼动画 json)。 + +--- + +## 二、数据流(预览阶段) + +``` +参数面板(store) → 每帧生成/更新 骨骼池 + │ + 粒子数量 N ⇒ 在 root 下创建 N 根骨骼(Bone i) + │ + 每根骨骼设置 pos/rot/scale = 对应粒子的状态 + │ + 该粒子图片(Sprite) 作为 child 挂到该骨骼 → 骨骼带着图片动 +``` + +- 单根 `root` 骨骼:静止于原点(0,0),承载所有生成的子骨骼。 +- 生成的骨骼名:如 `p_0, p_1, ...`(与粒子一一对应),挂在 root 下。 + +--- + +## 三、渲染实现要点 + +- 画布用 Pixi 的 `Spine`(官方 spine-pixi)挂载骨架;但**不渲染 Spines 附件的贴图**(附件我们不用)。 +- 为**每根粒子骨骼**添加一个 `Sprite`(粒子贴图)作为其 child(Pixi `Bone` 可通过 `Spine` 组件的 slot/骨骼挂载子对象)。 + - 替代方案:不真正用 Spine 渲染骨骼,而是**自己维护一根"逻辑骨骼"+一个 Sprite**——每帧从参数计算粒子状态,直接移动 Sprite,同时记录这根"逻辑骨骼"便于导出。**这是更可控的实现**(避免 Spine 运行时挂载复杂)。 + +> 我建议用**方案 B(逻辑骨骼 + Sprite)**:预览时用 Sprite 显示粒子,同时维护"每颗粒子对应一根逻辑骨骼"的数据结构(名字/父级=root/位置/旋转/缩放随时间序列),既满足预览又为导出 json 提供干净的数据。避免陷入 Spine 附件的复杂挂载。若你要严格用 Spine 的 Bone 对象,我再切方案 A。 + +--- + +## 四、参数面板(保留现有字段,去掉"加载骨骼/跟随骨骼"相关) + +保留并完善: +- 系统:名称 +- 发射:持续/爆发、**粒子数量/速率**、发射形状(点/圆/矩形/锥体) +- 方向:角度、扩散 +- 粒子属性:速度/生命/缩放/透明度/旋转(各 min→max) +- 外观:颜色(起始/结束/寿命渐变)、混合模式、粒子贴图(内置选择/上传) +- 物理:重力 + +移除/调整: +- ~~跟随骨骼开关~~、~~骨骼下拉~~(不再"跟随某骨骼",而是 root 下生成) +- ~~加载示例骨骼~~ 按钮(不要再有) + +--- + +## 五、导出(后续阶段,先做预览) + +- 把每根粒子逻辑骨骼的**位置/旋转/缩放随时间的序列** dump 成 Spine 骨骼时间轴 json。 +- 骨架 = 1 个 root + N 根生成的子骨骼。 + +--- + +## 六、待你确认的实现方式(仅一点) + +预览阶段粒子的"骨骼"用哪种实现: + +| 方案 | 说明 | 取舍 | +|---|---|---| +| **B(推荐)** | 自维护"逻辑骨骼"数据 + Sprite 显示粒子;导出时从逻辑骨骼 dump json | 实现简单可控、与导出天然对齐 | +| **A** | 用 spine-pixi 的 `Bone` 对象,粒子 Sprite 真实挂到 Bone 下 | 更贴近 Spine,但挂载/渲染复杂 | + +> 我倾向 **B**。请确认选 A 还是 B(或让我默认按 B 开始)。