diff --git a/TUICallKit-Vue3/pages.json b/TUICallKit-Vue3/pages.json
index 0ab33978..759b8eee 100644
--- a/TUICallKit-Vue3/pages.json
+++ b/TUICallKit-Vue3/pages.json
@@ -223,6 +223,15 @@
"backgroundColor": "#c7d2fe",
"disableScroll": true
}
+ },
+ {
+ "path": "endless-game/index",
+ "style": {
+ "navigationStyle": "custom",
+ "navigationBarTitleText": "控糖消消乐",
+ "backgroundColor": "#eefbf4",
+ "disableScroll": false
+ }
}
]
}
diff --git a/TUICallKit-Vue3/tongji/endless-game/README.md b/TUICallKit-Vue3/tongji/endless-game/README.md
new file mode 100644
index 00000000..2afa8a5a
--- /dev/null
+++ b/TUICallKit-Vue3/tongji/endless-game/README.md
@@ -0,0 +1,56 @@
+# 控糖消消乐独立功能包
+
+此目录包含“控糖消消乐”无尽三消版的全部运行文件,可以整体复制到另一个 uni-app Vue 3 项目的 `tongji/endless-game/` 下。
+
+## 目录内容
+
+- `index.vue`:页面、棋盘算法、关卡主题、任务、道具、三/四/五连奖励和适老化交互。
+- `game-endless.scss`:完整页面与动画样式。
+- `components/TongjiIcon.vue`:本功能包使用的图标组件。
+- `composables/useGameSfx.js`:滑动、掉落、消除、连击和大奖音效。
+- `utils/svgDataUrl.js`:图标编码工具。
+- `assets/food/`:本游戏使用的全部食品与驼乳粉图片。
+- `viteAssetOutput.js`:把食品图输出到本功能分包,避免增加微信小程序主包体积。
+
+## 迁移步骤
+
+1. 整体复制 `endless-game` 文件夹到目标项目的 `tongji/` 目录。
+2. 在目标项目的 `vite.config` 中引入素材输出规则,并放到 `build.rollupOptions.output`:
+
+```js
+import { endlessGameAssetFileNames } from './tongji/endless-game/viteAssetOutput.js'
+
+// defineConfig 内
+build: {
+ rollupOptions: {
+ output: { assetFileNames: endlessGameAssetFileNames }
+ }
+}
+```
+
+3. 在目标项目 `pages.json` 的 `tongji` 分包 `pages` 数组中加入:
+
+```json
+{
+ "path": "endless-game/index",
+ "style": {
+ "navigationStyle": "custom",
+ "navigationBarTitleText": "控糖消消乐",
+ "backgroundColor": "#eefbf4",
+ "disableScroll": false
+ }
+}
+```
+
+4. 使用 `/tongji/endless-game/index` 打开游戏。
+5. 微信小程序后台需要允许音频域名:`https://gz-1349751149.cos.ap-guangzhou.myqcloud.com`。
+
+## 说明
+
+- 旧地址 `/tongji/pages/game` 和 `/tongji/pages/game-endless` 只是当前项目的兼容跳转,迁移时不需要复制。
+- 当前项目原来的“糖分突袭”源码保存在 `tongji/legacy-game/game.vue`,没有注册为页面,不参与打包。
+- 连消小目标按“连消×2”累计 2 次;连续 6 次普通消除没有连消时,下一次掉落会提供连消机会。“连消×3”保留为额外积分、驼乳粉与撒花惊喜,不阻挡主线任务。
+- 横向 3 个与竖向 3 个相交形成 L/T 形五消时,会在交点生成带高对比 L 标记的范围爆破棋子;该棋子再次被消除时清除周围九格。
+- 进入游戏时展示“本周 7 人同行榜”界面原型,包含前后名次、差距提示和可切换的同行/亲友鼓励;顶部“本周同行”可再次打开。当前榜单明确标注为预览数据,正式用户关系和周榜成绩需要后端接口接入。
+- 本功能包依赖 uni-app Vue 3 和 `@dcloudio/uni-app`,不依赖当前项目其他业务组件。
+- 食物风险等级和科普文案集中在 `index.vue` 的 `FOODS` 配置中,正式上线前仍需由医院医生审核。
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/apple.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/apple.jpg
new file mode 100644
index 00000000..1be0c448
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/apple.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/banana.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/banana.jpg
new file mode 100644
index 00000000..cd4ae5eb
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/banana.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/blackCoffee.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/blackCoffee.jpg
new file mode 100644
index 00000000..a6288bce
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/blackCoffee.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/broccoli.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/broccoli.jpg
new file mode 100644
index 00000000..c3bb36e7
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/broccoli.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/brownRice.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/brownRice.jpg
new file mode 100644
index 00000000..ba73bae0
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/brownRice.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/camel.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/camel.jpg
new file mode 100644
index 00000000..f4c4f518
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/camel.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/celery.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/celery.jpg
new file mode 100644
index 00000000..c248bee4
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/celery.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/centuryCongee.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/centuryCongee.jpg
new file mode 100644
index 00000000..643ef6c6
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/centuryCongee.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/chicken.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/chicken.jpg
new file mode 100644
index 00000000..1c155d22
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/chicken.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/coffee.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/coffee.jpg
new file mode 100644
index 00000000..df6f57be
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/coffee.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/congee.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/congee.jpg
new file mode 100644
index 00000000..da4234d8
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/congee.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/corn.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/corn.jpg
new file mode 100644
index 00000000..191a0376
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/corn.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/cucumber.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/cucumber.jpg
new file mode 100644
index 00000000..d1f53649
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/cucumber.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/egg.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/egg.jpg
new file mode 100644
index 00000000..5efadddd
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/egg.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/eightCongee.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/eightCongee.jpg
new file mode 100644
index 00000000..91837533
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/eightCongee.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/grainMantou.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/grainMantou.jpg
new file mode 100644
index 00000000..ea42db37
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/grainMantou.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/greenBean.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/greenBean.jpg
new file mode 100644
index 00000000..c34739c6
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/greenBean.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/lettuce.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/lettuce.jpg
new file mode 100644
index 00000000..b6420370
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/lettuce.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/mango.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/mango.jpg
new file mode 100644
index 00000000..a0ad188b
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/mango.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/milk.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/milk.jpg
new file mode 100644
index 00000000..e1b1fc52
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/milk.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/milkTea.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/milkTea.jpg
new file mode 100644
index 00000000..ca7ae135
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/milkTea.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/milletCongee.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/milletCongee.jpg
new file mode 100644
index 00000000..6e904a28
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/milletCongee.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/mushroom.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/mushroom.jpg
new file mode 100644
index 00000000..91c67712
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/mushroom.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/noodleSoup.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/noodleSoup.jpg
new file mode 100644
index 00000000..ca10d534
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/noodleSoup.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/onion.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/onion.jpg
new file mode 100644
index 00000000..a9863a2a
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/onion.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/orangeJuice.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/orangeJuice.jpg
new file mode 100644
index 00000000..c5841a2e
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/orangeJuice.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/pepper.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/pepper.jpg
new file mode 100644
index 00000000..dc624a3c
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/pepper.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/pineapple.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/pineapple.jpg
new file mode 100644
index 00000000..b50b07ce
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/pineapple.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/potato.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/potato.jpg
new file mode 100644
index 00000000..597ecbc2
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/potato.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/riceNoodleSoup.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/riceNoodleSoup.jpg
new file mode 100644
index 00000000..b782873b
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/riceNoodleSoup.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/shrimp.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/shrimp.jpg
new file mode 100644
index 00000000..2784bd16
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/shrimp.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/soda.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/soda.jpg
new file mode 100644
index 00000000..bc108a84
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/soda.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/strawberry.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/strawberry.jpg
new file mode 100644
index 00000000..11b341fc
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/strawberry.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/taro.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/taro.jpg
new file mode 100644
index 00000000..b016510b
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/taro.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/tea.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/tea.jpg
new file mode 100644
index 00000000..2e7c5c8c
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/tea.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/tomato.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/tomato.jpg
new file mode 100644
index 00000000..0ceb6f4f
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/tomato.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/udon.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/udon.jpg
new file mode 100644
index 00000000..ebd35d51
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/udon.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/assets/food/wonton.jpg b/TUICallKit-Vue3/tongji/endless-game/assets/food/wonton.jpg
new file mode 100644
index 00000000..b5876166
Binary files /dev/null and b/TUICallKit-Vue3/tongji/endless-game/assets/food/wonton.jpg differ
diff --git a/TUICallKit-Vue3/tongji/endless-game/components/TongjiIcon.vue b/TUICallKit-Vue3/tongji/endless-game/components/TongjiIcon.vue
new file mode 100644
index 00000000..056aae92
--- /dev/null
+++ b/TUICallKit-Vue3/tongji/endless-game/components/TongjiIcon.vue
@@ -0,0 +1,190 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/TUICallKit-Vue3/tongji/endless-game/composables/useGameSfx.js b/TUICallKit-Vue3/tongji/endless-game/composables/useGameSfx.js
new file mode 100644
index 00000000..2e49f363
--- /dev/null
+++ b/TUICallKit-Vue3/tongji/endless-game/composables/useGameSfx.js
@@ -0,0 +1,343 @@
+/**
+ * 糖分突袭 · 游戏音效(InnerAudioContext 池化,H5/小程序通用)
+ * 每种操作独立音轨 + playbackRate 变调,避免“全是同一个声”
+ */
+import { ref, onUnmounted } from 'vue'
+
+const STORAGE_KEY = 'tongji_game_sfx_enabled'
+const COS = 'https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file'
+
+/** 9 条基础素材(已上传 COS,小程序域名白名单内) */
+const SRC = {
+ tap: `${COS}/20260526/20260526105128557ef8669.mp3`,
+ swoosh: `${COS}/20260527/202605271539371ebe13672.mp3`,
+ pop: `${COS}/20260526/202605261051282a0a94508.mp3`,
+ buzz: `${COS}/20260527/202605271539376ff628472.mp3`,
+ chime: `${COS}/20260528/202605280937077bad76716.mp3`,
+ fanfare: `${COS}/20260528/20260528093707b021f5794.mp3`,
+ blast: `${COS}/20260528/20260528093707ebd4d5733.mp3`,
+ sparkle: `${COS}/20260528/20260528093707016f07427.mp3`,
+ stinger: `${COS}/20260528/20260528093709c669f4659.mp3`
+}
+
+/**
+ * 音效表:src / volume / rate(playbackRate) / pool
+ * rate 不同可在同一素材上听出明显差异
+ */
+const SFX = {
+ // —— 交互 ——
+ select: { src: SRC.tap, volume: 0.32, rate: 1.05, pool: 3 },
+ deselect: { src: SRC.tap, volume: 0.22, rate: 0.82, pool: 2 },
+ hint: { src: SRC.tap, volume: 0.26, rate: 1.28, pool: 2 },
+
+ swap: { src: SRC.swoosh, volume: 0.38, rate: 1.0, pool: 3 },
+ swapFail: { src: SRC.buzz, volume: 0.36, rate: 1.18, pool: 2 },
+
+ drop: { src: SRC.pop, volume: 0.3, rate: 0.88, pool: 4 },
+ dropLight: { src: SRC.pop, volume: 0.22, rate: 1.22, pool: 2 },
+
+ // —— 消除(按连数)——
+ match3: { src: SRC.chime, volume: 0.48, rate: 1.0, pool: 4 },
+ match4: { src: SRC.fanfare, volume: 0.54, rate: 1.06, pool: 3 },
+ match5: { src: SRC.blast, volume: 0.62, rate: 1.0, pool: 3 },
+
+ // —— 消除(按 GI 主色)——
+ matchLowGi: { src: SRC.chime, volume: 0.42, rate: 0.86, pool: 2 },
+ matchMidGi: { src: SRC.chime, volume: 0.46, rate: 1.02, pool: 2 },
+ matchHighGi: { src: SRC.buzz, volume: 0.44, rate: 0.92, pool: 2 },
+
+ // —— 连消 ——
+ combo2: { src: SRC.fanfare, volume: 0.56, rate: 1.12, pool: 2 },
+ combo3: { src: SRC.blast, volume: 0.64, rate: 1.08, pool: 2 },
+ comboMega: { src: SRC.blast, volume: 0.72, rate: 1.22, pool: 2 },
+
+ // —— 道具 ——
+ insulin: { src: SRC.sparkle, volume: 0.5, rate: 1.15, pool: 2 },
+ fiber: { src: SRC.swoosh, volume: 0.48, rate: 1.32, pool: 2 },
+ meal: { src: SRC.stinger, volume: 0.42, rate: 1.35, pool: 1 },
+ reshuffle: { src: SRC.swoosh, volume: 0.46, rate: 0.72, pool: 2 },
+
+ // —— 反馈 ——
+ score: { src: SRC.tap, volume: 0.28, rate: 1.38, pool: 2 },
+ goalTick: { src: SRC.chime, volume: 0.36, rate: 1.45, pool: 2 },
+ meterWarn: { src: SRC.buzz, volume: 0.5, rate: 1.0, pool: 2 },
+ meterDanger: { src: SRC.buzz, volume: 0.58, rate: 0.78, pool: 2 },
+ meterStable: { src: SRC.pop, volume: 0.24, rate: 1.05, pool: 1 },
+
+ win: { src: SRC.fanfare, volume: 0.66, rate: 1.0, pool: 1 },
+ winStinger: { src: SRC.stinger, volume: 0.38, rate: 1.5, pool: 1 },
+ lose: { src: SRC.buzz, volume: 0.52, rate: 0.68, pool: 1 },
+
+ // 兼容旧名
+ match: { src: SRC.chime, volume: 0.48, rate: 1.0, pool: 4 },
+ combo: { src: SRC.fanfare, volume: 0.56, rate: 1.1, pool: 3 },
+ heal: { src: SRC.pop, volume: 0.44, rate: 1.18, pool: 2 },
+ boost: { src: SRC.sparkle, volume: 0.52, rate: 1.0, pool: 2 },
+ invalid: { src: SRC.buzz, volume: 0.4, rate: 1.15, pool: 2 },
+ danger: { src: SRC.blast, volume: 0.58, rate: 0.95, pool: 2 }
+}
+
+const WARMUP_SFX = [
+ 'select', 'swap', 'swapFail', 'drop', 'dropLight',
+ 'match3', 'match4', 'match5', 'combo2', 'combo3', 'comboMega',
+ 'insulin', 'sparkle', 'reshuffle', 'lose'
+]
+
+function createAudioContext() {
+ if (typeof Audio !== 'undefined') {
+ const audio = new Audio()
+ audio.preload = 'auto'
+ return {
+ get src() { return audio.src },
+ set src(value) { audio.src = value },
+ get volume() { return audio.volume },
+ set volume(value) { audio.volume = value },
+ get playbackRate() { return audio.playbackRate },
+ set playbackRate(value) { audio.playbackRate = value },
+ play() {
+ const promise = audio.play()
+ if (promise?.catch) promise.catch(() => {})
+ },
+ stop() {
+ audio.pause()
+ try { audio.currentTime = 0 } catch (_) {}
+ },
+ seek(time) {
+ try { audio.currentTime = time } catch (_) {}
+ },
+ destroy() {
+ audio.pause()
+ audio.removeAttribute('src')
+ }
+ }
+ }
+ return uni.createInnerAudioContext()
+}
+
+class SfxPool {
+ constructor(src, size, volume, rate = 1) {
+ this.src = src
+ this.size = size
+ this.volume = volume
+ this.rate = rate
+ this.list = []
+ this.cursor = 0
+ this.warmedUp = false
+ }
+
+ createOne() {
+ const ctx = createAudioContext()
+ ctx.src = this.src
+ ctx.obeyMuteSwitch = false
+ ctx.autoplay = false
+ ctx.volume = this.volume
+ try {
+ ctx.playbackRate = this.rate
+ } catch (_) {}
+ this.list.push(ctx)
+ return ctx
+ }
+
+ create() {
+ while (this.list.length < this.size) this.createOne()
+ }
+
+ warmUp() {
+ if (this.warmedUp) return
+ const ctx = this.list[0] || this.createOne()
+ try {
+ ctx.volume = 0
+ ctx.play()
+ setTimeout(() => {
+ try {
+ ctx.stop()
+ ctx.volume = this.volume
+ } catch (_) {}
+ }, 60)
+ } catch (_) {}
+ if (this.size > 1) this.cursor = 1
+ this.warmedUp = true
+ }
+
+ play(scale = 1, rateMul = 1) {
+ if (this.list.length < this.size) this.create()
+ const ctx = this.list[this.cursor % this.list.length]
+ this.cursor += 1
+ const vol = Math.min(1, this.volume * scale)
+ const rate = Math.min(2, Math.max(0.5, this.rate * rateMul))
+ try {
+ ctx.volume = vol
+ try {
+ ctx.playbackRate = rate
+ } catch (_) {}
+ ctx.stop()
+ ctx.seek(0)
+ ctx.play()
+ } catch (_) {
+ try {
+ ctx.play()
+ } catch (e) {}
+ }
+ }
+
+ destroy() {
+ this.list.forEach((ctx) => {
+ try {
+ ctx.stop()
+ ctx.destroy()
+ } catch (_) {}
+ })
+ this.list = []
+ this.warmedUp = false
+ }
+}
+
+function dominantGi(tiles = []) {
+ const cnt = { low: 0, mid: 0, high: 0 }
+ tiles.forEach((t) => {
+ const g = t?.type?.gi
+ if (g && cnt[g] !== undefined) cnt[g] += 1
+ })
+ let best = 'mid'
+ let max = 0
+ Object.entries(cnt).forEach(([k, v]) => {
+ if (v > max) {
+ max = v
+ best = k
+ }
+ })
+ return best
+}
+
+export function useGameSfx() {
+ const enabled = ref(true)
+ const pools = {}
+
+ try {
+ const stored = uni.getStorageSync(STORAGE_KEY)
+ if (stored === false || stored === '0' || stored === 0) {
+ enabled.value = false
+ }
+ } catch (_) {}
+
+ function ensureAudioOption() {
+ // #ifdef MP-WEIXIN
+ try {
+ uni.setInnerAudioOption({
+ obeyMuteSwitch: false,
+ mixWithOther: true
+ })
+ } catch (_) {}
+ // #endif
+ }
+
+ function getPool(name) {
+ const cfg = SFX[name]
+ if (!cfg) return null
+ if (!pools[name]) {
+ pools[name] = new SfxPool(cfg.src, cfg.pool, cfg.volume, cfg.rate ?? 1)
+ }
+ return pools[name]
+ }
+
+ function warmUp() {
+ if (!enabled.value) return
+ ensureAudioOption()
+ const names = typeof Audio !== 'undefined' ? ['select'] : WARMUP_SFX
+ names.forEach((name) => {
+ getPool(name)?.warmUp()
+ })
+ }
+
+ function play(name, scale = 1, rateMul = 1) {
+ if (!enabled.value) return
+ getPool(name)?.play(scale, rateMul)
+ }
+
+ function playLayer(primary, secondary, delayMs = 70, secScale = 0.6) {
+ play(primary)
+ if (secondary) {
+ setTimeout(() => play(secondary, secScale), delayMs)
+ }
+ }
+
+ function playMatch({ matchCount = 3, chain = 0, tiles = [] } = {}) {
+ if (!enabled.value) return
+ const giTrack = (() => {
+ const gi = dominantGi(tiles)
+ if (gi === 'low') return 'matchLowGi'
+ if (gi === 'high') return 'matchHighGi'
+ return 'matchMidGi'
+ })()
+
+ if (chain >= 3) {
+ playLayer('comboMega', giTrack, 90, 0.55)
+ return
+ }
+ if (chain === 2) {
+ playLayer('combo3', giTrack, 80, 0.5)
+ return
+ }
+ if (chain === 1) {
+ playLayer('combo2', giTrack, 70, 0.45)
+ return
+ }
+
+ if (matchCount >= 5) {
+ playLayer('match5', 'matchHighGi', 85, 0.5)
+ } else if (matchCount >= 4) {
+ playLayer('match4', giTrack, 65, 0.48)
+ } else {
+ play(giTrack, 1, 1)
+ setTimeout(() => play('match3', 0.85), 40)
+ }
+ }
+
+ function playDrop(count = 1) {
+ if (!enabled.value || count <= 0) return
+ const name = count >= 4 ? 'drop' : 'dropLight'
+ play(name, Math.min(1.2, 0.85 + count * 0.04), count >= 6 ? 0.9 : 1)
+ }
+
+ function playMeter(delta, level) {
+ if (!enabled.value) return
+ if (level > 70) play('meterDanger', 1 + (level - 70) * 0.02)
+ else if (level < 30) play('meterWarn', 0.9, 0.95)
+ else if (delta > 8) play('meterWarn', 0.75)
+ else if (delta < -5) play('meterStable', 1.1)
+ }
+
+ function toggleEnabled() {
+ enabled.value = !enabled.value
+ try {
+ uni.setStorageSync(STORAGE_KEY, enabled.value ? '1' : '0')
+ } catch (_) {}
+ if (enabled.value) {
+ warmUp()
+ play('select')
+ }
+ }
+
+ function destroyAll() {
+ Object.keys(pools).forEach((key) => {
+ pools[key]?.destroy()
+ delete pools[key]
+ })
+ }
+
+ onUnmounted(() => {
+ destroyAll()
+ })
+
+ return {
+ enabled,
+ play,
+ playLayer,
+ playMatch,
+ playDrop,
+ playMeter,
+ warmUp,
+ toggleEnabled,
+ destroyAll
+ }
+}
diff --git a/TUICallKit-Vue3/tongji/endless-game/game-endless.scss b/TUICallKit-Vue3/tongji/endless-game/game-endless.scss
new file mode 100644
index 00000000..069de3fc
--- /dev/null
+++ b/TUICallKit-Vue3/tongji/endless-game/game-endless.scss
@@ -0,0 +1,559 @@
+.eg-page {
+ min-height: 100vh;
+ color: #173b32;
+ background: linear-gradient(180deg, #eefbf4 0%, #f9f3dd 56%, #fffaf1 100%);
+ font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
+}
+
+.eg-confetti-layer { position: fixed; z-index: 80; inset: 0; overflow: hidden; pointer-events: none; }
+.eg-confetti-piece {
+ position: absolute;
+ top: -70rpx;
+ display: block;
+ border-radius: 3rpx;
+ box-shadow: 0 2rpx 4rpx rgba(0,0,0,.12);
+ animation-name: egConfettiFall;
+ animation-timing-function: cubic-bezier(.18,.72,.35,1);
+ animation-fill-mode: both;
+}
+.eg-confetti-piece.is-emoji { width: auto !important; height: auto !important; background: transparent !important; box-shadow: none; font-size: 42rpx; }
+
+.eg-nav {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-right: 28rpx;
+ padding-bottom: 16rpx;
+ padding-left: 28rpx;
+}
+
+.eg-nav-btn {
+ display: flex;
+ width: 72rpx;
+ height: 72rpx;
+ align-items: center;
+ justify-content: center;
+ border: 2rpx solid rgba(21, 94, 75, .12);
+ border-radius: 24rpx;
+ background: rgba(255,255,255,.82);
+}
+
+.eg-nav-actions { display: flex; width: 156rpx; justify-content: flex-end; gap: 10rpx; }
+.eg-nav-btn--small { width: 68rpx; height: 68rpx; border-radius: 22rpx; }
+
+.eg-title-wrap { position: absolute; left: 50%; display: flex; flex-direction: column; align-items: center; transform: translateX(-50%); }
+.eg-title { color: #155e4b; font-size: 40rpx; font-weight: 800; }
+.eg-subtitle { margin-top: 2rpx; color: #648278; font-size: 24rpx; }
+.eg-content { padding: 0 16rpx 48rpx; }
+
+.eg-score-card {
+ display: flex;
+ align-items: center;
+ margin-bottom: 16rpx;
+ padding: 18rpx 24rpx;
+ border: 2rpx solid rgba(21, 94, 75, .1);
+ border-radius: 28rpx;
+ background: rgba(255,255,255,.9);
+ box-shadow: 0 10rpx 28rpx rgba(32, 78, 43, .08);
+}
+
+.eg-stat { display: flex; flex: 1; flex-direction: column; align-items: center; }
+.eg-stat--main { border-right: 2rpx solid #e2eee8; border-left: 2rpx solid #e2eee8; }
+.eg-stat-label { color: #71847d; font-size: 24rpx; }
+.eg-stat-value { margin-top: 4rpx; color: #204e2b; font-size: 38rpx; font-weight: 800; }
+.eg-stat-score { margin-top: 2rpx; color: #e16f24; font-size: 46rpx; font-weight: 900; }
+.eg-stat--rank { position: relative; cursor: pointer; }
+.eg-stat--rank.is-locked { opacity: .52; }
+.eg-stat-rank-value { margin-top: 1rpx; color: #176b52; font-size: 35rpx; font-weight: 1000; line-height: 1.08; }
+.eg-stat-rank-hint { margin-top: 2rpx; color: #df7427; font-size: 18rpx; font-weight: 800; }
+
+.eg-task-card {
+ margin-bottom: 16rpx;
+ padding: 18rpx 22rpx;
+ border-radius: 24rpx;
+ background: rgba(255, 249, 223, .94);
+ box-shadow: 0 8rpx 22rpx rgba(118, 89, 25, .08);
+}
+.eg-task-card { position: relative; overflow: visible; }
+
+.eg-task-reward-flight {
+ position: absolute;
+ z-index: 72;
+ top: 4rpx;
+ left: 42%;
+ display: flex;
+ width: 112rpx;
+ height: 92rpx;
+ align-items: center;
+ justify-content: center;
+ border: 4rpx solid rgba(255,255,255,.96);
+ border-radius: 24rpx;
+ background: #fff7e8;
+ box-shadow: 0 10rpx 24rpx rgba(159,94,23,.28);
+ animation: egTaskRewardFlight 1.12s cubic-bezier(.18,.78,.22,1) both;
+ pointer-events: none;
+}
+.eg-task-reward-flight > image { position: relative; z-index: 2; width: 78rpx; height: 66rpx; }
+.eg-task-reward-flight > text { position: absolute; z-index: 3; top: -14rpx; right: -16rpx; display: flex; width: 48rpx; height: 48rpx; align-items: center; justify-content: center; border: 4rpx solid #fff; border-radius: 50%; color: #fff; background: #ed7625; box-shadow: 0 5rpx 12rpx rgba(191,75,19,.3); font-size: 25rpx; font-weight: 1000; }
+.eg-task-reward-glow { position: absolute; z-index: 1; inset: -15rpx; border-radius: 32rpx; background: radial-gradient(circle, rgba(255,210,67,.52), rgba(255,210,67,0) 68%); animation: egRewardGlow .42s ease-in-out infinite alternate; }
+
+.eg-task-head { display: flex; align-items: center; justify-content: space-between; color: #7c5b1b; font-size: 27rpx; font-weight: 700; }
+.eg-task-name { display: flex; align-items: center; gap: 10rpx; }
+.eg-task-count { color: #9a6718; font-size: 29rpx; }
+.eg-progress { height: 14rpx; margin-top: 12rpx; overflow: hidden; border-radius: 999rpx; background: #eadfbd; }
+.eg-progress-fill { height: 100%; border-radius: inherit; background: linear-gradient(90deg, #f0a128, #f4c34f); transition: width .25s; }
+
+.eg-board-shell {
+ position: relative;
+ padding: 14rpx;
+ border: 2rpx solid rgba(34, 103, 79, .16);
+ border-radius: 34rpx;
+ background: rgba(255,255,255,.92);
+ box-shadow: 0 18rpx 44rpx rgba(29, 78, 59, .12);
+}
+
+.eg-board-top { display: flex; align-items: center; justify-content: space-between; margin: 0 4rpx 14rpx; }
+.eg-board-bonuses { display: flex; align-items: center; gap: 8rpx; }
+.eg-combo { padding: 8rpx 16rpx; border-radius: 999rpx; color: #45685d; background: #e8f3ee; font-size: 25rpx; font-weight: 700; }
+.eg-combo.is-hot { color: #fff; background: linear-gradient(135deg, #f59e0b, #ea580c); }
+.eg-double-state { padding: 8rpx 14rpx; border: 2rpx solid rgba(255,255,255,.9); border-radius: 999rpx; color: #fff; background: linear-gradient(135deg, #7c3aed, #d946ef); box-shadow: 0 4rpx 12rpx rgba(124,58,237,.24); font-size: 23rpx; font-weight: 900; white-space: nowrap; }
+.eg-risk { display: flex; align-items: center; gap: 10rpx; color: #8d512c; font-size: 24rpx; }
+.eg-risk-dots { display: flex; gap: 5rpx; }
+.eg-risk-dot { width: 12rpx; height: 12rpx; border-radius: 50%; background: #ead8c9; }
+.eg-risk-dot.on { background: #e96b3b; box-shadow: 0 0 0 3rpx rgba(233,107,59,.12); }
+.eg-board { display: flex; flex-direction: column; gap: 8rpx; opacity: 1; transition: opacity .15s; }
+.eg-board.is-busy { opacity: .82; }
+.eg-row { display: flex; gap: 8rpx; }
+
+.eg-cell {
+ position: relative;
+ display: flex;
+ height: 166rpx;
+ flex: 1;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ border: 5rpx solid transparent;
+ border-radius: 26rpx;
+ box-shadow: inset 0 -5rpx 0 rgba(0,0,0,.05), 0 5rpx 12rpx rgba(31, 66, 53, .08);
+ transition: transform .16s cubic-bezier(.2,.8,.2,1), border-color .15s, opacity .16s, filter .16s;
+ will-change: transform;
+}
+
+.eg-cell.is-dragging { transition: transform .055s linear, border-color .15s; }
+.eg-cell.is-selected { z-index: 2; border-color: #167d61; transform: scale(1.06); box-shadow: 0 0 0 6rpx rgba(22,125,97,.14); }
+.eg-cell.is-camel-target { border-color: #f59e0b; animation: egPulse 1s infinite; }
+.eg-replace-target { position: absolute; z-index: 7; right: 7rpx; bottom: 6rpx; padding: 4rpx 10rpx; border: 2rpx solid #fff; border-radius: 999rpx; color: #fff; background: #1570a6; box-shadow: 0 3rpx 9rpx rgba(20,91,135,.28); font-size: 20rpx; font-weight: 900; }
+.eg-cell.is-clearing {
+ z-index: 8;
+ border-color: #fff;
+ animation: egHit .22s cubic-bezier(.2,.9,.3,1) forwards;
+ filter: brightness(1.28) saturate(1.25);
+}
+.eg-cell.is-yellow,
+.eg-cell.is-cream,
+.eg-cell.is-red,
+.eg-cell.is-purple { background: #fffaf0; }
+.eg-cell.is-level-low { background: #f4faf6; }
+.eg-cell.is-level-mid { background: #fff9eb; }
+.eg-cell.is-orange,
+.eg-cell.is-level-high { background: #fff0e4; }
+.eg-cell.is-milk { background: #edf7ff; }
+.eg-cell.is-high { border-color: #ef7b45; }
+.eg-food-image { width: 132rpx; height: 92rpx; margin-top: 7rpx; border-radius: 17rpx; filter: saturate(1.06) contrast(1.03); }
+.eg-food-name { margin-top: 8rpx; color: #203d34; font-size: 29rpx; font-weight: 900; letter-spacing: 1rpx; line-height: 1.1; }
+.eg-sugar-tag { position: absolute; top: 5rpx; right: 5rpx; min-width: 52rpx; padding: 4rpx 10rpx; border: 2rpx solid #fff; border-radius: 999rpx; color: #fff; box-shadow: 0 3rpx 8rpx rgba(25,55,45,.22); font-size: 23rpx; font-weight: 900; line-height: 1.25; text-align: center; }
+.eg-sugar-tag.is-low { color: #316a54; border-color: rgba(255,255,255,.82); background: #dcefe6; box-shadow: 0 2rpx 5rpx rgba(32,101,75,.1); }
+.eg-sugar-tag.is-mid { color: #735a15; border-color: rgba(255,255,255,.9); background: #f7e7a8; box-shadow: 0 2rpx 6rpx rgba(137,91,0,.15); }
+.eg-sugar-tag.is-high { background: #d9431f; box-shadow: 0 4rpx 10rpx rgba(163,45,21,.32); }
+.eg-sugar-tag.is-prop { background: #2563a8; }
+.eg-special-mark { position: absolute; z-index: 12; bottom: 5rpx; left: 6rpx; display: flex; width: 40rpx; height: 40rpx; align-items: center; justify-content: center; border: 3rpx solid rgba(255,255,255,.96); border-radius: 50%; color: #fff; background: #6d28d9; box-shadow: 0 4rpx 11rpx rgba(73,31,130,.3); font-size: 23rpx; font-weight: 900; }
+.eg-special-mark.is-row { width: 58rpx; height: 40rpx; border-radius: 999rpx; background: linear-gradient(135deg, #ffd64d, #ff8a18 48%, #e83b22); box-shadow: 0 0 0 4rpx rgba(255,172,30,.2), 0 4rpx 15rpx rgba(203,59,26,.45); animation: egFlameReady .7s ease-in-out infinite alternate; }
+.eg-special-mark.is-col { width: 40rpx; height: 58rpx; border-radius: 999rpx; background: linear-gradient(180deg, #ffd64d, #ff8a18 48%, #e83b22); box-shadow: 0 0 0 4rpx rgba(255,172,30,.2), 0 4rpx 15rpx rgba(203,59,26,.45); animation: egFlameReady .7s ease-in-out infinite alternate; }
+.eg-special-mark.is-burst { width: 48rpx; height: 48rpx; border-color: #fff3a8; border-radius: 15rpx 50% 50%; background: linear-gradient(145deg, #7c3aed 5%, #dd3b70 48%, #ff8a18 100%); box-shadow: 0 4rpx 13rpx rgba(108,35,155,.38), 0 0 12rpx rgba(255,179,38,.3); animation: egLReady 1.25s ease-in-out infinite alternate; }
+.eg-mini-flame { position: relative; width: 25rpx; height: 30rpx; border-radius: 70% 32% 68% 40%; background: linear-gradient(135deg, #fff8a8 8%, #ffc21f 42%, #f04420 84%); box-shadow: 0 0 14rpx rgba(255,230,92,.95); transform: rotate(43deg); }
+.eg-mini-flame-core { position: absolute; right: 4rpx; bottom: 3rpx; width: 10rpx; height: 15rpx; border-radius: 70% 35% 70% 42%; background: #fffbd1; box-shadow: 0 0 7rpx rgba(255,255,210,.95); }
+.eg-l-special { position: relative; width: 29rpx; height: 29rpx; filter: drop-shadow(0 1rpx 2rpx rgba(73,22,105,.3)); }
+.eg-l-arm { position: absolute; left: 3rpx; bottom: 3rpx; border-radius: 999rpx; background: #fff9b8; box-shadow: 0 0 6rpx rgba(255,249,184,.92); }
+.eg-l-arm.is-vertical { width: 8rpx; height: 25rpx; }
+.eg-l-arm.is-horizontal { width: 25rpx; height: 8rpx; }
+.eg-l-core { position: absolute; left: 1rpx; bottom: 1rpx; width: 12rpx; height: 12rpx; border: 2rpx solid #fff; border-radius: 50%; background: #ff5b25; }
+.eg-cell.is-fire-clearing { border-color: #ff7a1a; box-shadow: inset 0 0 25rpx rgba(255,102,18,.36), 0 0 18rpx rgba(255,105,20,.48); filter: brightness(1.35) saturate(1.42); }
+.eg-cell.is-clearing.is-fire-clearing { animation: egFireCellHit .36s cubic-bezier(.2,.78,.22,1) forwards; }
+.eg-fire-clear { position: absolute; z-index: 14; inset: 2rpx; overflow: hidden; border: 3rpx solid rgba(255,210,71,.92); border-radius: 22rpx; opacity: .96; background: linear-gradient(180deg, rgba(255,230,86,.12), rgba(239,63,27,.22)); pointer-events: none; }
+.eg-fire-glow { position: absolute; inset: 6%; border-radius: 18rpx; background: radial-gradient(circle, rgba(255,246,177,.74), rgba(255,131,22,.32) 47%, rgba(222,47,23,0) 75%); animation: egFireGlow .34s ease-out both; }
+.eg-fire-sweep { position: absolute; top: 23%; left: -48%; width: 168%; height: 55%; border-radius: 60% 45% 55% 42%; background: linear-gradient(90deg, rgba(255,184,38,0), rgba(255,238,124,.94) 28%, rgba(255,133,22,.92) 53%, rgba(226,54,25,.82) 72%, rgba(255,184,38,0)); box-shadow: 0 0 27rpx rgba(255,91,17,.72); animation: egFireSweep .34s cubic-bezier(.2,.78,.22,1) both; }
+.eg-fire-flame { position: absolute; bottom: 13%; width: 25rpx; height: 39rpx; border-radius: 72% 35% 68% 42%; background: linear-gradient(145deg, #fff7a6 4%, #ffc329 39%, #ff7419 66%, #dc321f 100%); box-shadow: 0 0 15rpx rgba(255,124,21,.88); transform: rotate(42deg); animation: egFireFlame .34s ease-out both; }
+.eg-fire-flame > view { position: absolute; right: 5rpx; bottom: 4rpx; width: 10rpx; height: 18rpx; border-radius: 70% 35% 70% 42%; background: #fffbd6; }
+.eg-fire-flame.is-left { left: 16%; animation-delay: .01s; }
+.eg-fire-flame.is-center { left: 43%; bottom: 19%; transform: rotate(42deg) scale(1.18); animation-delay: .035s; }
+.eg-fire-flame.is-right { right: 15%; animation-delay: .065s; }
+.eg-fire-spark { position: absolute; width: 10rpx; height: 17rpx; border-radius: 70% 35% 70% 42%; background: #ffe45e; box-shadow: 0 0 11rpx rgba(255,139,28,.9); animation: egFireSpark .34s ease-out both; }
+.eg-fire-spark.is-one { top: 46%; left: 25%; }
+.eg-fire-spark.is-two { top: 34%; left: 62%; animation-delay: .035s; }
+.eg-fire-spark.is-three { top: 52%; left: 78%; animation-delay: .07s; }
+.eg-board-tip { display: flex; align-items: center; justify-content: center; gap: 8rpx; min-height: 58rpx; margin-top: 12rpx; color: #684817; font-size: 26rpx; font-weight: 700; text-align: center; }
+.eg-impact-text {
+ position: absolute;
+ z-index: 20;
+ top: 46%;
+ left: 50%;
+ padding: 10rpx 24rpx;
+ border: 4rpx solid rgba(255,255,255,.9);
+ border-radius: 999rpx;
+ color: #fff;
+ background: linear-gradient(135deg, #f59e0b, #e4572e);
+ box-shadow: 0 10rpx 28rpx rgba(190, 70, 25, .35);
+ font-size: 34rpx;
+ font-weight: 900;
+ transform: translate(-50%, -50%);
+ animation: egImpact .52s cubic-bezier(.16,.82,.3,1) forwards;
+ pointer-events: none;
+}
+.eg-impact-text.is-level-2 { font-size: 40rpx; background: linear-gradient(135deg, #8b5cf6, #ec4899); }
+.eg-impact-text.is-level-3 { font-size: 46rpx; background: linear-gradient(135deg, #ef4444, #f59e0b); box-shadow: 0 12rpx 36rpx rgba(239,68,68,.42); }
+.eg-jackpot {
+ position: absolute;
+ z-index: 30;
+ top: 48%;
+ left: 50%;
+ width: 540rpx;
+ padding: 24rpx 22rpx 20rpx;
+ overflow: hidden;
+ border: 7rpx solid #ffd83d;
+ border-radius: 36rpx;
+ color: #fff;
+ background: radial-gradient(circle at 50% 0%, #ef4444, #9f1239 65%, #64102a);
+ box-shadow: 0 0 0 7rpx #fff2a8, 0 22rpx 52rpx rgba(100,16,42,.5);
+ text-align: center;
+ transform: translate(-50%, -50%);
+ animation: egJackpotIn 1.35s cubic-bezier(.16,.85,.25,1) forwards;
+ pointer-events: none;
+}
+.eg-jackpot.is-match-4 { background: radial-gradient(circle at 50% 0%, #8b5cf6, #5b21b6 68%, #35106d); }
+.eg-jackpot.is-match-5 { border-color: #fff36a; background: radial-gradient(circle at 50% 0%, #ff6b24, #d7193f 58%, #760c2a); animation-duration: 1.9s; }
+.eg-jackpot.is-match-3 {
+ width: 414rpx;
+ padding: 20rpx 22rpx 18rpx;
+ border-width: 4rpx;
+ border-color: #f1c663;
+ color: #31574b;
+ background: linear-gradient(155deg, #f7fff9, #fff5d9);
+ box-shadow: 0 0 0 4rpx rgba(255,255,255,.84), 0 14rpx 34rpx rgba(98,82,39,.2);
+ animation: egTripleIn 1.12s cubic-bezier(.2,.82,.28,1) forwards;
+}
+.eg-jackpot.is-match-3 .eg-jackpot-lights { display: none; }
+.eg-jackpot.is-match-3 .eg-jackpot-kicker { color: #5b8c73; font-size: 21rpx; letter-spacing: 3rpx; text-shadow: none; }
+.eg-jackpot.is-match-3 .eg-jackpot-title { color: #365b4e; font-size: 31rpx; text-shadow: none; }
+.eg-jackpot.is-match-3 .eg-jackpot-reward { color: #ad7021; font-size: 25rpx; }
+.eg-jackpot.is-match-3 .eg-jackpot-note { color: #829087; }
+.eg-triple-cans { position: relative; display: flex; justify-content: center; gap: 12rpx; margin: 12rpx 0 10rpx; }
+.eg-triple-can { display: flex; width: 78rpx; height: 76rpx; align-items: center; justify-content: center; border: 3rpx solid #f4d996; border-radius: 19rpx; background: rgba(255,255,255,.9); box-shadow: 0 5rpx 12rpx rgba(128,92,30,.12); animation: egTripleCan .42s cubic-bezier(.2,.86,.26,1) both; }
+.eg-triple-can:nth-child(2) { animation-delay: .07s; }
+.eg-triple-can:nth-child(3) { animation-delay: .14s; }
+.eg-triple-can > image { width: 62rpx; height: 58rpx; }
+
+.eg-jackpot.is-match-4 {
+ width: 548rpx;
+ padding: 25rpx 22rpx 22rpx;
+ border-color: #ffd95b;
+ background: radial-gradient(circle at 50% -10%, #a97cff, #6031b5 58%, #341066);
+ box-shadow: 0 0 0 6rpx #fff0a9, 0 0 36rpx rgba(255,210,76,.5), 0 24rpx 58rpx rgba(53,16,109,.48);
+ animation: egFourIn 1.88s cubic-bezier(.16,.84,.24,1) forwards;
+}
+.eg-jackpot.is-match-4 .eg-jackpot-kicker { color: #ffef8d; font-size: 24rpx; letter-spacing: 3rpx; }
+.eg-jackpot.is-match-4 .eg-jackpot-title { margin-top: 9rpx; color: #fff; font-size: 39rpx; }
+.eg-jackpot.is-match-4 .eg-jackpot-reward { color: #fff2a8; }
+.eg-four-teaser { position: relative; margin-top: 13rpx; }
+.eg-four-cans { display: flex; align-items: center; justify-content: center; gap: 8rpx; }
+.eg-four-can { display: flex; width: 76rpx; height: 92rpx; align-items: center; justify-content: center; border: 4rpx solid #ffe078; border-radius: 18rpx; background: linear-gradient(180deg, #fffef7, #ffe9a5); box-shadow: inset 0 -5rpx 0 rgba(171,106,17,.12), 0 7rpx 13rpx rgba(34,12,67,.28); animation: egFourCan .48s cubic-bezier(.18,.9,.25,1.2) both; }
+.eg-four-can:nth-child(2) { animation-delay: .07s; }
+.eg-four-can:nth-child(3) { animation-delay: .14s; }
+.eg-four-can:nth-child(4) { animation-delay: .21s; }
+.eg-four-can > image { width: 64rpx; height: 70rpx; }
+.eg-four-can.is-locked { border-style: dashed; border-color: rgba(255,241,161,.86); color: #fff6ae; background: rgba(48,18,91,.54); box-shadow: inset 0 0 16rpx rgba(255,225,91,.18), 0 0 18rpx rgba(255,222,91,.36); font-size: 48rpx; font-weight: 1000; animation: egLockedCan .68s .34s ease-in-out infinite alternate; }
+.eg-four-promise { display: inline-flex; margin-top: 15rpx; padding: 8rpx 18rpx; border: 2rpx solid rgba(255,247,184,.72); border-radius: 999rpx; color: #3d176f; background: linear-gradient(90deg, #fff3a3, #ffd85b); box-shadow: 0 5rpx 15rpx rgba(28,7,62,.22); font-size: 25rpx; font-weight: 1000; animation: egPromisePulse .72s ease-in-out infinite alternate; }
+.eg-five-celebration { position: relative; margin: 15rpx 0 10rpx; }
+.eg-five-cans { display: flex; align-items: center; justify-content: center; gap: 8rpx; }
+.eg-five-can { display: flex; width: 78rpx; height: 93rpx; align-items: center; justify-content: center; border: 4rpx solid #fff17b; border-radius: 18rpx; background: linear-gradient(180deg, #fff, #fff2ac); box-shadow: inset 0 -5rpx 0 rgba(184,91,12,.13), 0 7rpx 0 #bd5b13, 0 0 16rpx rgba(255,240,101,.5); animation: egFiveCan .56s cubic-bezier(.16,.92,.24,1.2) both; }
+.eg-five-can:nth-child(2) { animation-delay: .07s; }
+.eg-five-can:nth-child(3) { animation-delay: .14s; }
+.eg-five-can:nth-child(4) { animation-delay: .21s; }
+.eg-five-can:nth-child(5) { animation-delay: .28s; }
+.eg-five-can > image { width: 65rpx; height: 72rpx; }
+.eg-five-sevens { display: flex; justify-content: center; gap: 8rpx; margin-top: 13rpx; }
+.eg-five-sevens > text { display: flex; width: 58rpx; height: 55rpx; align-items: center; justify-content: center; border: 3rpx solid #fff5a7; border-radius: 13rpx; color: #fff36a; background: linear-gradient(155deg, #d7193f, #9f1239); box-shadow: 0 5rpx 0 #70102b, 0 0 13rpx rgba(255,243,106,.45); font-size: 43rpx; font-weight: 1000; line-height: 1; text-shadow: 0 3rpx 0 #8f1733; }
+.eg-jackpot-lights { position: absolute; inset: 8rpx; border: 4rpx dotted rgba(255,255,255,.88); border-radius: 25rpx; animation: egLights .24s steps(2) infinite; }
+.eg-jackpot-kicker { position: relative; display: block; color: #fff36a; font-size: 28rpx; font-weight: 900; letter-spacing: 6rpx; text-shadow: 0 3rpx 0 rgba(83,19,25,.45); }
+.eg-jackpot-reels { position: relative; display: flex; justify-content: center; gap: 12rpx; margin: 13rpx 0; }
+.eg-jackpot-reel { display: flex; width: 112rpx; height: 126rpx; flex-direction: column; align-items: center; justify-content: center; border: 6rpx solid #ffcf31; border-radius: 20rpx; background: linear-gradient(180deg, #fff 0%, #fff7cf 48%, #ffd55e 50%, #fff 53%, #fff8dc 100%); box-shadow: inset 0 0 18rpx rgba(137,78,10,.25), 0 7rpx 0 #a85a0a; animation: egReelStop .52s cubic-bezier(.2,.9,.25,1) both; }
+.eg-jackpot-reel:nth-child(2) { animation-delay: .09s; }
+.eg-jackpot-reel:nth-child(3) { animation-delay: .18s; }
+.eg-jackpot-seven { color: #e11d48; font-size: 61rpx; font-weight: 1000; line-height: .85; text-shadow: 0 3rpx 0 #ffd1d8; }
+.eg-jackpot-milk { width: 54rpx; height: 36rpx; margin-top: 3rpx; border-radius: 7rpx; }
+.eg-jackpot-title { position: relative; display: block; font-size: 37rpx; font-weight: 900; text-shadow: 0 4rpx 0 rgba(73,10,30,.5); }
+.eg-jackpot-points { position: relative; display: block; margin-top: 2rpx; color: #fff36a; font-size: 66rpx; font-weight: 1000; line-height: 1.05; letter-spacing: 2rpx; text-shadow: 0 5rpx 0 #9e2813, 0 0 18rpx rgba(255,243,106,.85); animation: egPointsPop .62s .42s cubic-bezier(.16,.9,.25,1.25) both; }
+.eg-jackpot-reward { position: relative; display: block; margin-top: 7rpx; color: #fff5a5; font-size: 27rpx; font-weight: 800; line-height: 1.35; }
+.eg-jackpot-note { position: relative; display: block; margin-top: 8rpx; color: rgba(255,255,255,.8); font-size: 22rpx; }
+.eg-tools { margin-top: 16rpx; }
+.eg-tool { display: flex; align-items: center; padding: 16rpx 18rpx; border: 3rpx solid #c9e0d6; border-radius: 26rpx; background: rgba(255,255,255,.92); box-shadow: 0 7rpx 18rpx rgba(24, 84, 63, .07); }
+.eg-tool.active { border-color: #f59e0b; background: #fffbeb; }
+.eg-tool.disabled { opacity: .45; }
+.eg-tool-icon { position: relative; display: flex; width: 88rpx; height: 88rpx; align-items: center; justify-content: center; overflow: visible; border-radius: 24rpx; font-size: 46rpx; background: linear-gradient(145deg, #e8f7ff, #d4ecff); box-shadow: inset 0 -4rpx 0 rgba(33,104,151,.08); }
+.eg-tool-food-image { width: 76rpx; height: 58rpx; border-radius: 14rpx; }
+.eg-replace-badge { position: absolute; right: -12rpx; bottom: -8rpx; display: flex; min-width: 78rpx; height: 38rpx; align-items: center; justify-content: center; gap: 2rpx; padding: 0 8rpx; border: 3rpx solid #fff; border-radius: 999rpx; color: #fff; background: linear-gradient(135deg, #2389b9, #145d91); box-shadow: 0 5rpx 12rpx rgba(20,93,145,.3); }
+.eg-replace-badge .tj-icon-wrap { width: 22rpx; height: 22rpx; }
+.eg-replace-badge > text { font-size: 20rpx; font-weight: 900; line-height: 1; }
+.eg-tool-copy { display: flex; flex: 1; flex-direction: column; margin-left: 16rpx; }
+.eg-tool-name { color: #244a3d; font-size: 29rpx; font-weight: 800; }
+.eg-tool-desc { margin-top: 5rpx; color: #6c8179; font-size: 25rpx; }
+.eg-tool-count { color: #155e4b; font-size: 32rpx; font-weight: 900; }
+.eg-legend { display: flex; justify-content: center; gap: 18rpx; margin-top: 18rpx; color: #53685f; font-size: 25rpx; font-weight: 700; }
+.eg-dot--safe { color: #4cae7b; }
+.eg-dot--mid { color: #e2aa19; }
+.eg-dot--high { color: #e4572e; }
+
+.eg-weekly-overlay { position: fixed; z-index: 90; inset: 0; display: flex; align-items: center; justify-content: center; padding: 28rpx; background: rgba(16, 55, 43, .66); backdrop-filter: blur(5px); }
+.eg-weekly-card { width: 100%; max-width: 664rpx; max-height: calc(100vh - 56rpx); overflow-y: auto; padding: 28rpx 26rpx 22rpx; border: 5rpx solid rgba(255,255,255,.96); border-radius: 38rpx; background: linear-gradient(160deg, #f5fff9 0%, #fffdf3 56%, #fff4db 100%); box-shadow: 0 30rpx 80rpx rgba(5,45,32,.38); animation: egWeeklyIn .36s cubic-bezier(.18,.88,.27,1.08) both; }
+.eg-weekly-head { display: flex; align-items: center; justify-content: space-between; }
+.eg-weekly-title-line { display: flex; align-items: center; gap: 10rpx; }
+.eg-weekly-title { color: #155e4b; font-size: 37rpx; font-weight: 1000; }
+.eg-weekly-preview { padding: 5rpx 10rpx; border-radius: 999rpx; color: #9a5b14; background: #fff0bd; font-size: 19rpx; font-weight: 900; }
+.eg-weekly-sub { display: block; margin-top: 5rpx; color: #6b837a; font-size: 23rpx; font-weight: 700; }
+.eg-weekly-medal { display: flex; width: 76rpx; height: 76rpx; align-items: center; justify-content: center; border: 5rpx solid #fff3b2; border-radius: 50%; color: #fff; background: linear-gradient(145deg, #f5ad24, #e26925); box-shadow: 0 8rpx 0 #b85421, 0 12rpx 24rpx rgba(181,84,33,.24); font-size: 39rpx; font-weight: 1000; }
+.eg-weekly-progress-card { display: flex; align-items: center; justify-content: space-between; margin-top: 20rpx; padding: 16rpx 18rpx; border: 3rpx solid #d7ebe2; border-radius: 24rpx; background: rgba(255,255,255,.9); }
+.eg-weekly-progress-label { display: block; color: #657b73; font-size: 22rpx; font-weight: 700; }
+.eg-weekly-progress-value { display: block; margin-top: -2rpx; color: #e06b24; font-size: 43rpx; font-weight: 1000; }
+.eg-weekly-progress-copy { display: flex; flex-direction: column; align-items: flex-end; color: #235b49; font-size: 24rpx; font-weight: 900; line-height: 1.55; }
+.eg-weekly-progress-copy text:last-child { color: #b86820; }
+.eg-weekly-list { display: flex; flex-direction: column; gap: 7rpx; margin-top: 15rpx; }
+.eg-weekly-row { display: flex; min-height: 68rpx; align-items: center; padding: 7rpx 14rpx; border: 2rpx solid transparent; border-radius: 19rpx; color: #345c50; background: rgba(255,255,255,.72); }
+.eg-weekly-row.is-me { border-color: #f2bd48; color: #174f3e; background: linear-gradient(90deg, #fff2bb, #fff9e3); box-shadow: 0 5rpx 14rpx rgba(153,102,17,.12); transform: scale(1.015); }
+.eg-weekly-place { width: 43rpx; color: #6b7d76; font-size: 27rpx; font-weight: 1000; text-align: center; }
+.eg-weekly-row:nth-child(1) .eg-weekly-place { color: #dd7b17; font-size: 31rpx; }
+.eg-weekly-avatar { display: flex; width: 52rpx; height: 52rpx; flex: 0 0 52rpx; align-items: center; justify-content: center; margin-left: 6rpx; border: 3rpx solid rgba(255,255,255,.9); border-radius: 50%; color: #fff; background: #5d9b84; box-shadow: 0 3rpx 9rpx rgba(30,78,61,.16); font-size: 24rpx; font-weight: 1000; }
+.eg-weekly-avatar.is-tone-2 { background: #e39932; }
+.eg-weekly-avatar.is-tone-3 { background: #6c83c8; }
+.eg-weekly-avatar.is-tone-4 { background: #e5683e; }
+.eg-weekly-avatar.is-tone-5 { background: #a673bd; }
+.eg-weekly-avatar.is-tone-6 { background: #448e9c; }
+.eg-weekly-avatar.is-tone-7 { background: #c27b49; }
+.eg-weekly-name { flex: 1; margin-left: 13rpx; overflow: hidden; font-size: 27rpx; font-weight: 850; text-overflow: ellipsis; white-space: nowrap; }
+.eg-weekly-me { margin-right: 8rpx; padding: 3rpx 9rpx; border-radius: 999rpx; color: #fff; background: #dd6b27; font-size: 18rpx; font-weight: 900; }
+.eg-weekly-count { min-width: 70rpx; color: #315b4d; font-size: 29rpx; font-weight: 1000; text-align: right; }
+.eg-cheer-card { margin-top: 16rpx; padding: 15rpx 17rpx 14rpx; border: 3rpx solid #f0d38d; border-radius: 23rpx; background: linear-gradient(135deg, #fff9d9, #fff1c2); }
+.eg-cheer-card.is-family { border-color: #d9c8ef; background: linear-gradient(135deg, #f8f2ff, #efe7ff); }
+.eg-cheer-card.is-bright { border-color: #f4c77b; background: linear-gradient(135deg, #fff8dc, #ffeec7); }
+.eg-cheer-top { display: flex; align-items: center; justify-content: space-between; }
+.eg-cheer-source { color: #8e5c18; font-size: 21rpx; font-weight: 900; }
+.eg-cheer-card.is-family .eg-cheer-source { color: #77539c; }
+.eg-cheer-change { padding: 5rpx 12rpx; border-radius: 999rpx; color: #176b52; background: rgba(255,255,255,.75); font-size: 19rpx; font-weight: 900; }
+.eg-cheer-copy { display: block; margin-top: 7rpx; color: #55462d; font-size: 28rpx; font-weight: 900; line-height: 1.42; }
+.eg-cheer-note { display: block; margin-top: 5rpx; color: #8d7c77; font-size: 18rpx; }
+.eg-weekly-start { display: flex; height: 84rpx; align-items: center; justify-content: center; margin-top: 18rpx; border-radius: 24rpx; color: #fff; background: linear-gradient(135deg, #24906c, #12654a); box-shadow: 0 9rpx 20rpx rgba(18,101,74,.24); font-size: 30rpx; font-weight: 1000; }
+.eg-weekly-footnote { display: block; margin-top: 10rpx; color: #8b8b7d; font-size: 18rpx; text-align: center; }
+
+.eg-stage-overlay { position: fixed; z-index: 58; inset: 0; display: flex; align-items: center; justify-content: center; padding: 40rpx; background: rgba(22,62,50,.42); pointer-events: none; }
+.eg-stage-card { width: 100%; max-width: 620rpx; padding: 34rpx 28rpx 30rpx; border: 6rpx solid #fff; border-radius: 38rpx; background: linear-gradient(155deg, #f5fff9, #fff8da); box-shadow: 0 26rpx 70rpx rgba(10,56,41,.3); text-align: center; animation: egStageIn 1.8s cubic-bezier(.16,.86,.28,1) both; }
+.eg-stage-kicker { display: block; color: #16815f; font-size: 23rpx; font-weight: 900; letter-spacing: 3rpx; }
+.eg-stage-number { display: block; margin-top: 5rpx; color: #e47722; font-size: 48rpx; font-weight: 1000; line-height: 1.1; }
+.eg-stage-title { display: block; margin-top: 7rpx; color: #194e3d; font-size: 38rpx; font-weight: 900; }
+.eg-stage-foods { display: flex; justify-content: center; gap: 9rpx; margin-top: 22rpx; }
+.eg-stage-food { display: flex; width: 92rpx; min-height: 110rpx; flex-direction: column; align-items: center; justify-content: center; border: 3rpx solid #dbece4; border-radius: 20rpx; color: #315b4e; background: #fff; font-size: 20rpx; font-weight: 800; }
+.eg-stage-food > image { width: 72rpx; height: 52rpx; border-radius: 12rpx; }
+.eg-stage-food > text { margin-top: 8rpx; }
+.eg-stage-food.is-danger { border-color: #ef8b52; color: #a43c20; background: #fff1e6; box-shadow: 0 0 0 4rpx rgba(239,139,82,.12); }
+.eg-stage-tip { display: block; margin-top: 20rpx; color: #6e776d; font-size: 21rpx; font-weight: 700; }
+.eg-reshuffle-overlay { position: fixed; z-index: 65; inset: 0; display: flex; align-items: center; justify-content: center; padding: 46rpx; background: rgba(19,55,45,.5); }
+.eg-reshuffle-card { width: 100%; max-width: 540rpx; padding: 38rpx 30rpx; border: 5rpx solid rgba(255,255,255,.95); border-radius: 36rpx; background: linear-gradient(155deg, #f4fff9, #e6f6ef); box-shadow: 0 24rpx 64rpx rgba(10,48,37,.3); text-align: center; }
+.eg-reshuffle-icon { display: flex; width: 94rpx; height: 94rpx; align-items: center; justify-content: center; margin: 0 auto 18rpx; border-radius: 50%; background: linear-gradient(135deg, #2aa87b, #126a50); box-shadow: 0 10rpx 24rpx rgba(18,106,80,.28); animation: egReshuffleSpin .85s linear infinite; }
+.eg-reshuffle-title { display: block; color: #174e3d; font-size: 36rpx; font-weight: 900; }
+.eg-reshuffle-desc { display: block; margin-top: 10rpx; color: #5b746b; font-size: 24rpx; font-weight: 700; }
+
+.eg-overlay { position: fixed; z-index: 50; top: 0; right: 0; bottom: 0; left: 0; display: flex; align-items: center; justify-content: center; padding: 36rpx; background: rgba(20, 48, 40, .62); }
+.eg-modal { width: 100%; max-width: 650rpx; padding: 34rpx 28rpx 28rpx; border-radius: 38rpx; background: #fffdf8; box-shadow: 0 28rpx 70rpx rgba(0,0,0,.22); }
+.eg-modal-icon { display: block; font-size: 68rpx; text-align: center; }
+.eg-modal-title { display: block; margin-top: 6rpx; color: #204e3d; font-size: 38rpx; font-weight: 900; text-align: center; }
+.eg-modal-sub { display: block; margin: 8rpx 0 22rpx; color: #718078; font-size: 24rpx; text-align: center; }
+.eg-result-warning { display: flex; width: 92rpx; height: 92rpx; align-items: center; justify-content: center; margin: 0 auto; border-radius: 50%; color: #fff; background: #e4572e; font-size: 60rpx; font-weight: 900; }
+.eg-result-food { display: block; margin: 12rpx 0 18rpx; color: #c34b27; font-size: 28rpx; font-weight: 800; text-align: center; }
+.eg-knowledge-card { padding: 22rpx; border-radius: 24rpx; background: #fff3e7; }
+.eg-knowledge-title { display: block; color: #8d4427; font-size: 26rpx; font-weight: 800; }
+.eg-knowledge-copy { display: block; margin-top: 9rpx; color: #5f514a; font-size: 23rpx; line-height: 1.65; }
+.eg-knowledge-review { display: block; margin-top: 12rpx; color: #9b7768; font-size: 19rpx; }
+.eg-result-score { display: flex; align-items: center; justify-content: space-between; margin: 20rpx 6rpx; color: #466258; font-size: 25rpx; }
+.eg-result-score text:last-child { color: #e16f24; font-size: 36rpx; font-weight: 900; }
+.eg-result-best { display: flex; align-items: center; justify-content: space-between; margin: -12rpx 6rpx 20rpx; color: #75867f; font-size: 22rpx; }
+.eg-result-best text:last-child { color: #315d4e; font-size: 27rpx; font-weight: 900; }
+.eg-primary-btn, .eg-secondary-btn { display: flex; height: 88rpx; align-items: center; justify-content: center; border-radius: 24rpx; font-size: 28rpx; font-weight: 800; }
+.eg-primary-btn { color: #fff; background: linear-gradient(135deg, #238b68, #126649); box-shadow: 0 10rpx 22rpx rgba(18,102,73,.23); }
+.eg-secondary-btn { margin-top: 12rpx; color: #45665b; background: #edf4f1; }
+@keyframes egPulse {
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(245,158,11,.2); }
+ 50% { box-shadow: 0 0 0 8rpx rgba(245,158,11,.12); }
+}
+
+@keyframes egWeeklyIn {
+ 0% { opacity: 0; transform: translateY(34rpx) scale(.91); }
+ 100% { opacity: 1; transform: translateY(0) scale(1); }
+}
+
+@keyframes egHit {
+ 0% { opacity: 1; transform: scale(1); }
+ 42% { opacity: 1; transform: scale(1.18) rotate(-3deg); }
+ 100% { opacity: 0; transform: scale(.32) rotate(7deg); }
+}
+
+@keyframes egTaskRewardFlight {
+ 0% { opacity: 0; transform: translate3d(-20rpx, 0, 0) scale(.62) rotate(-8deg); }
+ 16% { opacity: 1; transform: translate3d(0, -24rpx, 0) scale(1.1) rotate(3deg); }
+ 72% { opacity: 1; transform: translate3d(270rpx, 900rpx, 0) scale(.68) rotate(8deg); }
+ 100% { opacity: 0; transform: translate3d(350rpx, 1205rpx, 0) scale(.3) rotate(2deg); }
+}
+
+@keyframes egRewardGlow {
+ from { opacity: .4; transform: scale(.86); }
+ to { opacity: .9; transform: scale(1.12); }
+}
+
+@keyframes egFireSweep {
+ 0% { opacity: 0; transform: translate3d(-38%, 20rpx, 0) scaleX(.6); }
+ 28% { opacity: 1; }
+ 72% { opacity: .96; }
+ 100% { opacity: 0; transform: translate3d(58%, -15rpx, 0) scaleX(1.08); }
+}
+
+@keyframes egFireSpark {
+ 0% { opacity: 0; transform: translateY(14rpx) scale(.5) rotate(35deg); }
+ 38% { opacity: 1; }
+ 100% { opacity: 0; transform: translateY(-38rpx) scale(1.05) rotate(58deg); }
+}
+
+@keyframes egFireGlow {
+ 0% { opacity: 0; transform: scale(.72); }
+ 38% { opacity: 1; transform: scale(1.08); }
+ 100% { opacity: 0; transform: scale(1.28); }
+}
+
+@keyframes egFireCellHit {
+ 0% { opacity: 1; transform: scale(1); }
+ 24% { opacity: 1; transform: scale(1.09); }
+ 72% { opacity: 1; transform: scale(1.02); }
+ 100% { opacity: 0; transform: scale(.48); }
+}
+
+@keyframes egFireFlame {
+ 0% { opacity: 0; transform: translateY(20rpx) rotate(42deg) scale(.55); }
+ 38% { opacity: 1; transform: translateY(0) rotate(42deg) scale(1.05); }
+ 100% { opacity: 0; transform: translateY(-31rpx) rotate(48deg) scale(.86); }
+}
+
+@keyframes egFlameReady {
+ from { transform: scale(.94); filter: brightness(.96); }
+ to { transform: scale(1.08); filter: brightness(1.18); }
+}
+
+@keyframes egLReady {
+ from { transform: scale(.94) rotate(-2deg); filter: brightness(.96); }
+ to { transform: scale(1.06) rotate(2deg); filter: brightness(1.12); }
+}
+
+@keyframes egImpact {
+ 0% { opacity: 0; transform: translate(-50%, -42%) scale(.55); }
+ 35% { opacity: 1; transform: translate(-50%, -50%) scale(1.16); }
+ 72% { opacity: 1; transform: translate(-50%, -54%) scale(1); }
+ 100% { opacity: 0; transform: translate(-50%, -76%) scale(.92); }
+}
+
+@keyframes egJackpotIn {
+ 0% { opacity: 0; transform: translate(-50%, -45%) scale(.48) rotate(-4deg); }
+ 20% { opacity: 1; transform: translate(-50%, -50%) scale(1.09) rotate(2deg); }
+ 31% { transform: translate(-50%, -50%) scale(.98) rotate(0); }
+ 78% { opacity: 1; transform: translate(-50%, -53%) scale(1); }
+ 100% { opacity: 0; transform: translate(-50%, -68%) scale(.92); }
+}
+
+@keyframes egTripleIn {
+ 0% { opacity: 0; transform: translate(-50%, -44%) scale(.82); }
+ 20% { opacity: 1; transform: translate(-50%, -50%) scale(1.03); }
+ 74% { opacity: 1; transform: translate(-50%, -52%) scale(1); }
+ 100% { opacity: 0; transform: translate(-50%, -60%) scale(.96); }
+}
+
+@keyframes egTripleCan {
+ 0% { opacity: 0; transform: translateY(18rpx) scale(.72); }
+ 100% { opacity: 1; transform: translateY(0) scale(1); }
+}
+
+@keyframes egFourIn {
+ 0% { opacity: 0; transform: translate(-50%, -44%) scale(.62) rotate(-3deg); }
+ 18% { opacity: 1; transform: translate(-50%, -50%) scale(1.08) rotate(1deg); }
+ 29% { transform: translate(-50%, -50%) scale(.98) rotate(0); }
+ 78% { opacity: 1; transform: translate(-50%, -52%) scale(1); }
+ 100% { opacity: 0; transform: translate(-50%, -65%) scale(.94); }
+}
+
+@keyframes egFourCan {
+ 0% { opacity: 0; transform: translateY(-22rpx) rotate(-7deg) scale(.65); }
+ 75% { opacity: 1; transform: translateY(5rpx) rotate(2deg) scale(1.05); }
+ 100% { opacity: 1; transform: translateY(0) rotate(0) scale(1); }
+}
+
+@keyframes egFiveCan {
+ 0% { opacity: 0; transform: translateY(-40rpx) scale(.52) rotate(-8deg); filter: brightness(1.5); }
+ 70% { opacity: 1; transform: translateY(7rpx) scale(1.08) rotate(2deg); }
+ 100% { opacity: 1; transform: translateY(0) scale(1) rotate(0); filter: brightness(1); }
+}
+
+@keyframes egLockedCan {
+ from { opacity: .62; transform: scale(.92); filter: brightness(.9); }
+ to { opacity: 1; transform: scale(1.06); filter: brightness(1.2); }
+}
+
+@keyframes egPromisePulse {
+ from { transform: scale(.97); box-shadow: 0 5rpx 15rpx rgba(28,7,62,.22); }
+ to { transform: scale(1.03); box-shadow: 0 7rpx 21rpx rgba(255,221,89,.38); }
+}
+
+@keyframes egReelStop {
+ 0% { opacity: .2; transform: translateY(-48rpx) scaleY(1.4); filter: blur(5rpx); }
+ 70% { opacity: 1; transform: translateY(7rpx) scaleY(.94); filter: blur(0); }
+ 100% { transform: translateY(0) scaleY(1); }
+}
+
+@keyframes egLights {
+ 0% { opacity: .35; }
+ 100% { opacity: 1; }
+}
+
+@keyframes egConfettiFall {
+ 0% { opacity: 0; transform: translate3d(0, -8vh, 0) rotate(0deg) scale(.7); }
+ 9% { opacity: 1; }
+ 48% { transform: translate3d(42rpx, 48vh, 0) rotate(420deg) scale(1); }
+ 100% { opacity: .92; transform: translate3d(-28rpx, 112vh, 0) rotate(920deg) scale(.86); }
+}
+
+@keyframes egPointsPop {
+ 0% { opacity: 0; transform: scale(.35) rotate(-7deg); }
+ 55% { opacity: 1; transform: scale(1.28) rotate(3deg); }
+ 78% { transform: scale(.92) rotate(0); }
+ 100% { opacity: 1; transform: scale(1); }
+}
+
+@keyframes egStageIn {
+ 0% { opacity: 0; transform: scale(.7) translateY(34rpx); }
+ 18% { opacity: 1; transform: scale(1.06) translateY(0); }
+ 30%, 76% { opacity: 1; transform: scale(1); }
+ 100% { opacity: 0; transform: scale(.96) translateY(-22rpx); }
+}
+
+@keyframes egReshuffleSpin {
+ from { transform: rotate(0); }
+ to { transform: rotate(360deg); }
+}
+
+@media screen and (max-height: 760px) {
+ .eg-task-card { padding-top: 13rpx; padding-bottom: 13rpx; }
+}
diff --git a/TUICallKit-Vue3/tongji/endless-game/index.vue b/TUICallKit-Vue3/tongji/endless-game/index.vue
new file mode 100644
index 00000000..2684ea8e
--- /dev/null
+++ b/TUICallKit-Vue3/tongji/endless-game/index.vue
@@ -0,0 +1,1459 @@
+
+
+
+ {{ piece.emoji }}
+
+
+
+
+
+
+ 控糖消消乐
+ 第{{ themeIndex + 1 }}站 · {{ currentTheme.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 本局认糖
+ {{ learned }}
+
+
+ 得分
+ {{ score }}
+
+
+ 本周同行
+ 第{{ weeklyMyRank }}名
+ 点击查看
+
+
+
+
+
+
+
+ 小目标:{{ currentTask.label }}
+
+ {{ Math.min(taskProgress, currentTask.target) }}/{{ currentTask.target }}
+
+
+
+
+
+ +1
+
+
+
+
+
+
+ 连消 ×{{ Math.max(combo, 1) }}
+ 双倍得分 ×{{ feastMoves }}
+
+
+ 高糖食物 {{ highCount }}/3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ✦
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ foodOf(tile.key).short }}
+
+ {{ foodOf(tile.key).levelLabel || '低糖' }}
+
+ 可替换
+
+
+
+
+ {{ impactText }}
+
+
+
+ {{ camelJackpot.kicker }}
+
+
+
+
+
+
+
+
+
+ ?
+
+
+ 还差 1 罐,777 大奖即将开启
+
+
+
+
+
+
+
+
+ 7
+
+
+ {{ camelJackpot.title }}
+ 777 ×2
+ {{ camelJackpot.reward }}
+ 仅为游戏内娱乐奖励
+
+
+
+
+ {{ statusText }}
+
+
+
+
+
+
+
+
+
+ 替换
+
+
+
+ 新疆纯驼乳粉
+ 替换{{ foodOf(currentTheme.danger).short }} · 三杯连消换主题
+
+ ×{{ camelCount }}
+
+
+
+
+ ● 低糖
+ ● 中糖
+ ● 主动三消高糖会结束
+
+
+
+
+
+
+
+
+ 本周7人同行榜
+ 界面预览
+
+ {{ weeklyRangeLabel }} · 每周一更新
+
+ {{ weeklyMyRank }}
+
+
+
+
+ 我的本周认糖
+ {{ weeklyMyCount }}
+
+
+ 当前第 {{ weeklyMyRank }} 名 · 本周上升 {{ weeklyRankRise }} 名
+ 当前第 {{ weeklyMyRank }} 名 · 继续积累认糖
+ 太棒了,本周暂列第一!
+ 再认 {{ weeklyDistance }} 个超过上一名
+
+
+
+
+
+ {{ player.rank }}
+ {{ player.avatar }}
+ {{ player.name }}
+ 我
+ {{ player.count }}
+
+
+
+
+
+ {{ currentCheer.source }}
+ 换一句
+
+ “{{ currentCheer.text }}”
+ 亲友自定义内容,不作为医学判断
+
+
+ 开始消除
+
+
+
+
+
+
+
+
+
+ {{ reshuffleDone ? '洗牌完成' : '没有可消除组合' }}
+ {{ reshuffleDone ? '新的消除机会已经出现' : '正在为您重新洗牌,请稍候' }}
+
+
+
+
+
+ 新菜单已解锁
+ 第 {{ stageMeta.number }} 站
+ {{ stageMeta.title }}
+
+
+
+ {{ food.short }}
+
+
+ 本组橙框食物可用驼乳粉替换
+
+
+
+
+
+ !
+ 本局结束
+ 你消除了:{{ endedFood.name }}
+
+ 为什么要注意?
+ {{ endedFood.tip }}
+ 科普内容上线前由甄养堂医生审核
+
+ 本局得分{{ score }}
+ 历史最高{{ bestScore }}
+ 再玩一局
+ 返回血糖周报
+
+
+
+
+
+
+
+
+
diff --git a/TUICallKit-Vue3/tongji/endless-game/utils/svgDataUrl.js b/TUICallKit-Vue3/tongji/endless-game/utils/svgDataUrl.js
new file mode 100644
index 00000000..045b6282
--- /dev/null
+++ b/TUICallKit-Vue3/tongji/endless-game/utils/svgDataUrl.js
@@ -0,0 +1,49 @@
+/**
+ * 将 SVG 字符串转为小程序可用的 data URL(base64 在真机上更稳定)
+ */
+
+function utf8ToBytes(str) {
+ const encoded = encodeURIComponent(str)
+ const bytes = []
+ for (let i = 0; i < encoded.length; i++) {
+ if (encoded.charCodeAt(i) === 37) {
+ bytes.push(parseInt(encoded.substring(i + 1, i + 3), 16))
+ i += 2
+ } else {
+ bytes.push(encoded.charCodeAt(i))
+ }
+ }
+ return new Uint8Array(bytes)
+}
+
+function bytesToBase64(bytes) {
+ if (typeof uni !== 'undefined' && typeof uni.arrayBufferToBase64 === 'function') {
+ return uni.arrayBufferToBase64(bytes.buffer)
+ }
+ if (typeof btoa !== 'undefined') {
+ let binary = ''
+ for (let i = 0; i < bytes.length; i++) {
+ binary += String.fromCharCode(bytes[i])
+ }
+ return btoa(binary)
+ }
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+ let result = ''
+ for (let i = 0; i < bytes.length; i += 3) {
+ const a = bytes[i]
+ const b = i + 1 < bytes.length ? bytes[i + 1] : 0
+ const c = i + 2 < bytes.length ? bytes[i + 2] : 0
+ result += chars[a >> 2]
+ result += chars[((a & 3) << 4) | (b >> 4)]
+ result += i + 1 < bytes.length ? chars[((b & 15) << 2) | (c >> 6)] : '='
+ result += i + 2 < bytes.length ? chars[c & 63] : '='
+ }
+ return result
+}
+
+export function svgToDataUrl(svg) {
+ const normalized = String(svg || '').trim()
+ if (!normalized) return ''
+ const base64 = bytesToBase64(utf8ToBytes(normalized))
+ return `data:image/svg+xml;base64,${base64}`
+}
diff --git a/TUICallKit-Vue3/tongji/endless-game/viteAssetOutput.js b/TUICallKit-Vue3/tongji/endless-game/viteAssetOutput.js
new file mode 100644
index 00000000..b80d3e00
--- /dev/null
+++ b/TUICallKit-Vue3/tongji/endless-game/viteAssetOutput.js
@@ -0,0 +1,33 @@
+const FOOD_ASSET_NAMES = new Set([
+ 'apple.jpg', 'banana.jpg', 'blackCoffee.jpg', 'broccoli.jpg', 'brownRice.jpg',
+ 'camel.jpg', 'celery.jpg', 'centuryCongee.jpg', 'chicken.jpg', 'coffee.jpg',
+ 'congee.jpg', 'corn.jpg', 'cucumber.jpg', 'egg.jpg', 'eightCongee.jpg',
+ 'grainMantou.jpg', 'greenBean.jpg', 'lettuce.jpg', 'mango.jpg', 'milk.jpg',
+ 'milkTea.jpg', 'milletCongee.jpg', 'mushroom.jpg', 'noodleSoup.jpg', 'onion.jpg',
+ 'orangeJuice.jpg', 'pepper.jpg', 'pineapple.jpg', 'potato.jpg', 'riceNoodleSoup.jpg',
+ 'shrimp.jpg', 'soda.jpg', 'strawberry.jpg', 'taro.jpg', 'tea.jpg', 'tomato.jpg',
+ 'udon.jpg', 'wonton.jpg'
+])
+
+/**
+ * 让游戏食品图进入 tongji 分包,避免增加微信小程序主包体积。
+ * 迁移到其他 uni-app 项目时,在 vite.config 中复用此函数即可。
+ */
+export function endlessGameAssetFileNames(assetInfo = {}) {
+ const candidates = [
+ assetInfo.name,
+ ...(assetInfo.names || []),
+ assetInfo.originalFileName,
+ ...(assetInfo.originalFileNames || [])
+ ].filter(Boolean)
+
+ const isEndlessFood = candidates.some((value) => {
+ const normalized = String(value).replace(/\\/g, '/')
+ const basename = normalized.split('/').pop()
+ return normalized.includes('tongji/endless-game/assets/food/') || FOOD_ASSET_NAMES.has(basename)
+ })
+
+ return isEndlessFood
+ ? 'tongji/endless-game/assets/food/[name].[hash][extname]'
+ : 'assets/[name].[hash][extname]'
+}
diff --git a/TUICallKit-Vue3/tongji/pages/weekly.vue b/TUICallKit-Vue3/tongji/pages/weekly.vue
index 353204ab..dd348e07 100644
--- a/TUICallKit-Vue3/tongji/pages/weekly.vue
+++ b/TUICallKit-Vue3/tongji/pages/weekly.vue
@@ -1514,7 +1514,7 @@ async function goMorePage() {
function goGamePage() {
uni.navigateTo({
- url: '/tongji/pages/game',
+ url: '/tongji/endless-game/index',
fail() {
uni.showToast({ title: '暂时无法打开游戏', icon: 'none' })
}
diff --git a/TUICallKit-Vue3/vite.config.ts b/TUICallKit-Vue3/vite.config.ts
index cb8a4bc7..99a36031 100644
--- a/TUICallKit-Vue3/vite.config.ts
+++ b/TUICallKit-Vue3/vite.config.ts
@@ -1,10 +1,18 @@
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
+import { endlessGameAssetFileNames } from './tongji/endless-game/viteAssetOutput.js'
// uni-app 工程根目录就是源码目录(HBuilderX 兼容)
// 编译产物默认输出到 ./dist//
export default defineConfig({
plugins: [uni()],
+ build: {
+ rollupOptions: {
+ output: {
+ assetFileNames: endlessGameAssetFileNames,
+ },
+ },
+ },
css: {
preprocessorOptions: {
scss: {